fix: merge conflict

This commit is contained in:
Nabin Hait 2021-05-31 11:21:18 +05:30
commit a3d1b6973a
104 changed files with 2251 additions and 1477 deletions

View File

@ -29,4 +29,5 @@ ignore =
B950, B950,
W191, W191,
max-line-length = 200 max-line-length = 200
exclude=.github/helper/semgrep_rules

View File

@ -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"

View File

@ -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

View File

@ -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])

View File

@ -51,3 +51,11 @@ _(f"what" + f"this is also not cool")
_("") _("")
# ruleid: frappe-translation-empty-string # ruleid: frappe-translation-empty-string
_('') _('')
class Test:
# ok: frappe-translation-python-splitting
def __init__(
args
):
pass

View File

@ -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\.]_\([^\)]*\\\s*' # lines broken by `\`
- pattern-regex: '[\s\.]_\(\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.

9
.github/helper/semgrep_rules/ux.js vendored Normal file
View File

@ -0,0 +1,9 @@
// ok: frappe-missing-translate-function-js
frappe.msgprint('{{ _("Both login and password required") }}');
// ruleid: frappe-missing-translate-function-js
frappe.msgprint('What');
// ok: frappe-missing-translate-function-js
frappe.throw(' {{ _("Both login and password required") }}. ');

View File

@ -2,30 +2,30 @@ import frappe
from frappe import msgprint, throw, _ from frappe import msgprint, throw, _
# ruleid: frappe-missing-translate-function # ruleid: frappe-missing-translate-function-python
throw("Error Occured") throw("Error Occured")
# ruleid: frappe-missing-translate-function # ruleid: frappe-missing-translate-function-python
frappe.throw("Error Occured") frappe.throw("Error Occured")
# ruleid: frappe-missing-translate-function # ruleid: frappe-missing-translate-function-python
frappe.msgprint("Useful message") frappe.msgprint("Useful message")
# ruleid: frappe-missing-translate-function # ruleid: frappe-missing-translate-function-python
msgprint("Useful message") msgprint("Useful message")
# ok: frappe-missing-translate-function # ok: frappe-missing-translate-function-python
translatedmessage = _("Hello") translatedmessage = _("Hello")
# ok: frappe-missing-translate-function # ok: frappe-missing-translate-function-python
throw(translatedmessage) throw(translatedmessage)
# ok: frappe-missing-translate-function # ok: frappe-missing-translate-function-python
msgprint(translatedmessage) msgprint(translatedmessage)
# ok: frappe-missing-translate-function # ok: frappe-missing-translate-function-python
msgprint(_("Helpful message")) msgprint(_("Helpful message"))
# ok: frappe-missing-translate-function # ok: frappe-missing-translate-function-python
frappe.throw(_("Error occured")) frappe.throw(_("Error occured"))

View File

@ -1,15 +1,30 @@
rules: rules:
- id: frappe-missing-translate-function - id: frappe-missing-translate-function-python
pattern-either: pattern-either:
- patterns: - patterns:
- pattern: frappe.msgprint("...", ...) - pattern: frappe.msgprint("...", ...)
- pattern-not: frappe.msgprint(_("..."), ...) - pattern-not: frappe.msgprint(_("..."), ...)
- pattern-not: frappe.msgprint(__("..."), ...)
- patterns: - patterns:
- pattern: frappe.throw("...", ...) - pattern: frappe.throw("...", ...)
- pattern-not: frappe.throw(_("..."), ...) - pattern-not: frappe.throw(_("..."), ...)
- pattern-not: frappe.throw(__("..."), ...)
message: | message: |
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
languages: [python, javascript, json] languages: [python]
severity: ERROR
- id: frappe-missing-translate-function-js
pattern-either:
- patterns:
- pattern: frappe.msgprint("...", ...)
- pattern-not: frappe.msgprint(__("..."), ...)
# ignore microtemplating e.g. msgprint("{{ _("server side translation") }}")
- pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...)
- patterns:
- pattern: frappe.throw("...", ...)
- pattern-not: frappe.throw(__("..."), ...)
# ignore microtemplating
- pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...)
message: |
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
languages: [javascript]
severity: ERROR severity: ERROR

View File

@ -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

View File

@ -1 +0,0 @@
disable=access-member-before-definition

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '13.3.1' __version__ = '13.4.0'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -41,7 +41,7 @@ def build_conditions(process_type, account, company):
if account: if account:
conditions += "AND %s='%s'"%(deferred_account, account) conditions += "AND %s='%s'"%(deferred_account, account)
elif company: elif company:
conditions += "AND p.company='%s'"%(company) conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions return conditions
@ -360,12 +360,10 @@ def make_gl_entries(doc, credit_account, debit_account, against,
frappe.flags.deferred_accounting_error = True frappe.flags.deferred_accounting_error = True
def send_mail(deferred_process): def send_mail(deferred_process):
title = _("Error while processing deferred accounting for {0}".format(deferred_process)) title = _("Error while processing deferred accounting for {0}").format(deferred_process)
content = _(""" link = get_link_to_form('Process Deferred Accounting', deferred_process)
Deferred accounting failed for some invoices: content = _("Deferred accounting failed for some invoices:") + "\n"
Please check Process Deferred Accounting {0} content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link)
and submit manually after resolving errors
""").format(get_link_to_form('Process Deferred Accounting', deferred_process))
sendmail_to_system_managers(title, content) sendmail_to_system_managers(title, content)
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against, def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,

View File

@ -75,8 +75,13 @@ class GLEntry(Document):
def pl_must_have_cost_center(self): def pl_must_have_cost_center(self):
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
if not self.cost_center and self.voucher_type != 'Period Closing Voucher': if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.") msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
.format(self.voucher_type, self.voucher_no, self.account)) self.voucher_type, self.voucher_no, self.account)
msg += " "
msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
self.voucher_type)
frappe.throw(msg, title=_("Missing Cost Center"))
def validate_dimensions_for_pl_and_bs(self): def validate_dimensions_for_pl_and_bs(self):
account_type = frappe.db.get_value("Account", self.account, "report_type") account_type = frappe.db.get_value("Account", self.account, "report_type")

View File

@ -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
} }

View File

@ -39,7 +39,11 @@ class JournalEntry(AccountsController):
self.validate_multi_currency() self.validate_multi_currency()
self.set_amounts_in_company_currency() self.set_amounts_in_company_currency()
self.validate_debit_credit_amount() self.validate_debit_credit_amount()
self.validate_total_debit_and_credit()
# Do not validate while importing via data import
if not frappe.flags.in_import:
self.validate_total_debit_and_credit()
self.validate_against_jv() self.validate_against_jv()
self.validate_reference_doc() self.validate_reference_doc()
self.set_against_account() self.set_against_account()

View File

@ -0,0 +1,17 @@
frappe.ui.form.on("Journal Entry", {
refresh: function(frm) {
frm.set_query('company_address', function(doc) {
if(!doc.company) {
frappe.throw(__('Please set Company'));
}
return {
query: 'frappe.contacts.doctype.address.address.address_query',
filters: {
link_doctype: 'Company',
link_name: doc.company
}
};
});
}
});

View File

