removing currency filter and added rate conversion while fetching timesheets in SI
This commit is contained in:
commit
35137ba9e0
3
.flake8
3
.flake8
@ -29,4 +29,5 @@ ignore =
|
|||||||
B950,
|
B950,
|
||||||
W191,
|
W191,
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
|
exclude=.github/helper/semgrep_rules
|
||||||
|
@ -4,25 +4,61 @@ from frappe import _, flt
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
# ruleid: frappe-modifying-but-not-comitting
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.value_of_goods == 0:
|
if self.value_of_goods == 0:
|
||||||
frappe.throw(_('Value of goods cannot be 0'))
|
frappe.throw(_('Value of goods cannot be 0'))
|
||||||
# ruleid: frappe-modifying-after-submit
|
|
||||||
self.status = 'Submitted'
|
self.status = 'Submitted'
|
||||||
|
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if flt(self.per_billed) < 100:
|
if self.value_of_goods == 0:
|
||||||
self.update_billing_status()
|
frappe.throw(_('Value of goods cannot be 0'))
|
||||||
else:
|
self.status = 'Submitted'
|
||||||
# todook: frappe-modifying-after-submit
|
self.db_set('status', 'Submitted')
|
||||||
self.status = "Completed"
|
|
||||||
self.db_set("status", "Completed")
|
|
||||||
|
|
||||||
class TestDoc(Document):
|
# ok: frappe-modifying-but-not-comitting
|
||||||
pass
|
def on_submit(self):
|
||||||
|
if self.value_of_goods == 0:
|
||||||
|
frappe.throw(_('Value of goods cannot be 0'))
|
||||||
|
x = "y"
|
||||||
|
self.status = x
|
||||||
|
self.db_set('status', x)
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
#ruleid: frappe-modifying-child-tables-while-iterating
|
# ok: frappe-modifying-but-not-comitting
|
||||||
for item in self.child_table:
|
def on_submit(self):
|
||||||
if item.value < 0:
|
x = "y"
|
||||||
self.remove(item)
|
self.status = x
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
# ruleid: frappe-modifying-but-not-comitting-other-method
|
||||||
|
class DoctypeClass(Document):
|
||||||
|
def on_submit(self):
|
||||||
|
self.good_method()
|
||||||
|
self.tainted_method()
|
||||||
|
|
||||||
|
def tainted_method(self):
|
||||||
|
self.status = "uptate"
|
||||||
|
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting-other-method
|
||||||
|
class DoctypeClass(Document):
|
||||||
|
def on_submit(self):
|
||||||
|
self.good_method()
|
||||||
|
self.tainted_method()
|
||||||
|
|
||||||
|
def tainted_method(self):
|
||||||
|
self.status = "update"
|
||||||
|
self.db_set("status", "update")
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting-other-method
|
||||||
|
class DoctypeClass(Document):
|
||||||
|
def on_submit(self):
|
||||||
|
self.good_method()
|
||||||
|
self.tainted_method()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def tainted_method(self):
|
||||||
|
self.status = "uptate"
|
||||||
|
@ -1,32 +1,93 @@
|
|||||||
# This file specifies rules for correctness according to how frappe doctype data model works.
|
# This file specifies rules for correctness according to how frappe doctype data model works.
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
- id: frappe-modifying-after-submit
|
- id: frappe-modifying-but-not-comitting
|
||||||
patterns:
|
patterns:
|
||||||
- pattern: self.$ATTR = ...
|
- pattern: |
|
||||||
- pattern-inside: |
|
def $METHOD(self, ...):
|
||||||
def on_submit(self, ...):
|
|
||||||
...
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
- pattern-not: |
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
...
|
||||||
|
self.db_set(..., self.$ATTR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = $SOME_VAR
|
||||||
|
...
|
||||||
|
self.db_set(..., $SOME_VAR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = $SOME_VAR
|
||||||
|
...
|
||||||
|
self.save()
|
||||||
- metavariable-regex:
|
- metavariable-regex:
|
||||||
metavariable: '$ATTR'
|
metavariable: '$ATTR'
|
||||||
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
||||||
regex: '^(?!status_updater)(.*)$'
|
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||||
|
- metavariable-regex:
|
||||||
|
metavariable: "$METHOD"
|
||||||
|
regex: "(on_submit|on_cancel)"
|
||||||
message: |
|
message: |
|
||||||
Doctype modified after submission. Please check if modification of self.$ATTR is commited to database.
|
DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database.
|
||||||
languages: [python]
|
languages: [python]
|
||||||
severity: ERROR
|
severity: ERROR
|
||||||
|
|
||||||
- id: frappe-modifying-after-cancel
|
- id: frappe-modifying-but-not-comitting-other-method
|
||||||
patterns:
|
patterns:
|
||||||
- pattern: self.$ATTR = ...
|
- pattern: |
|
||||||
- pattern-inside: |
|
class $DOCTYPE(...):
|
||||||
def on_cancel(self, ...):
|
def $METHOD(self, ...):
|
||||||
...
|
...
|
||||||
- metavariable-regex:
|
self.$ANOTHER_METHOD()
|
||||||
metavariable: '$ATTR'
|
...
|
||||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
...
|
||||||
|
self.db_set(..., self.$ATTR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = $SOME_VAR
|
||||||
|
...
|
||||||
|
self.db_set(..., $SOME_VAR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
self.save()
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
- metavariable-regex:
|
||||||
|
metavariable: "$METHOD"
|
||||||
|
regex: "(on_submit|on_cancel)"
|
||||||
message: |
|
message: |
|
||||||
Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database.
|
self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database.
|
||||||
languages: [python]
|
languages: [python]
|
||||||
severity: ERROR
|
severity: ERROR
|
||||||
|
|
||||||
|
7
.github/helper/semgrep_rules/translate.js
vendored
7
.github/helper/semgrep_rules/translate.js
vendored
@ -35,3 +35,10 @@ __('You have' + 'subscribers in your mailing list.')
|
|||||||
// ruleid: frappe-translation-js-splitting
|
// ruleid: frappe-translation-js-splitting
|
||||||
__('You have {0} subscribers' +
|
__('You have {0} subscribers' +
|
||||||
'in your mailing list', [subscribers.length])
|
'in your mailing list', [subscribers.length])
|
||||||
|
|
||||||
|
// ok: frappe-translation-js-splitting
|
||||||
|
__("Ctrl+Enter to add comment")
|
||||||
|
|
||||||
|
// ruleid: frappe-translation-js-splitting
|
||||||
|
__('You have {0} subscribers \
|
||||||
|
in your mailing list', [subscribers.length])
|
||||||
|
9
.github/helper/semgrep_rules/translate.yml
vendored
9
.github/helper/semgrep_rules/translate.yml
vendored
@ -42,9 +42,10 @@ rules:
|
|||||||
|
|
||||||
- id: frappe-translation-python-splitting
|
- id: frappe-translation-python-splitting
|
||||||
pattern-either:
|
pattern-either:
|
||||||
- pattern: _(...) + ... + _(...)
|
- pattern: _(...) + _(...)
|
||||||
- pattern: _("..." + "...")
|
- pattern: _("..." + "...")
|
||||||
- pattern-regex: '_\([^\)]*\\\s*'
|
- pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\`
|
||||||
|
- pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( )
|
||||||
message: |
|
message: |
|
||||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
@ -53,8 +54,8 @@ rules:
|
|||||||
|
|
||||||
- id: frappe-translation-js-splitting
|
- id: frappe-translation-js-splitting
|
||||||
pattern-either:
|
pattern-either:
|
||||||
- pattern-regex: '__\([^\)]*[\+\\]\s*'
|
- pattern-regex: '__\([^\)]*[\\]\s+'
|
||||||
- pattern: __('...' + '...')
|
- pattern: __('...' + '...', ...)
|
||||||
- pattern: __('...') + __('...')
|
- pattern: __('...') + __('...')
|
||||||
message: |
|
message: |
|
||||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||||
|
12
.github/workflows/semgrep.yml
vendored
12
.github/workflows/semgrep.yml
vendored
@ -4,6 +4,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- develop
|
||||||
|
- version-13-hotfix
|
||||||
|
- version-13-pre-release
|
||||||
jobs:
|
jobs:
|
||||||
semgrep:
|
semgrep:
|
||||||
name: Frappe Linter
|
name: Frappe Linter
|
||||||
@ -14,11 +16,19 @@ jobs:
|
|||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
- name: Run semgrep
|
|
||||||
|
- name: Setup semgrep
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -q semgrep
|
python -m pip install -q semgrep
|
||||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||||
|
|
||||||
|
- name: Semgrep errors
|
||||||
|
run: |
|
||||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
||||||
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
||||||
|
|
||||||
|
- name: Semgrep warnings
|
||||||
|
run: |
|
||||||
|
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
||||||
|
@ -239,6 +239,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
|||||||
"withdrawal",
|
"withdrawal",
|
||||||
"description",
|
"description",
|
||||||
"reference_number",
|
"reference_number",
|
||||||
|
"bank_account"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -146,7 +146,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.__islocal && !doc.import_file\n",
|
"depends_on": "eval:!doc.__islocal && !doc.import_file\n",
|
||||||
"description": "Must be a publicly accessible Google Sheets URL",
|
"description": "Must be a publicly accessible Google Sheets URL and adding Bank Account column is necessary for importing via Google Sheets",
|
||||||
"fieldname": "google_sheets_url",
|
"fieldname": "google_sheets_url",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Import from Google Sheets"
|
"label": "Import from Google Sheets"
|
||||||
@ -202,7 +202,7 @@
|
|||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-10 19:29:59.027325",
|
"modified": "2021-05-12 14:17:37.777246",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank Statement Import",
|
"name": "Bank Statement Import",
|
||||||
@ -224,4 +224,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,13 @@ class BankStatementImport(DataImport):
|
|||||||
|
|
||||||
def start_import(self):
|
def start_import(self):
|
||||||
|
|
||||||
|
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
|
||||||
|
self.import_file, self.google_sheets_url
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'Bank Account' not in json.dumps(preview):
|
||||||
|
frappe.throw(_("Please add the Bank Account column"))
|
||||||
|
|
||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
@ -67,6 +74,7 @@ class BankStatementImport(DataImport):
|
|||||||
data_import=self.name,
|
data_import=self.name,
|
||||||
bank_account=self.bank_account,
|
bank_account=self.bank_account,
|
||||||
import_file_path=self.import_file,
|
import_file_path=self.import_file,
|
||||||
|
google_sheets_url=self.google_sheets_url,
|
||||||
bank=self.bank,
|
bank=self.bank,
|
||||||
template_options=self.template_options,
|
template_options=self.template_options,
|
||||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||||
@ -90,18 +98,20 @@ def download_errored_template(data_import_name):
|
|||||||
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
|
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
|
||||||
data_import.export_errored_rows()
|
data_import.export_errored_rows()
|
||||||
|
|
||||||
def start_import(data_import, bank_account, import_file_path, bank, template_options):
|
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
|
||||||
"""This method runs in background job"""
|
"""This method runs in background job"""
|
||||||
|
|
||||||
update_mapping_db(bank, template_options)
|
update_mapping_db(bank, template_options)
|
||||||
|
|
||||||
data_import = frappe.get_doc("Bank Statement Import", data_import)
|
data_import = frappe.get_doc("Bank Statement Import", data_import)
|
||||||
|
file = import_file_path if import_file_path else google_sheets_url
|
||||||
|
|
||||||
import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records")
|
import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
|
||||||
data = import_file.raw_data
|
data = import_file.raw_data
|
||||||
|
|
||||||
add_bank_account(data, bank_account)
|
if import_file_path:
|
||||||
write_files(import_file, data)
|
add_bank_account(data, bank_account)
|
||||||
|
write_files(import_file, data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
i = Importer(data_import.reference_doctype, data_import=data_import)
|
i = Importer(data_import.reference_doctype, data_import=data_import)
|
||||||
|
@ -1,196 +1,82 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"creation": "2018-01-02 15:48:58.768352",
|
||||||
"allow_import": 0,
|
"doctype": "DocType",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"beta": 0,
|
"engine": "InnoDB",
|
||||||
"creation": "2018-01-02 15:48:58.768352",
|
"field_order": [
|
||||||
"custom": 0,
|
"company",
|
||||||
"docstatus": 0,
|
"cgst_account",
|
||||||
"doctype": "DocType",
|
"sgst_account",
|
||||||
"document_type": "",
|
"igst_account",
|
||||||
"editable_grid": 1,
|
"cess_account",
|
||||||
"engine": "InnoDB",
|
"is_reverse_charge_account"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 1,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "company",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Company",
|
||||||
"fieldname": "company",
|
"options": "Company",
|
||||||
"fieldtype": "Link",
|
"reqd": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Company",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "cgst_account",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "CGST Account",
|
||||||
"fieldname": "cgst_account",
|
"options": "Account",
|
||||||
"fieldtype": "Link",
|
"reqd": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "CGST Account",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "sgst_account",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "SGST Account",
|
||||||
"fieldname": "sgst_account",
|
"options": "Account",
|
||||||
"fieldtype": "Link",
|
"reqd": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "SGST Account",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "igst_account",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "IGST Account",
|
||||||
"fieldname": "igst_account",
|
"options": "Account",
|
||||||
"fieldtype": "Link",
|
"reqd": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "IGST Account",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "cess_account",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "CESS Account",
|
||||||
"fieldname": "cess_account",
|
"options": "Account"
|
||||||
"fieldtype": "Link",
|
},
|
||||||
"hidden": 0,
|
{
|
||||||
"ignore_user_permissions": 0,
|
"columns": 1,
|
||||||
"ignore_xss_filter": 0,
|
"default": "0",
|
||||||
"in_filter": 0,
|
"fieldname": "is_reverse_charge_account",
|
||||||
"in_global_search": 0,
|
"fieldtype": "Check",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Is Reverse Charge Account"
|
||||||
"label": "CESS Account",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
"istable": 1,
|
||||||
"hide_toolbar": 0,
|
"links": [],
|
||||||
"idx": 0,
|
"modified": "2021-04-09 12:30:25.889993",
|
||||||
"image_view": 0,
|
"modified_by": "Administrator",
|
||||||
"in_create": 0,
|
"module": "Accounts",
|
||||||
"is_submittable": 0,
|
"name": "GST Account",
|
||||||
"issingle": 0,
|
"owner": "Administrator",
|
||||||
"istable": 1,
|
"permissions": [],
|
||||||
"max_attachments": 0,
|
"quick_entry": 1,
|
||||||
"modified": "2018-01-02 15:52:22.335988",
|
"sort_field": "modified",
|
||||||
"modified_by": "Administrator",
|
"sort_order": "DESC",
|
||||||
"module": "Accounts",
|
"track_changes": 1
|
||||||
"name": "GST Account",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -1380,7 +1380,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-30 22:45:58.334107",
|
"modified": "2021-04-30 22:45:58.334107",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
var me = this;
|
var me = this;
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice'];
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet'];
|
||||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||||
// show debit_to in print format
|
// show debit_to in print format
|
||||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||||
@ -582,6 +582,16 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("adjustment_against", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
customer: frm.doc.customer,
|
||||||
|
docstatus: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Delivery Note': 'Delivery',
|
'Delivery Note': 'Delivery',
|
||||||
'Sales Invoice': 'Return / Credit Note',
|
'Sales Invoice': 'Return / Credit Note',
|
||||||
@ -809,8 +819,20 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
add_timesheet_row: function(frm, row, exchange_rate) {
|
||||||
|
frm.add_child('timesheets',{
|
||||||
|
'activity_type': row.activity_type,
|
||||||
|
'description': row.description,
|
||||||
|
'time_sheet': row.parent,
|
||||||
|
'billing_hours': row.billing_hours,
|
||||||
|
'billing_amount': flt(row.billing_amount) * flt(exchange_rate),
|
||||||
|
'timesheet_detail': row.name
|
||||||
|
});
|
||||||
|
frm.refresh_field('timesheets')
|
||||||
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.project && frm.doc.docstatus===0 && !frm.doc.is_return) {
|
if (frm.doc.docstatus===0 && !frm.doc.is_return) {
|
||||||
frm.add_custom_button(__('Fetch Timesheet'), function() {
|
frm.add_custom_button(__('Fetch Timesheet'), function() {
|
||||||
let d = new frappe.ui.Dialog({
|
let d = new frappe.ui.Dialog({
|
||||||
title: __('Fetch Timesheet'),
|
title: __('Fetch Timesheet'),
|
||||||
@ -821,15 +843,6 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label" : __("Currency"),
|
|
||||||
"fieldname": "currency",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Currency",
|
|
||||||
"default": frm.doc.currency,
|
|
||||||
"reqd": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
fieldtype: 'Column Break',
|
fieldtype: 'Column Break',
|
||||||
fieldname: 'col_break_1',
|
fieldname: 'col_break_1',
|
||||||
@ -845,9 +858,7 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Project",
|
"options": "Project",
|
||||||
"default": frm.doc.project,
|
"default": frm.doc.project
|
||||||
"reqd": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
@ -857,24 +868,33 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
args: {
|
args: {
|
||||||
from_time: data.from_time,
|
from_time: data.from_time,
|
||||||
to_time: data.to_time,
|
to_time: data.to_time,
|
||||||
project: data.project,
|
project: data.project
|
||||||
currency: data.currency
|
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) {
|
if(!r.exc) {
|
||||||
if(r.message.length > 0) {
|
if(r.message.length > 0) {
|
||||||
frm.clear_table('timesheets')
|
frm.clear_table('timesheets')
|
||||||
r.message.forEach((d) => {
|
r.message.forEach((d) => {
|
||||||
frm.add_child('timesheets',{
|
let exchange_rate = 1.0;
|
||||||
'activity_type': d.activity_type,
|
if (frm.doc.currency != d.currency) {
|
||||||
'description': d.description,
|
frappe.call({
|
||||||
'time_sheet': d.parent,
|
method: "erpnext.setup.utils.get_exchange_rate",
|
||||||
'billing_hours': d.billing_hours,
|
args: {
|
||||||
'billing_amount': d.billing_amount,
|
from_currency: d.currency,
|
||||||
'timesheet_detail': d.name
|
to_currency: frm.doc.currency
|
||||||
});
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
exchange_rate = r.message;
|
||||||
|
frm.events.add_timesheet_row(frm, d, exchange_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
frm.events.add_timesheet_row(frm, d, exchange_rate);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
frm.refresh_field('timesheets')
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
frappe.msgprint(__('No Timesheet Found.'))
|
frappe.msgprint(__('No Timesheet Found.'))
|
||||||
@ -890,6 +910,10 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.is_debit_note) {
|
||||||
|
frm.set_df_property('return_against', 'label', 'Adjustment Against');
|
||||||
|
}
|
||||||
|
|
||||||
if (frappe.boot.active_domains.includes("Healthcare")) {
|
if (frappe.boot.active_domains.includes("Healthcare")) {
|
||||||
frm.set_df_property("patient", "hidden", 0);
|
frm.set_df_property("patient", "hidden", 0);
|
||||||
frm.set_df_property("patient_name", "hidden", 0);
|
frm.set_df_property("patient_name", "hidden", 0);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"is_pos",
|
"is_pos",
|
||||||
"is_consolidated",
|
"is_consolidated",
|
||||||
"is_return",
|
"is_return",
|
||||||
|
"is_debit_note",
|
||||||
"update_billed_amount_in_sales_order",
|
"update_billed_amount_in_sales_order",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"company",
|
"company",
|
||||||
@ -392,7 +393,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "return_against",
|
"depends_on": "eval:doc.return_against || doc.is_debit_note",
|
||||||
"fieldname": "return_against",
|
"fieldname": "return_against",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -401,7 +402,7 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Sales Invoice",
|
"options": "Sales Invoice",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only_depends_on": "eval:doc.is_return",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1954,6 +1955,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"fieldname": "is_debit_note",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Debit Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": 0,
|
||||||
"depends_on": "grand_total",
|
"depends_on": "grand_total",
|
||||||
"fieldname": "disable_rounded_total",
|
"fieldname": "disable_rounded_total",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
@ -21,7 +21,10 @@ def get_party_details(inv):
|
|||||||
else:
|
else:
|
||||||
party_type = 'Supplier'
|
party_type = 'Supplier'
|
||||||
party = inv.supplier
|
party = inv.supplier
|
||||||
|
|
||||||
|
if not party:
|
||||||
|
frappe.throw(_("Please select {0} first").format(party_type))
|
||||||
|
|
||||||
return party_type, party
|
return party_type, party
|
||||||
|
|
||||||
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||||
@ -324,7 +327,7 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post
|
|||||||
net_total, ldc.certificate_limit
|
net_total, ldc.certificate_limit
|
||||||
):
|
):
|
||||||
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
|
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
|
||||||
|
|
||||||
return tds_amount
|
return tds_amount
|
||||||
|
|
||||||
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
|
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
|
||||||
|
@ -135,7 +135,7 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit
|
|||||||
|
|
||||||
# from consolidated financial statement
|
# from consolidated financial statement
|
||||||
if filters.get('accumulated_in_group_company'):
|
if filters.get('accumulated_in_group_company'):
|
||||||
period_list = get_filtered_list_for_consolidated_report(period_list)
|
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||||
|
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
key = period if consolidated else period.key
|
key = period if consolidated else period.key
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"hide_custom": 0,
|
"hide_custom": 0,
|
||||||
"icon": "accounting",
|
"icon": "accounting",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
|
"is_default": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Accounting",
|
"label": "Accounting",
|
||||||
"links": [
|
"links": [
|
||||||
@ -625,9 +626,9 @@
|
|||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"is_query_report": 0,
|
"is_query_report": 0,
|
||||||
"label": "Bank Reconciliation",
|
"label": "Bank Reconciliation Tool",
|
||||||
"link_to": "bank-reconciliation",
|
"link_to": "Bank Reconciliation Tool",
|
||||||
"link_type": "Page",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
@ -641,26 +642,6 @@
|
|||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Bank Statement Transaction Entry",
|
|
||||||
"link_to": "Bank Statement Transaction Entry",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Bank Statement Settings",
|
|
||||||
"link_to": "Bank Statement Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"is_query_report": 0,
|
"is_query_report": 0,
|
||||||
@ -1071,7 +1052,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-03-04 00:38:35.349024",
|
"modified": "2021-05-12 11:48:01.905144",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Accounting",
|
||||||
|
@ -195,8 +195,7 @@ class Asset(AccountsController):
|
|||||||
# If depreciation is already completed (for double declining balance)
|
# If depreciation is already completed (for double declining balance)
|
||||||
if skip_row: continue
|
if skip_row: continue
|
||||||
|
|
||||||
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
|
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d)
|
||||||
d.total_number_of_depreciations, d)
|
|
||||||
|
|
||||||
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
||||||
schedule_date = add_months(d.depreciation_start_date,
|
schedule_date = add_months(d.depreciation_start_date,
|
||||||
@ -208,7 +207,7 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
# For first row
|
# For first row
|
||||||
if has_pro_rata and n==0:
|
if has_pro_rata and n==0:
|
||||||
depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
|
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
|
||||||
self.available_for_use_date, d.depreciation_start_date)
|
self.available_for_use_date, d.depreciation_start_date)
|
||||||
|
|
||||||
# For first depr schedule date will be the start date
|
# For first depr schedule date will be the start date
|
||||||
@ -220,7 +219,7 @@ class Asset(AccountsController):
|
|||||||
to_date = add_months(self.available_for_use_date,
|
to_date = add_months(self.available_for_use_date,
|
||||||
n * cint(d.frequency_of_depreciation))
|
n * cint(d.frequency_of_depreciation))
|
||||||
|
|
||||||
depreciation_amount, days, months = get_pro_rata_amt(d,
|
depreciation_amount, days, months = self.get_pro_rata_amt(d,
|
||||||
depreciation_amount, schedule_date, to_date)
|
depreciation_amount, schedule_date, to_date)
|
||||||
|
|
||||||
monthly_schedule_date = add_months(schedule_date, 1)
|
monthly_schedule_date = add_months(schedule_date, 1)
|
||||||
@ -365,24 +364,6 @@ class Asset(AccountsController):
|
|||||||
def get_value_after_depreciation(self, idx):
|
def get_value_after_depreciation(self, idx):
|
||||||
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
|
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
|
||||||
|
|
||||||
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
|
|
||||||
precision = self.precision("gross_purchase_amount")
|
|
||||||
|
|
||||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
|
||||||
depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked))
|
|
||||||
|
|
||||||
if not depreciation_left:
|
|
||||||
frappe.msgprint(_("All the depreciations has been booked"))
|
|
||||||
depreciation_amount = flt(row.expected_value_after_useful_life)
|
|
||||||
return depreciation_amount
|
|
||||||
|
|
||||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
|
||||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
|
||||||
else:
|
|
||||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
|
|
||||||
|
|
||||||
return depreciation_amount
|
|
||||||
|
|
||||||
def validate_expected_value_after_useful_life(self):
|
def validate_expected_value_after_useful_life(self):
|
||||||
for row in self.get('finance_books'):
|
for row in self.get('finance_books'):
|
||||||
accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount
|
accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount
|
||||||
@ -575,6 +556,13 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
return 100 * (1 - flt(depreciation_rate, float_precision))
|
return 100 * (1 - flt(depreciation_rate, float_precision))
|
||||||
|
|
||||||
|
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
|
||||||
|
days = date_diff(to_date, from_date)
|
||||||
|
months = month_diff(to_date, from_date)
|
||||||
|
total_days = get_total_days(to_date, row.frequency_of_depreciation)
|
||||||
|
|
||||||
|
return (depreciation_amount * flt(days)) / flt(total_days), days, months
|
||||||
|
|
||||||
def update_maintenance_status():
|
def update_maintenance_status():
|
||||||
assets = frappe.get_all(
|
assets = frappe.get_all(
|
||||||
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
|
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
|
||||||
@ -758,15 +746,20 @@ def make_asset_movement(assets, purpose=None):
|
|||||||
def is_cwip_accounting_enabled(asset_category):
|
def is_cwip_accounting_enabled(asset_category):
|
||||||
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
|
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
|
||||||
|
|
||||||
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
|
|
||||||
days = date_diff(to_date, from_date)
|
|
||||||
months = month_diff(to_date, from_date)
|
|
||||||
total_days = get_total_days(to_date, row.frequency_of_depreciation)
|
|
||||||
|
|
||||||
return (depreciation_amount * flt(days)) / flt(total_days), days, months
|
|
||||||
|
|
||||||
def get_total_days(date, frequency):
|
def get_total_days(date, frequency):
|
||||||
period_start_date = add_months(date,
|
period_start_date = add_months(date,
|
||||||
cint(frequency) * -1)
|
cint(frequency) * -1)
|
||||||
|
|
||||||
return date_diff(date, period_start_date)
|
return date_diff(date, period_start_date)
|
||||||
|
|
||||||
|
@erpnext.allow_regional
|
||||||
|
def get_depreciation_amount(asset, depreciable_value, row):
|
||||||
|
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
||||||
|
|
||||||
|
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||||
|
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||||
|
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||||
|
else:
|
||||||
|
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
||||||
|
|
||||||
|
return depreciation_amount
|
@ -635,6 +635,45 @@ class TestAsset(unittest.TestCase):
|
|||||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||||
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
|
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
|
||||||
|
|
||||||
|
def test_discounted_wdv_depreciation_rate_for_indian_region(self):
|
||||||
|
# set indian company
|
||||||
|
company_flag = frappe.flags.company
|
||||||
|
frappe.flags.company = "_Test Company"
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||||
|
qty=1, rate=8000.0, location="Test Location")
|
||||||
|
|
||||||
|
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||||
|
asset = frappe.get_doc('Asset', asset_name)
|
||||||
|
asset.calculate_depreciation = 1
|
||||||
|
asset.available_for_use_date = '2030-06-12'
|
||||||
|
asset.purchase_date = '2030-01-01'
|
||||||
|
asset.append("finance_books", {
|
||||||
|
"expected_value_after_useful_life": 1000,
|
||||||
|
"depreciation_method": "Written Down Value",
|
||||||
|
"total_number_of_depreciations": 3,
|
||||||
|
"frequency_of_depreciation": 12,
|
||||||
|
"depreciation_start_date": "2030-12-31"
|
||||||
|
})
|
||||||
|
asset.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
|
||||||
|
|
||||||
|
expected_schedules = [
|
||||||
|
["2030-12-31", 1106.85, 1106.85],
|
||||||
|
["2031-12-31", 3446.58, 4553.43],
|
||||||
|
["2032-12-31", 1723.29, 6276.72],
|
||||||
|
["2033-06-12", 723.28, 7000.00]
|
||||||
|
]
|
||||||
|
|
||||||
|
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
|
||||||
|
for d in asset.get("schedules")]
|
||||||
|
|
||||||
|
self.assertEqual(schedules, expected_schedules)
|
||||||
|
|
||||||
|
# reset indian company
|
||||||
|
frappe.flags.company = company_flag
|
||||||
|
|
||||||
def create_asset_data():
|
def create_asset_data():
|
||||||
if not frappe.db.exists("Asset Category", "Computers"):
|
if not frappe.db.exists("Asset Category", "Computers"):
|
||||||
create_asset_category()
|
create_asset_category()
|
||||||
|
@ -62,6 +62,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
for supplier in self.suppliers:
|
for supplier in self.suppliers:
|
||||||
supplier.email_sent = 0
|
supplier.email_sent = 0
|
||||||
supplier.quote_status = 'Pending'
|
supplier.quote_status = 'Pending'
|
||||||
|
self.send_to_supplier()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
frappe.db.set(self, 'status', 'Cancelled')
|
frappe.db.set(self, 'status', 'Cancelled')
|
||||||
@ -81,7 +82,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
def send_to_supplier(self):
|
def send_to_supplier(self):
|
||||||
"""Sends RFQ mail to involved suppliers."""
|
"""Sends RFQ mail to involved suppliers."""
|
||||||
for rfq_supplier in self.suppliers:
|
for rfq_supplier in self.suppliers:
|
||||||
if rfq_supplier.send_email:
|
if rfq_supplier.email_id is not None and rfq_supplier.send_email:
|
||||||
self.validate_email_id(rfq_supplier)
|
self.validate_email_id(rfq_supplier)
|
||||||
|
|
||||||
# make new user if required
|
# make new user if required
|
||||||
|
@ -368,6 +368,11 @@ class AccountsController(TransactionBase):
|
|||||||
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
|
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
|
||||||
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
|
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
|
||||||
|
|
||||||
|
# Double check for cost center
|
||||||
|
# Items add via promotional scheme may not have cost center set
|
||||||
|
if hasattr(item, 'cost_center') and not item.get('cost_center'):
|
||||||
|
item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company))
|
||||||
|
|
||||||
if ret.get("pricing_rules"):
|
if ret.get("pricing_rules"):
|
||||||
self.apply_pricing_rule_on_items(item, ret)
|
self.apply_pricing_rule_on_items(item, ret)
|
||||||
self.set_pricing_rule_details(item, ret)
|
self.set_pricing_rule_details(item, ret)
|
||||||
|
@ -100,6 +100,10 @@ status_map = {
|
|||||||
["Queued", "eval:self.status == 'Queued'"],
|
["Queued", "eval:self.status == 'Queued'"],
|
||||||
["Failed", "eval:self.status == 'Failed'"],
|
["Failed", "eval:self.status == 'Failed'"],
|
||||||
["Cancelled", "eval:self.docstatus == 2"],
|
["Cancelled", "eval:self.docstatus == 2"],
|
||||||
|
],
|
||||||
|
"Transaction Deletion Record": [
|
||||||
|
["Draft", None],
|
||||||
|
["Completed", "eval:self.docstatus == 1"],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,8 +379,7 @@ class StockController(AccountsController):
|
|||||||
link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection)
|
link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection)
|
||||||
frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError)
|
frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError)
|
||||||
|
|
||||||
qa_failed = any([r.status=="Rejected" for r in qa_doc.readings])
|
if qa_doc.status != 'Accepted':
|
||||||
if qa_failed:
|
|
||||||
frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}")
|
frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}")
|
||||||
.format(d.idx, d.item_code), QualityInspectionRejectedError)
|
.format(d.idx, d.item_code), QualityInspectionRejectedError)
|
||||||
elif qa_required :
|
elif qa_required :
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, base64, hashlib, hmac, json
|
import frappe, base64, hashlib, hmac, json
|
||||||
|
from frappe.utils import cstr
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
def verify_request():
|
def verify_request():
|
||||||
@ -146,22 +147,19 @@ def rename_address(address, customer):
|
|||||||
|
|
||||||
def link_items(items_list, woocommerce_settings, sys_lang):
|
def link_items(items_list, woocommerce_settings, sys_lang):
|
||||||
for item_data in items_list:
|
for item_data in items_list:
|
||||||
item_woo_com_id = item_data.get("product_id")
|
item_woo_com_id = cstr(item_data.get("product_id"))
|
||||||
|
|
||||||
if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}):
|
if not frappe.db.get_value("Item", {"woocommerce_id": item_woo_com_id}, 'name'):
|
||||||
#Edit Item
|
|
||||||
item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id})
|
|
||||||
else:
|
|
||||||
#Create Item
|
#Create Item
|
||||||
item = frappe.new_doc("Item")
|
item = frappe.new_doc("Item")
|
||||||
|
item.item_code = _("woocommerce - {0}", sys_lang).format(item_woo_com_id)
|
||||||
|
item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang)
|
||||||
|
item.item_group = _("WooCommerce Products", sys_lang)
|
||||||
|
|
||||||
item.item_name = item_data.get("name")
|
item.item_name = item_data.get("name")
|
||||||
item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id"))
|
item.woocommerce_id = item_woo_com_id
|
||||||
item.woocommerce_id = item_data.get("product_id")
|
item.flags.ignore_mandatory = True
|
||||||
item.item_group = _("WooCommerce Products", sys_lang)
|
item.save()
|
||||||
item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang)
|
|
||||||
item.flags.ignore_mandatory = True
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
def create_sales_order(order, woocommerce_settings, customer_name, sys_lang):
|
def create_sales_order(order, woocommerce_settings, customer_name, sys_lang):
|
||||||
new_sales_order = frappe.new_doc("Sales Order")
|
new_sales_order = frappe.new_doc("Sales Order")
|
||||||
@ -194,12 +192,12 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
|
|||||||
|
|
||||||
for item in order.get("line_items"):
|
for item in order.get("line_items"):
|
||||||
woocomm_item_id = item.get("product_id")
|
woocomm_item_id = item.get("product_id")
|
||||||
found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
|
found_item = frappe.get_doc("Item", {"woocommerce_id": cstr(woocomm_item_id)})
|
||||||
|
|
||||||
ordered_items_tax = item.get("total_tax")
|
ordered_items_tax = item.get("total_tax")
|
||||||
|
|
||||||
new_sales_order.append("items",{
|
new_sales_order.append("items", {
|
||||||
"item_code": found_item.item_code,
|
"item_code": found_item.name,
|
||||||
"item_name": found_item.item_name,
|
"item_name": found_item.item_name,
|
||||||
"description": found_item.item_name,
|
"description": found_item.item_name,
|
||||||
"delivery_date": new_sales_order.delivery_date,
|
"delivery_date": new_sales_order.delivery_date,
|
||||||
@ -207,7 +205,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
|
|||||||
"qty": item.get("quantity"),
|
"qty": item.get("quantity"),
|
||||||
"rate": item.get("price"),
|
"rate": item.get("price"),
|
||||||
"warehouse": woocommerce_settings.warehouse or default_warehouse
|
"warehouse": woocommerce_settings.warehouse or default_warehouse
|
||||||
})
|
})
|
||||||
|
|
||||||
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
|
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
|
||||||
|
|
||||||
|
@ -90,9 +90,9 @@ def add_bank_accounts(response, bank, company):
|
|||||||
"bank": bank["bank_name"],
|
"bank": bank["bank_name"],
|
||||||
"account": default_gl_account.account,
|
"account": default_gl_account.account,
|
||||||
"account_name": account["name"],
|
"account_name": account["name"],
|
||||||
"account_type": account["type"] or "",
|
"account_type": account.get("type", ""),
|
||||||
"account_subtype": account["subtype"] or "",
|
"account_subtype": account.get("subtype", ""),
|
||||||
"mask": account["mask"] or "",
|
"mask": account.get("mask", ""),
|
||||||
"integration_id": account["id"],
|
"integration_id": account["id"],
|
||||||
"is_company_account": 1,
|
"is_company_account": 1,
|
||||||
"company": company
|
"company": company
|
||||||
|
@ -32,10 +32,12 @@ def create_customer(shopify_customer, shopify_settings):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
def create_customer_address(customer, shopify_customer):
|
def create_customer_address(customer, shopify_customer):
|
||||||
if not shopify_customer.get("addresses"):
|
addresses = shopify_customer.get("addresses", [])
|
||||||
return
|
|
||||||
|
|
||||||
for i, address in enumerate(shopify_customer.get("addresses")):
|
if not addresses and "default_address" in shopify_customer:
|
||||||
|
addresses.append(shopify_customer["default_address"])
|
||||||
|
|
||||||
|
for i, address in enumerate(addresses):
|
||||||
address_title, address_type = get_address_title_and_type(customer.customer_name, i)
|
address_title, address_type = get_address_title_and_type(customer.customer_name, i)
|
||||||
try :
|
try :
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
|
@ -268,10 +268,12 @@ doc_events = {
|
|||||||
},
|
},
|
||||||
"Purchase Invoice": {
|
"Purchase Invoice": {
|
||||||
"validate": [
|
"validate": [
|
||||||
"erpnext.regional.india.utils.update_grand_total_for_rcm",
|
"erpnext.regional.india.utils.validate_reverse_charge_transaction",
|
||||||
|
"erpnext.regional.india.utils.update_itc_availed_fields",
|
||||||
"erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm",
|
"erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm",
|
||||||
"erpnext.regional.united_arab_emirates.utils.validate_returns"
|
"erpnext.regional.united_arab_emirates.utils.validate_returns",
|
||||||
]
|
"erpnext.regional.india.utils.update_taxable_values"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"Payment Entry": {
|
"Payment Entry": {
|
||||||
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
|
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
|
||||||
@ -304,8 +306,7 @@ doc_events = {
|
|||||||
# if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
|
# if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
|
||||||
auto_cancel_exempted_doctypes= [
|
auto_cancel_exempted_doctypes= [
|
||||||
"Payment Entry",
|
"Payment Entry",
|
||||||
"Inpatient Medication Entry",
|
"Inpatient Medication Entry"
|
||||||
"Timesheet"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
|
after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
|
||||||
@ -366,10 +367,8 @@ scheduler_events = {
|
|||||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
|
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
|
||||||
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
|
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
|
||||||
"erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy",
|
|
||||||
"erpnext.hr.utils.generate_leave_encashment",
|
"erpnext.hr.utils.generate_leave_encashment",
|
||||||
"erpnext.hr.utils.allocate_earned_leaves",
|
"erpnext.hr.utils.allocate_earned_leaves",
|
||||||
"erpnext.hr.utils.grant_leaves_automatically",
|
|
||||||
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
||||||
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
||||||
"erpnext.crm.doctype.lead.lead.daily_open_lead"
|
"erpnext.crm.doctype.lead.lead.daily_open_lead"
|
||||||
@ -426,8 +425,8 @@ regional_overrides = {
|
|||||||
'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
|
'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
|
||||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||||
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
|
||||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
|
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
|
||||||
},
|
},
|
||||||
'United Arab Emirates': {
|
'United Arab Emirates': {
|
||||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
|
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
|
||||||
|
@ -11,8 +11,12 @@ def get_data():
|
|||||||
},
|
},
|
||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
'label': _('Leave and Attendance'),
|
'label': _('Attendance'),
|
||||||
'items': ['Attendance', 'Attendance Request', 'Leave Application', 'Leave Allocation', 'Employee Checkin']
|
'items': ['Attendance', 'Attendance Request', 'Employee Checkin']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Leave'),
|
||||||
|
'items': ['Leave Application', 'Leave Allocation', 'Leave Policy Assignment']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': _('Lifecycle'),
|
'label': _('Lifecycle'),
|
||||||
@ -30,10 +34,6 @@ def get_data():
|
|||||||
'label': _('Benefit'),
|
'label': _('Benefit'),
|
||||||
'items': ['Employee Benefit Application', 'Employee Benefit Claim']
|
'items': ['Employee Benefit Application', 'Employee Benefit Claim']
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'label': _('Evaluation'),
|
|
||||||
'items': ['Appraisal']
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
'label': _('Payroll'),
|
'label': _('Payroll'),
|
||||||
'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
|
'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
|
||||||
@ -42,5 +42,9 @@ def get_data():
|
|||||||
'label': _('Training'),
|
'label': _('Training'),
|
||||||
'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map']
|
'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'label': _('Evaluation'),
|
||||||
|
'items': ['Appraisal']
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
"show_leaves_of_all_department_members_in_calendar",
|
"show_leaves_of_all_department_members_in_calendar",
|
||||||
"auto_leave_encashment",
|
"auto_leave_encashment",
|
||||||
"restrict_backdated_leave_application",
|
"restrict_backdated_leave_application",
|
||||||
"automatically_allocate_leaves_based_on_leave_policy",
|
|
||||||
"hiring_settings",
|
"hiring_settings",
|
||||||
"check_vacancies"
|
"check_vacancies"
|
||||||
],
|
],
|
||||||
@ -133,12 +132,6 @@
|
|||||||
"label": "Role Allowed to Create Backdated Leave Application",
|
"label": "Role Allowed to Create Backdated Leave Application",
|
||||||
"options": "Role"
|
"options": "Role"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "automatically_allocate_leaves_based_on_leave_policy",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Automatically Allocate Leaves Based On Leave Policy"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "send_leave_notification",
|
"fieldname": "send_leave_notification",
|
||||||
@ -155,7 +148,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-26 10:52:56.192773",
|
"modified": "2021-05-11 10:52:56.192773",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "HR Settings",
|
"name": "HR Settings",
|
||||||
|
@ -446,8 +446,6 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
|
|
||||||
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
||||||
|
|
||||||
frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
|
|
||||||
|
|
||||||
from erpnext.hr.utils import allocate_earned_leaves
|
from erpnext.hr.utils import allocate_earned_leaves
|
||||||
i = 0
|
i = 0
|
||||||
while(i<14):
|
while(i<14):
|
||||||
|
@ -44,10 +44,6 @@ class TestLeaveEncashment(unittest.TestCase):
|
|||||||
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
|
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
|
||||||
other_details={"leave_encashment_amount_per_day": 50})
|
other_details={"leave_encashment_amount_per_day": 50})
|
||||||
|
|
||||||
#grant Leaves
|
|
||||||
frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
|
|
||||||
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
|
for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
|
||||||
frappe.db.sql("delete from `tab%s`" % dt)
|
frappe.db.sql("delete from `tab%s`" % dt)
|
||||||
|
@ -7,7 +7,7 @@ def get_data():
|
|||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
'label': _('Leaves'),
|
'label': _('Leaves'),
|
||||||
'items': ['Leave Allocation']
|
'items': ['Leave Policy Assignment', 'Leave Allocation']
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -4,35 +4,22 @@
|
|||||||
frappe.ui.form.on('Leave Policy Assignment', {
|
frappe.ui.form.on('Leave Policy Assignment', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
|
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
|
||||||
},
|
|
||||||
|
|
||||||
refresh: function(frm) {
|
frm.set_query('leave_policy', function() {
|
||||||
if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) {
|
return {
|
||||||
frm.add_custom_button(__("Grant Leave"), function() {
|
filters: {
|
||||||
|
"docstatus": 1
|
||||||
frappe.call({
|
}
|
||||||
doc: frm.doc,
|
};
|
||||||
method: "grant_leave_alloc_for_employee",
|
});
|
||||||
callback: function(r) {
|
frm.set_query('leave_period', function() {
|
||||||
let leave_allocations = r.message;
|
return {
|
||||||
let msg = frm.events.get_success_message(leave_allocations);
|
filters: {
|
||||||
frappe.msgprint(msg);
|
"is_active": 1,
|
||||||
cur_frm.refresh();
|
"company": frm.doc.company
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get_success_message: function(leave_allocations) {
|
|
||||||
let msg = __("Leaves has been granted successfully");
|
|
||||||
msg += "<br><table class='table table-bordered'>";
|
|
||||||
msg += "<tr><th>"+__('Leave Type')+"</th><th>"+__("Leave Allocation")+"</th><th>"+__("Leaves Granted")+"</th><tr>";
|
|
||||||
for (let key in leave_allocations) {
|
|
||||||
msg += "<tr><th>"+key+"</th><td>"+leave_allocations[key]["name"]+"</td><td>"+leave_allocations[key]["leaves"]+"</td></tr>";
|
|
||||||
}
|
|
||||||
msg += "</table>";
|
|
||||||
return msg;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
assignment_based_on: function(frm) {
|
assignment_based_on: function(frm) {
|
||||||
|
@ -17,6 +17,9 @@ class LeavePolicyAssignment(Document):
|
|||||||
self.validate_policy_assignment_overlap()
|
self.validate_policy_assignment_overlap()
|
||||||
self.set_dates()
|
self.set_dates()
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.grant_leave_alloc_for_employee()
|
||||||
|
|
||||||
def set_dates(self):
|
def set_dates(self):
|
||||||
if self.assignment_based_on == "Leave Period":
|
if self.assignment_based_on == "Leave Period":
|
||||||
self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
|
self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
|
||||||
@ -75,7 +78,7 @@ class LeavePolicyAssignment(Document):
|
|||||||
from_date=self.effective_from,
|
from_date=self.effective_from,
|
||||||
to_date=self.effective_to,
|
to_date=self.effective_to,
|
||||||
new_leaves_allocated=new_leaves_allocated,
|
new_leaves_allocated=new_leaves_allocated,
|
||||||
leave_period=self.leave_period or None,
|
leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else '',
|
||||||
leave_policy_assignment = self.name,
|
leave_policy_assignment = self.name,
|
||||||
leave_policy = self.leave_policy,
|
leave_policy = self.leave_policy,
|
||||||
carry_forward=carry_forward
|
carry_forward=carry_forward
|
||||||
@ -131,22 +134,6 @@ class LeavePolicyAssignment(Document):
|
|||||||
return new_leaves_allocated
|
return new_leaves_allocated
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def grant_leave_for_multiple_employees(leave_policy_assignments):
|
|
||||||
leave_policy_assignments = json.loads(leave_policy_assignments)
|
|
||||||
not_granted = []
|
|
||||||
for assignment in leave_policy_assignments:
|
|
||||||
try:
|
|
||||||
frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee()
|
|
||||||
except Exception:
|
|
||||||
not_granted.append(assignment)
|
|
||||||
|
|
||||||
if len(not_granted):
|
|
||||||
msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents")
|
|
||||||
else:
|
|
||||||
msg = _("Leave granted Successfully")
|
|
||||||
frappe.msgprint(msg)
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_assignment_for_multiple_employees(employees, data):
|
def create_assignment_for_multiple_employees(employees, data):
|
||||||
|
|
||||||
@ -166,29 +153,18 @@ def create_assignment_for_multiple_employees(employees, data):
|
|||||||
assignment.effective_to = getdate(data.effective_to) or None
|
assignment.effective_to = getdate(data.effective_to) or None
|
||||||
assignment.leave_period = data.leave_period or None
|
assignment.leave_period = data.leave_period or None
|
||||||
assignment.carry_forward = data.carry_forward
|
assignment.carry_forward = data.carry_forward
|
||||||
|
|
||||||
assignment.save()
|
assignment.save()
|
||||||
assignment.submit()
|
try:
|
||||||
|
assignment.submit()
|
||||||
|
except frappe.exceptions.ValidationError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
docs_name.append(assignment.name)
|
docs_name.append(assignment.name)
|
||||||
|
|
||||||
return docs_name
|
return docs_name
|
||||||
|
|
||||||
|
|
||||||
def automatically_allocate_leaves_based_on_leave_policy():
|
|
||||||
today = getdate()
|
|
||||||
automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value(
|
|
||||||
'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy'
|
|
||||||
)
|
|
||||||
|
|
||||||
pending_assignments = frappe.get_list(
|
|
||||||
"Leave Policy Assignment",
|
|
||||||
filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today}
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy:
|
|
||||||
for assignment in pending_assignments:
|
|
||||||
frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
|
|
||||||
|
|
||||||
|
|
||||||
def get_leave_type_details():
|
def get_leave_type_details():
|
||||||
leave_type_details = frappe._dict()
|
leave_type_details = frappe._dict()
|
||||||
leave_types = frappe.get_all("Leave Type",
|
leave_types = frappe.get_all("Leave Type",
|
||||||
@ -197,4 +173,3 @@ def get_leave_type_details():
|
|||||||
for d in leave_types:
|
for d in leave_types:
|
||||||
leave_type_details.setdefault(d.name, d)
|
leave_type_details.setdefault(d.name, d)
|
||||||
return leave_type_details
|
return leave_type_details
|
||||||
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'leave_policy_assignment',
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Leaves'),
|
||||||
|
'items': ['Leave Allocation']
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
@ -6,6 +6,7 @@ frappe.listview_settings['Leave Policy Assignment'] = {
|
|||||||
doctype: "Employee",
|
doctype: "Employee",
|
||||||
target: cur_list,
|
target: cur_list,
|
||||||
setters: {
|
setters: {
|
||||||
|
employee_name: '',
|
||||||
company: '',
|
company: '',
|
||||||
department: '',
|
department: '',
|
||||||
},
|
},
|
||||||
@ -92,37 +93,6 @@ frappe.listview_settings['Leave Policy Assignment'] = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
list_view.page.add_inner_button(__("Grant Leaves"), function () {
|
|
||||||
me.dialog = new frappe.ui.form.MultiSelectDialog({
|
|
||||||
doctype: "Leave Policy Assignment",
|
|
||||||
target: cur_list,
|
|
||||||
setters: {
|
|
||||||
company: '',
|
|
||||||
employee: '',
|
|
||||||
},
|
|
||||||
get_query() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
docstatus: ['=', 1],
|
|
||||||
leaves_allocated: ['=', 0]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
add_filters_group: 1,
|
|
||||||
primary_action_label: "Grant Leaves",
|
|
||||||
action(leave_policy_assignments) {
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees',
|
|
||||||
async: false,
|
|
||||||
args: {
|
|
||||||
leave_policy_assignments: leave_policy_assignments
|
|
||||||
}
|
|
||||||
});
|
|
||||||
me.dialog.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
set_effective_date: function () {
|
set_effective_date: function () {
|
||||||
|
@ -35,7 +35,6 @@ class TestLeavePolicyAssignment(unittest.TestCase):
|
|||||||
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
||||||
|
|
||||||
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
|
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
|
||||||
leave_policy_assignment_doc.grant_leave_alloc_for_employee()
|
|
||||||
leave_policy_assignment_doc.reload()
|
leave_policy_assignment_doc.reload()
|
||||||
|
|
||||||
self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
|
self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
|
||||||
@ -73,7 +72,6 @@ class TestLeavePolicyAssignment(unittest.TestCase):
|
|||||||
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
|
||||||
|
|
||||||
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
|
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
|
||||||
leave_policy_assignment_doc.grant_leave_alloc_for_employee()
|
|
||||||
leave_policy_assignment_doc.reload()
|
leave_policy_assignment_doc.reload()
|
||||||
|
|
||||||
|
|
||||||
|
@ -500,13 +500,6 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co
|
|||||||
total_claimed_amount = sum_of_claimed_amount[0].total_amount
|
total_claimed_amount = sum_of_claimed_amount[0].total_amount
|
||||||
return total_claimed_amount
|
return total_claimed_amount
|
||||||
|
|
||||||
def grant_leaves_automatically():
|
|
||||||
automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_singles_value("HR Settings", "automatically_allocate_leaves_based_on_leave_policy")
|
|
||||||
if automatically_allocate_leaves_based_on_leave_policy:
|
|
||||||
lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
|
|
||||||
for assignment in lpa:
|
|
||||||
frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
|
|
||||||
|
|
||||||
def share_doc_with_approver(doc, user):
|
def share_doc_with_approver(doc, user):
|
||||||
# if approver does not have permissions, share
|
# if approver does not have permissions, share
|
||||||
if not frappe.has_permission(doc=doc, ptype="submit", user=user):
|
if not frappe.has_permission(doc=doc, ptype="submit", user=user):
|
||||||
|
@ -769,6 +769,7 @@ erpnext.patches.v13_0.rename_discharge_date_in_ip_record
|
|||||||
erpnext.patches.v12_0.create_taxable_value_field
|
erpnext.patches.v12_0.create_taxable_value_field
|
||||||
erpnext.patches.v12_0.add_gst_category_in_delivery_note
|
erpnext.patches.v12_0.add_gst_category_in_delivery_note
|
||||||
erpnext.patches.v12_0.purchase_receipt_status
|
erpnext.patches.v12_0.purchase_receipt_status
|
||||||
|
erpnext.patches.v12_0.create_itc_reversal_custom_fields
|
||||||
erpnext.patches.v13_0.fix_non_unique_represents_company
|
erpnext.patches.v13_0.fix_non_unique_represents_company
|
||||||
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
||||||
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
|
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
|
||||||
|
115
erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
Normal file
115
erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
|
from erpnext.regional.india.utils import get_gst_accounts
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name'])
|
||||||
|
if not company:
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.reload_doc("regional", "doctype", "gst_settings")
|
||||||
|
frappe.reload_doc("accounts", "doctype", "gst_account")
|
||||||
|
|
||||||
|
journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
|
||||||
|
make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
|
||||||
|
|
||||||
|
custom_fields = {
|
||||||
|
'Journal Entry': [
|
||||||
|
dict(fieldname='reversal_type', label='Reversal Type',
|
||||||
|
fieldtype='Select', insert_after='voucher_type', print_hide=1,
|
||||||
|
options="As per rules 42 & 43 of CGST Rules\nOthers",
|
||||||
|
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||||
|
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
|
||||||
|
dict(fieldname='company_address', label='Company Address',
|
||||||
|
fieldtype='Link', options='Address', insert_after='reversal_type',
|
||||||
|
print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||||
|
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
|
||||||
|
dict(fieldname='company_gstin', label='Company GSTIN',
|
||||||
|
fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1,
|
||||||
|
fetch_from='company_address.gstin',
|
||||||
|
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||||
|
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'")
|
||||||
|
],
|
||||||
|
'Purchase Invoice': [
|
||||||
|
dict(fieldname='eligibility_for_itc', label='Eligibility For ITC',
|
||||||
|
fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1,
|
||||||
|
options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC',
|
||||||
|
default="All Other ITC")
|
||||||
|
],
|
||||||
|
'Purchase Invoice Item': [
|
||||||
|
dict(fieldname='taxable_value', label='Taxable Value',
|
||||||
|
fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
|
||||||
|
print_hide=1)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
create_custom_fields(custom_fields, update=True)
|
||||||
|
|
||||||
|
# Patch ITC Availed fields from Data to Currency
|
||||||
|
# Patch Availed ITC for current fiscal_year
|
||||||
|
|
||||||
|
gst_accounts = get_gst_accounts(only_non_reverse_charge=1)
|
||||||
|
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE `tabCustom Field` SET fieldtype='Currency', options='Company:company:default_currency'
|
||||||
|
WHERE dt = 'Purchase Invoice' and fieldname in ('itc_integrated_tax', 'itc_state_tax', 'itc_central_tax',
|
||||||
|
'itc_cess_amount')
|
||||||
|
""")
|
||||||
|
|
||||||
|
frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_integrated_tax = '0'
|
||||||
|
WHERE trim(coalesce(itc_integrated_tax, '')) = '' """)
|
||||||
|
|
||||||
|
frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_state_tax = '0'
|
||||||
|
WHERE trim(coalesce(itc_state_tax, '')) = '' """)
|
||||||
|
|
||||||
|
frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_central_tax = '0'
|
||||||
|
WHERE trim(coalesce(itc_central_tax, '')) = '' """)
|
||||||
|
|
||||||
|
frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_cess_amount = '0'
|
||||||
|
WHERE trim(coalesce(itc_cess_amount, '')) = '' """)
|
||||||
|
|
||||||
|
# Get purchase invoices
|
||||||
|
invoices = frappe.get_all('Purchase Invoice',
|
||||||
|
{'posting_date': ('>=', '2021-04-01'), 'eligibility_for_itc': ('!=', 'Ineligible')},
|
||||||
|
['name'])
|
||||||
|
|
||||||
|
amount_map = {}
|
||||||
|
|
||||||
|
if invoices:
|
||||||
|
invoice_list = set([d.name for d in invoices])
|
||||||
|
|
||||||
|
# Get GST applied
|
||||||
|
amounts = frappe.db.sql("""
|
||||||
|
SELECT parent, account_head, sum(base_tax_amount_after_discount_amount) as amount
|
||||||
|
FROM `tabPurchase Taxes and Charges`
|
||||||
|
where parent in %s
|
||||||
|
GROUP BY parent, account_head
|
||||||
|
""", (invoice_list), as_dict=1)
|
||||||
|
|
||||||
|
for d in amounts:
|
||||||
|
amount_map.setdefault(d.parent,
|
||||||
|
{
|
||||||
|
'itc_integrated_tax': 0,
|
||||||
|
'itc_state_tax': 0,
|
||||||
|
'itc_central_tax': 0,
|
||||||
|
'itc_cess_amount': 0
|
||||||
|
})
|
||||||
|
|
||||||
|
if d.account_head in gst_accounts.get('igst_account'):
|
||||||
|
amount_map[d.parent]['itc_integrated_tax'] += d.amount
|
||||||
|
if d.account_head in gst_accounts.get('cgst_account'):
|
||||||
|
amount_map[d.parent]['itc_central_tax'] += d.amount
|
||||||
|
if d.account_head in gst_accounts.get('sgst_account'):
|
||||||
|
amount_map[d.parent]['itc_state_tax'] += d.amount
|
||||||
|
if d.account_head in gst_accounts.get('cess_account'):
|
||||||
|
amount_map[d.parent]['itc_cess_amount'] += d.amount
|
||||||
|
|
||||||
|
for invoice, values in amount_map.items():
|
||||||
|
frappe.db.set_value('Purchase Invoice', invoice, {
|
||||||
|
'itc_integrated_tax': values.get('itc_integrated_tax'),
|
||||||
|
'itc_central_tax': values.get('itc_central_tax'),
|
||||||
|
'itc_state_tax': values['itc_state_tax'],
|
||||||
|
'itc_cess_amount': values['itc_cess_amount'],
|
||||||
|
})
|
@ -145,8 +145,8 @@ class Timesheet(Document):
|
|||||||
data.project = data.project or frappe.db.get_value("Task", data.task, "project")
|
data.project = data.project or frappe.db.get_value("Task", data.task, "project")
|
||||||
|
|
||||||
def validate_project(self, data):
|
def validate_project(self, data):
|
||||||
if self.parent_project != data.project:
|
if self.parent_project and self.parent_project != data.project:
|
||||||
frappe.throw(_("Row {0}: Poject must be same as {1}.")).format(data.idx, self.parent_project)
|
frappe.throw(_("Row {0}: Project must be same as the one set in the Timesheet: {1}.")).format(data.idx, self.parent_project)
|
||||||
|
|
||||||
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
|
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
|
||||||
if not value or ignore_validation:
|
if not value or ignore_validation:
|
||||||
@ -208,27 +208,25 @@ class Timesheet(Document):
|
|||||||
ts_detail.billing_rate = 0.0
|
ts_detail.billing_rate = 0.0
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None, currency=None):
|
def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to_time=None):
|
||||||
condition = field = join = ''
|
condition = ''
|
||||||
|
if project:
|
||||||
|
condition += "and tsd.project = %(project)s"
|
||||||
if parent:
|
if parent:
|
||||||
condition = "AND tsd.parent = %(parent)s"
|
condition += "AND tsd.parent = %(parent)s"
|
||||||
if from_time and to_time:
|
if from_time and to_time:
|
||||||
condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
|
condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
|
||||||
if currency:
|
|
||||||
field = ", ts.currency as currency"
|
|
||||||
join = " INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent "
|
|
||||||
condition += " AND ts.currency = %(currency)s"
|
|
||||||
|
|
||||||
return frappe.db.sql("""SELECT tsd.name as name,
|
return frappe.db.sql("""SELECT tsd.name as name,
|
||||||
tsd.parent as parent, tsd.billing_hours as billing_hours,
|
tsd.parent as parent, tsd.billing_hours as billing_hours,
|
||||||
tsd.billing_amount as billing_amount, tsd.activity_type as activity_type,
|
tsd.billing_amount as billing_amount, tsd.activity_type as activity_type,
|
||||||
tsd.description as description {0}
|
tsd.description as description, ts.currency as currency
|
||||||
FROM `tabTimesheet Detail` tsd
|
FROM `tabTimesheet Detail` tsd
|
||||||
{1} where tsd.parenttype = 'Timesheet'
|
INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent
|
||||||
and tsd.docstatus=1
|
WHERE tsd.parenttype = 'Timesheet'
|
||||||
and tsd.project = %(project)s {2}
|
and tsd.docstatus=1 {0}
|
||||||
and tsd.is_billable = 1
|
and tsd.is_billable = 1
|
||||||
and tsd.sales_invoice is null""".format(field, join, condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time, 'currency': currency}, as_dict=1)
|
and tsd.sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
@ -1329,7 +1329,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
|
|
||||||
this.toggle_item_grid_columns(company_currency);
|
this.toggle_item_grid_columns(company_currency);
|
||||||
|
|
||||||
if(this.frm.fields_dict["operations"]) {
|
if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
|
||||||
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
|
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
|
||||||
this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations");
|
this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations");
|
||||||
|
|
||||||
@ -1340,7 +1340,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.frm.fields_dict["scrap_items"]) {
|
if (this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) {
|
||||||
this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items");
|
this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items");
|
||||||
this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items");
|
this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items");
|
||||||
|
|
||||||
@ -1351,13 +1351,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.frm.fields_dict["taxes"]) {
|
if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) {
|
||||||
this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes");
|
this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes");
|
||||||
|
|
||||||
this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes");
|
this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.frm.fields_dict["advances"]) {
|
if (this.frm.doc.advances && this.frm.doc.advances.length > 0) {
|
||||||
this.frm.set_currency_labels(["advance_amount", "allocated_amount"],
|
this.frm.set_currency_labels(["advance_amount", "allocated_amount"],
|
||||||
this.frm.doc.party_account_currency, "advances");
|
this.frm.doc.party_account_currency, "advances");
|
||||||
}
|
}
|
||||||
|
@ -74,9 +74,18 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
fieldname: 'qty',
|
fieldname: 'qty',
|
||||||
fieldtype:'Float',
|
fieldtype:'Float',
|
||||||
read_only: me.has_batch && !me.has_serial_no,
|
read_only: me.has_batch && !me.has_serial_no,
|
||||||
label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'),
|
label: __(me.has_batch && !me.has_serial_no ? 'Selected Qty' : 'Qty'),
|
||||||
default: flt(me.item.stock_qty),
|
default: flt(me.item.stock_qty),
|
||||||
},
|
},
|
||||||
|
...get_pending_qty_fields(me),
|
||||||
|
{
|
||||||
|
fieldname: 'uom',
|
||||||
|
read_only: 1,
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'UOM',
|
||||||
|
label: __('UOM'),
|
||||||
|
default: me.item.uom
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'auto_fetch_button',
|
fieldname: 'auto_fetch_button',
|
||||||
fieldtype:'Button',
|
fieldtype:'Button',
|
||||||
@ -173,6 +182,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
|
|
||||||
if (this.has_batch && !this.has_serial_no) {
|
if (this.has_batch && !this.has_serial_no) {
|
||||||
this.update_total_qty();
|
this.update_total_qty();
|
||||||
|
this.update_pending_qtys();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dialog.show();
|
this.dialog.show();
|
||||||
@ -313,7 +323,21 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
|
|
||||||
qty_field.set_input(total_qty);
|
qty_field.set_input(total_qty);
|
||||||
},
|
},
|
||||||
|
update_pending_qtys: function() {
|
||||||
|
const pending_qty_field = this.dialog.fields_dict.pending_qty;
|
||||||
|
const total_selected_qty_field = this.dialog.fields_dict.total_selected_qty;
|
||||||
|
|
||||||
|
if (!pending_qty_field || !total_selected_qty_field) return;
|
||||||
|
|
||||||
|
const me = this;
|
||||||
|
const required_qty = this.dialog.fields_dict.required_qty.value;
|
||||||
|
const selected_qty = this.dialog.fields_dict.qty.value;
|
||||||
|
const total_selected_qty = selected_qty + calc_total_selected_qty(me);
|
||||||
|
const pending_qty = required_qty - total_selected_qty;
|
||||||
|
|
||||||
|
pending_qty_field.set_input(pending_qty);
|
||||||
|
total_selected_qty_field.set_input(total_selected_qty);
|
||||||
|
},
|
||||||
get_batch_fields: function() {
|
get_batch_fields: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
@ -415,6 +439,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
me.update_total_qty();
|
me.update_total_qty();
|
||||||
|
me.update_pending_qtys();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -511,3 +536,60 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function get_pending_qty_fields(me) {
|
||||||
|
if (!check_can_calculate_pending_qty(me)) return [];
|
||||||
|
const { frm: { doc: { fg_completed_qty }}, item: { item_code, stock_qty }} = me;
|
||||||
|
const { qty_consumed_per_unit } = erpnext.stock.bom.items[item_code];
|
||||||
|
|
||||||
|
const total_selected_qty = calc_total_selected_qty(me);
|
||||||
|
const required_qty = flt(fg_completed_qty) * flt(qty_consumed_per_unit);
|
||||||
|
const pending_qty = required_qty - (flt(stock_qty) + total_selected_qty);
|
||||||
|
|
||||||
|
const pending_qty_fields = [
|
||||||
|
{ fieldtype: 'Section Break', label: __('Pending Quantity') },
|
||||||
|
{
|
||||||
|
fieldname: 'required_qty',
|
||||||
|
read_only: 1,
|
||||||
|
fieldtype: 'Float',
|
||||||
|
label: __('Required Qty'),
|
||||||
|
default: required_qty
|
||||||
|
},
|
||||||
|
{ fieldtype: 'Column Break' },
|
||||||
|
{
|
||||||
|
fieldname: 'total_selected_qty',
|
||||||
|
read_only: 1,
|
||||||
|
fieldtype: 'Float',
|
||||||
|
label: __('Total Selected Qty'),
|
||||||
|
default: total_selected_qty
|
||||||
|
},
|
||||||
|
{ fieldtype: 'Column Break' },
|
||||||
|
{
|
||||||
|
fieldname: 'pending_qty',
|
||||||
|
read_only: 1,
|
||||||
|
fieldtype: 'Float',
|
||||||
|
label: __('Pending Qty'),
|
||||||
|
default: pending_qty
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return pending_qty_fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calc_total_selected_qty(me) {
|
||||||
|
const { frm: { doc: { items }}, item: { name, item_code }} = me;
|
||||||
|
const totalSelectedQty = items
|
||||||
|
.filter( item => ( item.name !== name ) && ( item.item_code === item_code ) )
|
||||||
|
.map( item => flt(item.qty) )
|
||||||
|
.reduce( (i, j) => i + j, 0);
|
||||||
|
return totalSelectedQty;
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_can_calculate_pending_qty(me) {
|
||||||
|
const { frm: { doc }, item } = me;
|
||||||
|
const docChecks = doc.bom_no
|
||||||
|
&& doc.fg_completed_qty
|
||||||
|
&& erpnext.stock.bom
|
||||||
|
&& erpnext.stock.bom.name === doc.bom_no;
|
||||||
|
const itemChecks = !!item;
|
||||||
|
return docChecks && itemChecks;
|
||||||
|
}
|
||||||
|
@ -129,11 +129,20 @@
|
|||||||
@extend .pointer-no-select;
|
@extend .pointer-no-select;
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: var(--shadow-base);
|
box-shadow: var(--shadow-base);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.02, 1.02);
|
transform: scale(1.02, 1.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-qty-pill {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
margin: var(--margin-sm);
|
||||||
|
justify-content: flex-end;
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.item-display {
|
.item-display {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -766,9 +775,10 @@
|
|||||||
> .payment-modes {
|
> .payment-modes {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-bottom: var(--padding-sm);
|
padding-bottom: var(--padding-sm);
|
||||||
margin-bottom: var(--margin-xs);
|
margin-bottom: var(--margin-sm);
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
> .payment-mode-wrapper {
|
> .payment-mode-wrapper {
|
||||||
min-width: 40%;
|
min-width: 40%;
|
||||||
@ -825,9 +835,24 @@
|
|||||||
> .fields-numpad-container {
|
> .fields-numpad-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
> .fields-section {
|
> .fields-section {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
padding-bottom: var(--margin-md);
|
||||||
|
|
||||||
|
.invoice-fields {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .number-pad {
|
> .number-pad {
|
||||||
@ -835,6 +860,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
max-width: 50%;
|
||||||
|
|
||||||
.numpad-container {
|
.numpad-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -861,6 +887,7 @@
|
|||||||
margin-bottom: var(--margin-sm);
|
margin-bottom: var(--margin-sm);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
> .totals {
|
> .totals {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -172,7 +172,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>(A) {{__("ITC Available (whether in full op part)")}}</b></td>
|
<td><b>(A) {{__("ITC Available (whether in full or part)")}}</b></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
@ -3,148 +3,21 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import os
|
||||||
|
import json
|
||||||
import frappe
|
import frappe
|
||||||
|
from six import iteritems
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
import json
|
from frappe.utils import flt, cstr
|
||||||
from six import iteritems
|
|
||||||
from frappe.utils import flt, getdate
|
|
||||||
from erpnext.regional.india import state_numbers
|
from erpnext.regional.india import state_numbers
|
||||||
|
|
||||||
class GSTR3BReport(Document):
|
class GSTR3BReport(Document):
|
||||||
def before_save(self):
|
def validate(self):
|
||||||
|
|
||||||
self.get_data()
|
self.get_data()
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
|
self.report_dict = json.loads(get_json('gstr_3b_report_template'))
|
||||||
self.report_dict = {
|
|
||||||
"gstin": "",
|
|
||||||
"ret_period": "",
|
|
||||||
"inward_sup": {
|
|
||||||
"isup_details": [
|
|
||||||
{
|
|
||||||
"ty": "GST",
|
|
||||||
"intra": 0,
|
|
||||||
"inter": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "NONGST",
|
|
||||||
"inter": 0,
|
|
||||||
"intra": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sup_details": {
|
|
||||||
"osup_zero": {
|
|
||||||
"csamt": 0,
|
|
||||||
"txval": 0,
|
|
||||||
"iamt": 0
|
|
||||||
},
|
|
||||||
"osup_nil_exmp": {
|
|
||||||
"txval": 0
|
|
||||||
},
|
|
||||||
"osup_det": {
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0,
|
|
||||||
"txval": 0,
|
|
||||||
"camt": 0,
|
|
||||||
"iamt": 0
|
|
||||||
},
|
|
||||||
"isup_rev": {
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0,
|
|
||||||
"txval": 0,
|
|
||||||
"camt": 0,
|
|
||||||
"iamt": 0
|
|
||||||
},
|
|
||||||
"osup_nongst": {
|
|
||||||
"txval": 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"inter_sup": {
|
|
||||||
"unreg_details": [],
|
|
||||||
"comp_details": [],
|
|
||||||
"uin_details": []
|
|
||||||
},
|
|
||||||
"itc_elg": {
|
|
||||||
"itc_avl": [
|
|
||||||
{
|
|
||||||
"csamt": 0,
|
|
||||||
"samt": 0,
|
|
||||||
"ty": "IMPG",
|
|
||||||
"camt": 0,
|
|
||||||
"iamt": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"csamt": 0,
|
|
||||||
"samt": 0,
|
|
||||||
"ty": "IMPS",
|
|
||||||
"camt": 0,
|
|
||||||
"iamt": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0,
|
|
||||||
"ty": "ISRC",
|
|
||||||
"camt": 0,
|
|
||||||
"iamt": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "ISD",
|
|
||||||
"iamt": 0,
|
|
||||||
"camt": 0,
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0,
|
|
||||||
"ty": "OTH",
|
|
||||||
"camt": 0,
|
|
||||||
"iamt": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"itc_rev": [
|
|
||||||
{
|
|
||||||
"ty": "RUL",
|
|
||||||
"iamt": 0,
|
|
||||||
"camt": 0,
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "OTH",
|
|
||||||
"iamt": 0,
|
|
||||||
"camt": 0,
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"itc_net": {
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0,
|
|
||||||
"camt": 0,
|
|
||||||
"iamt": 0
|
|
||||||
},
|
|
||||||
"itc_inelg": [
|
|
||||||
{
|
|
||||||
"ty": "RUL",
|
|
||||||
"iamt": 0,
|
|
||||||
"camt": 0,
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "OTH",
|
|
||||||
"iamt": 0,
|
|
||||||
"camt": 0,
|
|
||||||
"samt": 0,
|
|
||||||
"csamt": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.gst_details = self.get_company_gst_details()
|
self.gst_details = self.get_company_gst_details()
|
||||||
self.report_dict["gstin"] = self.gst_details.get("gstin")
|
self.report_dict["gstin"] = self.gst_details.get("gstin")
|
||||||
@ -152,23 +25,19 @@ class GSTR3BReport(Document):
|
|||||||
self.month_no = get_period(self.month)
|
self.month_no = get_period(self.month)
|
||||||
self.account_heads = self.get_account_heads()
|
self.account_heads = self.get_account_heads()
|
||||||
|
|
||||||
outward_supply_tax_amounts = self.get_tax_amounts("Sales Invoice")
|
self.get_outward_supply_details("Sales Invoice")
|
||||||
inward_supply_tax_amounts = self.get_tax_amounts("Purchase Invoice", reverse_charge="Y")
|
self.set_outward_taxable_supplies()
|
||||||
|
|
||||||
|
self.get_outward_supply_details("Purchase Invoice", reverse_charge=True)
|
||||||
|
self.set_supplies_liable_to_reverse_charge()
|
||||||
|
|
||||||
itc_details = self.get_itc_details()
|
itc_details = self.get_itc_details()
|
||||||
|
|
||||||
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"])
|
|
||||||
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"])
|
|
||||||
self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y")
|
|
||||||
self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2)
|
|
||||||
self.set_itc_details(itc_details)
|
self.set_itc_details(itc_details)
|
||||||
|
self.get_itc_reversal_entries()
|
||||||
inter_state_supplies = self.get_inter_state_supplies(self.gst_details.get("gst_state_number"))
|
|
||||||
inward_nil_exempt = self.get_inward_nil_exempt(self.gst_details.get("gst_state"))
|
inward_nil_exempt = self.get_inward_nil_exempt(self.gst_details.get("gst_state"))
|
||||||
self.set_inter_state_supply(inter_state_supplies)
|
|
||||||
self.set_inward_nil_exempt(inward_nil_exempt)
|
self.set_inward_nil_exempt(inward_nil_exempt)
|
||||||
|
|
||||||
self.missing_field_invoices = self.get_missing_field_invoices()
|
self.missing_field_invoices = self.get_missing_field_invoices()
|
||||||
|
|
||||||
self.json_output = frappe.as_json(self.report_dict)
|
self.json_output = frappe.as_json(self.report_dict)
|
||||||
|
|
||||||
def set_inward_nil_exempt(self, inward_nil_exempt):
|
def set_inward_nil_exempt(self, inward_nil_exempt):
|
||||||
@ -178,189 +47,95 @@ class GSTR3BReport(Document):
|
|||||||
self.report_dict["inward_sup"]["isup_details"][1]["intra"] = flt(inward_nil_exempt.get("non_gst").get("intra"), 2)
|
self.report_dict["inward_sup"]["isup_details"][1]["intra"] = flt(inward_nil_exempt.get("non_gst").get("intra"), 2)
|
||||||
|
|
||||||
def set_itc_details(self, itc_details):
|
def set_itc_details(self, itc_details):
|
||||||
|
itc_eligible_type_map = {
|
||||||
itc_type_map = {
|
|
||||||
'IMPG': 'Import Of Capital Goods',
|
'IMPG': 'Import Of Capital Goods',
|
||||||
'IMPS': 'Import Of Service',
|
'IMPS': 'Import Of Service',
|
||||||
|
'ISRC': 'ITC on Reverse Charge',
|
||||||
'ISD': 'Input Service Distributor',
|
'ISD': 'Input Service Distributor',
|
||||||
'OTH': 'All Other ITC'
|
'OTH': 'All Other ITC'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itc_ineligible_map = {
|
||||||
|
'RUL': 'Ineligible As Per Section 17(5)',
|
||||||
|
'OTH': 'Ineligible Others'
|
||||||
|
}
|
||||||
|
|
||||||
net_itc = self.report_dict["itc_elg"]["itc_net"]
|
net_itc = self.report_dict["itc_elg"]["itc_net"]
|
||||||
|
|
||||||
for d in self.report_dict["itc_elg"]["itc_avl"]:
|
for d in self.report_dict["itc_elg"]["itc_avl"]:
|
||||||
|
itc_type = itc_eligible_type_map.get(d["ty"])
|
||||||
itc_type = itc_type_map.get(d["ty"])
|
|
||||||
|
|
||||||
if d["ty"] == 'ISRC':
|
|
||||||
reverse_charge = ["Y"]
|
|
||||||
itc_type = 'All Other ITC'
|
|
||||||
gst_category = ['Unregistered', 'Overseas']
|
|
||||||
else:
|
|
||||||
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
|
|
||||||
reverse_charge = ["N", "Y"]
|
|
||||||
|
|
||||||
for account_head in self.account_heads:
|
|
||||||
for category in gst_category:
|
|
||||||
for charge_type in reverse_charge:
|
|
||||||
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
|
|
||||||
d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2)
|
|
||||||
|
|
||||||
for key in ['iamt', 'camt', 'samt', 'csamt']:
|
for key in ['iamt', 'camt', 'samt', 'csamt']:
|
||||||
|
d[key] = flt(itc_details.get(itc_type, {}).get(key))
|
||||||
net_itc[key] += flt(d[key], 2)
|
net_itc[key] += flt(d[key], 2)
|
||||||
|
|
||||||
for account_head in self.account_heads:
|
for d in self.report_dict["itc_elg"]["itc_inelg"]:
|
||||||
itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1]
|
itc_type = itc_ineligible_map.get(d["ty"])
|
||||||
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
|
for key in ['iamt', 'camt', 'samt', 'csamt']:
|
||||||
itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2)
|
d[key] = flt(itc_details.get(itc_type, {}).get(key))
|
||||||
|
|
||||||
def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"):
|
def get_itc_reversal_entries(self):
|
||||||
|
reversal_entries = frappe.db.sql("""
|
||||||
|
SELECT ja.account, j.reversal_type, sum(credit_in_account_currency) as amount
|
||||||
|
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
|
||||||
|
where j.docstatus = 1
|
||||||
|
and j.is_opening = 'No'
|
||||||
|
and ja.parent = j.name
|
||||||
|
and j.voucher_type = 'Reversal Of ITC'
|
||||||
|
and month(j.posting_date) = %s and year(j.posting_date) = %s
|
||||||
|
and j.company = %s and j.company_gstin = %s
|
||||||
|
GROUP BY ja.account, j.reversal_type""", (self.month_no, self.year, self.company,
|
||||||
|
self.gst_details.get("gstin")), as_dict=1)
|
||||||
|
|
||||||
account_map = {
|
net_itc = self.report_dict["itc_elg"]["itc_net"]
|
||||||
'sgst_account': 'samt',
|
|
||||||
'cess_account': 'csamt',
|
|
||||||
'cgst_account': 'camt',
|
|
||||||
'igst_account': 'iamt'
|
|
||||||
}
|
|
||||||
|
|
||||||
txval = 0
|
for entry in reversal_entries:
|
||||||
total_taxable_value = self.get_total_taxable_value(doctype, reverse_charge)
|
if entry.reversal_type == 'As per rules 42 & 43 of CGST Rules':
|
||||||
|
index = 0
|
||||||
|
else:
|
||||||
|
index = 1
|
||||||
|
|
||||||
for gst_category in gst_category_list:
|
for key in ['camt', 'samt', 'iamt', 'csamt']:
|
||||||
txval += total_taxable_value.get(gst_category,0)
|
if entry.account in self.account_heads.get(key):
|
||||||
for account_head in self.account_heads:
|
self.report_dict["itc_elg"]["itc_rev"][index][key] += flt(entry.amount)
|
||||||
for account_type, account_name in iteritems(account_head):
|
net_itc[key] -= flt(entry.amount)
|
||||||
if account_map.get(account_type) in self.report_dict.get(supply_type).get(supply_category):
|
|
||||||
self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \
|
|
||||||
flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2)
|
|
||||||
|
|
||||||
self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2)
|
|
||||||
|
|
||||||
def set_inter_state_supply(self, inter_state_supply):
|
|
||||||
osup_det = self.report_dict["sup_details"]["osup_det"]
|
|
||||||
|
|
||||||
for key, value in iteritems(inter_state_supply):
|
|
||||||
if key[0] == "Unregistered":
|
|
||||||
self.report_dict["inter_sup"]["unreg_details"].append(value)
|
|
||||||
|
|
||||||
if key[0] == "Registered Composition":
|
|
||||||
self.report_dict["inter_sup"]["comp_details"].append(value)
|
|
||||||
|
|
||||||
if key[0] == "UIN Holders":
|
|
||||||
self.report_dict["inter_sup"]["uin_details"].append(value)
|
|
||||||
|
|
||||||
def get_total_taxable_value(self, doctype, reverse_charge):
|
|
||||||
|
|
||||||
return frappe._dict(frappe.db.sql("""
|
|
||||||
select gst_category, sum(net_total) as total
|
|
||||||
from `tab{doctype}`
|
|
||||||
where docstatus = 1 and month(posting_date) = %s
|
|
||||||
and year(posting_date) = %s and reverse_charge = %s
|
|
||||||
and company = %s and company_gstin = %s
|
|
||||||
group by gst_category
|
|
||||||
""" #nosec
|
|
||||||
.format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin"))))
|
|
||||||
|
|
||||||
def get_itc_details(self):
|
def get_itc_details(self):
|
||||||
itc_amount = frappe.db.sql("""
|
itc_amounts = frappe.db.sql("""
|
||||||
select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount,
|
SELECT eligibility_for_itc, sum(itc_integrated_tax) as itc_integrated_tax,
|
||||||
t.account_head, s.eligibility_for_itc, s.reverse_charge
|
sum(itc_central_tax) as itc_central_tax,
|
||||||
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
|
sum(itc_state_tax) as itc_state_tax,
|
||||||
where s.docstatus = 1 and t.parent = s.name
|
sum(itc_cess_amount) as itc_cess_amount
|
||||||
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
|
FROM `tabPurchase Invoice`
|
||||||
and s.company_gstin = %s
|
WHERE docstatus = 1
|
||||||
group by t.account_head, s.gst_category, s.eligibility_for_itc
|
and is_opening = 'No'
|
||||||
""",
|
and month(posting_date) = %s and year(posting_date) = %s and company = %s
|
||||||
(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
and company_gstin = %s
|
||||||
|
GROUP BY eligibility_for_itc
|
||||||
|
""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||||
|
|
||||||
itc_details = {}
|
itc_details = {}
|
||||||
|
for d in itc_amounts:
|
||||||
for d in itc_amount:
|
itc_details.setdefault(d.eligibility_for_itc, {
|
||||||
itc_details.setdefault((d.gst_category, d.eligibility_for_itc, d.reverse_charge, d.account_head),{
|
'iamt': d.itc_integrated_tax,
|
||||||
"amount": d.tax_amount
|
'camt': d.itc_central_tax,
|
||||||
|
'samt': d.itc_state_tax,
|
||||||
|
'csamt': d.itc_cess_amount
|
||||||
})
|
})
|
||||||
|
|
||||||
return itc_details
|
return itc_details
|
||||||
|
|
||||||
def get_nil_rated_supply_value(self):
|
|
||||||
|
|
||||||
return frappe.db.sql("""
|
|
||||||
select sum(i.base_amount) as total from
|
|
||||||
`tabSales Invoice Item` i, `tabSales Invoice` s
|
|
||||||
where s.docstatus = 1 and i.parent = s.name and i.is_nil_exempt = 1
|
|
||||||
and month(s.posting_date) = %s and year(s.posting_date) = %s
|
|
||||||
and s.company = %s and s.company_gstin = %s""",
|
|
||||||
(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)[0].total
|
|
||||||
|
|
||||||
def get_inter_state_supplies(self, state_number):
|
|
||||||
inter_state_supply_tax = frappe.db.sql(""" select t.account_head, t.tax_amount_after_discount_amount as tax_amount,
|
|
||||||
s.name, s.net_total, s.place_of_supply, s.gst_category from `tabSales Invoice` s, `tabSales Taxes and Charges` t
|
|
||||||
where t.parent = s.name and s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s
|
|
||||||
and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders')
|
|
||||||
""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
|
||||||
|
|
||||||
inter_state_supply_tax_mapping = {}
|
|
||||||
inter_state_supply_details = {}
|
|
||||||
|
|
||||||
for d in inter_state_supply_tax:
|
|
||||||
inter_state_supply_tax_mapping.setdefault(d.name, {
|
|
||||||
'place_of_supply': d.place_of_supply,
|
|
||||||
'taxable_value': d.net_total,
|
|
||||||
'gst_category': d.gst_category,
|
|
||||||
'camt': 0.0,
|
|
||||||
'samt': 0.0,
|
|
||||||
'iamt': 0.0,
|
|
||||||
'csamt': 0.0
|
|
||||||
})
|
|
||||||
|
|
||||||
if d.account_head in [a.cgst_account for a in self.account_heads]:
|
|
||||||
inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount
|
|
||||||
|
|
||||||
if d.account_head in [a.sgst_account for a in self.account_heads]:
|
|
||||||
inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount
|
|
||||||
|
|
||||||
if d.account_head in [a.igst_account for a in self.account_heads]:
|
|
||||||
inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount
|
|
||||||
|
|
||||||
if d.account_head in [a.cess_account for a in self.account_heads]:
|
|
||||||
inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount
|
|
||||||
|
|
||||||
for key, value in iteritems(inter_state_supply_tax_mapping):
|
|
||||||
if value.get('place_of_supply'):
|
|
||||||
osup_det = self.report_dict["sup_details"]["osup_det"]
|
|
||||||
osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2)
|
|
||||||
osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2)
|
|
||||||
osup_det["camt"] = flt(osup_det["camt"] + value['camt'], 2)
|
|
||||||
osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2)
|
|
||||||
osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2)
|
|
||||||
|
|
||||||
if state_number != value.get('place_of_supply').split("-")[0]:
|
|
||||||
inter_state_supply_details.setdefault((value.get('gst_category'), value.get('place_of_supply')), {
|
|
||||||
"txval": 0.0,
|
|
||||||
"pos": value.get('place_of_supply').split("-")[0],
|
|
||||||
"iamt": 0.0
|
|
||||||
})
|
|
||||||
|
|
||||||
inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['txval'] += value['taxable_value']
|
|
||||||
inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['iamt'] += value['iamt']
|
|
||||||
|
|
||||||
return inter_state_supply_details
|
|
||||||
|
|
||||||
def get_inward_nil_exempt(self, state):
|
def get_inward_nil_exempt(self, state):
|
||||||
inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount,
|
inward_nil_exempt = frappe.db.sql("""
|
||||||
i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
|
SELECT p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst
|
||||||
where p.docstatus = 1 and p.name = i.parent
|
FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
|
||||||
|
WHERE p.docstatus = 1 and p.name = i.parent
|
||||||
|
and p.is_opening = 'No'
|
||||||
and p.gst_category != 'Registered Composition'
|
and p.gst_category != 'Registered Composition'
|
||||||
and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and
|
and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and
|
||||||
month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s
|
month(p.posting_date) = %s and year(p.posting_date) = %s
|
||||||
group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
and p.company = %s and p.company_gstin = %s
|
||||||
|
GROUP BY p.place_of_supply, i.is_nil_exempt, i.is_non_gst""",
|
||||||
inward_nil_exempt += frappe.db.sql("""SELECT sum(base_net_total) as base_amount, gst_category, place_of_supply
|
(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||||
FROM `tabPurchase Invoice`
|
|
||||||
WHERE docstatus = 1 and gst_category = 'Registered Composition'
|
|
||||||
and month(posting_date) = %s and year(posting_date) = %s
|
|
||||||
and company = %s and company_gstin = %s
|
|
||||||
group by place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
|
||||||
|
|
||||||
inward_nil_exempt_details = {
|
inward_nil_exempt_details = {
|
||||||
"gst": {
|
"gst": {
|
||||||
@ -388,37 +163,193 @@ class GSTR3BReport(Document):
|
|||||||
|
|
||||||
return inward_nil_exempt_details
|
return inward_nil_exempt_details
|
||||||
|
|
||||||
def get_tax_amounts(self, doctype, reverse_charge="N"):
|
def get_outward_supply_details(self, doctype, reverse_charge=None):
|
||||||
|
self.get_outward_tax_invoices(doctype, reverse_charge=reverse_charge)
|
||||||
|
self.get_outward_items(doctype)
|
||||||
|
self.get_outward_tax_details(doctype)
|
||||||
|
|
||||||
|
def get_outward_tax_invoices(self, doctype, reverse_charge=None):
|
||||||
|
self.invoices = []
|
||||||
|
self.invoice_detail_map = {}
|
||||||
|
condition = ''
|
||||||
|
|
||||||
|
if reverse_charge:
|
||||||
|
condition += "AND reverse_charge = 'Y'"
|
||||||
|
|
||||||
|
invoice_details = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
name, gst_category, export_type, place_of_supply
|
||||||
|
FROM
|
||||||
|
`tab{doctype}`
|
||||||
|
WHERE
|
||||||
|
docstatus = 1
|
||||||
|
AND month(posting_date) = %s
|
||||||
|
AND year(posting_date) = %s
|
||||||
|
AND company = %s
|
||||||
|
AND company_gstin = %s
|
||||||
|
AND is_opening = 'No'
|
||||||
|
{reverse_charge}
|
||||||
|
ORDER BY name
|
||||||
|
""".format(doctype=doctype, reverse_charge=condition), (self.month_no, self.year,
|
||||||
|
self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||||
|
|
||||||
|
for d in invoice_details:
|
||||||
|
self.invoice_detail_map.setdefault(d.name, d)
|
||||||
|
self.invoices.append(d.name)
|
||||||
|
|
||||||
|
def get_outward_items(self, doctype):
|
||||||
|
self.invoice_items = frappe._dict()
|
||||||
|
self.is_nil_exempt = []
|
||||||
|
self.is_non_gst = []
|
||||||
|
|
||||||
|
if self.get('invoices'):
|
||||||
|
item_details = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
item_code, parent, taxable_value, base_net_amount, item_tax_rate,
|
||||||
|
is_nil_exempt, is_non_gst
|
||||||
|
FROM
|
||||||
|
`tab%s Item`
|
||||||
|
WHERE parent in (%s)
|
||||||
|
""" % (doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
||||||
|
|
||||||
|
for d in item_details:
|
||||||
|
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
||||||
|
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
|
||||||
|
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details
|
||||||
|
if i.item_code == d.item_code and i.parent == d.parent))
|
||||||
|
|
||||||
|
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
|
||||||
|
self.is_nil_exempt.append(d.item_code)
|
||||||
|
|
||||||
|
if d.is_non_gst and d.item_code not in self.is_non_gst:
|
||||||
|
self.is_non_gst.append(d.item_code)
|
||||||
|
|
||||||
|
def get_outward_tax_details(self, doctype):
|
||||||
if doctype == "Sales Invoice":
|
if doctype == "Sales Invoice":
|
||||||
tax_template = 'Sales Taxes and Charges'
|
tax_template = 'Sales Taxes and Charges'
|
||||||
elif doctype == "Purchase Invoice":
|
elif doctype == "Purchase Invoice":
|
||||||
tax_template = 'Purchase Taxes and Charges'
|
tax_template = 'Purchase Taxes and Charges'
|
||||||
|
|
||||||
tax_amounts = frappe.db.sql("""
|
self.items_based_on_tax_rate = {}
|
||||||
select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head
|
self.invoice_cess = frappe._dict()
|
||||||
from `tab{doctype}` s , `tab{template}` t
|
self.cgst_sgst_invoices = []
|
||||||
where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
|
|
||||||
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
|
|
||||||
and s.company_gstin = %s
|
|
||||||
group by t.account_head, s.gst_category
|
|
||||||
""" #nosec
|
|
||||||
.format(doctype=doctype, template=tax_template),
|
|
||||||
(reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
|
||||||
|
|
||||||
tax_details = {}
|
if self.get('invoices'):
|
||||||
|
tax_details = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
|
||||||
|
FROM `tab%s`
|
||||||
|
WHERE
|
||||||
|
parenttype = %s and docstatus = 1
|
||||||
|
and parent in (%s)
|
||||||
|
ORDER BY account_head
|
||||||
|
""" % (tax_template, '%s', ', '.join(['%s']*len(self.invoices))),
|
||||||
|
tuple([doctype] + list(self.invoices)))
|
||||||
|
|
||||||
for d in tax_amounts:
|
for parent, account, item_wise_tax_detail, tax_amount in tax_details:
|
||||||
tax_details.setdefault(
|
if account in self.account_heads.get('csamt'):
|
||||||
(d.account_head,d.gst_category),{
|
self.invoice_cess.setdefault(parent, tax_amount)
|
||||||
"amount": d.get("tax_amount"),
|
else:
|
||||||
}
|
if item_wise_tax_detail:
|
||||||
)
|
try:
|
||||||
|
item_wise_tax_detail = json.loads(item_wise_tax_detail)
|
||||||
|
cgst_or_sgst = False
|
||||||
|
if account in self.account_heads.get('camt') \
|
||||||
|
or account in self.account_heads.get('samt'):
|
||||||
|
cgst_or_sgst = True
|
||||||
|
|
||||||
return tax_details
|
for item_code, tax_amounts in item_wise_tax_detail.items():
|
||||||
|
if not (cgst_or_sgst or account in self.account_heads.get('iamt') or
|
||||||
|
(item_code in self.is_non_gst + self.is_nil_exempt)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
tax_rate = tax_amounts[0]
|
||||||
|
if tax_rate:
|
||||||
|
if cgst_or_sgst:
|
||||||
|
tax_rate *= 2
|
||||||
|
if parent not in self.cgst_sgst_invoices:
|
||||||
|
self.cgst_sgst_invoices.append(parent)
|
||||||
|
|
||||||
|
rate_based_dict = self.items_based_on_tax_rate\
|
||||||
|
.setdefault(parent, {}).setdefault(tax_rate, [])
|
||||||
|
if item_code not in rate_based_dict:
|
||||||
|
rate_based_dict.append(item_code)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
if self.get('invoice_items'):
|
||||||
|
# Build itemised tax for export invoices, nil and exempted where tax table is blank
|
||||||
|
for invoice, items in iteritems(self.invoice_items):
|
||||||
|
if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')
|
||||||
|
== "Without Payment of Tax"):
|
||||||
|
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||||
|
|
||||||
|
def set_outward_taxable_supplies(self):
|
||||||
|
inter_state_supply_details = {}
|
||||||
|
|
||||||
|
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||||
|
for rate, items in items_based_on_rate.items():
|
||||||
|
for item_code, taxable_value in self.invoice_items.get(inv).items():
|
||||||
|
if item_code in items:
|
||||||
|
if item_code in self.is_nil_exempt:
|
||||||
|
self.report_dict['sup_details']['osup_nil_exmp']['txval'] += taxable_value
|
||||||
|
elif item_code in self.is_non_gst:
|
||||||
|
self.report_dict['sup_details']['osup_nongst']['txval'] += taxable_value
|
||||||
|
elif rate == 0:
|
||||||
|
self.report_dict['sup_details']['osup_zero']['txval'] += taxable_value
|
||||||
|
#self.report_dict['sup_details']['osup_zero'][key] += tax_amount
|
||||||
|
else:
|
||||||
|
if inv in self.cgst_sgst_invoices:
|
||||||
|
tax_rate = rate/2
|
||||||
|
self.report_dict['sup_details']['osup_det']['camt'] += (taxable_value * tax_rate /100)
|
||||||
|
self.report_dict['sup_details']['osup_det']['samt'] += (taxable_value * tax_rate /100)
|
||||||
|
self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
|
||||||
|
else:
|
||||||
|
self.report_dict['sup_details']['osup_det']['iamt'] += (taxable_value * rate /100)
|
||||||
|
self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
|
||||||
|
|
||||||
|
gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category')
|
||||||
|
place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply', '00-Other Territory')
|
||||||
|
|
||||||
|
if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \
|
||||||
|
self.gst_details.get("gst_state") != place_of_supply.split("-")[1]:
|
||||||
|
inter_state_supply_details.setdefault((gst_category, place_of_supply), {
|
||||||
|
"txval": 0.0,
|
||||||
|
"pos": place_of_supply.split("-")[0],
|
||||||
|
"iamt": 0.0
|
||||||
|
})
|
||||||
|
inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value
|
||||||
|
inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100)
|
||||||
|
|
||||||
|
self.set_inter_state_supply(inter_state_supply_details)
|
||||||
|
|
||||||
|
def set_supplies_liable_to_reverse_charge(self):
|
||||||
|
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||||
|
for rate, items in items_based_on_rate.items():
|
||||||
|
for item_code, taxable_value in self.invoice_items.get(inv).items():
|
||||||
|
if item_code in items:
|
||||||
|
if inv in self.cgst_sgst_invoices:
|
||||||
|
tax_rate = rate/2
|
||||||
|
self.report_dict['sup_details']['isup_rev']['camt'] += (taxable_value * tax_rate /100)
|
||||||
|
self.report_dict['sup_details']['isup_rev']['samt'] += (taxable_value * tax_rate /100)
|
||||||
|
self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value
|
||||||
|
else:
|
||||||
|
self.report_dict['sup_details']['isup_rev']['iamt'] += (taxable_value * rate /100)
|
||||||
|
self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value
|
||||||
|
|
||||||
|
def set_inter_state_supply(self, inter_state_supply):
|
||||||
|
for key, value in iteritems(inter_state_supply):
|
||||||
|
if key[0] == "Unregistered":
|
||||||
|
self.report_dict["inter_sup"]["unreg_details"].append(value)
|
||||||
|
|
||||||
|
if key[0] == "Registered Composition":
|
||||||
|
self.report_dict["inter_sup"]["comp_details"].append(value)
|
||||||
|
|
||||||
|
if key[0] == "UIN Holders":
|
||||||
|
self.report_dict["inter_sup"]["uin_details"].append(value)
|
||||||
|
|
||||||
def get_company_gst_details(self):
|
def get_company_gst_details(self):
|
||||||
|
|
||||||
gst_details = frappe.get_all("Address",
|
gst_details = frappe.get_all("Address",
|
||||||
fields=["gstin", "gst_state", "gst_state_number"],
|
fields=["gstin", "gst_state", "gst_state_number"],
|
||||||
filters={
|
filters={
|
||||||
@ -431,20 +362,28 @@ class GSTR3BReport(Document):
|
|||||||
frappe.throw(_("Please enter GSTIN and state for the Company Address {0}").format(self.company_address))
|
frappe.throw(_("Please enter GSTIN and state for the Company Address {0}").format(self.company_address))
|
||||||
|
|
||||||
def get_account_heads(self):
|
def get_account_heads(self):
|
||||||
|
account_map = {
|
||||||
|
'sgst_account': 'samt',
|
||||||
|
'cess_account': 'csamt',
|
||||||
|
'cgst_account': 'camt',
|
||||||
|
'igst_account': 'iamt'
|
||||||
|
}
|
||||||
|
|
||||||
account_heads = frappe.get_all("GST Account",
|
account_heads = {}
|
||||||
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"],
|
gst_settings_accounts = frappe.get_all("GST Account",
|
||||||
filters={
|
filters={'company': self.company, 'is_reverse_charge_account': 0},
|
||||||
"company":self.company
|
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
|
||||||
})
|
|
||||||
|
|
||||||
if account_heads:
|
if not gst_settings_accounts:
|
||||||
return account_heads
|
frappe.throw(_("Please set GST Accounts in GST Settings"))
|
||||||
else:
|
|
||||||
frappe.throw(_("Please set account heads in GST Settings for Compnay {0}").format(self.company))
|
for d in gst_settings_accounts:
|
||||||
|
for acc, val in d.items():
|
||||||
|
account_heads.setdefault(account_map.get(acc), []).append(val)
|
||||||
|
|
||||||
|
return account_heads
|
||||||
|
|
||||||
def get_missing_field_invoices(self):
|
def get_missing_field_invoices(self):
|
||||||
|
|
||||||
missing_field_invoices = []
|
missing_field_invoices = []
|
||||||
|
|
||||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
@ -456,26 +395,32 @@ class GSTR3BReport(Document):
|
|||||||
party_type = 'Supplier'
|
party_type = 'Supplier'
|
||||||
party = 'supplier'
|
party = 'supplier'
|
||||||
|
|
||||||
docnames = frappe.db.sql("""
|
docnames = frappe.db.sql(
|
||||||
select t1.name from `tab{doctype}` t1, `tab{party_type}` t2
|
"""
|
||||||
where t1.docstatus = 1 and month(t1.posting_date) = %s and year(t1.posting_date) = %s
|
SELECT t1.name FROM `tab{doctype}` t1, `tab{party_type}` t2
|
||||||
|
WHERE t1.docstatus = 1 and t1.is_opening = 'No'
|
||||||
|
and month(t1.posting_date) = %s and year(t1.posting_date) = %s
|
||||||
and t1.company = %s and t1.place_of_supply IS NULL and t1.{party} = t2.name and
|
and t1.company = %s and t1.place_of_supply IS NULL and t1.{party} = t2.name and
|
||||||
t2.gst_category != 'Overseas'
|
t2.gst_category != 'Overseas'
|
||||||
""".format(doctype = doctype, party_type = party_type, party=party), (self.month_no, self.year, self.company), as_dict=1) #nosec
|
""".format(doctype = doctype, party_type = party_type,
|
||||||
|
party=party) ,(self.month_no, self.year, self.company), as_dict=1) #nosec
|
||||||
|
|
||||||
for d in docnames:
|
for d in docnames:
|
||||||
missing_field_invoices.append(d.name)
|
missing_field_invoices.append(d.name)
|
||||||
|
|
||||||
return ",".join(missing_field_invoices)
|
return ",".join(missing_field_invoices)
|
||||||
|
|
||||||
def get_state_code(state):
|
def get_json(template):
|
||||||
|
file_path = os.path.join(os.path.dirname(__file__), '{template}.json'.format(template=template))
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
return cstr(f.read())
|
||||||
|
|
||||||
|
def get_state_code(state):
|
||||||
state_code = state_numbers.get(state)
|
state_code = state_numbers.get(state)
|
||||||
|
|
||||||
return state_code
|
return state_code
|
||||||
|
|
||||||
def get_period(month, year=None):
|
def get_period(month, year=None):
|
||||||
|
|
||||||
month_no = {
|
month_no = {
|
||||||
"January": 1,
|
"January": 1,
|
||||||
"February": 2,
|
"February": 2,
|
||||||
@ -499,13 +444,11 @@ def get_period(month, year=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def view_report(name):
|
def view_report(name):
|
||||||
|
|
||||||
json_data = frappe.get_value("GSTR 3B Report", name, 'json_output')
|
json_data = frappe.get_value("GSTR 3B Report", name, 'json_output')
|
||||||
return json.loads(json_data)
|
return json.loads(json_data)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_json(name):
|
def make_json(name):
|
||||||
|
|
||||||
json_data = frappe.get_value("GSTR 3B Report", name, 'json_output')
|
json_data = frappe.get_value("GSTR 3B Report", name, 'json_output')
|
||||||
file_name = "GST3B.json"
|
file_name = "GST3B.json"
|
||||||
frappe.local.response.filename = file_name
|
frappe.local.response.filename = file_name
|
||||||
|
@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"gstin": "",
|
||||||
|
"ret_period": "",
|
||||||
|
"inward_sup": {
|
||||||
|
"isup_details": [
|
||||||
|
{
|
||||||
|
"ty": "GST",
|
||||||
|
"intra": 0,
|
||||||
|
"inter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ty": "NONGST",
|
||||||
|
"inter": 0,
|
||||||
|
"intra": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sup_details": {
|
||||||
|
"osup_zero": {
|
||||||
|
"csamt": 0,
|
||||||
|
"txval": 0,
|
||||||
|
"iamt": 0
|
||||||
|
},
|
||||||
|
"osup_nil_exmp": {
|
||||||
|
"txval": 0
|
||||||
|
},
|
||||||
|
"osup_det": {
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0,
|
||||||
|
"txval": 0,
|
||||||
|
"camt": 0,
|
||||||
|
"iamt": 0
|
||||||
|
},
|
||||||
|
"isup_rev": {
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0,
|
||||||
|
"txval": 0,
|
||||||
|
"camt": 0,
|
||||||
|
"iamt": 0
|
||||||
|
},
|
||||||
|
"osup_nongst": {
|
||||||
|
"txval": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inter_sup": {
|
||||||
|
"unreg_details": [],
|
||||||
|
"comp_details": [],
|
||||||
|
"uin_details": []
|
||||||
|
},
|
||||||
|
"itc_elg": {
|
||||||
|
"itc_avl": [
|
||||||
|
{
|
||||||
|
"csamt": 0,
|
||||||
|
"samt": 0,
|
||||||
|
"ty": "IMPG",
|
||||||
|
"camt": 0,
|
||||||
|
"iamt": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"csamt": 0,
|
||||||
|
"samt": 0,
|
||||||
|
"ty": "IMPS",
|
||||||
|
"camt": 0,
|
||||||
|
"iamt": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0,
|
||||||
|
"ty": "ISRC",
|
||||||
|
"camt": 0,
|
||||||
|
"iamt": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ty": "ISD",
|
||||||
|
"iamt": 0,
|
||||||
|
"camt": 0,
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0,
|
||||||
|
"ty": "OTH",
|
||||||
|
"camt": 0,
|
||||||
|
"iamt": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"itc_rev": [
|
||||||
|
{
|
||||||
|
"ty": "RUL",
|
||||||
|
"iamt": 0,
|
||||||
|
"camt": 0,
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ty": "OTH",
|
||||||
|
"iamt": 0,
|
||||||
|
"camt": 0,
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"itc_net": {
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0,
|
||||||
|
"camt": 0,
|
||||||
|
"iamt": 0
|
||||||
|
},
|
||||||
|
"itc_inelg": [
|
||||||
|
{
|
||||||
|
"ty": "RUL",
|
||||||
|
"iamt": 0,
|
||||||
|
"camt": 0,
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ty": "OTH",
|
||||||
|
"iamt": 0,
|
||||||
|
"camt": 0,
|
||||||
|
"samt": 0,
|
||||||
|
"csamt": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -60,8 +60,7 @@ class TestGSTR3BReport(unittest.TestCase):
|
|||||||
|
|
||||||
output = json.loads(report.json_output)
|
output = json.loads(report.json_output)
|
||||||
|
|
||||||
self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 36),
|
self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 54)
|
||||||
self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18),
|
|
||||||
self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18),
|
self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18),
|
||||||
self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100),
|
self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100),
|
||||||
self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250)
|
self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250)
|
||||||
|
@ -114,9 +114,12 @@ def add_print_formats():
|
|||||||
|
|
||||||
def make_property_setters(patch=False):
|
def make_property_setters(patch=False):
|
||||||
# GST rules do not allow for an invoice no. bigger than 16 characters
|
# GST rules do not allow for an invoice no. bigger than 16 characters
|
||||||
|
journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
|
||||||
|
|
||||||
if not patch:
|
if not patch:
|
||||||
make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
|
make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
|
||||||
make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
|
make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
|
||||||
|
make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
|
||||||
|
|
||||||
def make_custom_fields(update=True):
|
def make_custom_fields(update=True):
|
||||||
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
||||||
@ -198,15 +201,20 @@ def make_custom_fields(update=True):
|
|||||||
purchase_invoice_itc_fields = [
|
purchase_invoice_itc_fields = [
|
||||||
dict(fieldname='eligibility_for_itc', label='Eligibility For ITC',
|
dict(fieldname='eligibility_for_itc', label='Eligibility For ITC',
|
||||||
fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1,
|
fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1,
|
||||||
options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nIneligible\nAll Other ITC', default="All Other ITC"),
|
options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC',
|
||||||
|
default="All Other ITC"),
|
||||||
dict(fieldname='itc_integrated_tax', label='Availed ITC Integrated Tax',
|
dict(fieldname='itc_integrated_tax', label='Availed ITC Integrated Tax',
|
||||||
fieldtype='Data', insert_after='eligibility_for_itc', print_hide=1),
|
fieldtype='Currency', insert_after='eligibility_for_itc',
|
||||||
|
options='Company:company:default_currency', print_hide=1),
|
||||||
dict(fieldname='itc_central_tax', label='Availed ITC Central Tax',
|
dict(fieldname='itc_central_tax', label='Availed ITC Central Tax',
|
||||||
fieldtype='Data', insert_after='itc_integrated_tax', print_hide=1),
|
fieldtype='Currency', insert_after='itc_integrated_tax',
|
||||||
|
options='Company:company:default_currency', print_hide=1),
|
||||||
dict(fieldname='itc_state_tax', label='Availed ITC State/UT Tax',
|
dict(fieldname='itc_state_tax', label='Availed ITC State/UT Tax',
|
||||||
fieldtype='Data', insert_after='itc_central_tax', print_hide=1),
|
fieldtype='Currency', insert_after='itc_central_tax',
|
||||||
|
options='Company:company:default_currency', print_hide=1),
|
||||||
dict(fieldname='itc_cess_amount', label='Availed ITC Cess',
|
dict(fieldname='itc_cess_amount', label='Availed ITC Cess',
|
||||||
fieldtype='Data', insert_after='itc_state_tax', print_hide=1),
|
fieldtype='Currency', insert_after='itc_state_tax',
|
||||||
|
options='Company:company:default_currency', print_hide=1),
|
||||||
]
|
]
|
||||||
|
|
||||||
sales_invoice_gst_fields = [
|
sales_invoice_gst_fields = [
|
||||||
@ -236,6 +244,23 @@ def make_custom_fields(update=True):
|
|||||||
depends_on="eval:doc.gst_category=='Overseas' "),
|
depends_on="eval:doc.gst_category=='Overseas' "),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
journal_entry_fields = [
|
||||||
|
dict(fieldname='reversal_type', label='Reversal Type',
|
||||||
|
fieldtype='Select', insert_after='voucher_type', print_hide=1,
|
||||||
|
options="As per rules 42 & 43 of CGST Rules\nOthers",
|
||||||
|
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||||
|
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
|
||||||
|
dict(fieldname='company_address', label='Company Address',
|
||||||
|
fieldtype='Link', options='Address', insert_after='reversal_type',
|
||||||
|
print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||||
|
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
|
||||||
|
dict(fieldname='company_gstin', label='Company GSTIN',
|
||||||
|
fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1,
|
||||||
|
fetch_from='company_address.gstin',
|
||||||
|
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||||
|
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'")
|
||||||
|
]
|
||||||
|
|
||||||
inter_state_gst_field = [
|
inter_state_gst_field = [
|
||||||
dict(fieldname='is_inter_state', label='Is Inter State',
|
dict(fieldname='is_inter_state', label='Is Inter State',
|
||||||
fieldtype='Check', insert_after='disabled', print_hide=1),
|
fieldtype='Check', insert_after='disabled', print_hide=1),
|
||||||
@ -430,13 +455,13 @@ def make_custom_fields(update=True):
|
|||||||
|
|
||||||
dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
|
dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
|
||||||
print_hide=1, hidden=1),
|
print_hide=1, hidden=1),
|
||||||
|
|
||||||
dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
|
dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
|
||||||
no_copy=1, print_hide=1),
|
no_copy=1, print_hide=1),
|
||||||
|
|
||||||
dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
|
dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
|
||||||
|
|
||||||
dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
|
dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
|
||||||
no_copy=1, print_hide=1),
|
no_copy=1, print_hide=1),
|
||||||
|
|
||||||
dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
|
dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
|
||||||
@ -469,6 +494,7 @@ def make_custom_fields(update=True):
|
|||||||
'Purchase Receipt': purchase_invoice_gst_fields,
|
'Purchase Receipt': purchase_invoice_gst_fields,
|
||||||
'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
|
'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
|
||||||
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category,
|
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category,
|
||||||
|
'Journal Entry': journal_entry_fields,
|
||||||
'Sales Order': sales_invoice_gst_fields,
|
'Sales Order': sales_invoice_gst_fields,
|
||||||
'Tax Category': inter_state_gst_field,
|
'Tax Category': inter_state_gst_field,
|
||||||
'Item': [
|
'Item': [
|
||||||
@ -486,7 +512,7 @@ def make_custom_fields(update=True):
|
|||||||
'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||||
'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||||
'Material Request Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Material Request Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
'Salary Component': [
|
'Salary Component': [
|
||||||
dict(fieldname= 'component_type',
|
dict(fieldname= 'component_type',
|
||||||
|
@ -204,8 +204,6 @@ def get_regional_address_details(party_details, doctype, company):
|
|||||||
|
|
||||||
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
|
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
|
||||||
master_doctype = "Sales Taxes and Charges Template"
|
master_doctype = "Sales Taxes and Charges Template"
|
||||||
|
|
||||||
get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
|
|
||||||
get_tax_template_based_on_category(master_doctype, company, party_details)
|
get_tax_template_based_on_category(master_doctype, company, party_details)
|
||||||
|
|
||||||
if party_details.get('taxes_and_charges'):
|
if party_details.get('taxes_and_charges'):
|
||||||
@ -216,7 +214,6 @@ def get_regional_address_details(party_details, doctype, company):
|
|||||||
|
|
||||||
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
|
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
|
||||||
master_doctype = "Purchase Taxes and Charges Template"
|
master_doctype = "Purchase Taxes and Charges Template"
|
||||||
get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
|
|
||||||
get_tax_template_based_on_category(master_doctype, company, party_details)
|
get_tax_template_based_on_category(master_doctype, company, party_details)
|
||||||
|
|
||||||
if party_details.get('taxes_and_charges'):
|
if party_details.get('taxes_and_charges'):
|
||||||
@ -283,20 +280,6 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code):
|
|||||||
{'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name')
|
{'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name')
|
||||||
return default_tax
|
return default_tax
|
||||||
|
|
||||||
def get_tax_template_for_sez(party_details, master_doctype, company, party_type):
|
|
||||||
|
|
||||||
gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))},
|
|
||||||
['gst_category', 'export_type'], as_dict=1)
|
|
||||||
|
|
||||||
if gst_details:
|
|
||||||
if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax':
|
|
||||||
default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0,
|
|
||||||
"gst_state": number_state_mapping[party_details.company_gstin[:2]]})
|
|
||||||
|
|
||||||
party_details["taxes_and_charges"] = default_tax
|
|
||||||
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_annual_eligible_hra_exemption(doc):
|
def calculate_annual_eligible_hra_exemption(doc):
|
||||||
basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"])
|
basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"])
|
||||||
if not (basic_component and hra_component):
|
if not (basic_component and hra_component):
|
||||||
@ -697,10 +680,19 @@ def validate_state_code(state_code, address):
|
|||||||
return int(state_code)
|
return int(state_code)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_gst_accounts(company, account_wise=False):
|
def get_gst_accounts(company=None, account_wise=False, only_reverse_charge=0, only_non_reverse_charge=0):
|
||||||
|
filters={"parent": "GST Settings"}
|
||||||
|
|
||||||
|
if company:
|
||||||
|
filters.update({'company': company})
|
||||||
|
if only_reverse_charge:
|
||||||
|
filters.update({'is_reverse_charge_account': 1})
|
||||||
|
elif only_non_reverse_charge:
|
||||||
|
filters.update({'is_reverse_charge_account': 0})
|
||||||
|
|
||||||
gst_accounts = frappe._dict()
|
gst_accounts = frappe._dict()
|
||||||
gst_settings_accounts = frappe.get_all("GST Account",
|
gst_settings_accounts = frappe.get_all("GST Account",
|
||||||
filters={"parent": "GST Settings", "company": company},
|
filters=filters,
|
||||||
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
|
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
|
||||||
|
|
||||||
if not gst_settings_accounts and not frappe.flags.in_test:
|
if not gst_settings_accounts and not frappe.flags.in_test:
|
||||||
@ -715,101 +707,63 @@ def get_gst_accounts(company, account_wise=False):
|
|||||||
|
|
||||||
return gst_accounts
|
return gst_accounts
|
||||||
|
|
||||||
def update_grand_total_for_rcm(doc, method):
|
def validate_reverse_charge_transaction(doc, method):
|
||||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||||
|
|
||||||
if country != 'India':
|
if country != 'India':
|
||||||
return
|
return
|
||||||
|
|
||||||
gst_tax, base_gst_tax = get_gst_tax_amount(doc)
|
base_gst_tax = 0
|
||||||
|
base_reverse_charge_booked = 0
|
||||||
if not base_gst_tax:
|
|
||||||
return
|
|
||||||
|
|
||||||
if doc.reverse_charge == 'Y':
|
if doc.reverse_charge == 'Y':
|
||||||
doc.taxes_and_charges_added -= gst_tax
|
gst_accounts = get_gst_accounts(doc.company, only_reverse_charge=1)
|
||||||
doc.total_taxes_and_charges -= gst_tax
|
reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
|
||||||
doc.base_taxes_and_charges_added -= base_gst_tax
|
+ gst_accounts.get('igst_account')
|
||||||
doc.base_total_taxes_and_charges -= base_gst_tax
|
|
||||||
|
|
||||||
update_totals(gst_tax, base_gst_tax, doc)
|
gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1)
|
||||||
|
non_reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
|
||||||
def update_totals(gst_tax, base_gst_tax, doc):
|
|
||||||
doc.base_grand_total -= base_gst_tax
|
|
||||||
doc.grand_total -= gst_tax
|
|
||||||
|
|
||||||
if doc.meta.get_field("rounded_total"):
|
|
||||||
if doc.is_rounded_total_disabled():
|
|
||||||
doc.outstanding_amount = doc.grand_total
|
|
||||||
else:
|
|
||||||
doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total,
|
|
||||||
doc.currency, doc.precision("rounded_total"))
|
|
||||||
|
|
||||||
doc.rounding_adjustment += flt(doc.rounded_total - doc.grand_total,
|
|
||||||
doc.precision("rounding_adjustment"))
|
|
||||||
|
|
||||||
doc.outstanding_amount = doc.rounded_total or doc.grand_total
|
|
||||||
|
|
||||||
doc.in_words = money_in_words(doc.grand_total, doc.currency)
|
|
||||||
doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company))
|
|
||||||
doc.set_payment_schedule()
|
|
||||||
|
|
||||||
def make_regional_gl_entries(gl_entries, doc):
|
|
||||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
|
||||||
|
|
||||||
if country != 'India':
|
|
||||||
return gl_entries
|
|
||||||
|
|
||||||
gst_tax, base_gst_tax = get_gst_tax_amount(doc)
|
|
||||||
|
|
||||||
if not base_gst_tax:
|
|
||||||
return gl_entries
|
|
||||||
|
|
||||||
if doc.reverse_charge == 'Y':
|
|
||||||
gst_accounts = get_gst_accounts(doc.company)
|
|
||||||
gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
|
|
||||||
+ gst_accounts.get('igst_account')
|
+ gst_accounts.get('igst_account')
|
||||||
|
|
||||||
for tax in doc.get('taxes'):
|
for tax in doc.get('taxes'):
|
||||||
if tax.category not in ("Total", "Valuation and Total"):
|
if tax.account_head in non_reverse_charge_accounts:
|
||||||
continue
|
if tax.add_deduct_tax == 'Add':
|
||||||
|
base_gst_tax += tax.base_tax_amount_after_discount_amount
|
||||||
|
else:
|
||||||
|
base_gst_tax += tax.base_tax_amount_after_discount_amount
|
||||||
|
elif tax.account_head in reverse_charge_accounts:
|
||||||
|
if tax.add_deduct_tax == 'Add':
|
||||||
|
base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount
|
||||||
|
else:
|
||||||
|
base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount
|
||||||
|
|
||||||
dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
|
if base_gst_tax != base_reverse_charge_booked:
|
||||||
if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
|
msg = _("Booked reverse charge is not equal to applied tax amount")
|
||||||
account_currency = get_account_currency(tax.account_head)
|
msg += "<br>"
|
||||||
|
msg += _("Please refer {gst_document_link} to learn more about how to setup and create reverse charge invoice").format(
|
||||||
|
gst_document_link='<a href="https://docs.erpnext.com/docs/user/manual/en/regional/india/gst-setup">GST Documentation</a>')
|
||||||
|
|
||||||
gl_entries.append(doc.get_gl_dict(
|
frappe.throw(msg)
|
||||||
{
|
|
||||||
"account": tax.account_head,
|
|
||||||
"cost_center": tax.cost_center,
|
|
||||||
"posting_date": doc.posting_date,
|
|
||||||
"against": doc.supplier,
|
|
||||||
dr_or_cr: tax.base_tax_amount_after_discount_amount,
|
|
||||||
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
|
|
||||||
if account_currency==doc.company_currency \
|
|
||||||
else tax.tax_amount_after_discount_amount
|
|
||||||
}, account_currency, item=tax)
|
|
||||||
)
|
|
||||||
|
|
||||||
return gl_entries
|
def update_itc_availed_fields(doc, method):
|
||||||
|
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||||
|
|
||||||
def get_gst_tax_amount(doc):
|
if country != 'India':
|
||||||
gst_accounts = get_gst_accounts(doc.company)
|
return
|
||||||
gst_account_list = gst_accounts.get('cgst_account', []) + gst_accounts.get('sgst_account', []) \
|
|
||||||
+ gst_accounts.get('igst_account', [])
|
|
||||||
|
|
||||||
base_gst_tax = 0
|
# Initialize values
|
||||||
gst_tax = 0
|
doc.itc_integrated_tax = doc.itc_state_tax = doc.itc_central_tax = doc.itc_cess_amount = 0
|
||||||
|
gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1)
|
||||||
|
|
||||||
for tax in doc.get('taxes'):
|
for tax in doc.get('taxes'):
|
||||||
if tax.category not in ("Total", "Valuation and Total"):
|
if tax.account_head in gst_accounts.get('igst_account', []):
|
||||||
continue
|
doc.itc_integrated_tax += flt(tax.base_tax_amount_after_discount_amount)
|
||||||
|
if tax.account_head in gst_accounts.get('sgst_account', []):
|
||||||
if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
|
doc.itc_state_tax += flt(tax.base_tax_amount_after_discount_amount)
|
||||||
base_gst_tax += tax.base_tax_amount_after_discount_amount
|
if tax.account_head in gst_accounts.get('cgst_account', []):
|
||||||
gst_tax += tax.tax_amount_after_discount_amount
|
doc.itc_central_tax += flt(tax.base_tax_amount_after_discount_amount)
|
||||||
|
if tax.account_head in gst_accounts.get('cess_account', []):
|
||||||
return gst_tax, base_gst_tax
|
doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_regional_round_off_accounts(company, account_list):
|
def get_regional_round_off_accounts(company, account_list):
|
||||||
@ -879,3 +833,24 @@ def update_taxable_values(doc, method):
|
|||||||
if total_charges != additional_taxes:
|
if total_charges != additional_taxes:
|
||||||
diff = additional_taxes - total_charges
|
diff = additional_taxes - total_charges
|
||||||
doc.get('items')[item_count - 1].taxable_value += diff
|
doc.get('items')[item_count - 1].taxable_value += diff
|
||||||
|
|
||||||
|
def get_depreciation_amount(asset, depreciable_value, row):
|
||||||
|
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
||||||
|
|
||||||
|
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||||
|
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||||
|
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||||
|
else:
|
||||||
|
rate_of_depreciation = row.rate_of_depreciation
|
||||||
|
# if its the first depreciation
|
||||||
|
if depreciable_value == asset.gross_purchase_amount:
|
||||||
|
# as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2
|
||||||
|
diff = date_diff(asset.available_for_use_date, row.depreciation_start_date)
|
||||||
|
if diff <= 180:
|
||||||
|
rate_of_depreciation = rate_of_depreciation / 2
|
||||||
|
frappe.msgprint(
|
||||||
|
_('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.'))
|
||||||
|
|
||||||
|
depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
|
||||||
|
|
||||||
|
return depreciation_amount
|
@ -46,7 +46,13 @@ frappe.query_reports["GSTR-1"] = {
|
|||||||
"label": __("Type of Business"),
|
"label": __("Type of Business"),
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"],
|
"options": [
|
||||||
|
{ "value": "B2B", "label": __("B2B Invoices - 4A, 4B, 4C, 6B, 6C") },
|
||||||
|
{ "value": "B2C Large", "label": __("B2C(Large) Invoices - 5A, 5B") },
|
||||||
|
{ "value": "B2C Small", "label": __("B2C(Small) Invoices - 7") },
|
||||||
|
{ "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") },
|
||||||
|
{ "value": "EXPORT", "label": __("Export Invoice - 6A") }
|
||||||
|
],
|
||||||
"default": "B2B"
|
"default": "B2B"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -32,6 +32,7 @@ class Gstr1Report(object):
|
|||||||
reverse_charge,
|
reverse_charge,
|
||||||
return_against,
|
return_against,
|
||||||
is_return,
|
is_return,
|
||||||
|
is_debit_note,
|
||||||
gst_category,
|
gst_category,
|
||||||
export_type,
|
export_type,
|
||||||
port_code,
|
port_code,
|
||||||
@ -42,7 +43,7 @@ class Gstr1Report(object):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.get_columns()
|
self.get_columns()
|
||||||
self.gst_accounts = get_gst_accounts(self.filters.company)
|
self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1)
|
||||||
self.get_invoice_data()
|
self.get_invoice_data()
|
||||||
|
|
||||||
if self.invoices:
|
if self.invoices:
|
||||||
@ -62,9 +63,9 @@ class Gstr1Report(object):
|
|||||||
for rate, items in items_based_on_rate.items():
|
for rate, items in items_based_on_rate.items():
|
||||||
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
|
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
|
||||||
|
|
||||||
if self.filters.get("type_of_business") == "CDNR":
|
if self.filters.get("type_of_business") == "CDNR-REG":
|
||||||
row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
|
row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
|
||||||
row.append("C" if invoice_details.return_against else "R")
|
row.append("C" if invoice_details.is_return else "D")
|
||||||
|
|
||||||
if taxable_value:
|
if taxable_value:
|
||||||
self.data.append(row)
|
self.data.append(row)
|
||||||
@ -105,7 +106,7 @@ class Gstr1Report(object):
|
|||||||
def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items):
|
def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items):
|
||||||
row = []
|
row = []
|
||||||
for fieldname in self.invoice_fields:
|
for fieldname in self.invoice_fields:
|
||||||
if self.filters.get("type_of_business") == "CDNR" and fieldname == "invoice_value":
|
if self.filters.get("type_of_business") == "CDNR-REG" and fieldname == "invoice_value":
|
||||||
row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total))
|
row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total))
|
||||||
elif fieldname == "invoice_value":
|
elif fieldname == "invoice_value":
|
||||||
row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
|
row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
|
||||||
@ -171,7 +172,7 @@ class Gstr1Report(object):
|
|||||||
|
|
||||||
|
|
||||||
if self.filters.get("type_of_business") == "B2B":
|
if self.filters.get("type_of_business") == "B2B":
|
||||||
conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1"
|
conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1"
|
||||||
|
|
||||||
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
|
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
|
||||||
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
|
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
|
||||||
@ -179,19 +180,19 @@ class Gstr1Report(object):
|
|||||||
frappe.throw(_("Please set B2C Limit in GST Settings."))
|
frappe.throw(_("Please set B2C Limit in GST Settings."))
|
||||||
|
|
||||||
if self.filters.get("type_of_business") == "B2C Large":
|
if self.filters.get("type_of_business") == "B2C Large":
|
||||||
conditions += """ and ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
|
conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
|
||||||
and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
|
AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
|
||||||
|
|
||||||
elif self.filters.get("type_of_business") == "B2C Small":
|
elif self.filters.get("type_of_business") == "B2C Small":
|
||||||
conditions += """ and (
|
conditions += """ AND (
|
||||||
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
|
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
|
||||||
or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
|
OR grand_total <= {0}) and is_return != 1 AND gst_category ='Unregistered' """.format(flt(b2c_limit))
|
||||||
|
|
||||||
elif self.filters.get("type_of_business") == "CDNR":
|
elif self.filters.get("type_of_business") == "CDNR-REG":
|
||||||
conditions += """ and is_return = 1 """
|
conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ')"""
|
||||||
|
|
||||||
elif self.filters.get("type_of_business") == "EXPORT":
|
elif self.filters.get("type_of_business") == "EXPORT":
|
||||||
conditions += """ and is_return !=1 and gst_category = 'Overseas' """
|
conditions += """ AND is_return !=1 and gst_category = 'Overseas' """
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
def get_invoice_items(self):
|
def get_invoice_items(self):
|
||||||
@ -403,7 +404,7 @@ class Gstr1Report(object):
|
|||||||
"width": 100
|
"width": 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
elif self.filters.get("type_of_business") == "CDNR":
|
elif self.filters.get("type_of_business") == "CDNR-REG":
|
||||||
self.invoice_columns = [
|
self.invoice_columns = [
|
||||||
{
|
{
|
||||||
"fieldname": "customer_gstin",
|
"fieldname": "customer_gstin",
|
||||||
@ -437,6 +438,17 @@ class Gstr1Report(object):
|
|||||||
"options": "Sales Invoice",
|
"options": "Sales Invoice",
|
||||||
"width":120
|
"width":120
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reverse_charge",
|
||||||
|
"label": "Reverse Charge",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "export_type",
|
||||||
|
"label": "Export Type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "reason_for_issuing_document",
|
"fieldname": "reason_for_issuing_document",
|
||||||
"label": "Reason For Issuing document",
|
"label": "Reason For Issuing document",
|
||||||
@ -449,6 +461,11 @@ class Gstr1Report(object):
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"width": 120
|
"width": 120
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "gst_category",
|
||||||
|
"label": "GST Category",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "invoice_value",
|
"fieldname": "invoice_value",
|
||||||
"label": "Invoice Value",
|
"label": "Invoice Value",
|
||||||
@ -458,10 +475,10 @@ class Gstr1Report(object):
|
|||||||
]
|
]
|
||||||
self.other_columns = [
|
self.other_columns = [
|
||||||
{
|
{
|
||||||
"fieldname": "cess_amount",
|
"fieldname": "cess_amount",
|
||||||
"label": "Cess Amount",
|
"label": "Cess Amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"width": 100
|
"width": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "pre_gst",
|
"fieldname": "pre_gst",
|
||||||
@ -589,6 +606,12 @@ def get_json(filters, report_name, data):
|
|||||||
|
|
||||||
out = get_export_json(res)
|
out = get_export_json(res)
|
||||||
gst_json["exp"] = out
|
gst_json["exp"] = out
|
||||||
|
elif filters["type_of_business"] == 'CDNR-REG':
|
||||||
|
for item in report_data[:-1]:
|
||||||
|
res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
|
||||||
|
|
||||||
|
out = get_cdnr_reg_json(res, gstin)
|
||||||
|
gst_json["cdnr"] = out
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'report_name': report_name,
|
'report_name': report_name,
|
||||||
@ -628,7 +651,6 @@ def get_b2b_json(res, gstin):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
def get_b2cs_json(data, gstin):
|
def get_b2cs_json(data, gstin):
|
||||||
|
|
||||||
company_state_number = gstin[0:2]
|
company_state_number = gstin[0:2]
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
@ -713,6 +735,54 @@ def get_export_json(res):
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def get_cdnr_reg_json(res, gstin):
|
||||||
|
out = []
|
||||||
|
|
||||||
|
for gst_in in res:
|
||||||
|
cdnr_item, inv = {"ctin": gst_in, "nt": []}, []
|
||||||
|
if not gst_in: continue
|
||||||
|
|
||||||
|
for number, invoice in iteritems(res[gst_in]):
|
||||||
|
if not invoice[0]["place_of_supply"]:
|
||||||
|
frappe.throw(_("""{0} not entered in Invoice {1}.
|
||||||
|
Please update and try again""").format(frappe.bold("Place Of Supply"),
|
||||||
|
frappe.bold(invoice[0]['invoice_number'])))
|
||||||
|
|
||||||
|
inv_item = {
|
||||||
|
"nt_num": invoice[0]["invoice_number"],
|
||||||
|
"nt_dt": getdate(invoice[0]["posting_date"]).strftime('%d-%m-%Y'),
|
||||||
|
"val": abs(flt(invoice[0]["invoice_value"])),
|
||||||
|
"ntty": invoice[0]["document_type"],
|
||||||
|
"pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]),
|
||||||
|
"rchrg": invoice[0]["reverse_charge"],
|
||||||
|
"inv_type": get_invoice_type_for_cdnr(invoice[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
inv_item["itms"] = []
|
||||||
|
for item in invoice:
|
||||||
|
inv_item["itms"].append(get_rate_and_tax_details(item, gstin))
|
||||||
|
|
||||||
|
inv.append(inv_item)
|
||||||
|
|
||||||
|
if not inv: continue
|
||||||
|
cdnr_item["nt"] = inv
|
||||||
|
out.append(cdnr_item)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
def get_invoice_type_for_cdnr(row):
|
||||||
|
if row.get('gst_category') == 'SEZ':
|
||||||
|
if row.get('export_type') == 'WPAY':
|
||||||
|
invoice_type = 'SEWP'
|
||||||
|
else:
|
||||||
|
invoice_type = 'SEWOP'
|
||||||
|
elif row.get('gst_category') == 'Deemed Export':
|
||||||
|
row.invoice_type = 'DE'
|
||||||
|
elif row.get('gst_category') == 'Registered Regular':
|
||||||
|
invoice_type = 'R'
|
||||||
|
|
||||||
|
return invoice_type
|
||||||
|
|
||||||
def get_basic_invoice_detail(row):
|
def get_basic_invoice_detail(row):
|
||||||
return {
|
return {
|
||||||
"inum": row["invoice_number"],
|
"inum": row["invoice_number"],
|
||||||
|
@ -56,10 +56,6 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
dialog.fields_dict.balance_details.grid.refresh();
|
dialog.fields_dict.balance_details.grid.refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const pos_profile_query = {
|
|
||||||
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
|
|
||||||
filters: { company: frappe.defaults.get_default('company') }
|
|
||||||
}
|
|
||||||
const dialog = new frappe.ui.Dialog({
|
const dialog = new frappe.ui.Dialog({
|
||||||
title: __('Create POS Opening Entry'),
|
title: __('Create POS Opening Entry'),
|
||||||
static: true,
|
static: true,
|
||||||
@ -105,6 +101,10 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
primary_action_label: __('Submit')
|
primary_action_label: __('Submit')
|
||||||
});
|
});
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
const pos_profile_query = {
|
||||||
|
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
|
||||||
|
filters: { company: dialog.fields_dict.company.get_value() }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare_app_defaults(data) {
|
async prepare_app_defaults(data) {
|
||||||
|
@ -90,14 +90,16 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
|
|
||||||
function get_item_image_html() {
|
function get_item_image_html() {
|
||||||
if (!me.hide_images && item_image) {
|
if (!me.hide_images && item_image) {
|
||||||
return `<div class="flex" style="margin: 8px; justify-content: flex-end">
|
return `<div class="item-qty-pill">
|
||||||
<span class="indicator-pill whitespace-nowrap ${indicator_color}" id="text">${qty_to_display}</span></div>
|
<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
|
||||||
|
</div>
|
||||||
<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
|
<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
|
||||||
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
|
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
|
||||||
</div>`;
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
return `<div class="flex" style="margin: 8px; justify-content: flex-end">
|
return `<div class="item-qty-pill">
|
||||||
<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span></div>
|
<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
|
||||||
|
</div>
|
||||||
<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
|
<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,9 +169,9 @@ frappe.ui.form.on("Company", {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions",
|
method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request",
|
||||||
args: {
|
args: {
|
||||||
company_name: data.company_name
|
company: data.company_name
|
||||||
},
|
},
|
||||||
freeze: true,
|
freeze: true,
|
||||||
callback: function(r, rt) {
|
callback: function(r, rt) {
|
||||||
|
@ -613,4 +613,13 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad
|
|||||||
if out:
|
if out:
|
||||||
return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
|
return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_transaction_deletion_request(company):
|
||||||
|
tdr = frappe.get_doc({
|
||||||
|
'doctype': 'Transaction Deletion Record',
|
||||||
|
'company': company
|
||||||
|
})
|
||||||
|
tdr.insert()
|
||||||
|
tdr.submit()
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe
|
|
||||||
|
|
||||||
from frappe.utils import cint
|
|
||||||
from frappe import _
|
|
||||||
from frappe.desk.notifications import clear_notifications
|
|
||||||
|
|
||||||
import functools
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def delete_company_transactions(company_name):
|
|
||||||
frappe.only_for("System Manager")
|
|
||||||
doc = frappe.get_doc("Company", company_name)
|
|
||||||
|
|
||||||
if frappe.session.user != doc.owner and frappe.session.user != 'Administrator':
|
|
||||||
frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
|
|
||||||
frappe.PermissionError)
|
|
||||||
|
|
||||||
delete_bins(company_name)
|
|
||||||
delete_lead_addresses(company_name)
|
|
||||||
|
|
||||||
for doctype in frappe.db.sql_list("""select parent from
|
|
||||||
tabDocField where fieldtype='Link' and options='Company'"""):
|
|
||||||
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
|
|
||||||
"Party Account", "Employee", "Sales Taxes and Charges Template",
|
|
||||||
"Purchase Taxes and Charges Template", "POS Profile", "BOM",
|
|
||||||
"Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account",
|
|
||||||
"Item Default", "Customer", "Supplier", "GST Account"):
|
|
||||||
delete_for_doctype(doctype, company_name)
|
|
||||||
|
|
||||||
# reset company values
|
|
||||||
doc.total_monthly_sales = 0
|
|
||||||
doc.sales_monthly_history = None
|
|
||||||
doc.save()
|
|
||||||
# Clear notification counts
|
|
||||||
clear_notifications()
|
|
||||||
|
|
||||||
def delete_for_doctype(doctype, company_name):
|
|
||||||
meta = frappe.get_meta(doctype)
|
|
||||||
company_fieldname = meta.get("fields", {"fieldtype": "Link",
|
|
||||||
"options": "Company"})[0].fieldname
|
|
||||||
|
|
||||||
if not meta.issingle:
|
|
||||||
if not meta.istable:
|
|
||||||
# delete communication
|
|
||||||
delete_communications(doctype, company_name, company_fieldname)
|
|
||||||
|
|
||||||
# delete children
|
|
||||||
for df in meta.get_table_fields():
|
|
||||||
frappe.db.sql("""delete from `tab{0}` where parent in
|
|
||||||
(select name from `tab{1}` where `{2}`=%s)""".format(df.options,
|
|
||||||
doctype, company_fieldname), company_name)
|
|
||||||
|
|
||||||
#delete version log
|
|
||||||
frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
|
|
||||||
(select name from `tab{0}` where `{1}`=%s)""".format(doctype,
|
|
||||||
company_fieldname), (doctype, company_name))
|
|
||||||
|
|
||||||
# delete parent
|
|
||||||
frappe.db.sql("""delete from `tab{0}`
|
|
||||||
where {1}= %s """.format(doctype, company_fieldname), company_name)
|
|
||||||
|
|
||||||
# reset series
|
|
||||||
naming_series = meta.get_field("naming_series")
|
|
||||||
if naming_series and naming_series.options:
|
|
||||||
prefixes = sorted(naming_series.options.split("\n"),
|
|
||||||
key=functools.cmp_to_key(lambda a, b: len(b) - len(a)))
|
|
||||||
|
|
||||||
for prefix in prefixes:
|
|
||||||
if prefix:
|
|
||||||
last = frappe.db.sql("""select max(name) from `tab{0}`
|
|
||||||
where name like %s""".format(doctype), prefix + "%")
|
|
||||||
if last and last[0][0]:
|
|
||||||
last = cint(last[0][0].replace(prefix, ""))
|
|
||||||
else:
|
|
||||||
last = 0
|
|
||||||
|
|
||||||
frappe.db.sql("""update tabSeries set current = %s
|
|
||||||
where name=%s""", (last, prefix))
|
|
||||||
|
|
||||||
def delete_bins(company_name):
|
|
||||||
frappe.db.sql("""delete from tabBin where warehouse in
|
|
||||||
(select name from tabWarehouse where company=%s)""", company_name)
|
|
||||||
|
|
||||||
def delete_lead_addresses(company_name):
|
|
||||||
"""Delete addresses to which leads are linked"""
|
|
||||||
leads = frappe.get_all("Lead", filters={"company": company_name})
|
|
||||||
leads = [ "'%s'"%row.get("name") for row in leads ]
|
|
||||||
addresses = []
|
|
||||||
if leads:
|
|
||||||
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
|
|
||||||
in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
if addresses:
|
|
||||||
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
|
||||||
|
|
||||||
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
|
|
||||||
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
|
||||||
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
|
||||||
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
|
|
||||||
|
|
||||||
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
|
|
||||||
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
def delete_communications(doctype, company_name, company_fieldname):
|
|
||||||
reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
|
|
||||||
reference_doc_names = [r.name for r in reference_docs]
|
|
||||||
|
|
||||||
communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]})
|
|
||||||
communication_names = [c.name for c in communications]
|
|
||||||
|
|
||||||
frappe.delete_doc("Communication", communication_names, ignore_permissions=True)
|
|
@ -86,15 +86,6 @@ class TestCompany(unittest.TestCase):
|
|||||||
self.delete_mode_of_payment(template)
|
self.delete_mode_of_payment(template)
|
||||||
frappe.delete_doc("Company", template)
|
frappe.delete_doc("Company", template)
|
||||||
|
|
||||||
def test_delete_communication(self):
|
|
||||||
from erpnext.setup.doctype.company.delete_company_transactions import delete_communications
|
|
||||||
company = create_child_company()
|
|
||||||
lead = create_test_lead_in_company(company)
|
|
||||||
communication = create_company_communication("Lead", lead)
|
|
||||||
delete_communications("Lead", "Test Company", "company")
|
|
||||||
self.assertFalse(frappe.db.exists("Communcation", communication))
|
|
||||||
self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication}))
|
|
||||||
|
|
||||||
def delete_mode_of_payment(self, company):
|
def delete_mode_of_payment(self, company):
|
||||||
frappe.db.sql(""" delete from `tabMode of Payment Account`
|
frappe.db.sql(""" delete from `tabMode of Payment Account`
|
||||||
where company =%s """, (company))
|
where company =%s """, (company))
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestTransactionDeletionRecord(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
create_company('Dunder Mifflin Paper Co')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def test_doctypes_contain_company_field(self):
|
||||||
|
tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
|
||||||
|
for doctype in tdr.doctypes:
|
||||||
|
contains_company = False
|
||||||
|
doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()['fields']
|
||||||
|
for doctype_field in doctype_fields:
|
||||||
|
if doctype_field['fieldtype'] == 'Link' and doctype_field['options'] == 'Company':
|
||||||
|
contains_company = True
|
||||||
|
break
|
||||||
|
self.assertTrue(contains_company)
|
||||||
|
|
||||||
|
def test_no_of_docs_is_correct(self):
|
||||||
|
for i in range(5):
|
||||||
|
create_task('Dunder Mifflin Paper Co')
|
||||||
|
tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
|
||||||
|
for doctype in tdr.doctypes:
|
||||||
|
if doctype.doctype_name == 'Task':
|
||||||
|
self.assertEqual(doctype.no_of_docs, 5)
|
||||||
|
|
||||||
|
def test_deletion_is_successful(self):
|
||||||
|
create_task('Dunder Mifflin Paper Co')
|
||||||
|
create_transaction_deletion_request('Dunder Mifflin Paper Co')
|
||||||
|
tasks_containing_company = frappe.get_all('Task',
|
||||||
|
filters = {
|
||||||
|
'company' : 'Dunder Mifflin Paper Co'
|
||||||
|
})
|
||||||
|
self.assertEqual(tasks_containing_company, [])
|
||||||
|
|
||||||
|
def create_company(company_name):
|
||||||
|
company = frappe.get_doc({
|
||||||
|
'doctype': 'Company',
|
||||||
|
'company_name': company_name,
|
||||||
|
'default_currency': 'INR'
|
||||||
|
})
|
||||||
|
company.insert(ignore_if_duplicate = True)
|
||||||
|
|
||||||
|
def create_transaction_deletion_request(company):
|
||||||
|
tdr = frappe.get_doc({
|
||||||
|
'doctype': 'Transaction Deletion Record',
|
||||||
|
'company': company
|
||||||
|
})
|
||||||
|
tdr.insert()
|
||||||
|
tdr.submit()
|
||||||
|
return tdr
|
||||||
|
|
||||||
|
|
||||||
|
def create_task(company):
|
||||||
|
task = frappe.get_doc({
|
||||||
|
'doctype': 'Task',
|
||||||
|
'company': company,
|
||||||
|
'subject': 'Delete'
|
||||||
|
})
|
||||||
|
task.insert()
|
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Transaction Deletion Record', {
|
||||||
|
onload: function(frm) {
|
||||||
|
if (frm.doc.docstatus == 0) {
|
||||||
|
let doctypes_to_be_ignored_array;
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored',
|
||||||
|
callback: function(r) {
|
||||||
|
doctypes_to_be_ignored_array = r.message;
|
||||||
|
populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm);
|
||||||
|
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
|
||||||
|
frm.refresh_field('doctypes_to_be_ignored');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true;
|
||||||
|
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
|
||||||
|
frm.refresh_field('doctypes_to_be_ignored');
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function(frm) {
|
||||||
|
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
|
||||||
|
frm.refresh_field('doctypes_to_be_ignored');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) {
|
||||||
|
if (!(frm.doc.doctypes_to_be_ignored)) {
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < doctypes_to_be_ignored_array.length; i++) {
|
||||||
|
frm.add_child('doctypes_to_be_ignored', {
|
||||||
|
doctype_name: doctypes_to_be_ignored_array[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "TDL.####",
|
||||||
|
"creation": "2021-04-06 20:17:18.404716",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"company",
|
||||||
|
"doctypes",
|
||||||
|
"doctypes_to_be_ignored",
|
||||||
|
"amended_from",
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "doctypes",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Summary",
|
||||||
|
"options": "Transaction Deletion Record Item",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "doctypes_to_be_ignored",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Excluded DocTypes",
|
||||||
|
"options": "Transaction Deletion Record Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Transaction Deletion Record",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Draft\nCompleted"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-05-08 23:13:48.049879",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Setup",
|
||||||
|
"name": "Transaction Deletion Record",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,147 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe.utils import cint
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe import _
|
||||||
|
from frappe.desk.notifications import clear_notifications
|
||||||
|
|
||||||
|
class TransactionDeletionRecord(Document):
|
||||||
|
def validate(self):
|
||||||
|
frappe.only_for('System Manager')
|
||||||
|
company_obj = frappe.get_doc('Company', self.company)
|
||||||
|
if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator':
|
||||||
|
frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'),
|
||||||
|
frappe.PermissionError)
|
||||||
|
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||||
|
for doctype in self.doctypes_to_be_ignored:
|
||||||
|
if doctype.doctype_name not in doctypes_to_be_ignored_list:
|
||||||
|
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed"))
|
||||||
|
|
||||||
|
def before_submit(self):
|
||||||
|
if not self.doctypes_to_be_ignored:
|
||||||
|
self.populate_doctypes_to_be_ignored_table()
|
||||||
|
|
||||||
|
self.delete_bins()
|
||||||
|
self.delete_lead_addresses()
|
||||||
|
|
||||||
|
company_obj = frappe.get_doc('Company', self.company)
|
||||||
|
# reset company values
|
||||||
|
company_obj.total_monthly_sales = 0
|
||||||
|
company_obj.sales_monthly_history = None
|
||||||
|
company_obj.save()
|
||||||
|
# Clear notification counts
|
||||||
|
clear_notifications()
|
||||||
|
|
||||||
|
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
||||||
|
tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
|
||||||
|
doctypes_to_be_ignored_list = singles
|
||||||
|
for doctype in self.doctypes_to_be_ignored:
|
||||||
|
doctypes_to_be_ignored_list.append(doctype.doctype_name)
|
||||||
|
|
||||||
|
docfields = frappe.get_all('DocField',
|
||||||
|
filters = {
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Company',
|
||||||
|
'parent': ['not in', doctypes_to_be_ignored_list]},
|
||||||
|
fields=['parent', 'fieldname'])
|
||||||
|
|
||||||
|
for docfield in docfields:
|
||||||
|
if docfield['parent'] != self.doctype:
|
||||||
|
no_of_docs = frappe.db.count(docfield['parent'], {
|
||||||
|
docfield['fieldname'] : self.company
|
||||||
|
})
|
||||||
|
|
||||||
|
if no_of_docs > 0:
|
||||||
|
self.delete_version_log(docfield['parent'], docfield['fieldname'])
|
||||||
|
self.delete_communications(docfield['parent'], docfield['fieldname'])
|
||||||
|
|
||||||
|
# populate DocTypes table
|
||||||
|
if docfield['parent'] not in tables:
|
||||||
|
self.append('doctypes', {
|
||||||
|
'doctype_name' : docfield['parent'],
|
||||||
|
'no_of_docs' : no_of_docs
|
||||||
|
})
|
||||||
|
|
||||||
|
# delete the docs linked with the specified company
|
||||||
|
frappe.db.delete(docfield['parent'], {
|
||||||
|
docfield['fieldname'] : self.company
|
||||||
|
})
|
||||||
|
|
||||||
|
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
||||||
|
if naming_series:
|
||||||
|
if '#' in naming_series:
|
||||||
|
self.update_naming_series(naming_series, docfield['parent'])
|
||||||
|
|
||||||
|
def populate_doctypes_to_be_ignored_table(self):
|
||||||
|
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||||
|
for doctype in doctypes_to_be_ignored_list:
|
||||||
|
self.append('doctypes_to_be_ignored', {
|
||||||
|
'doctype_name' : doctype
|
||||||
|
})
|
||||||
|
|
||||||
|
def update_naming_series(self, naming_series, doctype_name):
|
||||||
|
if '.' in naming_series:
|
||||||
|
prefix, hashes = naming_series.rsplit('.', 1)
|
||||||
|
else:
|
||||||
|
prefix, hashes = naming_series.rsplit('{', 1)
|
||||||
|
last = frappe.db.sql("""select max(name) from `tab{0}`
|
||||||
|
where name like %s""".format(doctype_name), prefix + '%')
|
||||||
|
if last and last[0][0]:
|
||||||
|
last = cint(last[0][0].replace(prefix, ''))
|
||||||
|
else:
|
||||||
|
last = 0
|
||||||
|
|
||||||
|
frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix))
|
||||||
|
|
||||||
|
def delete_version_log(self, doctype, company_fieldname):
|
||||||
|
frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
|
||||||
|
(select name from `tab{0}` where `{1}`=%s)""".format(doctype,
|
||||||
|
company_fieldname), (doctype, self.company))
|
||||||
|
|
||||||
|
def delete_communications(self, doctype, company_fieldname):
|
||||||
|
reference_docs = frappe.get_all(doctype, filters={company_fieldname:self.company})
|
||||||
|
reference_doc_names = [r.name for r in reference_docs]
|
||||||
|
|
||||||
|
communications = frappe.get_all('Communication', filters={'reference_doctype':doctype,'reference_name':['in', reference_doc_names]})
|
||||||
|
communication_names = [c.name for c in communications]
|
||||||
|
|
||||||
|
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
|
||||||
|
|
||||||
|
def delete_bins(self):
|
||||||
|
frappe.db.sql("""delete from tabBin where warehouse in
|
||||||
|
(select name from tabWarehouse where company=%s)""", self.company)
|
||||||
|
|
||||||
|
def delete_lead_addresses(self):
|
||||||
|
"""Delete addresses to which leads are linked"""
|
||||||
|
leads = frappe.get_all('Lead', filters={'company': self.company})
|
||||||
|
leads = ["'%s'" % row.get("name") for row in leads]
|
||||||
|
addresses = []
|
||||||
|
if leads:
|
||||||
|
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
|
||||||
|
in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
if addresses:
|
||||||
|
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
|
||||||
|
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
||||||
|
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
||||||
|
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
|
||||||
|
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_doctypes_to_be_ignored():
|
||||||
|
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
|
||||||
|
'Party Account', 'Employee', 'Sales Taxes and Charges Template',
|
||||||
|
'Purchase Taxes and Charges Template', 'POS Profile', 'BOM',
|
||||||
|
'Company', 'Bank Account', 'Item Tax Template', 'Mode of Payment',
|
||||||
|
'Item Default', 'Customer', 'Supplier', 'GST Account']
|
||||||
|
return doctypes_to_be_ignored_list
|
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
frappe.listview_settings['Transaction Deletion Record'] = {
|
||||||
|
get_indicator: function(doc) {
|
||||||
|
if (doc.docstatus == 0) {
|
||||||
|
return [__("Draft"), "red"];
|
||||||
|
} else {
|
||||||
|
return [__("Completed"), "green"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-04-07 07:34:00.124124",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"doctype_name",
|
||||||
|
"no_of_docs"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "doctype_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "DocType",
|
||||||
|
"options": "DocType",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "no_of_docs",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Number of Docs"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-05-08 23:10:46.166744",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Setup",
|
||||||
|
"name": "Transaction Deletion Record Item",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class TransactionDeletionRecordItem(Document):
|
||||||
|
pass
|
@ -732,7 +732,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
"doctype": target_doctype,
|
"doctype": target_doctype,
|
||||||
"postprocess": update_details,
|
"postprocess": update_details,
|
||||||
"field_no_map": [
|
"field_no_map": [
|
||||||
"taxes_and_charges"
|
"taxes_and_charges",
|
||||||
|
"set_warehouse"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
doctype +" Item": {
|
doctype +" Item": {
|
||||||
|
@ -243,16 +243,23 @@ class PurchaseReceipt(BuyingController):
|
|||||||
|
|
||||||
def get_gl_entries(self, warehouse_account=None):
|
def get_gl_entries(self, warehouse_account=None):
|
||||||
from erpnext.accounts.general_ledger import process_gl_map
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
|
gl_entries = []
|
||||||
|
|
||||||
|
self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account)
|
||||||
|
self.make_tax_gl_entries(gl_entries)
|
||||||
|
self.get_asset_gl_entry(gl_entries)
|
||||||
|
|
||||||
|
return process_gl_map(gl_entries)
|
||||||
|
|
||||||
|
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
|
||||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||||
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
|
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
|
||||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||||
auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
|
auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
|
||||||
|
|
||||||
gl_entries = []
|
|
||||||
warehouse_with_no_account = []
|
warehouse_with_no_account = []
|
||||||
negative_expense_to_be_booked = 0.0
|
|
||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty):
|
if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty):
|
||||||
if warehouse_account.get(d.warehouse):
|
if warehouse_account.get(d.warehouse):
|
||||||
@ -263,21 +270,22 @@ class PurchaseReceipt(BuyingController):
|
|||||||
if not stock_value_diff:
|
if not stock_value_diff:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
warehouse_account_name = warehouse_account[d.warehouse]["account"]
|
||||||
|
warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"]
|
||||||
|
supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account")
|
||||||
|
supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get("account_currency")
|
||||||
|
remarks = self.get("remarks") or _("Accounting Entry for Stock")
|
||||||
|
|
||||||
# If PR is sub-contracted and fg item rate is zero
|
# If PR is sub-contracted and fg item rate is zero
|
||||||
# in that case if account for shource and target warehouse are same,
|
# in that case if account for source and target warehouse are same,
|
||||||
# then GL entries should not be posted
|
# then GL entries should not be posted
|
||||||
if flt(stock_value_diff) == flt(d.rm_supp_cost) \
|
if flt(stock_value_diff) == flt(d.rm_supp_cost) \
|
||||||
and warehouse_account.get(self.supplier_warehouse) \
|
and warehouse_account.get(self.supplier_warehouse) \
|
||||||
and warehouse_account[d.warehouse]["account"] == warehouse_account[self.supplier_warehouse]["account"]:
|
and warehouse_account_name == supplier_warehouse_account:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks,
|
||||||
"account": warehouse_account[d.warehouse]["account"],
|
stock_rbnb, account_currency=warehouse_account_currency, item=d)
|
||||||
"against": stock_rbnb,
|
|
||||||
"cost_center": d.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"debit": stock_value_diff
|
|
||||||
}, warehouse_account[d.warehouse]["account_currency"], item=d))
|
|
||||||
|
|
||||||
# GL Entry for from warehouse or Stock Received but not billed
|
# GL Entry for from warehouse or Stock Received but not billed
|
||||||
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
||||||
@ -287,43 +295,28 @@ class PurchaseReceipt(BuyingController):
|
|||||||
credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \
|
credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \
|
||||||
if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount"))
|
if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount"))
|
||||||
if credit_amount:
|
if credit_amount:
|
||||||
gl_entries.append(self.get_gl_dict({
|
account = warehouse_account[d.from_warehouse]['account'] \
|
||||||
"account": warehouse_account[d.from_warehouse]['account'] \
|
if d.from_warehouse else stock_rbnb
|
||||||
if d.from_warehouse else stock_rbnb,
|
|
||||||
"against": warehouse_account[d.warehouse]["account"],
|
|
||||||
"cost_center": d.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")),
|
|
||||||
"debit_in_account_currency": -1 * credit_amount
|
|
||||||
}, credit_currency, item=d))
|
|
||||||
|
|
||||||
negative_expense_to_be_booked += flt(d.item_tax_amount)
|
self.add_gl_entry(gl_entries, account, d.cost_center,
|
||||||
|
-1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name,
|
||||||
|
debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d)
|
||||||
|
|
||||||
# Amount added through landed-cost-voucher
|
# Amount added through landed-cos-voucher
|
||||||
if d.landed_cost_voucher_amount and landed_cost_entries:
|
if d.landed_cost_voucher_amount and landed_cost_entries:
|
||||||
for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]):
|
for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]):
|
||||||
account_currency = get_account_currency(account)
|
account_currency = get_account_currency(account)
|
||||||
gl_entries.append(self.get_gl_dict({
|
credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or
|
||||||
"account": account,
|
account_currency!=self.company_currency) else flt(amount["amount"]))
|
||||||
"account_currency": account_currency,
|
|
||||||
"against": warehouse_account[d.warehouse]["account"],
|
self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks,
|
||||||
"cost_center": d.cost_center,
|
warehouse_account_name, credit_in_account_currency=flt(amount["amount"]),
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
account_currency=account_currency, project=d.project, item=d)
|
||||||
"credit": (flt(amount["base_amount"]) if (amount["base_amount"] or
|
|
||||||
account_currency!=self.company_currency) else flt(amount["amount"])),
|
|
||||||
"credit_in_account_currency": flt(amount["amount"]),
|
|
||||||
"project": d.project
|
|
||||||
}, item=d))
|
|
||||||
|
|
||||||
# sub-contracting warehouse
|
# sub-contracting warehouse
|
||||||
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
|
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
|
||||||
gl_entries.append(self.get_gl_dict({
|
self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost),
|
||||||
"account": warehouse_account[self.supplier_warehouse]["account"],
|
remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d)
|
||||||
"against": warehouse_account[d.warehouse]["account"],
|
|
||||||
"cost_center": d.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"credit": flt(d.rm_supp_cost)
|
|
||||||
}, warehouse_account[self.supplier_warehouse]["account_currency"], item=d))
|
|
||||||
|
|
||||||
# divisional loss adjustment
|
# divisional loss adjustment
|
||||||
valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \
|
valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \
|
||||||
@ -340,46 +333,32 @@ class PurchaseReceipt(BuyingController):
|
|||||||
|
|
||||||
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
|
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks,
|
||||||
"account": loss_account,
|
warehouse_account_name, account_currency=credit_currency, project=d.project, item=d)
|
||||||
"against": warehouse_account[d.warehouse]["account"],
|
|
||||||
"cost_center": cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"debit": divisional_loss,
|
|
||||||
"project": d.project
|
|
||||||
}, credit_currency, item=d))
|
|
||||||
|
|
||||||
elif d.warehouse not in warehouse_with_no_account or \
|
elif d.warehouse not in warehouse_with_no_account or \
|
||||||
d.rejected_warehouse not in warehouse_with_no_account:
|
d.rejected_warehouse not in warehouse_with_no_account:
|
||||||
warehouse_with_no_account.append(d.warehouse)
|
warehouse_with_no_account.append(d.warehouse)
|
||||||
elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
|
elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
|
||||||
|
|
||||||
service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
|
service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
|
||||||
credit_currency = get_account_currency(service_received_but_not_billed_account)
|
credit_currency = get_account_currency(service_received_but_not_billed_account)
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
|
||||||
"account": service_received_but_not_billed_account,
|
|
||||||
"against": d.expense_account,
|
|
||||||
"cost_center": d.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Service"),
|
|
||||||
"project": d.project,
|
|
||||||
"credit": d.amount,
|
|
||||||
"voucher_detail_no": d.name
|
|
||||||
}, credit_currency, item=d))
|
|
||||||
|
|
||||||
debit_currency = get_account_currency(d.expense_account)
|
debit_currency = get_account_currency(d.expense_account)
|
||||||
|
remarks = self.get("remarks") or _("Accounting Entry for Service")
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount,
|
||||||
"account": d.expense_account,
|
remarks, d.expense_account, account_currency=credit_currency, project=d.project,
|
||||||
"against": service_received_but_not_billed_account,
|
voucher_detail_no=d.name, item=d)
|
||||||
"cost_center": d.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Service"),
|
|
||||||
"project": d.project,
|
|
||||||
"debit": d.amount,
|
|
||||||
"voucher_detail_no": d.name
|
|
||||||
}, debit_currency, item=d))
|
|
||||||
|
|
||||||
self.get_asset_gl_entry(gl_entries)
|
self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account,
|
||||||
|
account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d)
|
||||||
|
|
||||||
|
if warehouse_with_no_account:
|
||||||
|
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
|
||||||
|
"\n".join(warehouse_with_no_account))
|
||||||
|
|
||||||
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
|
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||||
|
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')])
|
||||||
# Cost center-wise amount breakup for other charges included for valuation
|
# Cost center-wise amount breakup for other charges included for valuation
|
||||||
valuation_tax = {}
|
valuation_tax = {}
|
||||||
for tax in self.get("taxes"):
|
for tax in self.get("taxes"):
|
||||||
@ -420,23 +399,33 @@ class PurchaseReceipt(BuyingController):
|
|||||||
applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
|
applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
|
||||||
amount_including_divisional_loss -= applicable_amount
|
amount_including_divisional_loss -= applicable_amount
|
||||||
|
|
||||||
gl_entries.append(
|
self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"),
|
||||||
self.get_gl_dict({
|
against_account, item=tax)
|
||||||
"account": account,
|
|
||||||
"cost_center": tax.cost_center,
|
|
||||||
"credit": applicable_amount,
|
|
||||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
|
||||||
"against": against_account
|
|
||||||
}, item=tax)
|
|
||||||
)
|
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if warehouse_with_no_account:
|
def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account,
|
||||||
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
|
debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None,
|
||||||
"\n".join(warehouse_with_no_account))
|
project=None, voucher_detail_no=None, item=None):
|
||||||
|
gl_entry = {
|
||||||
|
"account": account,
|
||||||
|
"cost_center": cost_center,
|
||||||
|
"debit": debit,
|
||||||
|
"credit": credit,
|
||||||
|
"against_account": against_account,
|
||||||
|
"remarks": remarks,
|
||||||
|
}
|
||||||
|
|
||||||
return process_gl_map(gl_entries)
|
if voucher_detail_no:
|
||||||
|
gl_entry.update({"voucher_detail_no": voucher_detail_no})
|
||||||
|
|
||||||
|
if debit_in_account_currency:
|
||||||
|
gl_entry.update({"debit_in_account_currency": debit_in_account_currency})
|
||||||
|
|
||||||
|
if credit_in_account_currency:
|
||||||
|
gl_entry.update({"credit_in_account_currency": credit_in_account_currency})
|
||||||
|
|
||||||
|
gl_entries.append(self.get_gl_dict(gl_entry, item=item))
|
||||||
|
|
||||||
def get_asset_gl_entry(self, gl_entries):
|
def get_asset_gl_entry(self, gl_entries):
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
@ -458,30 +447,21 @@ class PurchaseReceipt(BuyingController):
|
|||||||
|
|
||||||
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
|
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
|
||||||
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
|
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
|
||||||
|
remarks = self.get("remarks") or _("Accounting Entry for Asset")
|
||||||
|
|
||||||
cwip_account_currency = get_account_currency(cwip_account)
|
cwip_account_currency = get_account_currency(cwip_account)
|
||||||
# debit cwip account
|
# debit cwip account
|
||||||
gl_entries.append(self.get_gl_dict({
|
debit_in_account_currency = (base_asset_amount
|
||||||
"account": cwip_account,
|
if cwip_account_currency == self.company_currency else asset_amount)
|
||||||
"against": arbnb_account,
|
self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks,
|
||||||
"cost_center": item.cost_center,
|
arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item)
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
|
||||||
"debit": base_asset_amount,
|
|
||||||
"debit_in_account_currency": (base_asset_amount
|
|
||||||
if cwip_account_currency == self.company_currency else asset_amount)
|
|
||||||
}, item=item))
|
|
||||||
|
|
||||||
asset_rbnb_currency = get_account_currency(arbnb_account)
|
asset_rbnb_currency = get_account_currency(arbnb_account)
|
||||||
# credit arbnb account
|
# credit arbnb account
|
||||||
gl_entries.append(self.get_gl_dict({
|
credit_in_account_currency = (base_asset_amount
|
||||||
"account": arbnb_account,
|
if asset_rbnb_currency == self.company_currency else asset_amount)
|
||||||
"against": cwip_account,
|
self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks,
|
||||||
"cost_center": item.cost_center,
|
cwip_account, credit_in_account_currency=credit_in_account_currency, item=item)
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
|
||||||
"credit": base_asset_amount,
|
|
||||||
"credit_in_account_currency": (base_asset_amount
|
|
||||||
if asset_rbnb_currency == self.company_currency else asset_amount)
|
|
||||||
}, item=item))
|
|
||||||
|
|
||||||
def add_lcv_gl_entries(self, item, gl_entries):
|
def add_lcv_gl_entries(self, item, gl_entries):
|
||||||
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
|
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
|
||||||
@ -492,23 +472,13 @@ class PurchaseReceipt(BuyingController):
|
|||||||
# This returns company's default cwip account
|
# This returns company's default cwip account
|
||||||
asset_account = get_asset_account("capital_work_in_progress_account", company=self.company)
|
asset_account = get_asset_account("capital_work_in_progress_account", company=self.company)
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
remarks = self.get("remarks") or _("Accounting Entry for Stock")
|
||||||
"account": expenses_included_in_asset_valuation,
|
|
||||||
"against": asset_account,
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"credit": flt(item.landed_cost_voucher_amount),
|
|
||||||
"project": item.project
|
|
||||||
}, item=item))
|
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount),
|
||||||
"account": asset_account,
|
remarks, asset_account, project=item.project, item=item)
|
||||||
"against": expenses_included_in_asset_valuation,
|
|
||||||
"cost_center": item.cost_center,
|
self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount),
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
remarks, expenses_included_in_asset_valuation, project=item.project, item=item)
|
||||||
"debit": flt(item.landed_cost_voucher_amount),
|
|
||||||
"project": item.project
|
|
||||||
}, item=item))
|
|
||||||
|
|
||||||
def update_assets(self, item, valuation_rate):
|
def update_assets(self, item, valuation_rate):
|
||||||
assets = frappe.db.get_all('Asset',
|
assets = frappe.db.get_all('Asset',
|
||||||
|
@ -27,10 +27,11 @@ class TestQualityInspection(unittest.TestCase):
|
|||||||
dn.reload()
|
dn.reload()
|
||||||
self.assertRaises(QualityInspectionRejectedError, dn.submit)
|
self.assertRaises(QualityInspectionRejectedError, dn.submit)
|
||||||
|
|
||||||
frappe.db.set_value("Quality Inspection Reading", {"parent": qa.name}, "status", "Accepted")
|
frappe.db.set_value("Quality Inspection", qa.name, "status", "Accepted")
|
||||||
dn.reload()
|
dn.reload()
|
||||||
dn.submit()
|
dn.submit()
|
||||||
|
|
||||||
|
qa.reload()
|
||||||
qa.cancel()
|
qa.cancel()
|
||||||
dn.reload()
|
dn.reload()
|
||||||
dn.cancel()
|
dn.cancel()
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint, get_link_to_form, add_to_date, today
|
from frappe.utils import cint, get_link_to_form, add_to_date, now, today
|
||||||
from erpnext.stock.stock_ledger import repost_future_sle
|
from erpnext.stock.stock_ledger import repost_future_sle
|
||||||
from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced
|
from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced
|
||||||
from frappe.utils.user import get_users_with_role
|
from frappe.utils.user import get_users_with_role
|
||||||
@ -127,9 +127,9 @@ def repost_entries():
|
|||||||
check_if_stock_and_account_balance_synced(today(), d.name)
|
check_if_stock_and_account_balance_synced(today(), d.name)
|
||||||
|
|
||||||
def get_repost_item_valuation_entries():
|
def get_repost_item_valuation_entries():
|
||||||
date = add_to_date(today(), hours=-3)
|
date = add_to_date(now(), hours=-3)
|
||||||
|
|
||||||
return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
|
return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
|
||||||
WHERE status != 'Completed' and creation <= %s and docstatus = 1
|
WHERE status != 'Completed' and creation <= %s and docstatus = 1
|
||||||
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
|
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
|
||||||
""", date, as_dict=1)
|
""", date, as_dict=1)
|
||||||
|
@ -107,6 +107,7 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
frappe.flags.hide_serial_batch_dialog = true;
|
frappe.flags.hide_serial_batch_dialog = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
attach_bom_items(frm.doc.bom_no);
|
||||||
},
|
},
|
||||||
|
|
||||||
setup_quality_inspection: function(frm) {
|
setup_quality_inspection: function(frm) {
|
||||||
@ -311,6 +312,7 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frm.trigger("setup_quality_inspection");
|
frm.trigger("setup_quality_inspection");
|
||||||
|
attach_bom_items(frm.doc.bom_no)
|
||||||
},
|
},
|
||||||
|
|
||||||
stock_entry_type: function(frm){
|
stock_entry_type: function(frm){
|
||||||
@ -919,6 +921,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
|||||||
method: "get_items",
|
method: "get_items",
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) refresh_field("items");
|
if(!r.exc) refresh_field("items");
|
||||||
|
if(me.frm.doc.bom_no) attach_bom_items(me.frm.doc.bom_no)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1064,4 +1067,22 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function attach_bom_items(bom_no) {
|
||||||
|
if (check_should_not_attach_bom_items(bom_no)) return
|
||||||
|
frappe.db.get_doc("BOM",bom_no).then(bom => {
|
||||||
|
const {name, items} = bom
|
||||||
|
erpnext.stock.bom = {name, items:{}}
|
||||||
|
items.forEach(item => {
|
||||||
|
erpnext.stock.bom.items[item.item_code] = item;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_should_not_attach_bom_items(bom_no) {
|
||||||
|
return (
|
||||||
|
bom_no === undefined ||
|
||||||
|
(erpnext.stock.bom && erpnext.stock.bom.name === bom_no)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm}));
|
$.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm}));
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
from frappe.utils import cint, nowdate
|
from frappe.utils import cint, flt
|
||||||
from frappe import throw, _
|
from frappe import throw, _
|
||||||
|
from collections import defaultdict
|
||||||
from frappe.utils.nestedset import NestedSet
|
from frappe.utils.nestedset import NestedSet
|
||||||
from erpnext.stock import get_warehouse_account
|
from erpnext.stock import get_warehouse_account
|
||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||||
@ -139,8 +140,6 @@ class Warehouse(NestedSet):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_children(doctype, parent=None, company=None, is_root=False):
|
def get_children(doctype, parent=None, company=None, is_root=False):
|
||||||
from erpnext.stock.utils import get_stock_value_from_bin
|
|
||||||
|
|
||||||
if is_root:
|
if is_root:
|
||||||
parent = ""
|
parent = ""
|
||||||
|
|
||||||
@ -153,13 +152,48 @@ def get_children(doctype, parent=None, company=None, is_root=False):
|
|||||||
|
|
||||||
warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by='name')
|
warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by='name')
|
||||||
|
|
||||||
|
company_currency = ''
|
||||||
|
if company:
|
||||||
|
company_currency = frappe.get_cached_value('Company', company, 'default_currency')
|
||||||
|
|
||||||
|
warehouse_wise_value = get_warehouse_wise_stock_value(company)
|
||||||
|
|
||||||
# return warehouses
|
# return warehouses
|
||||||
for wh in warehouses:
|
for wh in warehouses:
|
||||||
wh["balance"] = get_stock_value_from_bin(warehouse=wh.value)
|
wh["balance"] = warehouse_wise_value.get(wh.value)
|
||||||
if company:
|
if company_currency:
|
||||||
wh["company_currency"] = frappe.db.get_value('Company', company, 'default_currency')
|
wh["company_currency"] = company_currency
|
||||||
return warehouses
|
return warehouses
|
||||||
|
|
||||||
|
def get_warehouse_wise_stock_value(company):
|
||||||
|
warehouses = frappe.get_all('Warehouse',
|
||||||
|
fields = ['name', 'parent_warehouse'], filters = {'company': company})
|
||||||
|
parent_warehouse = {d.name : d.parent_warehouse for d in warehouses}
|
||||||
|
|
||||||
|
filters = {'warehouse': ('in', [data.name for data in warehouses])}
|
||||||
|
bin_data = frappe.get_all('Bin', fields = ['sum(stock_value) as stock_value', 'warehouse'],
|
||||||
|
filters = filters, group_by = 'warehouse')
|
||||||
|
|
||||||
|
warehouse_wise_stock_value = defaultdict(float)
|
||||||
|
for row in bin_data:
|
||||||
|
if not row.stock_value:
|
||||||
|
continue
|
||||||
|
|
||||||
|
warehouse_wise_stock_value[row.warehouse] = row.stock_value
|
||||||
|
update_value_in_parent_warehouse(warehouse_wise_stock_value,
|
||||||
|
parent_warehouse, row.warehouse, row.stock_value)
|
||||||
|
|
||||||
|
return warehouse_wise_stock_value
|
||||||
|
|
||||||
|
def update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value):
|
||||||
|
parent_warehouse = parent_warehouse_dict.get(warehouse)
|
||||||
|
if not parent_warehouse:
|
||||||
|
return
|
||||||
|
|
||||||
|
warehouse_wise_stock_value[parent_warehouse] += flt(stock_value)
|
||||||
|
update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict,
|
||||||
|
parent_warehouse, stock_value)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_node():
|
def add_node():
|
||||||
from frappe.desk.treeview import make_tree_args
|
from frappe.desk.treeview import make_tree_args
|
||||||
|
@ -20,7 +20,7 @@ frappe.treeview_settings['Warehouse'] = {
|
|||||||
onrender: function(node) {
|
onrender: function(node) {
|
||||||
if (node.data && node.data.balance!==undefined) {
|
if (node.data && node.data.balance!==undefined) {
|
||||||
$('<span class="balance-area pull-right">'
|
$('<span class="balance-area pull-right">'
|
||||||
+ format_currency(Math.abs(node.data.balance), node.data.company_currency)
|
+ format_currency((node.data.balance), node.data.company_currency)
|
||||||
+ '</span>').insertBefore(node.$ul);
|
+ '</span>').insertBefore(node.$ul);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user