@ -46,6 +46,7 @@
"reqd": 1 "reqd": 1
}, },
{ {
"default": "0",
"fieldname": "closing_amount", "fieldname": "closing_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
@ -57,7 +58,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-10-23 16:45:43.662034", "modified": "2021-05-19 20:08:44.523861",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Closing Entry Detail", "name": "POS Closing Entry Detail",

View File

@ -455,32 +455,26 @@ class POSInvoice(SalesInvoice):
@frappe.whitelist() @frappe.whitelist()
def get_stock_availability(item_code, warehouse): def get_stock_availability(item_code, warehouse):
latest_sle = frappe.db.sql("""select qty_after_transaction bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
from `tabStock Ledger Entry`
where item_code = %s and warehouse = %s where item_code = %s and warehouse = %s
order by posting_date desc, posting_time desc
limit 1""", (item_code, warehouse), as_dict=1) limit 1""", (item_code, warehouse), as_dict=1)
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0
if sle_qty and pos_sales_qty: return bin_qty - pos_sales_qty
return sle_qty - pos_sales_qty
else:
return sle_qty
def get_pos_reserved_qty(item_code, warehouse): def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent where p.name = p_item.parent
and p.consolidated_invoice is NULL and ifnull(p.consolidated_invoice, '') = ''
and p.docstatus = 1
and p_item.docstatus = 1 and p_item.docstatus = 1
and p_item.item_code = %s and p_item.item_code = %s
and p_item.warehouse = %s and p_item.warehouse = %s
""", (item_code, warehouse), as_dict=1) """, (item_code, warehouse), as_dict=1)
return reserved_qty[0].qty or 0 if reserved_qty else 0 return reserved_qty[0].qty or 0 if reserved_qty else 0
@frappe.whitelist() @frappe.whitelist()

View File

@ -42,8 +42,9 @@ class POSInvoiceMergeLog(Document):
if return_against_status != "Consolidated": if return_against_status != "Consolidated":
# if return entry is not getting merged in the current pos closing and if it is not consolidated # if return entry is not getting merged in the current pos closing and if it is not consolidated
bold_unconsolidated = frappe.bold("not Consolidated") bold_unconsolidated = frappe.bold("not Consolidated")
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ") msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}.")
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated)) .format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
msg += " "
msg += _("Original invoice should be consolidated before or along with the return invoice.") msg += _("Original invoice should be consolidated before or along with the return invoice.")
msg += "<br><br>" msg += "<br><br>"
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against) msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
@ -56,12 +57,12 @@ class POSInvoiceMergeLog(Document):
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0] sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
sales_invoice, credit_note = "", "" sales_invoice, credit_note = "", ""
if sales:
sales_invoice = self.process_merging_into_sales_invoice(sales)
if returns: if returns:
credit_note = self.process_merging_into_credit_note(returns) credit_note = self.process_merging_into_credit_note(returns)
if sales:
sales_invoice = self.process_merging_into_sales_invoice(sales)
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note) self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
@ -274,9 +275,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
closing_entry.db_set('error_message', '') closing_entry.db_set('error_message', '')
closing_entry.update_opening_entry() closing_entry.update_opening_entry()
except Exception: except Exception as e:
frappe.db.rollback() frappe.db.rollback()
message_log = frappe.message_log.pop() message_log = frappe.message_log.pop() if frappe.message_log else str(e)
error_message = safe_load_json(message_log) error_message = safe_load_json(message_log)
if closing_entry: if closing_entry:
@ -300,9 +301,9 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
closing_entry.db_set('error_message', '') closing_entry.db_set('error_message', '')
closing_entry.update_opening_entry(for_cancel=True) closing_entry.update_opening_entry(for_cancel=True)
except Exception: except Exception as e:
frappe.db.rollback() frappe.db.rollback()
message_log = frappe.message_log.pop() message_log = frappe.message_log.pop() if frappe.message_log else str(e)
error_message = safe_load_json(message_log) error_message = safe_load_json(message_log)
if closing_entry: if closing_entry:
@ -348,11 +349,9 @@ def job_already_enqueued(job_name):
return True return True
def safe_load_json(message): def safe_load_json(message):
JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
try: try:
json_message = json.loads(message).get('message') json_message = json.loads(message).get('message')
except JSONDecodeError: except Exception:
json_message = message json_message = message
return json_message return json_message

View File

@ -13,7 +13,6 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2> <h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
<div> <div>
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5> <h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>

View File

@ -286,7 +286,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2021-05-13 12:44:19.574844", "modified": "2021-05-21 11:14:22.426672",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Process Statement Of Accounts", "name": "Process Statement Of Accounts",

View File

@ -94,10 +94,11 @@ def get_report_pdf(doc, consolidated=True):
continue continue
html = frappe.render_template(template_path, \ html = frappe.render_template(template_path, \
{"filters": filters, "data": res, "ageing": ageing[0] if doc.include_ageing else None, {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None,
"letter_head": letter_head if doc.letter_head else None, "letter_head": letter_head if doc.letter_head else None,
"terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms') "terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms')
if doc.terms_and_conditions else None}) if doc.terms_and_conditions else None})
html = frappe.render_template(base_template_path, {"body": html, \ html = frappe.render_template(base_template_path, {"body": html, \
"css": get_print_style(), "title": "Statement For " + entry.customer}) "css": get_print_style(), "title": "Statement For " + entry.customer})
statement_dict[entry.customer] = html statement_dict[entry.customer] = html

View File

@ -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',
@ -897,6 +907,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);

View File

@ -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
}, },
{ {

View File

@ -1137,7 +1137,6 @@ class SalesInvoice(SellingController):
""" """
self.set_serial_no_against_delivery_note() self.set_serial_no_against_delivery_note()
self.validate_serial_against_delivery_note() self.validate_serial_against_delivery_note()
self.validate_serial_against_sales_invoice()
def set_serial_no_against_delivery_note(self): def set_serial_no_against_delivery_note(self):
for item in self.items: for item in self.items:
@ -1168,26 +1167,6 @@ class SalesInvoice(SellingController):
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format( frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
item.idx, item.qty, item.item_code, len(si_serial_nos))) item.idx, item.qty, item.item_code, len(si_serial_nos)))
def validate_serial_against_sales_invoice(self):
""" check if serial number is already used in other sales invoice """
for item in self.items:
if not item.serial_no:
continue
for serial_no in item.serial_no.split("\n"):
serial_no_details = frappe.db.get_value("Serial No", serial_no,
["sales_invoice", "item_code"], as_dict=1)
if not serial_no_details:
continue
if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \
and self.name != serial_no_details.sales_invoice:
sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
if sales_invoice_company == self.company:
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}")
.format(serial_no, serial_no_details.sales_invoice))
def update_project(self): def update_project(self):
if self.project: if self.project:
project = frappe.get_doc("Project", self.project) project = frappe.get_doc("Project", self.project)

View File

@ -933,12 +933,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse")) self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
"delivery_document_no"), si.name) "delivery_document_no"), si.name)
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"),
si.name)
# check if the serial number is already linked with any other Sales Invoice
_si = frappe.copy_doc(si.as_dict())
self.assertRaises(frappe.ValidationError, _si.insert)
return si return si

View File

@ -165,7 +165,7 @@ def add_data_for_operating_activities(
if profit_data: if profit_data:
profit_data.update({ profit_data.update({
"indent": 1, "indent": 1,
"parent_account": get_mapper_for(light_mappers, position=0)['section_header'] "parent_account": get_mapper_for(light_mappers, position=1)['section_header']
}) })
data.append(profit_data) data.append(profit_data)
section_data.append(profit_data) section_data.append(profit_data)
@ -312,10 +312,10 @@ def add_data_for_other_activities(
def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper): def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper):
data = [] data = []
operating_activities_mapper = get_mapper_for(light_mappers, position=0) operating_activities_mapper = get_mapper_for(light_mappers, position=1)
other_mappers = [ other_mappers = [
get_mapper_for(light_mappers, position=1), get_mapper_for(light_mappers, position=2),
get_mapper_for(light_mappers, position=2) get_mapper_for(light_mappers, position=3)
] ]
if operating_activities_mapper: if operating_activities_mapper:

View File

@ -0,0 +1,81 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
"filters": [
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
"fieldname": "fiscal_year",
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"reqd": 1,
"on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year;
if (!fiscal_year) {
return;
}
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
frappe.query_report.set_filter_value({
from_date: fy.year_start_date,
to_date: fy.year_end_date
});
});
}
},
{
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_start_date"),
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_end_date"),
},
{
"fieldname": "finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
"options": "Finance Book",
},
{
"fieldname": "dimension",
"label": __("Select Dimension"),
"fieldtype": "Select",
"options": get_accounting_dimension_options(),
"reqd": 1,
},
],
"formatter": erpnext.financial_statements.formatter,
"tree": true,
"name_field": "account",
"parent_field": "parent_account",
"initial_depth": 3
}
});
function get_accounting_dimension_options() {
let options =["", "Cost Center", "Project"];
frappe.db.get_list('Accounting Dimension',
{fields:['document_type']}).then((res) => {
res.forEach((dimension) => {
options.push(dimension.document_type);
});
});
return options
}

View File

@ -0,0 +1,22 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2021-04-09 16:48:59.548018",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2021-04-09 16:48:59.548018",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Dimension-wise Accounts Balance Report",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "GL Entry",
"report_name": "Dimension-wise Accounts Balance Report",
"report_type": "Script Report",
"roles": []
}

View File

@ -0,0 +1,213 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe, erpnext
from frappe import _
from frappe.utils import (flt, cstr)
from erpnext.accounts.report.financial_statements import filter_accounts, filter_out_zero_value_rows
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
from six import itervalues
def execute(filters=None):
validate_filters(filters)
dimension_items_list = get_dimension_items_list(filters.dimension, filters.company)
if not dimension_items_list:
return [], []
dimension_items_list = [''.join(d) for d in dimension_items_list]
columns = get_columns(dimension_items_list)
data = get_data(filters, dimension_items_list)
return columns, data
def get_data(filters, dimension_items_list):
company_currency = erpnext.get_company_currency(filters.company)
acc = frappe.db.sql("""
select
name, account_number, parent_account, lft, rgt, root_type,
report_type, account_name, include_in_gross, account_type, is_group
from
`tabAccount`
where
company=%s
order by lft""", (filters.company), as_dict=True)
if not acc:
return None
accounts, accounts_by_name, parent_children_map = filter_accounts(acc)
min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabAccount`
where company=%s""", (filters.company))[0]
account = frappe.db.sql_list("""select name from `tabAccount`
where lft >= %s and rgt <= %s and company = %s""", (min_lft, max_rgt, filters.company))
gl_entries_by_account = {}
set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account)
format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list)
accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list)
out = prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list)
out = filter_out_zero_value_rows(out, parent_children_map)
return out
def set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account):
for item in dimension_items_list:
condition = get_condition(filters.from_date, item, filters.dimension)
if account:
condition += " and account in ({})"\
.format(", ".join([frappe.db.escape(d) for d in account]))
gl_filters = {
"company": filters.get("company"),
"from_date": filters.get("from_date"),
"to_date": filters.get("to_date"),
"finance_book": cstr(filters.get("finance_book"))
}
gl_filters['item'] = ''.join(item)
if filters.get("include_default_book_entries"):
gl_filters["company_fb"] = frappe.db.get_value("Company",
filters.company, 'default_finance_book')
for key, value in filters.items():
if value:
gl_filters.update({
key: value
})
gl_entries = frappe.db.sql("""
select
posting_date, account, debit, credit, is_opening, fiscal_year,
debit_in_account_currency, credit_in_account_currency, account_currency
from
`tabGL Entry`
where
company=%(company)s
{condition}
and posting_date <= %(to_date)s
and is_cancelled = 0
order by account, posting_date""".format(
condition=condition),
gl_filters, as_dict=True) #nosec
for entry in gl_entries:
entry['dimension_item'] = ''.join(item)
gl_entries_by_account.setdefault(entry.account, []).append(entry)
def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list):
for entries in itervalues(gl_entries_by_account):
for entry in entries:
d = accounts_by_name.get(entry.account)
if not d:
frappe.msgprint(
_("Could not retrieve information for {0}.").format(entry.account), title="Error",
raise_exception=1
)
for item in dimension_items_list:
if item == entry.dimension_item:
d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit)
def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list):
data = []
for d in accounts:
has_value = False
total = 0
row = {
"account": d.name,
"parent_account": d.parent_account,
"indent": d.indent,
"from_date": filters.from_date,
"to_date": filters.to_date,
"currency": company_currency,
"account_name": ('{} - {}'.format(d.account_number, d.account_name)
if d.account_number else d.account_name)
}
for item in dimension_items_list:
row[frappe.scrub(item)] = flt(d.get(frappe.scrub(item), 0.0), 3)
if abs(row[frappe.scrub(item)]) >= 0.005:
# ignore zero values
has_value = True
total += flt(d.get(frappe.scrub(item), 0.0), 3)
row["has_value"] = has_value
row["total"] = total
data.append(row)
return data
def accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list):
"""accumulate children's values in parent accounts"""
for d in reversed(accounts):
if d.parent_account:
for item in dimension_items_list:
accounts_by_name[d.parent_account][frappe.scrub(item)] = \
accounts_by_name[d.parent_account].get(frappe.scrub(item), 0.0) + d.get(frappe.scrub(item), 0.0)
def get_condition(from_date, item, dimension):
conditions = []
if from_date:
conditions.append("posting_date >= %(from_date)s")
if dimension:
if dimension not in ['Cost Center', 'Project']:
if dimension in ['Customer', 'Supplier']:
dimension = 'Party'
else:
dimension = 'Voucher No'
txt = "{0} = %(item)s".format(frappe.scrub(dimension))
conditions.append(txt)
return " and {}".format(" and ".join(conditions)) if conditions else ""
def get_dimension_items_list(dimension, company):
meta = frappe.get_meta(dimension, cached=False)
fieldnames = [d.fieldname for d in meta.get("fields")]
filters = {}
if 'company' in fieldnames:
filters['company'] = company
return frappe.get_all(dimension, filters, as_list=True)
def get_columns(dimension_items_list, accumulated_values=1, company=None):
columns = [{
"fieldname": "account",
"label": _("Account"),
"fieldtype": "Link",
"options": "Account",
"width": 300
}]
if company:
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"hidden": 1
})
for item in dimension_items_list:
columns.append({
"fieldname": frappe.scrub(item),
"label": item,
"fieldtype": "Currency",
"options": "currency",
"width": 150
})
columns.append({
"fieldname": "total",
"label": "Total",
"fieldtype": "Currency",
"options": "currency",
"width": 150
})
return columns

View File

@ -166,6 +166,11 @@ frappe.query_reports["General Ledger"] = {
"fieldname": "show_cancelled_entries", "fieldname": "show_cancelled_entries",
"label": __("Show Cancelled Entries"), "label": __("Show Cancelled Entries"),
"fieldtype": "Check" "fieldtype": "Check"
},
{
"fieldname": "show_net_values_in_party_account",
"label": __("Show Net Values in Party Account"),
"fieldtype": "Check"
} }
] ]
} }

View File

@ -344,6 +344,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
consolidated_gle = OrderedDict() consolidated_gle = OrderedDict()
group_by = group_by_field(filters.get('group_by')) group_by = group_by_field(filters.get('group_by'))
if filters.get('show_net_values_in_party_account'):
account_type_map = get_account_type_map(filters.get('company'))
def update_value_in_dict(data, key, gle): def update_value_in_dict(data, key, gle):
data[key].debit += flt(gle.debit) data[key].debit += flt(gle.debit)
data[key].credit += flt(gle.credit) data[key].credit += flt(gle.credit)
@ -351,6 +354,24 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
if filters.get('show_net_values_in_party_account') and \
account_type_map.get(data[key].account) in ('Receivable', 'Payable'):
net_value = flt(data[key].debit) - flt(data[key].credit)
net_value_in_account_currency = flt(data[key].debit_in_account_currency) \
- flt(data[key].credit_in_account_currency)
if net_value < 0:
dr_or_cr = 'credit'
rev_dr_or_cr = 'debit'
else:
dr_or_cr = 'debit'
rev_dr_or_cr = 'credit'
data[key][dr_or_cr] = abs(net_value)
data[key][dr_or_cr+'_in_account_currency'] = abs(net_value_in_account_currency)
data[key][rev_dr_or_cr] = 0
data[key][rev_dr_or_cr+'_in_account_currency'] = 0
if data[key].against_voucher and gle.against_voucher: if data[key].against_voucher and gle.against_voucher:
data[key].against_voucher += ', ' + gle.against_voucher data[key].against_voucher += ', ' + gle.against_voucher
@ -388,6 +409,12 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
return totals, entries return totals, entries
def get_account_type_map(company):
account_type_map = frappe._dict(frappe.get_all('Account', fields=['name', 'account_type'],
filters={'company': company}, as_list=1))
return account_type_map
def get_result_as_list(data, filters): def get_result_as_list(data, filters):
balance, balance_in_account_currency = 0, 0 balance, balance_in_account_currency = 0, 0
inv_details = get_supplier_invoice_details() inv_details = get_supplier_invoice_details()

View File

@ -81,8 +81,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
presentation_currency = currency_info['presentation_currency'] presentation_currency = currency_info['presentation_currency']
company_currency = currency_info['company_currency'] company_currency = currency_info['company_currency']
pl_accounts = [d.name for d in frappe.get_list('Account', account_currencies = list(set(entry['account_currency'] for entry in gl_entries))
filters={'report_type': 'Profit and Loss', 'company': company})]
for entry in gl_entries: for entry in gl_entries:
account = entry['account'] account = entry['account']
@ -92,10 +91,15 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
credit_in_account_currency = flt(entry['credit_in_account_currency']) credit_in_account_currency = flt(entry['credit_in_account_currency'])
account_currency = entry['account_currency'] account_currency = entry['account_currency']
if account_currency != presentation_currency: if len(account_currencies) == 1 and account_currency == presentation_currency:
value = debit or credit if entry.get('debit'):
entry['debit'] = debit_in_account_currency
date = entry['posting_date'] if account in pl_accounts else currency_info['report_date'] if entry.get('credit'):
entry['credit'] = credit_in_account_currency
else:
value = debit or credit
date = currency_info['report_date']
converted_value = convert(value, presentation_currency, company_currency, date) converted_value = convert(value, presentation_currency, company_currency, date)
if entry.get('debit'): if entry.get('debit'):
@ -104,13 +108,6 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
if entry.get('credit'): if entry.get('credit'):
entry['credit'] = converted_value entry['credit'] = converted_value
elif account_currency == presentation_currency:
if entry.get('debit'):
entry['debit'] = debit_in_account_currency
if entry.get('credit'):
entry['credit'] = credit_in_account_currency
converted_gl_list.append(entry) converted_gl_list.append(entry)
return converted_gl_list return converted_gl_list

View File

@ -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

View File

@ -383,8 +383,14 @@
"icon": "fa fa-user", "icon": "fa fa-user",
"idx": 370, "idx": 370,
"image_field": "image", "image_field": "image",
"links": [], "links": [
"modified": "2021-01-06 19:51:40.939087", {
"group": "Item Group",
"link_doctype": "Supplier Item Group",
"link_fieldname": "supplier"
}
],
"modified": "2021-05-18 15:10:11.087191",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier", "name": "Supplier",

View File

@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Supplier Item Group', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,77 @@
{
"actions": [],
"creation": "2021-05-07 18:16:40.621421",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"supplier",
"item_group"
],
"fields": [
{
"fieldname": "supplier",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Supplier",
"options": "Supplier",
"reqd": 1
},
{
"fieldname": "item_group",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Group",
"options": "Item Group",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-05-19 13:48:16.742303",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Item Group",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,18 @@
# -*- 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 import _
from frappe.model.document import Document
class SupplierItemGroup(Document):
def validate(self):
exists = frappe.db.exists({
'doctype': 'Supplier Item Group',
'supplier': self.supplier,
'item_group': self.item_group
})
if exists:
frappe.throw(_("Item Group has already been linked to this supplier."))

View File

@ -0,0 +1,10 @@
# -*- 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 TestSupplierItemGroup(unittest.TestCase):
pass

View File

@ -0,0 +1,54 @@
# Version 13.4.0 Release Notes
### Features & Enhancements
- Multiple GST enhancement and fixes ([#25249](https://github.com/frappe/erpnext/pull/25249))
- Linking supplier with an item group for filtering items ([#25683](https://github.com/frappe/erpnext/pull/25683))
- Leave Policy Assignment Refactor ([#24327](https://github.com/frappe/erpnext/pull/24327))
- Dimension-wise Accounts Balance Report ([#25260](https://github.com/frappe/erpnext/pull/25260))
- Show net values in Party Accounts ([#25714](https://github.com/frappe/erpnext/pull/25714))
- Add pending qty section to batch/serial selector dialog ([#25519](https://github.com/frappe/erpnext/pull/25519))
- enhancements in Training Event ([#25782](https://github.com/frappe/erpnext/pull/25782))
- Refactored timesheet ([#25701](https://github.com/frappe/erpnext/pull/25701))
### Fixes
- Process Statement of Accounts formatting ([#25777](https://github.com/frappe/erpnext/pull/25777))
- Removed serial no validation for sales invoice ([#25817](https://github.com/frappe/erpnext/pull/25817))
- Fetch email id from dialog box in pos past order summary ([#25808](https://github.com/frappe/erpnext/pull/25808))
- Don't map set warehouse from delivery note to purchase receipt ([#25672](https://github.com/frappe/erpnext/pull/25672))
- Apply permission while selecting projects ([#25765](https://github.com/frappe/erpnext/pull/25765))
- Error on adding bank account to plaid ([#25658](https://github.com/frappe/erpnext/pull/25658))
- Set disable rounded total if it is globally enabled ([#25789](https://github.com/frappe/erpnext/pull/25789))
- Wrong amount on CR side in general ledger report for customer when different account currencies are involved ([#25654](https://github.com/frappe/erpnext/pull/25654))
- Stock move dialog duplicate submit actions (V13) ([#25486](https://github.com/frappe/erpnext/pull/25486))
- Cashflow mapper not showing data ([#25815](https://github.com/frappe/erpnext/pull/25815))
- Ignore rounding diff while importing JV using data import ([#25816](https://github.com/frappe/erpnext/pull/25816))
- Woocommerce order sync issue ([#25688](https://github.com/frappe/erpnext/pull/25688))
- Expected amount in pos closing payments table ([#25737](https://github.com/frappe/erpnext/pull/25737))
- Show only company addresses for ITC reversal entry ([#25867](https://github.com/frappe/erpnext/pull/25867))
- Timeout error while loading warehouse tree ([#25694](https://github.com/frappe/erpnext/pull/25694))
- Plaid Withdrawals and Deposits are recorded incorrectly ([#25784](https://github.com/frappe/erpnext/pull/25784))
- Return case for item with available qty equal to one ([#25760](https://github.com/frappe/erpnext/pull/25760))
- The status of repost item valuation showing In Progress since long time ([#25754](https://github.com/frappe/erpnext/pull/25754))
- Updated applicable charges form in landed cost voucher ([#25732](https://github.com/frappe/erpnext/pull/25732))
- Rearrange buttons for Company DocType ([#25617](https://github.com/frappe/erpnext/pull/25617))
- Show uom for item in selector dialog ([#25697](https://github.com/frappe/erpnext/pull/25697))
- Warehouse not found in stock entry ([#25776](https://github.com/frappe/erpnext/pull/25776))
- Use dictionary filter instead of list (bp #25874 pre-release) ([#25875](https://github.com/frappe/erpnext/pull/25875))
- Send emails on rfq submit ([#25695](https://github.com/frappe/erpnext/pull/25695))
- Cannot bypass e-invoicing for non gst item invoices ([#25759](https://github.com/frappe/erpnext/pull/25759))
- Validation message of quality inspection in purchase receipt ([#25666](https://github.com/frappe/erpnext/pull/25666))
- Dialog variable assignment after definition in POS ([#25681](https://github.com/frappe/erpnext/pull/25681))
- Wrong quantity after transaction for parallel stock transactions ([#25779](https://github.com/frappe/erpnext/pull/25779))
- Item Variant Details Report ([#25797](https://github.com/frappe/erpnext/pull/25797))
- Duplicate stock entry on multiple click ([#25742](https://github.com/frappe/erpnext/pull/25742))
- Bank statement import via google sheet ([#25676](https://github.com/frappe/erpnext/pull/25676))
- Change today to now to get data for reposting ([#25702](https://github.com/frappe/erpnext/pull/25702))
- Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet ([#25698](https://github.com/frappe/erpnext/pull/25698))
- Ageing error in PSOA ([#25857](https://github.com/frappe/erpnext/pull/25857))
- Breaking cost center validation ([#25660](https://github.com/frappe/erpnext/pull/25660))
- Project filter for Kanban Board ([#25744](https://github.com/frappe/erpnext/pull/25744))
- Show allow zero valuation only when auto checked ([#25778](https://github.com/frappe/erpnext/pull/25778))
- Missing cost center message on creating gl entries ([#25755](https://github.com/frappe/erpnext/pull/25755))
- Address template with upper filter throws jinja error ([#25756](https://github.com/frappe/erpnext/pull/25756))

View File

@ -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)

View File

@ -204,7 +204,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if "description" in searchfields: if "description" in searchfields:
searchfields.remove("description") searchfields.remove("description")
columns = '' columns = ''
extra_searchfields = [field for field in searchfields extra_searchfields = [field for field in searchfields
if not field in ["name", "item_group", "description"]] if not field in ["name", "item_group", "description"]]
@ -216,11 +216,22 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if not field in searchfields] if not field in searchfields]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
if filters.get('supplier'):
item_group_list = frappe.get_all('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
item_groups = []
for i in item_group_list:
item_groups.append(i.item_group)
del filters['supplier']
if item_groups:
filters['item_group'] = ['in', item_groups]
description_cond = '' description_cond = ''
if frappe.db.count('Item', cache=True) < 50000: if frappe.db.count('Item', cache=True) < 50000:
# scan description only if items are less than 50000 # scan description only if items are less than 50000
description_cond = 'or tabItem.description LIKE %(txt)s' description_cond = 'or tabItem.description LIKE %(txt)s'
return frappe.db.sql("""select tabItem.name, return frappe.db.sql("""select tabItem.name,
if(length(tabItem.item_name) > 40, if(length(tabItem.item_name) > 40,
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,

View File

@ -76,12 +76,12 @@ status_map = {
["Stopped", "eval:self.status == 'Stopped'"], ["Stopped", "eval:self.status == 'Stopped'"],
["Cancelled", "eval:self.docstatus == 2"], ["Cancelled", "eval:self.docstatus == 2"],
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"], ["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], ["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"], ["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"], ["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], ["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], ["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"] ["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
], ],
"Bank Transaction": [ "Bank Transaction": [

View File

@ -7,6 +7,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import urllib import urllib
from urllib.parse import quote
import hashlib import hashlib
import hmac import hmac
import base64 import base64
@ -68,8 +69,9 @@ def calc_md5(string):
""" """
md = hashlib.md5() md = hashlib.md5()
md.update(string) md.update(string)
return base64.encodestring(md.digest()).strip('\n') if six.PY2 \ return base64.encodebytes(md.digest()).decode().strip()
else base64.encodebytes(md.digest()).decode().strip()
def remove_empty(d): def remove_empty(d):
""" """
@ -177,7 +179,6 @@ class MWS(object):
'SignatureMethod': 'HmacSHA256', 'SignatureMethod': 'HmacSHA256',
} }
params.update(extra_data) params.update(extra_data)
quote = urllib.quote if six.PY2 else urllib.parse.quote
request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)]) request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)])
signature = self.calc_signature(method, request_description) signature = self.calc_signature(method, request_description)
url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature)) url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature))

View File

@ -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
@ -183,11 +183,11 @@ def new_bank_transaction(transaction):
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"])) bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
if float(transaction["amount"]) >= 0: if float(transaction["amount"]) >= 0:
debit = float(transaction["amount"])
credit = 0
else:
debit = 0 debit = 0
credit = abs(float(transaction["amount"])) credit = float(transaction["amount"])
else:
debit = abs(float(transaction["amount"]))
credit = 0
status = "Pending" if transaction["pending"] == "True" else "Settled" status = "Pending" if transaction["pending"] == "True" else "Settled"

View File

@ -269,10 +269,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"],
@ -366,10 +368,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,7 +426,6 @@ 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' 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
}, },

View File

@ -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']
},
] ]
} }

View File

@ -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",

View File

@ -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):

View File

@ -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)

View File

@ -7,7 +7,7 @@ def get_data():
'transactions': [ 'transactions': [
{ {
'label': _('Leaves'), 'label': _('Leaves'),
'items': ['Leave Allocation'] 'items': ['Leave Policy Assignment', 'Leave Allocation']
}, },
] ]
} }

View File

@ -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) {

View File

@ -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

View File

@ -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']
},
]
}

View File

@ -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 () {

View File

@ -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()

View File

@ -2,23 +2,41 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Training Event', { frappe.ui.form.on('Training Event', {
onload_post_render: function(frm) { onload_post_render: function (frm) {
frm.get_field("employees").grid.set_multiple_add("employee"); frm.get_field("employees").grid.set_multiple_add("employee");
}, },
refresh: function(frm) { refresh: function (frm) {
if(!frm.doc.__islocal) { if (!frm.doc.__islocal) {
frm.add_custom_button(__("Training Result"), function() { frm.add_custom_button(__("Training Result"), function () {
frappe.route_options = { frappe.route_options = {
training_event: frm.doc.name training_event: frm.doc.name
} };
frappe.set_route("List", "Training Result"); frappe.set_route("List", "Training Result");
}); });
frm.add_custom_button(__("Training Feedback"), function() { frm.add_custom_button(__("Training Feedback"), function () {
frappe.route_options = { frappe.route_options = {
training_event: frm.doc.name training_event: frm.doc.name
} };
frappe.set_route("List", "Training Feedback"); frappe.set_route("List", "Training Feedback");
}); });
} }
} }
}); });
frappe.ui.form.on("Training Event Employee", {
employee: function (frm) {
let emp = [];
for (let d in frm.doc.employees) {
if (frm.doc.employees[d].employee) {
emp.push(frm.doc.employees[d].employee);
}
}
frm.set_query("employee", "employees", function () {
return {
filters: {
name: ["NOT IN", emp]
}
};
});
}
});

View File

@ -1,241 +1,80 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2016-08-08 05:33:39.965305",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2016-08-08 05:33:39.965305", "employee",
"custom": 0, "employee_name",
"docstatus": 0, "department",
"doctype": "DocType", "column_break_3",
"document_type": "", "status",
"editable_grid": 1, "attendance",
"is_mandatory"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "employee",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Employee",
"collapsible": 0, "options": "Employee"
"columns": 0, },
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.employee_name",
"allow_in_quick_entry": 0, "fieldname": "employee_name",
"allow_on_submit": 0, "fieldtype": "Read Only",
"bold": 0, "label": "Employee Name"
"collapsible": 0, },
"columns": 0,
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.department",
"allow_in_quick_entry": 0, "fieldname": "department",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "label": "Department",
"collapsible": 0, "options": "Department",
"columns": 0, "read_only": 1
"fetch_from": "employee.department", },
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_3",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_on_submit": 1,
"allow_in_quick_entry": 0, "default": "Open",
"allow_on_submit": 1, "fieldname": "status",
"bold": 0, "fieldtype": "Select",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Status",
"default": "Open", "no_copy": 1,
"fieldname": "status", "options": "Open\nInvited\nCompleted\nFeedback Submitted"
"fieldtype": "Select", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Status",
"length": 0,
"no_copy": 1,
"options": "Open\nInvited\nCompleted\nFeedback Submitted",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "attendance",
"allow_in_quick_entry": 0, "fieldtype": "Select",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Attendance",
"collapsible": 0, "options": "Present\nAbsent"
"columns": 0, },
"fieldname": "attendance", {
"fieldtype": "Select", "columns": 2,
"hidden": 0, "default": "1",
"ignore_user_permissions": 0, "fieldname": "is_mandatory",
"ignore_xss_filter": 0, "fieldtype": "Check",
"in_filter": 0, "in_list_view": 1,
"in_global_search": 0, "label": "Is Mandatory"
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Attendance",
"length": 0,
"no_copy": 0,
"options": "Mandatory\nOptional",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "index_web_pages_for_search": 1,
"hide_heading": 0, "istable": 1,
"hide_toolbar": 0, "links": [],
"idx": 0, "modified": "2021-05-21 12:41:59.336237",
"image_view": 0, "modified_by": "Administrator",
"in_create": 0, "module": "HR",
"is_submittable": 0, "name": "Training Event Employee",
"issingle": 0, "owner": "Administrator",
"istable": 1, "permissions": [],
"max_attachments": 0, "quick_entry": 1,
"modified": "2019-01-30 11:28:16.170333", "sort_field": "modified",
"modified_by": "Administrator", "sort_order": "DESC"
"module": "HR",
"name": "Training Event Employee",
"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": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -11,16 +11,18 @@
"event": "Submit", "event": "Submit",
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{ doc.introduction }}</li>\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n </ul>\n {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>", "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n {{ doc.introduction }}\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n {% if doc.is_mandatory %}\n <li>Note: This Training Event is mandatory</li>\n {% endif %}\n </ul>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
"modified": "2019-11-29 15:38:31.805409", "modified": "2021-05-24 16:29:13.165930",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Training Scheduled", "name": "Training Scheduled",
"owner": "Administrator", "owner": "Administrator",
"recipients": [ "recipients": [
{ {
"email_by_document_field": "employee_emails" "receiver_by_document_field": "employee_emails"
} }
], ],
"send_system_notification": 0,
"send_to_all_assignees": 0,
"subject": "Training Scheduled: {{ doc.name }}" "subject": "Training Scheduled: {{ doc.name }}"
} }

View File

@ -35,6 +35,9 @@
</li> </li>
{% endif %} {% endif %}
<li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li> <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
{% if doc.is_mandatory %}
<li>Note: This Training Event is mandatory</li>
{% endif %}
</ul> </ul>
</div> </div>
</td> </td>

View File

@ -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):

View File

@ -76,9 +76,9 @@ frappe.ui.form.on("Work Order", {
frm.set_query("production_item", function() { frm.set_query("production_item", function() {
return { return {
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query",
filters:[ filters: {
['is_stock_item', '=',1] "is_stock_item": 1,
] }
}; };
}); });

View File

@ -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
@ -779,3 +780,4 @@ erpnext.patches.v13_0.germany_make_custom_fields
erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.germany_fill_debtor_creditor_number
erpnext.patches.v13_0.set_pos_closing_as_failed erpnext.patches.v13_0.set_pos_closing_as_failed
erpnext.patches.v13_0.update_timesheet_changes erpnext.patches.v13_0.update_timesheet_changes
erpnext.patches.v13_0.set_training_event_attendance

View 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'],
})

View File

@ -0,0 +1,9 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc('hr', 'doctype', 'training_event')
frappe.reload_doc('hr', 'doctype', 'training_event_employee')
frappe.db.sql("update `tabTraining Event Employee` set `attendance` = 'Present'")
frappe.db.sql("update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'")

View File

@ -87,7 +87,7 @@ frappe.ui.form.on("Project", {
frm.add_custom_button(__("Kanban Board"), () => { frm.add_custom_button(__("Kanban Board"), () => {
frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', {
project: frm.doc.project_name project: frm.doc.name
}).then(() => { }).then(() => {
frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name);
}); });

View File

@ -523,8 +523,9 @@ def update_project_sales_billing():
def create_kanban_board_if_not_exists(project): def create_kanban_board_if_not_exists(project):
from frappe.desk.doctype.kanban_board.kanban_board import quick_kanban_board from frappe.desk.doctype.kanban_board.kanban_board import quick_kanban_board
if not frappe.db.exists('Kanban Board', project): project = frappe.get_doc('Project', project)
quick_kanban_board('Task', project, 'status', project) if not frappe.db.exists('Kanban Board', project.project_name):
quick_kanban_board('Task', project.project_name, 'status', project.name)
return True return True

View File

@ -5,12 +5,6 @@ frappe.provide("erpnext.projects");
frappe.ui.form.on("Task", { frappe.ui.form.on("Task", {
setup: function (frm) { setup: function (frm) {
frm.set_query("project", function () {
return {
query: "erpnext.projects.doctype.task.task.get_project"
}
});
frm.make_methods = { frm.make_methods = {
'Timesheet': () => frappe.model.open_mapped_doc({ 'Timesheet': () => frappe.model.open_mapped_doc({
method: 'erpnext.projects.doctype.task.task.make_timesheet', method: 'erpnext.projects.doctype.task.task.make_timesheet',

View File

@ -8,7 +8,7 @@ from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_
from erpnext.projects.report.project_profitability.project_profitability import execute from erpnext.projects.report.project_profitability.project_profitability import execute
class TestProjectProfitability(unittest.TestCase): class TestProjectProfitability(unittest.TestCase):
@classmethod
def setUp(self): def setUp(self):
emp = make_employee('test_employee_9@salary.com', company='_Test Company') emp = make_employee('test_employee_9@salary.com', company='_Test Company')
if not frappe.db.exists('Salary Component', 'Timesheet Component'): if not frappe.db.exists('Salary Component', 'Timesheet Component'):
@ -21,7 +21,7 @@ class TestProjectProfitability(unittest.TestCase):
self.sales_invoice.due_date = nowdate() self.sales_invoice.due_date = nowdate()
self.sales_invoice.submit() self.sales_invoice.submit()
frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 8) frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8)
def test_project_profitability(self): def test_project_profitability(self):
filters = { filters = {
@ -55,4 +55,4 @@ class TestProjectProfitability(unittest.TestCase):
def tearDown(self): def tearDown(self):
frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel()
frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
frappe.get_doc("Timesheet", self.timesheet.name).cancel() frappe.get_doc("Timesheet", self.timesheet.name).cancel()

View File

@ -84,13 +84,13 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac
if (me.frm.doc.is_subcontracted == "Yes") { if (me.frm.doc.is_subcontracted == "Yes") {
return{ return{
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query",
filters:{ 'is_sub_contracted_item': 1 } filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 }
} }
} }
else { else {
return{ return{
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query",
filters: {'is_purchase_item': 1} filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1 }
} }
} }
}); });

View File

@ -74,9 +74,18 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
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 SerialNoBatchSelector {
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();
@ -314,7 +324,23 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
qty_field.set_input(total_qty); qty_field.set_input(total_qty);
} }
get_batch_fields() { 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() {
var me = this; var me = this;
return [ return [
@ -415,6 +441,7 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
} }
me.update_total_qty(); me.update_total_qty();
me.update_pending_qtys();
} }
}, },
], ],
@ -510,4 +537,61 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
} }
]; ];
} }
}; });
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;
}

View File

@ -1,4 +1,4 @@
{{ address_line1 }}<br> {{ address_line1 }}<br>
{% if address_line2 %}{{ address_line2 }}<br>{% endif -%} {% if address_line2 %}{{ address_line2 }}<br>{% endif -%}
{{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}<br>{% endif -%} {{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}<br>{% endif -%}
{% if country != "United States" %}{{ country|upper }}{% endif -%} {% if country != "United States" %}{{ country }}{% endif -%}

View File

@ -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>

View File

@ -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') or '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

View File

@ -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
}
]
}
}

View File

@ -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)

View File

@ -55,8 +55,7 @@ def get_datev_csv(data, filters, csv_class):
quoting=QUOTE_NONNUMERIC quoting=QUOTE_NONNUMERIC
) )
if not six.PY2: data = data.encode('latin_1', errors='replace')
data = data.encode('latin_1', errors='replace')
header = get_header(filters, csv_class) header = get_header(filters, csv_class)
header = ';'.join(header).encode('latin_1', errors='replace') header = ';'.join(header).encode('latin_1', errors='replace')

View File

@ -43,8 +43,9 @@ def validate_eligibility(doc):
invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
no_taxes_applied = not doc.get('taxes') no_taxes_applied = not doc.get('taxes')
has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst'))
if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied: if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item:
return False return False
return True return True
@ -533,11 +534,9 @@ def santize_einvoice_fields(einvoice):
return einvoice return einvoice
def safe_json_load(json_string): def safe_json_load(json_string):
JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
try: try:
return json.loads(json_string) return json.loads(json_string)
except JSONDecodeError as e: except json.JSONDecodeError as e:
# print a snippet of 40 characters around the location where error occured # print a snippet of 40 characters around the location where error occured
pos = e.pos pos = e.pos
start, end = max(0, pos-20), min(len(json_string)-1, pos+20) start, end = max(0, pos-20), min(len(json_string)-1, pos+20)

View File

@ -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',

View File

@ -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,13 +680,22 @@ 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 and not frappe.flags.in_migrate:
frappe.throw(_("Please set GST Accounts in GST Settings")) frappe.throw(_("Please set GST Accounts in GST Settings"))
for d in gst_settings_accounts: for d in gst_settings_accounts:
@ -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):

View File

@ -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"
} }
], ],

View File

@ -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"],

View File

@ -241,7 +241,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
send_email() { send_email() {
const frm = this.events.get_frm(); const frm = this.events.get_frm();
const recipients = this.email_dialog.get_values().recipients; const recipients = this.email_dialog.get_values().email_id;
const doc = this.doc || frm.doc; const doc = this.doc || frm.doc;
const print_format = frm.pos_print_format; const print_format = frm.pos_print_format;

View File

@ -90,12 +90,6 @@ frappe.ui.form.on("Company", {
frm.toggle_enable("default_currency", (frm.doc.__onload && frm.toggle_enable("default_currency", (frm.doc.__onload &&
!frm.doc.__onload.transactions_exist)); !frm.doc.__onload.transactions_exist));
if (frm.has_perm('write')) {
frm.add_custom_button(__('Create Tax Template'), function() {
frm.trigger("make_default_tax_template");
});
}
if (frappe.perm.has_perm("Cost Center", 0, 'read')) { if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() { frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}); frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});
@ -121,17 +115,21 @@ frappe.ui.form.on("Company", {
} }
if (frm.has_perm('write')) { if (frm.has_perm('write')) {
frm.add_custom_button(__('Default Tax Template'), function() { frm.add_custom_button(__('Create Tax Template'), function() {
frm.trigger("make_default_tax_template"); frm.trigger("make_default_tax_template");
}, __('Create')); }, __('Manage'));
}
if (frappe.user.has_role('System Manager')) {
if (frm.has_perm('write')) {
frm.add_custom_button(__('Delete Transactions'), function() {
frm.trigger("delete_company_transactions");
}, __('Manage'));
}
} }
} }
erpnext.company.set_chart_of_accounts_options(frm.doc); erpnext.company.set_chart_of_accounts_options(frm.doc);
if (!frappe.user.has_role('System Manager')) {
frm.get_field("delete_company_transactions").hide();
}
}, },
make_default_tax_template: function(frm) { make_default_tax_template: function(frm) {
@ -145,11 +143,6 @@ frappe.ui.form.on("Company", {
}) })
}, },
onload_post_render: function(frm) {
if(frm.get_field("delete_company_transactions").$input)
frm.get_field("delete_company_transactions").$input.addClass("btn-danger");
},
country: function(frm) { country: function(frm) {
erpnext.company.set_chart_of_accounts_options(frm.doc); erpnext.company.set_chart_of_accounts_options(frm.doc);
}, },

View File

@ -99,7 +99,6 @@
"company_description", "company_description",
"registration_info", "registration_info",
"registration_details", "registration_details",
"delete_company_transactions",
"lft", "lft",
"rgt", "rgt",
"old_parent" "old_parent"
@ -666,11 +665,6 @@
"oldfieldname": "registration_details", "oldfieldname": "registration_details",
"oldfieldtype": "Code" "oldfieldtype": "Code"
}, },
{
"fieldname": "delete_company_transactions",
"fieldtype": "Button",
"label": "Delete Company Transactions"
},
{ {
"fieldname": "lft", "fieldname": "lft",
"fieldtype": "Int", "fieldtype": "Int",
@ -747,7 +741,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2021-02-16 15:53:37.167589", "modified": "2021-05-07 03:11:28.189740",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",

View File

@ -59,13 +59,15 @@ class GlobalDefaults(Document):
# Make property setters to hide rounded total fields # Make property setters to hide rounded total fields
for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
"Supplier Quotation", "Purchase Order", "Purchase Invoice"): "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"):
make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "disable_rounded_total", "default", cint(self.disable_rounded_total), "Text", validate_fields_for_doctype=False)
def toggle_in_words(self): def toggle_in_words(self):
self.disable_in_words = cint(self.disable_in_words) self.disable_in_words = cint(self.disable_in_words)

View File

@ -268,7 +268,9 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call
frappe.call({ frappe.call({
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
args: values, args: values,
btn: dialog.get_primary_btn(),
freeze: true, freeze: true,
freeze_message: __('Creating Stock Entry'),
callback: function (r) { callback: function (r) {
frappe.show_alert(__('Stock Entry {0} created', frappe.show_alert(__('Stock Entry {0} created',
['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>'])); ['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));

View File

@ -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": {

View File

@ -10,8 +10,8 @@
"exchange_rate", "exchange_rate",
"description", "description",
"col_break3", "col_break3",
"base_amount", "amount",
"amount" "base_amount"
], ],
"fields": [ "fields": [
{ {
@ -59,7 +59,7 @@
{ {
"fieldname": "base_amount", "fieldname": "base_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Base Amount", "label": "Amount (Company Currency)",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1 "read_only": 1
} }
@ -67,7 +67,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-12-26 01:07:23.233604", "modified": "2021-05-17 13:57:10.807980",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Landed Cost Taxes and Charges", "name": "Landed Cost Taxes and Charges",

View File

@ -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',

View File

@ -140,4 +140,4 @@ def get_repost_item_valuation_entries():
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)

View File

@ -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){
@ -598,7 +600,6 @@ frappe.ui.form.on('Stock Entry', {
add_to_transit: function(frm) { add_to_transit: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') { if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
frm.set_value('to_warehouse', ''); frm.set_value('to_warehouse', '');
frm.set_value('stock_entry_type', 'Material Transfer');
frm.fields_dict.to_warehouse.get_query = function() { frm.fields_dict.to_warehouse.get_query = function() {
return { return {
filters:{ filters:{
@ -608,12 +609,13 @@ frappe.ui.form.on('Stock Entry', {
} }
}; };
}; };
frm.trigger('set_tansit_warehouse'); frm.trigger('set_transit_warehouse');
} }
}, },
set_tansit_warehouse: function(frm) { set_transit_warehouse: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse) { if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse
&& frm.doc.from_warehouse) {
let dt = frm.doc.from_warehouse ? 'Warehouse' : 'Company'; let dt = frm.doc.from_warehouse ? 'Warehouse' : 'Company';
let dn = frm.doc.from_warehouse ? frm.doc.from_warehouse : frm.doc.company; let dn = frm.doc.from_warehouse ? frm.doc.from_warehouse : frm.doc.company;
frappe.db.get_value(dt, dn, 'default_in_transit_warehouse', (r) => { frappe.db.get_value(dt, dn, 'default_in_transit_warehouse', (r) => {
@ -919,6 +921,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
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)
} }
}); });
} }
@ -981,8 +984,8 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse; if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
} }
from_warehouse(doc) { from_warehouse: function(doc) {
this.frm.trigger('set_tansit_warehouse'); this.frm.trigger('set_transit_warehouse');
this.set_warehouse_in_children(doc.items, "s_warehouse", doc.from_warehouse); this.set_warehouse_in_children(doc.items, "s_warehouse", doc.from_warehouse);
} }
@ -1064,4 +1067,22 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
} }
extend_cscript(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); 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}));

View File

@ -637,6 +637,8 @@
{ {
"default": "0", "default": "0",
"depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry", "depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry",
"fetch_from": "stock_entry_type.add_to_transit",
"fetch_if_empty": 1,
"fieldname": "add_to_transit", "fieldname": "add_to_transit",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Add to Transit", "label": "Add to Transit",
@ -655,7 +657,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-12-09 14:58:13.267321", "modified": "2021-05-21 11:29:11.917161",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry", "name": "Stock Entry",

View File

@ -6,7 +6,8 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"purpose" "purpose",
"add_to_transit"
], ],
"fields": [ "fields": [
{ {
@ -18,10 +19,17 @@
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor", "options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"reqd": 1, "reqd": 1,
"set_only_once": 1 "set_only_once": 1
},
{
"default": "0",
"depends_on": "eval: doc.purpose == 'Material Transfer'",
"fieldname": "add_to_transit",
"fieldtype": "Check",
"label": "Add to Transit"
} }
], ],
"links": [], "links": [],
"modified": "2020-08-10 23:24:37.160817", "modified": "2021-05-21 11:27:01.144110",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Type", "name": "Stock Entry Type",

View File

@ -7,4 +7,6 @@ from __future__ import unicode_literals
from frappe.model.document import Document from frappe.model.document import Document
class StockEntryType(Document): class StockEntryType(Document):
pass def validate(self):
if self.add_to_transit and self.purpose != 'Material Transfer':
self.add_to_transit = 0

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2015-02-17 01:06:05.072764", "creation": "2015-02-17 01:06:05.072764",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Other", "document_type": "Other",
@ -170,6 +171,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "allow_zero_valuation_rate",
"fieldname": "allow_zero_valuation_rate", "fieldname": "allow_zero_valuation_rate",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Allow Zero Valuation Rate", "label": "Allow Zero Valuation Rate",
@ -179,7 +181,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-03-23 11:09:44.407157", "modified": "2021-05-21 12:13:33.041266",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Reconciliation Item", "name": "Stock Reconciliation Item",
@ -189,4 +191,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1 "track_changes": 1
} }

View File

@ -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

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