mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-22 04:10:37 +00:00
Compare commits
223 Commits
v13.7.0
...
enterprise
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca433fd56 | ||
|
|
80d0f432a3 | ||
|
|
4c6398665b | ||
|
|
8880d13227 | ||
|
|
8cb560c753 | ||
|
|
b86213c890 | ||
|
|
6fffc90b46 | ||
|
|
64267faa8f | ||
|
|
6eded547f5 | ||
|
|
d4ae1febe3 | ||
|
|
50b4b70589 | ||
|
|
9c7a9f3a13 | ||
|
|
57cd273f7c | ||
|
|
020b8ae110 | ||
|
|
a6d276a06f | ||
|
|
f8343890b9 | ||
|
|
c6fe601efa | ||
|
|
bfd824c56e | ||
|
|
6ac68f3bc7 | ||
|
|
8c7d9efa9d | ||
|
|
8ed7a21cd5 | ||
|
|
1cd2f36b23 | ||
|
|
868a6cb26d | ||
|
|
441adf763f | ||
|
|
5a7fad8a6a | ||
|
|
82d0cfc68e | ||
|
|
940356d28a | ||
|
|
a07423ebb5 | ||
|
|
7a97b6d6a8 | ||
|
|
9d8efbe1db | ||
|
|
831ce88d1c | ||
|
|
af84ce9216 | ||
|
|
61d014d249 | ||
|
|
93dd238577 | ||
|
|
3fcc5e3134 | ||
|
|
19a0ca1980 | ||
|
|
356a55258e | ||
|
|
cba847b051 | ||
|
|
a661667e2a | ||
|
|
5fe7d80a64 | ||
|
|
c468e4a93d | ||
|
|
50b76d04bf | ||
|
|
ed68f11a46 | ||
|
|
f3ae7584c5 | ||
|
|
64454a5dc8 | ||
|
|
7aa802a073 | ||
|
|
60a0b7fe77 | ||
|
|
017ed3f5c1 | ||
|
|
9052a3b1a8 | ||
|
|
9ef157b23b | ||
|
|
45d506c489 | ||
|
|
338f436a1c | ||
|
|
7551bcf421 | ||
|
|
56c67743ab | ||
|
|
5b32fa5ccd | ||
|
|
13466bba90 | ||
|
|
f46f25ba53 | ||
|
|
21d918c194 | ||
|
|
bad69f6c39 | ||
|
|
71642c3172 | ||
|
|
bf91fe985e | ||
|
|
f229e62703 | ||
|
|
da72b9b840 | ||
|
|
94c492c085 | ||
|
|
bc187c4b84 | ||
|
|
8e108473c2 | ||
|
|
b965bcc6e8 | ||
|
|
8387613fdf | ||
|
|
c03f8c9e3f | ||
|
|
20691f0ba2 | ||
|
|
3c83691353 | ||
|
|
f1ebe4ef8b | ||
|
|
86c2cf5ab9 | ||
|
|
2725e61087 | ||
|
|
d1f1345a7f | ||
|
|
d82fd8ded1 | ||
|
|
def7183979 | ||
|
|
d2541caded | ||
|
|
fe515321f2 | ||
|
|
02e7de0e45 | ||
|
|
4a294bead2 | ||
|
|
f7c73a3069 | ||
|
|
e042ed69a1 | ||
|
|
8635d39865 | ||
|
|
beeaf0b04a | ||
|
|
96812893f1 | ||
|
|
a2595a0931 | ||
|
|
509d507d6e | ||
|
|
e33be287b9 | ||
|
|
31256698a9 | ||
|
|
78f7c92549 | ||
|
|
9e1de9c206 | ||
|
|
6fab603a23 | ||
|
|
fa4d314f7b | ||
|
|
55690c8165 | ||
|
|
cb65c6ce64 | ||
|
|
9871e124d0 | ||
|
|
08fddac884 | ||
|
|
d1b396637c | ||
|
|
27bbb7002d | ||
|
|
2d7f04dd1a | ||
|
|
49c5cd66f3 | ||
|
|
9ab18b5341 | ||
|
|
9fa92c912b | ||
|
|
72eb72f66f | ||
|
|
50b188214d | ||
|
|
f9da88cb15 | ||
|
|
6928fc17c6 | ||
|
|
f3158ea448 | ||
|
|
ac6bfeaa8d | ||
|
|
f60cba28ab | ||
|
|
57514f7b1d | ||
|
|
41705acbd9 | ||
|
|
2d225e621f | ||
|
|
c14aa45720 | ||
|
|
d99d56b0e8 | ||
|
|
cbe5718db9 | ||
|
|
85c8daae9c | ||
|
|
92273cade0 | ||
|
|
ce79faa738 | ||
|
|
d5ff636159 | ||
|
|
d8ed9dfcf4 | ||
|
|
9ddf33715e | ||
|
|
3362c080b6 | ||
|
|
13e9aa5956 | ||
|
|
627a8a8cfd | ||
|
|
b164070a4f | ||
|
|
958edc7b83 | ||
|
|
2ac0a1a1a0 | ||
|
|
9b9b18c286 | ||
|
|
26a9d38547 | ||
|
|
d319e10883 | ||
|
|
4d444559ff | ||
|
|
74b97b5ec9 | ||
|
|
1524e7f5ba | ||
|
|
12f7befa13 | ||
|
|
4aebb64165 | ||
|
|
219623279f | ||
|
|
fd197a3aab | ||
|
|
a5f8accd63 | ||
|
|
e244560fb9 | ||
|
|
7558e7f115 | ||
|
|
2c67894135 | ||
|
|
513375f264 | ||
|
|
7a89033163 | ||
|
|
ac721ae147 | ||
|
|
9c04079d04 | ||
|
|
9168bb369a | ||
|
|
cbf7e1b676 | ||
|
|
adfdc71844 | ||
|
|
96eb147bb6 | ||
|
|
75f66f78d7 | ||
|
|
fc20ff02d0 | ||
|
|
eef8e4a9d4 | ||
|
|
0d190bb930 | ||
|
|
ec5f6c78d0 | ||
|
|
c76b4430cf | ||
|
|
d9b6fe0d0f | ||
|
|
53fb5f778d | ||
|
|
84f23ac8d2 | ||
|
|
4a2e4748ac | ||
|
|
30f771b26c | ||
|
|
855e9030f2 | ||
|
|
7fb64d1645 | ||
|
|
1298956482 | ||
|
|
ca6e4bed9f | ||
|
|
b75b556bbb | ||
|
|
45e6cffa4f | ||
|
|
38994bd494 | ||
|
|
e282effaed | ||
|
|
77f2d2d01e | ||
|
|
24a25cd834 | ||
|
|
14a9ec0592 | ||
|
|
034ad28387 | ||
|
|
eae7c1891f | ||
|
|
c13ac4ab11 | ||
|
|
171ee51507 | ||
|
|
904b8481cf | ||
|
|
38fa3a3f89 | ||
|
|
991d3cdd76 | ||
|
|
865900fd2d | ||
|
|
98c9b0e9ed | ||
|
|
8230569f23 | ||
|
|
098a833424 | ||
|
|
dad6a596eb | ||
|
|
bcbb1c68b4 | ||
|
|
1e898c0f5b | ||
|
|
88c0cd6f7a | ||
|
|
2924ff830e | ||
|
|
bc490e246e | ||
|
|
60de73b904 | ||
|
|
af13348817 | ||
|
|
16b309e66e | ||
|
|
12fb02ed2a | ||
|
|
223efec26e | ||
|
|
10e8316240 | ||
|
|
8106d70e70 | ||
|
|
c368c5b20b | ||
|
|
b45d90153c | ||
|
|
6f79c4c348 | ||
|
|
23b907df1a | ||
|
|
672c8bb112 | ||
|
|
bb22972708 | ||
|
|
6ac69672f2 | ||
|
|
34997ae7c5 | ||
|
|
3e29361549 | ||
|
|
39a7a4f0ea | ||
|
|
f9656ed790 | ||
|
|
c5a43a59e0 | ||
|
|
f5f883b6a0 | ||
|
|
390bafef08 | ||
|
|
342d0d1bbb | ||
|
|
e6be20a94d | ||
|
|
25b3507809 | ||
|
|
0a9a4d5f88 | ||
|
|
d66559b6e4 | ||
|
|
e16a632edb | ||
|
|
ccd0237ae8 | ||
|
|
e93ed7d324 | ||
|
|
ec2d4c41d6 | ||
|
|
15a89f83c3 | ||
|
|
de03bc2cb3 | ||
|
|
4367f1c4b6 |
@@ -98,8 +98,6 @@ rules:
|
||||
languages: [python]
|
||||
severity: WARNING
|
||||
paths:
|
||||
exclude:
|
||||
- test_*.py
|
||||
include:
|
||||
- "*/**/doctype/*"
|
||||
|
||||
|
||||
15
.github/helper/semgrep_rules/security.yml
vendored
15
.github/helper/semgrep_rules/security.yml
vendored
@@ -8,18 +8,3 @@ rules:
|
||||
dynamic content. Avoid it or use safe_eval().
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-sqli-format-strings
|
||||
patterns:
|
||||
- pattern-inside: |
|
||||
@frappe.whitelist()
|
||||
def $FUNC(...):
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: frappe.db.sql("..." % ...)
|
||||
- pattern: frappe.db.sql(f"...", ...)
|
||||
- pattern: frappe.db.sql("...".format(...), ...)
|
||||
message: |
|
||||
Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
|
||||
languages: [python]
|
||||
severity: WARNING
|
||||
|
||||
36
.github/workflows/semgrep.yml
vendored
36
.github/workflows/semgrep.yml
vendored
@@ -1,34 +1,18 @@
|
||||
name: Semgrep
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- version-13-hotfix
|
||||
- version-13-pre-release
|
||||
pull_request: { }
|
||||
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Frappe Linter
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup python3
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Setup semgrep
|
||||
run: |
|
||||
python -m pip install -q semgrep
|
||||
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)
|
||||
[[ -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
|
||||
|
||||
- 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
|
||||
- uses: actions/checkout@v2
|
||||
- uses: returntocorp/semgrep-action@v1
|
||||
env:
|
||||
SEMGREP_TIMEOUT: 120
|
||||
with:
|
||||
config: >-
|
||||
r/python.lang.correctness
|
||||
.github/helper/semgrep_rules
|
||||
|
||||
43
CODEOWNERS
43
CODEOWNERS
@@ -3,16 +3,33 @@
|
||||
# These owners will be the default owners for everything in
|
||||
# the repo. Unless a later match takes precedence,
|
||||
|
||||
manufacturing/ @rohitwaghchaure @marination
|
||||
accounts/ @deepeshgarg007 @nextchamp-saqib
|
||||
loan_management/ @deepeshgarg007 @rohitwaghchaure
|
||||
pos* @nextchamp-saqib @rohitwaghchaure
|
||||
assets/ @nextchamp-saqib @deepeshgarg007
|
||||
stock/ @marination @rohitwaghchaure
|
||||
buying/ @marination @deepeshgarg007
|
||||
hr/ @Anurag810 @rohitwaghchaure
|
||||
projects/ @hrwX @nextchamp-saqib
|
||||
support/ @hrwX @marination
|
||||
healthcare/ @ruchamahabal @marination
|
||||
erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib
|
||||
requirements.txt @gavindsouza
|
||||
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007
|
||||
erpnext/assets/ @nextchamp-saqib @deepeshgarg007
|
||||
erpnext/erpnext_integrations/ @nextchamp-saqib
|
||||
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
||||
erpnext/regional @nextchamp-saqib @deepeshgarg007
|
||||
erpnext/selling @nextchamp-saqib @deepeshgarg007
|
||||
erpnext/support/ @nextchamp-saqib @deepeshgarg007
|
||||
pos* @nextchamp-saqib
|
||||
|
||||
erpnext/buying/ @marination @rohitwaghchaure @ankush
|
||||
erpnext/e_commerce/ @marination
|
||||
erpnext/maintenance/ @marination @rohitwaghchaure
|
||||
erpnext/manufacturing/ @marination @rohitwaghchaure @ankush
|
||||
erpnext/portal/ @marination
|
||||
erpnext/quality_management/ @marination @rohitwaghchaure
|
||||
erpnext/shopping_cart/ @marination
|
||||
erpnext/stock/ @marination @rohitwaghchaure @ankush
|
||||
|
||||
erpnext/crm/ @ruchamahabal
|
||||
erpnext/education/ @ruchamahabal
|
||||
erpnext/healthcare/ @ruchamahabal
|
||||
erpnext/hr/ @ruchamahabal
|
||||
erpnext/non_profit/ @ruchamahabal
|
||||
erpnext/payroll @ruchamahabal
|
||||
erpnext/projects/ @ruchamahabal
|
||||
|
||||
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||
|
||||
.github/ @surajshetty3416 @ankush
|
||||
requirements.txt @gavindsouza
|
||||
|
||||
@@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.7.0'
|
||||
__version__ = '13.8.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"unlink_advance_payment_on_cancelation_of_order",
|
||||
"post_change_gl_entries",
|
||||
"enable_discount_accounting",
|
||||
"tax_settings_section",
|
||||
"determine_address_tax_category_from",
|
||||
"column_break_19",
|
||||
@@ -261,6 +262,13 @@
|
||||
"fieldname": "post_change_gl_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create Ledger Entries for Change Amount"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
|
||||
"fieldname": "enable_discount_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Discount Accounting"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -268,7 +276,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-17 20:26:03.721202",
|
||||
"modified": "2021-07-12 18:54:29.084958",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -21,6 +21,7 @@ class AccountsSettings(Document):
|
||||
|
||||
self.validate_stale_days()
|
||||
self.enable_payment_schedule_in_print()
|
||||
self.toggle_discount_accounting_fields()
|
||||
|
||||
def validate_stale_days(self):
|
||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||
@@ -33,3 +34,22 @@ class AccountsSettings(Document):
|
||||
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
|
||||
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
|
||||
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
|
||||
|
||||
def toggle_discount_accounting_fields(self):
|
||||
enable_discount_accounting = cint(self.enable_discount_accounting)
|
||||
|
||||
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
|
||||
make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
|
||||
else:
|
||||
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
|
||||
|
||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
make_property_setter(doctype, "additional_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
|
||||
else:
|
||||
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
|
||||
|
||||
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
||||
@@ -391,5 +391,5 @@ def set_default_accounts(company):
|
||||
})
|
||||
|
||||
company.save()
|
||||
install_country_fixtures(company.name)
|
||||
install_country_fixtures(company.name, company.country)
|
||||
company.create_default_tax_template()
|
||||
|
||||
@@ -27,6 +27,9 @@ class ExchangeRateRevaluation(Document):
|
||||
if not (self.company and self.posting_date):
|
||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ('GL Entry')
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_journal_entry_condition(self):
|
||||
total_debit = frappe.db.get_value("Journal Entry Account", {
|
||||
@@ -99,10 +102,12 @@ class ExchangeRateRevaluation(Document):
|
||||
sum(debit) - sum(credit) as balance
|
||||
from `tabGL Entry`
|
||||
where account in (%s)
|
||||
group by account, party_type, party
|
||||
and posting_date <= %s
|
||||
and is_cancelled = 0
|
||||
group by account, NULLIF(party_type,''), NULLIF(party,'')
|
||||
having sum(debit) != sum(credit)
|
||||
order by account
|
||||
""" % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
|
||||
""" % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
|
||||
|
||||
return account_details
|
||||
|
||||
@@ -143,9 +148,9 @@ class ExchangeRateRevaluation(Document):
|
||||
"party_type": d.get("party_type"),
|
||||
"party": d.get("party"),
|
||||
"account_currency": d.get("account_currency"),
|
||||
"balance": d.get("balance_in_account_currency"),
|
||||
dr_or_cr: abs(d.get("balance_in_account_currency")),
|
||||
"exchange_rate":d.get("new_exchange_rate"),
|
||||
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
|
||||
dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
|
||||
"exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
|
||||
"reference_type": "Exchange Rate Revaluation",
|
||||
"reference_name": self.name,
|
||||
})
|
||||
@@ -154,9 +159,9 @@ class ExchangeRateRevaluation(Document):
|
||||
"party_type": d.get("party_type"),
|
||||
"party": d.get("party"),
|
||||
"account_currency": d.get("account_currency"),
|
||||
"balance": d.get("balance_in_account_currency"),
|
||||
reverse_dr_or_cr: abs(d.get("balance_in_account_currency")),
|
||||
"exchange_rate": d.get("current_exchange_rate"),
|
||||
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
|
||||
reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
|
||||
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
|
||||
"reference_type": "Exchange Rate Revaluation",
|
||||
"reference_name": self.name
|
||||
})
|
||||
@@ -185,9 +190,9 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
|
||||
|
||||
account_details = {}
|
||||
company_currency = erpnext.get_company_currency(company)
|
||||
balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False)
|
||||
balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
|
||||
if balance:
|
||||
balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party)
|
||||
balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
|
||||
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
|
||||
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
|
||||
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
|
||||
|
||||
@@ -531,8 +531,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
source_exchange_rate: function(frm) {
|
||||
if (frm.doc.paid_amount) {
|
||||
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
|
||||
if(!frm.set_paid_amount_based_on_received_amount &&
|
||||
(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) {
|
||||
// target exchange rate should always be same as source if both account currencies is same
|
||||
if(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
|
||||
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
|
||||
}
|
||||
|
||||
@@ -691,7 +691,7 @@
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
|
||||
"depends_on": "eval:doc.received_amount",
|
||||
"fieldname": "received_amount_after_tax",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
|
||||
@@ -45,7 +45,7 @@ class PaymentEntry(AccountsController):
|
||||
self.party_account = self.paid_to
|
||||
self.party_account_currency = self.paid_to_account_currency
|
||||
|
||||
def validate(self):
|
||||
def validate(self, on_reference_unlink=False):
|
||||
self.setup_party_account_field()
|
||||
self.set_missing_values()
|
||||
self.validate_payment_type()
|
||||
@@ -55,16 +55,18 @@ class PaymentEntry(AccountsController):
|
||||
self.validate_mandatory()
|
||||
self.validate_reference_documents()
|
||||
self.set_tax_withholding()
|
||||
self.apply_taxes()
|
||||
self.set_amounts()
|
||||
self.validate_amounts()
|
||||
self.apply_taxes()
|
||||
self.clear_unallocated_reference_document_rows()
|
||||
self.validate_payment_against_negative_invoice()
|
||||
self.validate_transaction_reference()
|
||||
self.set_title()
|
||||
self.set_remarks()
|
||||
self.validate_duplicate_entry()
|
||||
self.validate_allocated_amount()
|
||||
self.validate_paid_invoices()
|
||||
if not on_reference_unlink:
|
||||
self.validate_allocated_amount()
|
||||
self.validate_paid_invoices()
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
self.set_status()
|
||||
|
||||
@@ -183,6 +185,13 @@ class PaymentEntry(AccountsController):
|
||||
d.reference_name, self.party_account_currency)
|
||||
|
||||
for field, value in iteritems(ref_details):
|
||||
if d.exchange_gain_loss:
|
||||
# for cases where gain/loss is booked into invoice
|
||||
# exchange_gain_loss is calculated from invoice & populated
|
||||
# and row.exchange_rate is already set to payment entry's exchange rate
|
||||
# refer -> `update_reference_in_payment_entry()` in utils.py
|
||||
continue
|
||||
|
||||
if field == 'exchange_rate' or not d.get(field) or force:
|
||||
d.db_set(field, value)
|
||||
|
||||
@@ -229,7 +238,9 @@ class PaymentEntry(AccountsController):
|
||||
self.company_currency, self.posting_date)
|
||||
|
||||
def set_target_exchange_rate(self, ref_doc=None):
|
||||
if self.paid_to and not self.target_exchange_rate:
|
||||
if self.paid_from_account_currency == self.paid_to_account_currency:
|
||||
self.target_exchange_rate = self.source_exchange_rate
|
||||
elif self.paid_to and not self.target_exchange_rate:
|
||||
if ref_doc:
|
||||
if self.paid_to_account_currency == ref_doc.currency:
|
||||
self.target_exchange_rate = ref_doc.get("exchange_rate")
|
||||
@@ -466,6 +477,14 @@ class PaymentEntry(AccountsController):
|
||||
self.set_unallocated_amount()
|
||||
self.set_difference_amount()
|
||||
|
||||
def validate_amounts(self):
|
||||
self.validate_received_amount()
|
||||
|
||||
def validate_received_amount(self):
|
||||
if self.paid_from_account_currency == self.paid_to_account_currency:
|
||||
if self.paid_amount != self.received_amount:
|
||||
frappe.throw(_("Received Amount cannot be greater than Paid Amount"))
|
||||
|
||||
def set_received_amount(self):
|
||||
self.base_received_amount = self.base_paid_amount
|
||||
|
||||
@@ -511,8 +530,10 @@ class PaymentEntry(AccountsController):
|
||||
base_total_allocated_amount += flt(flt(d.allocated_amount) * flt(d.exchange_rate),
|
||||
self.precision("base_paid_amount"))
|
||||
|
||||
self.total_allocated_amount = abs(total_allocated_amount)
|
||||
self.base_total_allocated_amount = abs(base_total_allocated_amount)
|
||||
# Do not use absolute values as only credit notes could be allocated
|
||||
# and total allocated should be negative in that scenario
|
||||
self.total_allocated_amount = total_allocated_amount
|
||||
self.base_total_allocated_amount = base_total_allocated_amount
|
||||
|
||||
def set_unallocated_amount(self):
|
||||
self.unallocated_amount = 0
|
||||
@@ -685,8 +706,8 @@ class PaymentEntry(AccountsController):
|
||||
gl_entries.append(gle)
|
||||
|
||||
if self.unallocated_amount:
|
||||
base_unallocated_amount = self.unallocated_amount * \
|
||||
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
|
||||
exchange_rate = self.get_exchange_rate()
|
||||
base_unallocated_amount = (self.unallocated_amount * exchange_rate)
|
||||
|
||||
gle = party_gl_dict.copy()
|
||||
|
||||
@@ -834,10 +855,17 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
if account_details:
|
||||
row.update(account_details)
|
||||
|
||||
if not row.get('amount'):
|
||||
# if no difference amount
|
||||
return
|
||||
|
||||
self.append('deductions', row)
|
||||
self.set_unallocated_amount()
|
||||
|
||||
def get_exchange_rate(self):
|
||||
return self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate
|
||||
|
||||
def initialize_taxes(self):
|
||||
for tax in self.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"total_amount",
|
||||
"outstanding_amount",
|
||||
"allocated_amount",
|
||||
"exchange_rate"
|
||||
"exchange_rate",
|
||||
"exchange_gain_loss"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -90,12 +91,19 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term"
|
||||
},
|
||||
{
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-10 11:25:47.144392",
|
||||
"modified": "2021-04-21 13:30:11.605388",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
|
||||
@@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
jv.flags.ignore_mandatory = True
|
||||
jv.submit()
|
||||
@@ -168,7 +168,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
||||
frappe.throw(_("Invalid {0}").format(args.get(field)))
|
||||
|
||||
parent_groups = frappe.db.sql_list("""select name from `tab%s`
|
||||
where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||
where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||
|
||||
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||
|
||||
@@ -365,7 +365,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
items_add: function(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
this.frm.script_manager.copy_from_first_row("items", row,
|
||||
["expense_account", "cost_center", "project"]);
|
||||
["expense_account", "discount_account", "cost_center", "project"]);
|
||||
},
|
||||
|
||||
on_submit: function() {
|
||||
@@ -499,6 +499,16 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
'Payment Entry': 'Payment'
|
||||
}
|
||||
|
||||
frm.set_query("additional_discount_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0,
|
||||
report_type: "Profit and Loss",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.fields_dict['items'].grid.get_field('deferred_expense_account').get_query = function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
@@ -508,6 +518,16 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
'report_type': 'Profit and Loss',
|
||||
'company': doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -446,11 +446,13 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
self.make_supplier_gl_entry(gl_entries)
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_discount_gl_entries(gl_entries)
|
||||
|
||||
if self.check_asset_cwip_enabled():
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
self.allocate_advance_taxes(gl_entries)
|
||||
@@ -607,7 +609,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
|
||||
|
||||
if not item.is_fixed_asset:
|
||||
amount = flt(item.base_net_amount, item.precision("base_net_amount"))
|
||||
if frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'):
|
||||
amount = flt(item.base_amount, item.precision("base_amount"))
|
||||
else:
|
||||
amount = flt(item.base_net_amount, item.precision("base_net_amount"))
|
||||
|
||||
else:
|
||||
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
|
||||
|
||||
|
||||
@@ -230,6 +230,48 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
enable_discount_accounting()
|
||||
|
||||
discount_account = create_account(account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC", company="_Test Company")
|
||||
pi = make_purchase_invoice(discount_account=discount_account, discount_amount=100)
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 350.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 250.0, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 100.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
|
||||
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
enable_discount_accounting()
|
||||
additional_discount_account = create_account(account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC", company="_Test Company")
|
||||
|
||||
pi = make_purchase_invoice(qty=1, rate=100, do_not_save=1, parent_cost_center="Main - _TC")
|
||||
pi.apply_discount_on = "Grand Total"
|
||||
pi.additional_discount_account = additional_discount_account
|
||||
pi.additional_discount_percentage = 20
|
||||
pi.append("taxes", {
|
||||
"charge_type": "Actual",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "Test",
|
||||
"tax_amount": 20
|
||||
})
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 100.0, 0.0, nowdate()],
|
||||
["_Test Account VAT - _TC", 20.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 96.0, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 24.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
|
||||
def test_purchase_invoice_change_naming_series(self):
|
||||
pi = frappe.copy_doc(test_records[1])
|
||||
pi.insert()
|
||||
@@ -953,6 +995,120 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
def test_gain_loss_with_advance_entry(self):
|
||||
unlink_enabled = frappe.db.get_value(
|
||||
"Accounts Settings", "Accounts Settings",
|
||||
"unlink_payment_on_cancel_of_invoice")
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings",
|
||||
"unlink_payment_on_cancel_of_invoice", 1)
|
||||
|
||||
original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account")
|
||||
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC")
|
||||
|
||||
pay = frappe.get_doc({
|
||||
'doctype': 'Payment Entry',
|
||||
'company': '_Test Company',
|
||||
'payment_type': 'Pay',
|
||||
'party_type': 'Supplier',
|
||||
'party': '_Test Supplier USD',
|
||||
'paid_to': '_Test Payable USD - _TC',
|
||||
'paid_from': 'Cash - _TC',
|
||||
'paid_amount': 70000,
|
||||
'target_exchange_rate': 70,
|
||||
'received_amount': 1000,
|
||||
})
|
||||
pay.insert()
|
||||
pay.submit()
|
||||
|
||||
pi = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
|
||||
conversion_rate=75, rate=500, do_not_save=1, qty=1)
|
||||
pi.cost_center = "_Test Cost Center - _TC"
|
||||
pi.advances = []
|
||||
pi.append("advances", {
|
||||
"reference_type": "Payment Entry",
|
||||
"reference_name": pay.name,
|
||||
"advance_amount": 1000,
|
||||
"remarks": pay.remarks,
|
||||
"allocated_amount": 500,
|
||||
"ref_exchange_rate": 70
|
||||
})
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 37500.0],
|
||||
["_Test Payable USD - _TC", -35000.0],
|
||||
["Exchange Gain/Loss - _TC", -2500.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||
where voucher_no=%s
|
||||
group by account
|
||||
order by account asc""", (pi.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||
|
||||
pi_2 = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
|
||||
conversion_rate=73, rate=500, do_not_save=1, qty=1)
|
||||
pi_2.cost_center = "_Test Cost Center - _TC"
|
||||
pi_2.advances = []
|
||||
pi_2.append("advances", {
|
||||
"reference_type": "Payment Entry",
|
||||
"reference_name": pay.name,
|
||||
"advance_amount": 500,
|
||||
"remarks": pay.remarks,
|
||||
"allocated_amount": 500,
|
||||
"ref_exchange_rate": 70
|
||||
})
|
||||
pi_2.save()
|
||||
pi_2.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 36500.0],
|
||||
["_Test Payable USD - _TC", -35000.0],
|
||||
["Exchange Gain/Loss - _TC", -1500.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||
where voucher_no=%s
|
||||
group by account order by account asc""", (pi_2.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||
|
||||
expected_gle = [
|
||||
["_Test Payable USD - _TC", 70000.0],
|
||||
["Cash - _TC", -70000.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||
where voucher_no=%s and is_cancelled=0
|
||||
group by account order by account asc""", (pay.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||
|
||||
pi.reload()
|
||||
pi.cancel()
|
||||
|
||||
pi_2.reload()
|
||||
pi_2.cancel()
|
||||
|
||||
pay.reload()
|
||||
pay.cancel()
|
||||
|
||||
frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled)
|
||||
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
|
||||
|
||||
def test_purchase_invoice_advance_taxes(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
@@ -1026,6 +1182,18 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.amount)
|
||||
|
||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
||||
from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no=%s and posting_date >= %s
|
||||
order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
doc.assertEqual(expected_gle[i][0], gle.account)
|
||||
doc.assertEqual(expected_gle[i][1], gle.debit)
|
||||
doc.assertEqual(expected_gle[i][2], gle.credit)
|
||||
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||
|
||||
def update_tax_witholding_category(company, account, date):
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
@@ -1056,6 +1224,11 @@ def unlink_payment_on_cancel_of_invoice(enable=1):
|
||||
accounts_settings.unlink_payment_on_cancellation_of_invoice = enable
|
||||
accounts_settings.save()
|
||||
|
||||
def enable_discount_accounting(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
accounts_settings.enable_discount_accounting = enable
|
||||
accounts_settings.save()
|
||||
|
||||
def make_purchase_invoice(**args):
|
||||
pi = frappe.new_doc("Purchase Invoice")
|
||||
args = frappe._dict(args)
|
||||
@@ -1078,6 +1251,7 @@ def make_purchase_invoice(**args):
|
||||
pi.return_against = args.return_against
|
||||
pi.is_subcontracted = args.is_subcontracted or "No"
|
||||
pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
|
||||
pi.cost_center = args.parent_cost_center
|
||||
|
||||
pi.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
@@ -1086,7 +1260,9 @@ def make_purchase_invoice(**args):
|
||||
"received_qty": args.received_qty or 0,
|
||||
"rejected_qty": args.rejected_qty or 0,
|
||||
"rate": args.rate or 50,
|
||||
'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
|
||||
"expense_account": args.expense_account or '_Test Account Cost for Goods Sold - _TC',
|
||||
"discount_account": args.discount_account or None,
|
||||
"discount_amount": args.discount_amount or 0,
|
||||
"conversion_factor": 1.0,
|
||||
"serial_no": args.serial_no,
|
||||
"stock_uom": args.uom or "_Test UOM",
|
||||
|
||||
@@ -1,235 +1,127 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-03-08 15:36:46",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2013-03-08 15:36:46",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"remarks",
|
||||
"reference_row",
|
||||
"col_break1",
|
||||
"advance_amount",
|
||||
"allocated_amount",
|
||||
"exchange_gain_loss",
|
||||
"ref_exchange_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Type",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "journal_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "180px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Type",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "journal_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"print_width": "180px",
|
||||
"read_only": 1,
|
||||
"width": "180px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 3,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Remarks",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Remarks",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"width": "150px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Row",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "jv_detail_no",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "80px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Reference Row",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "jv_detail_no",
|
||||
"oldfieldtype": "Date",
|
||||
"print_hide": 1,
|
||||
"print_width": "80px",
|
||||
"read_only": 1,
|
||||
"width": "80px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Advance Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "advance_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Advance Amount",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "advance_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"width": "100px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "allocated_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "100px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated Amount",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "allocated_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"print_width": "100px",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ref_exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Reference Exchange Rate",
|
||||
"non_negative": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2016-08-26 02:30:54.407138",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Advance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-20 16:26:53.820530",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Advance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -73,6 +73,7 @@
|
||||
"manufacturer_part_no",
|
||||
"accounting",
|
||||
"expense_account",
|
||||
"discount_account",
|
||||
"col_break5",
|
||||
"is_fixed_asset",
|
||||
"asset_location",
|
||||
@@ -849,12 +850,18 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Discount Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-16 19:57:03.101571",
|
||||
"modified": "2021-07-13 02:04:37.787882",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -347,7 +347,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
|
||||
items_add: function(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "cost_center"]);
|
||||
this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "discount_account", "cost_center"]);
|
||||
},
|
||||
|
||||
set_dynamic_labels: function() {
|
||||
@@ -510,7 +510,6 @@ cur_frm.set_query("income_account", "items", function(doc) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Cost Center in Details Table
|
||||
// -----------------------------
|
||||
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
|
||||
@@ -592,6 +591,16 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("additional_discount_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0,
|
||||
report_type: "Profit and Loss",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
@@ -618,6 +627,17 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
}
|
||||
}
|
||||
|
||||
// discount account
|
||||
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
'report_type': 'Profit and Loss',
|
||||
'company': doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict['items'].grid.get_field('deferred_revenue_account').get_query = function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
"section_break_49",
|
||||
"apply_discount_on",
|
||||
"base_discount_amount",
|
||||
"additional_discount_account",
|
||||
"column_break_51",
|
||||
"additional_discount_percentage",
|
||||
"discount_amount",
|
||||
@@ -1966,6 +1967,12 @@
|
||||
"fieldname": "disable_rounded_total",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Rounded Total"
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Additional Discount Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -1978,7 +1985,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-05-20 22:48:33.988881",
|
||||
"modified": "2021-07-15 21:57:17.544279",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -840,11 +840,13 @@ class SalesInvoice(SellingController):
|
||||
self.make_customer_gl_entry(gl_entries)
|
||||
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
self.allocate_advance_taxes(gl_entries)
|
||||
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_discount_gl_entries(gl_entries)
|
||||
|
||||
# merge gl entries before adding pos entries
|
||||
gl_entries = merge_similar_entries(gl_entries)
|
||||
@@ -884,18 +886,22 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
|
||||
|
||||
if flt(tax.base_tax_amount_after_discount_amount):
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": tax.account_head,
|
||||
"against": self.customer,
|
||||
"credit": flt(tax.base_tax_amount_after_discount_amount,
|
||||
"credit": flt(base_amount,
|
||||
tax.precision("tax_amount_after_discount_amount")),
|
||||
"credit_in_account_currency": (flt(tax.base_tax_amount_after_discount_amount,
|
||||
"credit_in_account_currency": (flt(base_amount,
|
||||
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
|
||||
flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))),
|
||||
flt(amount, tax.precision("tax_amount_after_discount_amount"))),
|
||||
"cost_center": tax.cost_center
|
||||
}, account_currency, item=tax)
|
||||
)
|
||||
@@ -914,6 +920,8 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def make_item_gl_entries(self, gl_entries):
|
||||
# income account gl entries
|
||||
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
|
||||
|
||||
for item in self.get("items"):
|
||||
if flt(item.base_net_amount, item.precision("base_net_amount")):
|
||||
if item.is_fixed_asset:
|
||||
@@ -939,15 +947,17 @@ class SalesInvoice(SellingController):
|
||||
income_account = (item.income_account
|
||||
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
|
||||
|
||||
amount, base_amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
|
||||
|
||||
account_currency = get_account_currency(income_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": income_account,
|
||||
"against": self.customer,
|
||||
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
|
||||
"credit": flt(base_amount, item.precision("base_net_amount")),
|
||||
"credit_in_account_currency": (flt(base_amount, item.precision("base_net_amount"))
|
||||
if account_currency==self.company_currency
|
||||
else flt(item.net_amount, item.precision("net_amount"))),
|
||||
else flt(amount, item.precision("net_amount"))),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item)
|
||||
@@ -958,6 +968,12 @@ class SalesInvoice(SellingController):
|
||||
erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
gl_entries += super(SalesInvoice, self).get_gl_entries()
|
||||
|
||||
def set_asset_status(self, asset):
|
||||
if self.is_return:
|
||||
asset.set_status()
|
||||
else:
|
||||
asset.set_status("Sold" if self.docstatus==1 else None)
|
||||
|
||||
def make_loyalty_point_redemption_gle(self, gl_entries):
|
||||
if cint(self.redeem_loyalty_points):
|
||||
gl_entries.append(
|
||||
|
||||
@@ -1984,6 +1984,53 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
sales_invoice.save()
|
||||
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
||||
|
||||
def test_sales_invoice_with_discount_accounting_enabled(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
|
||||
|
||||
enable_discount_accounting()
|
||||
|
||||
discount_account = create_account(account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC", company="_Test Company")
|
||||
si = create_sales_invoice(discount_account=discount_account, discount_amount=100)
|
||||
|
||||
expected_gle = [
|
||||
["Debtors - _TC", 100.0, 0.0, nowdate()],
|
||||
["Discount Account - _TC", 100.0, 0.0, nowdate()],
|
||||
["Sales - _TC", 0.0, 200.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||
|
||||
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
|
||||
|
||||
enable_discount_accounting()
|
||||
additional_discount_account = create_account(account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC", company="_Test Company")
|
||||
|
||||
si = create_sales_invoice(rate=100, parent_cost_center='Main - _TC', do_not_save=1)
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.additional_discount_account = additional_discount_account
|
||||
si.additional_discount_percentage = 20
|
||||
si.append("taxes", {
|
||||
"charge_type": "Actual",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "Test",
|
||||
"rate": 0,
|
||||
"tax_amount": 20
|
||||
})
|
||||
si.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account VAT - _TC", 0.0, 20.0, nowdate()],
|
||||
["Debtors - _TC", 96.0, 0.0, nowdate()],
|
||||
["Discount Account - _TC", 24.0, 0.0, nowdate()],
|
||||
["Sales - _TC", 0.0, 100.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
si.naming_series = 'INV-2020-.#####'
|
||||
@@ -2152,6 +2199,7 @@ def create_sales_invoice(**args):
|
||||
si.currency=args.currency or "INR"
|
||||
si.conversion_rate = args.conversion_rate or 1
|
||||
si.naming_series = args.naming_series or "T-SINV-"
|
||||
si.cost_center = args.parent_cost_center
|
||||
|
||||
si.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
@@ -2165,6 +2213,8 @@ def create_sales_invoice(**args):
|
||||
"rate": args.rate if args.get("rate") is not None else 100,
|
||||
"income_account": args.income_account or "Sales - _TC",
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"discount_account": args.discount_account or None,
|
||||
"discount_amount": args.discount_amount or 0,
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
"conversion_factor": 1
|
||||
|
||||
@@ -1,235 +1,128 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:41",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2013-02-22 01:27:41",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"remarks",
|
||||
"reference_row",
|
||||
"col_break1",
|
||||
"advance_amount",
|
||||
"allocated_amount",
|
||||
"exchange_gain_loss",
|
||||
"ref_exchange_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Type",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "journal_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "250px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Type",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "journal_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"print_width": "250px",
|
||||
"read_only": 1,
|
||||
"width": "250px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 3,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Remarks",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Remarks",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"width": "150px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Row",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "jv_detail_no",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Reference Row",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "jv_detail_no",
|
||||
"oldfieldtype": "Data",
|
||||
"print_hide": 1,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"width": "120px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Advance amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "advance_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Advance amount",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "advance_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"width": "120px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "allocated_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated amount",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "allocated_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"print_width": "120px",
|
||||
"width": "120px"
|
||||
},
|
||||
{
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ref_exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Reference Exchange Rate",
|
||||
"non_negative": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2016-08-26 02:36:10.718057",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Advance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-04 20:25:49.832052",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Advance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -63,6 +63,7 @@
|
||||
"finance_book",
|
||||
"col_break4",
|
||||
"expense_account",
|
||||
"discount_account",
|
||||
"deferred_revenue",
|
||||
"deferred_revenue_account",
|
||||
"service_stop_date",
|
||||
@@ -821,12 +822,18 @@
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Discount Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-23 01:05:22.123527",
|
||||
"modified": "2021-07-05 15:07:22.857128",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
"base_tax_amount",
|
||||
"base_total",
|
||||
"base_tax_amount_after_discount_amount",
|
||||
"item_wise_tax_detail"
|
||||
"item_wise_tax_detail",
|
||||
"dont_recompute_tax"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -200,13 +201,22 @@
|
||||
"fieldname": "included_in_paid_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Considered In Paid Amount"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "dont_recompute_tax",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Dont Recompute tax",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-14 01:44:36.899147",
|
||||
"modified": "2021-07-27 12:40:59.051803",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Taxes and Charges",
|
||||
|
||||
@@ -1,263 +1,151 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 18:42:06.431683",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"creation": "2018-04-13 18:42:06.431683",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"category_details_section",
|
||||
"category_name",
|
||||
"round_off_tax_amount",
|
||||
"column_break_2",
|
||||
"consider_party_ledger_amount",
|
||||
"tax_on_excess_amount",
|
||||
"section_break_8",
|
||||
"rates",
|
||||
"section_break_7",
|
||||
"accounts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "category_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Category Name",
|
||||
"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
|
||||
},
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Tax Withholding Rates",
|
||||
"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
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rates",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rates",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tax Withholding Rate",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Account Details",
|
||||
"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
|
||||
},
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Accounts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tax Withholding Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"label": "Accounts",
|
||||
"options": "Tax Withholding Account",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "category_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Category Details",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
|
||||
"fieldname": "consider_party_ledger_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider Entire Party Ledger Amount",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Tax will be withheld only for amount exceeding the cumulative threshold",
|
||||
"fieldname": "tax_on_excess_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Only Deduct Tax On Excess Amount ",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"description": "Checking this will round off the tax amount to the nearest integer",
|
||||
"fieldname": "round_off_tax_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Round Off Tax Amount",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-07-17 22:53:26.193179",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Category",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-27 21:47:34.396071",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Category",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, getdate
|
||||
from frappe.utils import flt, getdate, cint
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
class TaxWithholdingCategory(Document):
|
||||
@@ -86,7 +86,10 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
|
||||
"rate": tax_rate_detail.tax_withholding_rate,
|
||||
"threshold": tax_rate_detail.single_threshold,
|
||||
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
|
||||
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
|
||||
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
|
||||
"consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
|
||||
"tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
|
||||
"round_off_tax_amount": tax_withholding.round_off_tax_amount
|
||||
})
|
||||
|
||||
def get_tax_withholding_rates(tax_withholding, fiscal_year):
|
||||
@@ -145,6 +148,7 @@ def get_lower_deduction_certificate(fiscal_year, pan_no):
|
||||
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
|
||||
fiscal_year = fiscal_year_details[0]
|
||||
|
||||
|
||||
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
taxable_vouchers = vouchers + advance_vouchers
|
||||
@@ -235,10 +239,18 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
|
||||
tds_amount = 0
|
||||
invoice_filters = {
|
||||
'name': ('in', vouchers),
|
||||
'docstatus': 1
|
||||
}
|
||||
|
||||
supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
|
||||
'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
|
||||
}, 'sum(net_total)') or 0.0
|
||||
field = 'sum(net_total)'
|
||||
|
||||
if not cint(tax_details.consider_party_ledger_amount):
|
||||
invoice_filters.update({'apply_tds': 1})
|
||||
field = 'sum(grand_total)'
|
||||
|
||||
supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
|
||||
|
||||
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
|
||||
'parent': ('in', vouchers), 'docstatus': 1,
|
||||
@@ -255,6 +267,13 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||
|
||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount):
|
||||
# Get net total again as TDS is calculated on net total
|
||||
# Grand is used to just check for threshold breach
|
||||
net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0
|
||||
net_total += inv.net_total
|
||||
supp_credit_amt = net_total - cumulative_threshold
|
||||
|
||||
if ldc and is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto,
|
||||
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
|
||||
@@ -263,6 +282,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
else:
|
||||
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
|
||||
|
||||
if cint(tax_details.round_off_tax_amount):
|
||||
tds_amount = round(tds_amount)
|
||||
|
||||
return tds_amount
|
||||
|
||||
|
||||
@@ -87,6 +87,31 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_tax_withholding_category_checks(self):
|
||||
invoices = []
|
||||
frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
|
||||
|
||||
# First Invoice with no tds check
|
||||
pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True)
|
||||
pi.apply_tds = 0
|
||||
pi.save()
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
# Second Invoice will apply TDS checked
|
||||
pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000)
|
||||
pi1.submit()
|
||||
invoices.append(pi1)
|
||||
|
||||
# Cumulative threshold is 30000
|
||||
# Threshold calculation should be on both the invoices
|
||||
# TDS should be applied only on 1000
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
|
||||
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
|
||||
def test_cumulative_threshold_tcs(self):
|
||||
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
|
||||
invoices = []
|
||||
@@ -195,7 +220,7 @@ def create_sales_invoice(**args):
|
||||
|
||||
def create_records():
|
||||
# create a new suppliers
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']:
|
||||
if frappe.db.exists('Supplier', name):
|
||||
continue
|
||||
|
||||
@@ -311,3 +336,23 @@ def create_tax_with_holding_category():
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": "New TDS Category",
|
||||
"category_name": "New TDS Category",
|
||||
"round_off_tax_amount": 1,
|
||||
"consider_party_ledger_amount": 1,
|
||||
"tax_on_excess_amount": 1,
|
||||
"rates": [{
|
||||
'fiscal_year': fiscal_year,
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 0,
|
||||
'cumulative_threshold': 30000
|
||||
}],
|
||||
"accounts": [{
|
||||
'company': '_Test Company',
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
@@ -99,7 +99,6 @@ class ReceivablePayableReport(object):
|
||||
voucher_no = gle.voucher_no,
|
||||
party = gle.party,
|
||||
posting_date = gle.posting_date,
|
||||
remarks = gle.remarks,
|
||||
account_currency = gle.account_currency,
|
||||
invoiced = 0.0,
|
||||
paid = 0.0,
|
||||
@@ -579,7 +578,7 @@ class ReceivablePayableReport(object):
|
||||
self.gl_entries = frappe.db.sql("""
|
||||
select
|
||||
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
|
||||
against_voucher_type, against_voucher, account_currency, remarks, {0}
|
||||
against_voucher_type, against_voucher, account_currency, {0}
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
@@ -792,8 +791,6 @@ class ReceivablePayableReport(object):
|
||||
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
|
||||
options='Supplier Group')
|
||||
|
||||
self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200)
|
||||
|
||||
def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
|
||||
if not fieldname: fieldname = scrub(label)
|
||||
if fieldtype=='Currency': options='currency'
|
||||
|
||||
@@ -55,9 +55,11 @@ def validate_filters(filters, account_details):
|
||||
if not account_details.get(account):
|
||||
frappe.throw(_("Account {0} does not exists").format(account))
|
||||
|
||||
if (filters.get("account") and filters.get("group_by") == _('Group by Account')
|
||||
and account_details[filters.account].is_group == 0):
|
||||
frappe.throw(_("Can not filter based on Account, if grouped by Account"))
|
||||
if (filters.get("account") and filters.get("group_by") == _('Group by Account')):
|
||||
filters.account = frappe.parse_json(filters.get('account'))
|
||||
for account in filters.account:
|
||||
if account_details[account].is_group == 0:
|
||||
frappe.throw(_("Can not filter based on Child Account, if grouped by Account"))
|
||||
|
||||
if (filters.get("voucher_no")
|
||||
and filters.get("group_by") in [_('Group by Voucher')]):
|
||||
|
||||
@@ -241,6 +241,7 @@ class GrossProfitGenerator(object):
|
||||
sle.voucher_detail_no == row.item_row:
|
||||
previous_stock_value = len(my_sle) > i+1 and \
|
||||
flt(my_sle[i+1].stock_value) or 0.0
|
||||
|
||||
if previous_stock_value:
|
||||
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
||||
else:
|
||||
@@ -335,7 +336,7 @@ class GrossProfitGenerator(object):
|
||||
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where company=%(company)s
|
||||
where company=%(company)s and is_cancelled = 0
|
||||
order by
|
||||
item_code desc, warehouse desc, posting_date desc,
|
||||
posting_time desc, creation desc""", self.filters, as_dict=True)
|
||||
|
||||
@@ -472,7 +472,8 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
|
||||
"total_amount": d.grand_total,
|
||||
"outstanding_amount": d.outstanding_amount,
|
||||
"allocated_amount": d.allocated_amount,
|
||||
"exchange_rate": d.exchange_rate
|
||||
"exchange_rate": d.exchange_rate if not d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
||||
"exchange_gain_loss": d.exchange_gain_loss # only populated from invoice in case of advance allocation
|
||||
}
|
||||
|
||||
if d.voucher_detail_no:
|
||||
@@ -498,12 +499,15 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
|
||||
payment_entry.set_amounts()
|
||||
|
||||
if d.difference_amount and d.difference_account:
|
||||
payment_entry.set_gain_or_loss(account_details={
|
||||
account_details = {
|
||||
'account': d.difference_account,
|
||||
'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company',
|
||||
payment_entry.company, "cost_center"),
|
||||
'amount': d.difference_amount
|
||||
})
|
||||
payment_entry.company, "cost_center")
|
||||
}
|
||||
if d.difference_amount:
|
||||
account_details['amount'] = d.difference_amount
|
||||
|
||||
payment_entry.set_gain_or_loss(account_details=account_details)
|
||||
|
||||
if not do_not_save:
|
||||
payment_entry.save(ignore_permissions=True)
|
||||
@@ -549,10 +553,14 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
|
||||
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
|
||||
|
||||
for pe in linked_pe:
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe)
|
||||
pe_doc.set_total_allocated_amount()
|
||||
pe_doc.set_unallocated_amount()
|
||||
pe_doc.clear_unallocated_reference_document_rows()
|
||||
try:
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe)
|
||||
pe_doc.validate(on_reference_unlink=True)
|
||||
except Exception as e:
|
||||
msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
|
||||
msg += '<br>'
|
||||
msg += _("Please cancel payment entry manually first and then resubmit")
|
||||
frappe.throw(msg, title=_("Payment Unlink Error"))
|
||||
|
||||
frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
|
||||
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
|
||||
@@ -962,7 +970,7 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
|
||||
for e in existing_gle:
|
||||
if entry.account == e.account:
|
||||
account_existed = True
|
||||
if (entry.account == e.account and entry.against_account == e.against_account
|
||||
if (entry.account == e.account
|
||||
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
|
||||
and ( flt(entry.debit, precision) != flt(e.debit, precision) or
|
||||
flt(entry.credit, precision) != flt(e.credit, precision))):
|
||||
|
||||
39
erpnext/change_log/v13/v13_8_0.md
Normal file
39
erpnext/change_log/v13/v13_8_0.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Version 13.8.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
- Report to show COGS by item groups ([#26222](https://github.com/frappe/erpnext/pull/26222))
|
||||
- Enhancements in TDS ([#26677](https://github.com/frappe/erpnext/pull/26677))
|
||||
- API Endpoint to update halted Razorpay subscriptions ([#26564](https://github.com/frappe/erpnext/pull/26564))
|
||||
|
||||
### Fixes
|
||||
- Incorrect bom name ([#26600](https://github.com/frappe/erpnext/pull/26600))
|
||||
- Exchange rate revaluation posting date and precision fixes ([#26651](https://github.com/frappe/erpnext/pull/26651))
|
||||
- POS item cart dom updates ([#26460](https://github.com/frappe/erpnext/pull/26460))
|
||||
- General Ledger report not working with filter group by ([#26439](https://github.com/frappe/erpnext/pull/26438))
|
||||
- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206))
|
||||
- Validation check for batch for stock reconciliation type in stock entry ([#26487](https://github.com/frappe/erpnext/pull/26487))
|
||||
- Improved UX for additional discount field ([#26502](https://github.com/frappe/erpnext/pull/26502))
|
||||
- Add missing cess amount in GSTR-3B report ([#26644](https://github.com/frappe/erpnext/pull/26644))
|
||||
- Optimized code for reposting item valuation ([#26431](https://github.com/frappe/erpnext/pull/26431))
|
||||
- FG item not fetched in manufacture entry ([#26508](https://github.com/frappe/erpnext/pull/26508))
|
||||
- Errors on parallel requests creation of company for India ([#26420](https://github.com/frappe/erpnext/pull/26420))
|
||||
- Incorrect valuation rate calculation in gross profit report ([#26558](https://github.com/frappe/erpnext/pull/26558))
|
||||
- Empty "against account" in Purchase Receipt GLE ([#26712](https://github.com/frappe/erpnext/pull/26712))
|
||||
- Remove cancelled entries from Stock and Account Value comparison report ([#26721](https://github.com/frappe/erpnext/pull/26721))
|
||||
- Remove manual permission checking ([#26691](https://github.com/frappe/erpnext/pull/26691))
|
||||
- Delete child docs when parent doc is deleted ([#26518](https://github.com/frappe/erpnext/pull/26518))
|
||||
- GST Reports timeout issue ([#26646](https://github.com/frappe/erpnext/pull/26646))
|
||||
- Parent condition in pricing rules ([#26727](https://github.com/frappe/erpnext/pull/26727))
|
||||
- Added Company filters for Loan ([#26294](https://github.com/frappe/erpnext/pull/26294))
|
||||
- Incorrect discount amount on amended document ([#26292](https://github.com/frappe/erpnext/pull/26292))
|
||||
- Exchange gain loss not set for advances linked with invoices ([#26436](https://github.com/frappe/erpnext/pull/26436))
|
||||
- Unallocated amount in Payment Entry after taxes ([#26412](https://github.com/frappe/erpnext/pull/26412))
|
||||
- Wrong operation time in Work Order ([#26613](https://github.com/frappe/erpnext/pull/26613))
|
||||
- Serial No and Batch validation ([#26614](https://github.com/frappe/erpnext/pull/26614))
|
||||
- Gl Entries for exchange gain loss ([#26734](https://github.com/frappe/erpnext/pull/26734))
|
||||
- TDS computation summary shows cancelled invoices ([#26485](https://github.com/frappe/erpnext/pull/26485))
|
||||
- Price List rate not fetched for return sales invoice fixed ([#26560](https://github.com/frappe/erpnext/pull/26560))
|
||||
- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576))
|
||||
- Ignore mandatory fields while creating payment reconciliation Journal Entry ([#26643](https://github.com/frappe/erpnext/pull/26643))
|
||||
- Unable to download GSTR-1 json ([#26418](https://github.com/frappe/erpnext/pull/26418))
|
||||
- Paging buttons not working on item group portal page ([#26498](https://github.com/frappe/erpnext/pull/26498))
|
||||
@@ -124,6 +124,8 @@ class AccountsController(TransactionBase):
|
||||
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
|
||||
self.set_advances()
|
||||
|
||||
self.set_advance_gain_or_loss()
|
||||
|
||||
if self.is_return:
|
||||
self.validate_qty()
|
||||
else:
|
||||
@@ -584,15 +586,18 @@ class AccountsController(TransactionBase):
|
||||
allocated_amount = min(amount - advance_allocated, d.amount)
|
||||
advance_allocated += flt(allocated_amount)
|
||||
|
||||
self.append("advances", {
|
||||
advance_row = {
|
||||
"doctype": self.doctype + " Advance",
|
||||
"reference_type": d.reference_type,
|
||||
"reference_name": d.reference_name,
|
||||
"reference_row": d.reference_row,
|
||||
"remarks": d.remarks,
|
||||
"advance_amount": flt(d.amount),
|
||||
"allocated_amount": allocated_amount
|
||||
})
|
||||
"allocated_amount": allocated_amount,
|
||||
"ref_exchange_rate": flt(d.exchange_rate) # exchange_rate of advance entry
|
||||
}
|
||||
|
||||
self.append("advances", advance_row)
|
||||
|
||||
def get_advance_entries(self, include_unallocated=True):
|
||||
if self.doctype == "Sales Invoice":
|
||||
@@ -650,6 +655,71 @@ class AccountsController(TransactionBase):
|
||||
"Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.")
|
||||
.format(d.reference_name, d.against_order))
|
||||
|
||||
def set_advance_gain_or_loss(self):
|
||||
if not self.get("advances"):
|
||||
return
|
||||
|
||||
for d in self.get("advances"):
|
||||
advance_exchange_rate = d.ref_exchange_rate
|
||||
if (d.allocated_amount and self.conversion_rate != 1
|
||||
and self.conversion_rate != advance_exchange_rate):
|
||||
|
||||
base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
|
||||
base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
|
||||
difference = base_allocated_amount_in_ref_rate - base_allocated_amount_in_inv_rate
|
||||
|
||||
d.exchange_gain_loss = difference
|
||||
|
||||
def make_exchange_gain_loss_gl_entries(self, gl_entries):
|
||||
if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
|
||||
for d in self.get("advances"):
|
||||
if d.exchange_gain_loss:
|
||||
is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
|
||||
party = self.supplier if is_purchase_invoice else self.customer
|
||||
party_account = self.credit_to if is_purchase_invoice else self.debit_to
|
||||
party_type = "Supplier" if is_purchase_invoice else "Customer"
|
||||
|
||||
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
|
||||
if not gain_loss_account:
|
||||
frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
|
||||
.format(self.get('company')))
|
||||
account_currency = get_account_currency(gain_loss_account)
|
||||
if account_currency != self.company_currency:
|
||||
frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
|
||||
|
||||
# for purchase
|
||||
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
|
||||
if not is_purchase_invoice:
|
||||
# just reverse for sales?
|
||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": gain_loss_account,
|
||||
"account_currency": account_currency,
|
||||
"against": party,
|
||||
dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
|
||||
dr_or_cr: abs(d.exchange_gain_loss),
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project
|
||||
}, item=d)
|
||||
)
|
||||
|
||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": party_account,
|
||||
"party_type": party_type,
|
||||
"party": party,
|
||||
"against": gain_loss_account,
|
||||
dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate),
|
||||
dr_or_cr: abs(d.exchange_gain_loss),
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
|
||||
def update_against_document_in_jv(self):
|
||||
"""
|
||||
Links invoice and advance voucher:
|
||||
@@ -690,7 +760,9 @@ class AccountsController(TransactionBase):
|
||||
if self.party_account_currency != self.company_currency else 1),
|
||||
'grand_total': (self.base_grand_total
|
||||
if self.party_account_currency == self.company_currency else self.grand_total),
|
||||
'outstanding_amount': self.outstanding_amount
|
||||
'outstanding_amount': self.outstanding_amount,
|
||||
'difference_account': frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account'),
|
||||
'exchange_gain_loss': flt(d.get('exchange_gain_loss'))
|
||||
})
|
||||
lst.append(args)
|
||||
|
||||
@@ -741,6 +813,89 @@ class AccountsController(TransactionBase):
|
||||
tax_map[tax.account_head] -= allocated_amount
|
||||
allocated_tax_map[tax.account_head] -= allocated_amount
|
||||
|
||||
def get_amount_and_base_amount(self, item, enable_discount_accounting):
|
||||
amount = item.net_amount
|
||||
base_amount = item.base_net_amount
|
||||
|
||||
if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account'):
|
||||
amount = item.amount
|
||||
base_amount = item.base_amount
|
||||
|
||||
return amount, base_amount
|
||||
|
||||
def get_tax_amounts(self, tax, enable_discount_accounting):
|
||||
amount = tax.tax_amount_after_discount_amount
|
||||
base_amount = tax.base_tax_amount_after_discount_amount
|
||||
|
||||
if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account') \
|
||||
and self.get('apply_discount_on') == 'Grand Total':
|
||||
amount = tax.tax_amount
|
||||
base_amount = tax.base_tax_amount
|
||||
|
||||
return amount, base_amount
|
||||
|
||||
def make_discount_gl_entries(self, gl_entries):
|
||||
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
|
||||
|
||||
if enable_discount_accounting:
|
||||
if self.doctype == "Purchase Invoice":
|
||||
dr_or_cr = "credit"
|
||||
rev_dr_cr = "debit"
|
||||
supplier_or_customer = self.supplier
|
||||
|
||||
else:
|
||||
dr_or_cr = "debit"
|
||||
rev_dr_cr = "credit"
|
||||
supplier_or_customer = self.customer
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.get('discount_amount') and item.get('discount_account'):
|
||||
discount_amount = item.discount_amount * item.qty
|
||||
if self.doctype == "Purchase Invoice":
|
||||
income_or_expense_account = (item.expense_account
|
||||
if (not item.enable_deferred_expense or self.is_return)
|
||||
else item.deferred_expense_account)
|
||||
else:
|
||||
income_or_expense_account = (item.income_account
|
||||
if (not item.enable_deferred_revenue or self.is_return)
|
||||
else item.deferred_revenue_account)
|
||||
|
||||
account_currency = get_account_currency(item.discount_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.discount_account,
|
||||
"against": supplier_or_customer,
|
||||
dr_or_cr: flt(discount_amount, item.precision('discount_amount')),
|
||||
dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
|
||||
item.precision('discount_amount')),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
|
||||
account_currency = get_account_currency(income_or_expense_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": income_or_expense_account,
|
||||
"against": supplier_or_customer,
|
||||
rev_dr_cr: flt(discount_amount, item.precision('discount_amount')),
|
||||
rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
|
||||
item.precision('discount_amount')),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
|
||||
if self.get('discount_amount') and self.get('additional_discount_account'):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.additional_discount_account,
|
||||
"against": supplier_or_customer,
|
||||
dr_or_cr: self.discount_amount,
|
||||
"cost_center": self.cost_center
|
||||
}, item=self)
|
||||
)
|
||||
|
||||
def allocate_advance_taxes(self, gl_entries):
|
||||
tax_map = self.get_tax_map()
|
||||
for pe in self.get("advances"):
|
||||
@@ -1045,8 +1200,11 @@ class AccountsController(TransactionBase):
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
|
||||
d.outstanding = d.payment_amount
|
||||
elif not d.invoice_portion:
|
||||
d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
|
||||
|
||||
|
||||
def set_due_date(self):
|
||||
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
|
||||
@@ -1289,6 +1447,8 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
|
||||
party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
|
||||
currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
|
||||
payment_type = "Receive" if party_type == "Customer" else "Pay"
|
||||
exchange_rate_field = "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
|
||||
|
||||
payment_entries_against_order, unallocated_payment_entries = [], []
|
||||
limit_cond = "limit %s" % limit if limit else ""
|
||||
|
||||
@@ -1305,27 +1465,28 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
|
||||
"Payment Entry" as reference_type, t1.name as reference_name,
|
||||
t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
|
||||
t2.reference_name as against_order, t1.posting_date,
|
||||
t1.{0} as currency
|
||||
t1.{0} as currency, t1.{4} as exchange_rate
|
||||
from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
|
||||
where
|
||||
t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
|
||||
and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
|
||||
and t2.reference_doctype = %s {2}
|
||||
order by t1.posting_date {3}
|
||||
""".format(currency_field, party_account_field, reference_condition, limit_cond),
|
||||
""".format(currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field),
|
||||
[party_account, payment_type, party_type, party,
|
||||
order_doctype] + order_list, as_dict=1)
|
||||
|
||||
if include_unallocated:
|
||||
unallocated_payment_entries = frappe.db.sql("""
|
||||
select "Payment Entry" as reference_type, name as reference_name,
|
||||
remarks, unallocated_amount as amount
|
||||
remarks, unallocated_amount as amount, {2} as exchange_rate
|
||||
from `tabPayment Entry`
|
||||
where
|
||||
{0} = %s and party_type = %s and party = %s and payment_type = %s
|
||||
and docstatus = 1 and unallocated_amount > 0
|
||||
order by posting_date {1}
|
||||
""".format(party_account_field, limit_cond), (party_account, party_type, party, payment_type), as_dict=1)
|
||||
""".format(party_account_field, limit_cond, exchange_rate_field),
|
||||
(party_account, party_type, party, payment_type), as_dict=1)
|
||||
|
||||
return list(payment_entries_against_order) + list(unallocated_payment_entries)
|
||||
|
||||
|
||||
@@ -407,6 +407,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
||||
where
|
||||
batch.disabled = 0
|
||||
and sle.is_cancelled = 0
|
||||
and sle.item_code = %(item_code)s
|
||||
and sle.warehouse = %(warehouse)s
|
||||
and (sle.batch_no like %(txt)s
|
||||
|
||||
@@ -53,12 +53,17 @@ class StockController(AccountsController):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
for d in self.get("items"):
|
||||
if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
for serial_no_data in frappe.get_all("Serial No",
|
||||
filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]):
|
||||
if serial_no_data.batch_no != d.batch_no:
|
||||
serial_nos = frappe.get_all("Serial No",
|
||||
fields=["batch_no", "name", "warehouse"],
|
||||
filters={
|
||||
"name": ("in", get_serial_nos(d.serial_no))
|
||||
}
|
||||
)
|
||||
|
||||
for row in serial_nos:
|
||||
if row.warehouse and row.batch_no != d.batch_no:
|
||||
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
|
||||
.format(d.idx, serial_no_data.name, d.batch_no))
|
||||
.format(d.idx, row.name, d.batch_no))
|
||||
|
||||
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
|
||||
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
|
||||
|
||||
@@ -152,7 +152,7 @@ class calculate_taxes_and_totals(object):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self.doc)
|
||||
|
||||
if not self.doc.get('is_consolidated'):
|
||||
if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
|
||||
tax.item_wise_tax_detail = {}
|
||||
|
||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||
@@ -347,7 +347,7 @@ class calculate_taxes_and_totals(object):
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
current_tax_amount = tax_rate * item.qty
|
||||
|
||||
if not self.doc.get("is_consolidated"):
|
||||
if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
|
||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||
|
||||
return current_tax_amount
|
||||
@@ -455,7 +455,8 @@ class calculate_taxes_and_totals(object):
|
||||
def _cleanup(self):
|
||||
if not self.doc.get('is_consolidated'):
|
||||
for tax in self.doc.get("taxes"):
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||
if not tax.get("dont_recompute_tax"):
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||
|
||||
def set_discount_amount(self):
|
||||
if self.doc.additional_discount_percentage:
|
||||
|
||||
@@ -24,7 +24,8 @@ doctype_js = {
|
||||
"Address": "public/js/address.js",
|
||||
"Communication": "public/js/communication.js",
|
||||
"Event": "public/js/event.js",
|
||||
"Newsletter": "public/js/newsletter.js"
|
||||
"Newsletter": "public/js/newsletter.js",
|
||||
"Contact": "public/js/contact.js"
|
||||
}
|
||||
|
||||
override_doctype_class = {
|
||||
|
||||
@@ -9,7 +9,7 @@ from frappe.utils import flt, getdate
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import set_employee_name
|
||||
from erpnext.hr.utils import set_employee_name, validate_active_employee
|
||||
|
||||
class Appraisal(Document):
|
||||
def validate(self):
|
||||
@@ -19,6 +19,7 @@ class Appraisal(Document):
|
||||
if not self.goals:
|
||||
frappe.throw(_("Goals cannot be empty"))
|
||||
|
||||
validate_active_employee(self.employee)
|
||||
set_employee_name(self)
|
||||
self.validate_dates()
|
||||
self.validate_existing_appraisal()
|
||||
|
||||
@@ -8,11 +8,13 @@ from frappe.utils import getdate, nowdate
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr, get_datetime, formatdate
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class Attendance(Document):
|
||||
def validate(self):
|
||||
from erpnext.controllers.status_updater import validate_status
|
||||
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_attendance_date()
|
||||
self.validate_duplicate_record()
|
||||
self.validate_employee_status()
|
||||
|
||||
@@ -8,10 +8,11 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import date_diff, add_days, getdate
|
||||
from erpnext.hr.doctype.employee.employee import is_holiday
|
||||
from erpnext.hr.utils import validate_dates
|
||||
from erpnext.hr.utils import validate_dates, validate_active_employee
|
||||
|
||||
class AttendanceRequest(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
validate_dates(self, self.from_date, self.to_date)
|
||||
if self.half_day:
|
||||
if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date):
|
||||
|
||||
@@ -7,12 +7,13 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import date_diff, add_days, getdate, cint, format_date
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
|
||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \
|
||||
get_holidays_for_employee, create_additional_leave_ledger_entry
|
||||
|
||||
class CompensatoryLeaveRequest(Document):
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
validate_dates(self, self.work_from_date, self.work_end_date)
|
||||
if self.half_day:
|
||||
if not self.half_day_date:
|
||||
|
||||
@@ -13,8 +13,10 @@ from frappe.model.document import Document
|
||||
from erpnext.utilities.transaction_base import delete_events
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
class EmployeeUserDisabledError(frappe.ValidationError): pass
|
||||
class EmployeeLeftValidationError(frappe.ValidationError): pass
|
||||
class EmployeeUserDisabledError(frappe.ValidationError):
|
||||
pass
|
||||
class InactiveEmployeeStatusError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
class Employee(NestedSet):
|
||||
nsm_parent_field = 'reports_to'
|
||||
@@ -196,7 +198,7 @@ class Employee(NestedSet):
|
||||
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
|
||||
message += "</li></ul><br>"
|
||||
message += _("Please make sure the employees above report to another Active employee.")
|
||||
throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee"))
|
||||
throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee"))
|
||||
if not self.relieving_date:
|
||||
throw(_("Please enter relieving date."))
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import frappe
|
||||
import erpnext
|
||||
import unittest
|
||||
import frappe.utils
|
||||
from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError
|
||||
from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
|
||||
|
||||
test_records = frappe.get_test_records('Employee')
|
||||
|
||||
@@ -45,10 +45,33 @@ class TestEmployee(unittest.TestCase):
|
||||
employee2_doc.save()
|
||||
employee1_doc.reload()
|
||||
employee1_doc.status = 'Left'
|
||||
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
|
||||
self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
|
||||
|
||||
def test_employee_status_inactive(self):
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
|
||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
|
||||
|
||||
employee = make_employee("test_employee_status@company.com")
|
||||
employee_doc = frappe.get_doc("Employee", employee)
|
||||
employee_doc.status = "Inactive"
|
||||
employee_doc.save()
|
||||
employee_doc.reload()
|
||||
|
||||
make_holiday_list()
|
||||
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
|
||||
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
|
||||
salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
|
||||
employee=employee_doc.name, company=employee_doc.company)
|
||||
salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name)
|
||||
|
||||
self.assertRaises(InactiveEmployeeStatusError, salary_slip.save)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def make_employee(user, company=None, **kwargs):
|
||||
""
|
||||
if not frappe.db.get_value("User", user):
|
||||
frappe.get_doc({
|
||||
"doctype": "User",
|
||||
@@ -80,4 +103,5 @@ def make_employee(user, company=None, **kwargs):
|
||||
employee.insert()
|
||||
return employee.name
|
||||
else:
|
||||
frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active")
|
||||
return frappe.get_value("Employee", {"employee_name":user}, "name")
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, nowdate
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class EmployeeAdvanceOverPayment(frappe.ValidationError):
|
||||
pass
|
||||
@@ -18,6 +19,7 @@ class EmployeeAdvance(Document):
|
||||
'make_payment_via_journal_entry')
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.set_status()
|
||||
|
||||
def on_cancel(self):
|
||||
@@ -183,9 +185,9 @@ def make_return_entry(employee, company, employee_advance_name, return_amount,
|
||||
bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
||||
if not bank_cash_account:
|
||||
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
|
||||
|
||||
|
||||
advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
|
||||
|
||||
|
||||
je = frappe.new_doc('Journal Entry')
|
||||
je.posting_date = nowdate()
|
||||
je.voucher_type = get_voucher_type(mode_of_payment)
|
||||
|
||||
@@ -9,9 +9,11 @@ from frappe.model.document import Document
|
||||
from frappe import _
|
||||
|
||||
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class EmployeeCheckin(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_duplicate_log()
|
||||
self.fetch_shift()
|
||||
|
||||
@@ -122,7 +124,7 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
|
||||
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
||||
"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
|
||||
Zero is returned for all invalid cases.
|
||||
|
||||
|
||||
:param logs: The List of 'Employee Checkin'.
|
||||
:param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin'
|
||||
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
|
||||
|
||||
@@ -7,12 +7,11 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
from erpnext.hr.utils import update_employee
|
||||
from erpnext.hr.utils import update_employee, validate_active_employee
|
||||
|
||||
class EmployeePromotion(Document):
|
||||
def validate(self):
|
||||
if frappe.get_value("Employee", self.employee, "status") != "Active":
|
||||
frappe.throw(_("Cannot promote Employee with status Left or Inactive"))
|
||||
validate_active_employee(self.employee)
|
||||
|
||||
def before_submit(self):
|
||||
if getdate(self.promotion_date) > getdate():
|
||||
|
||||
@@ -7,9 +7,11 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class EmployeeReferral(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.referrer)
|
||||
self.set_full_name()
|
||||
self.set_referral_bonus_payment_status()
|
||||
|
||||
|
||||
@@ -10,10 +10,6 @@ from frappe.utils import getdate
|
||||
from erpnext.hr.utils import update_employee
|
||||
|
||||
class EmployeeTransfer(Document):
|
||||
def validate(self):
|
||||
if frappe.get_value("Employee", self.employee, "status") != "Active":
|
||||
frappe.throw(_("Cannot transfer Employee with status Left or Inactive"))
|
||||
|
||||
def before_submit(self):
|
||||
if getdate(self.transfer_date) > getdate():
|
||||
frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe, erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import get_fullname, flt, cstr, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import set_employee_name, share_doc_with_approver
|
||||
from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
||||
@@ -23,6 +23,7 @@ class ExpenseClaim(AccountsController):
|
||||
'make_payment_via_journal_entry')
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_advances()
|
||||
self.validate_sanctioned_amount()
|
||||
self.calculate_total_amount()
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee
|
||||
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
|
||||
@@ -22,6 +22,7 @@ class LeaveApplication(Document):
|
||||
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
set_employee_name(self)
|
||||
self.validate_dates()
|
||||
self.validate_balance_leaves()
|
||||
|
||||
@@ -7,7 +7,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate, nowdate, flt
|
||||
from erpnext.hr.utils import set_employee_name
|
||||
from erpnext.hr.utils import set_employee_name, validate_active_employee
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
|
||||
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
|
||||
@@ -15,6 +15,7 @@ from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leav
|
||||
class LeaveEncashment(Document):
|
||||
def validate(self):
|
||||
set_employee_name(self)
|
||||
validate_active_employee(self.employee)
|
||||
self.get_leave_details_for_encashment()
|
||||
self.validate_salary_structure()
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@ from frappe.model.document import Document
|
||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
class ShiftAssignment(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_overlapping_dates()
|
||||
|
||||
if self.end_date and self.end_date <= self.start_date:
|
||||
|
||||
@@ -7,12 +7,13 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import formatdate, getdate
|
||||
from erpnext.hr.utils import share_doc_with_approver
|
||||
from erpnext.hr.utils import share_doc_with_approver, validate_active_employee
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
|
||||
class ShiftRequest(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_dates()
|
||||
self.validate_shift_request_overlap_dates()
|
||||
self.validate_approver()
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class TravelRequest(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
|
||||
import erpnext
|
||||
import frappe
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate,
|
||||
get_datetime, getdate, nowdate, today, unique)
|
||||
|
||||
get_datetime, getdate, nowdate, today, unique, get_link_to_form)
|
||||
|
||||
class DuplicateDeclarationError(frappe.ValidationError): pass
|
||||
|
||||
@@ -20,6 +19,7 @@ class EmployeeBoardingController(Document):
|
||||
Assign to the concerned person and roles as per the onboarding/separation template
|
||||
'''
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
# remove the task if linked before submitting the form
|
||||
if self.amended_from:
|
||||
for activity in self.activities:
|
||||
@@ -522,3 +522,8 @@ def share_doc_with_approver(doc, user):
|
||||
approver = approvers.get(doc.doctype)
|
||||
if doc_before_save.get(approver) != doc.get(approver):
|
||||
frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
|
||||
|
||||
def validate_active_employee(employee):
|
||||
if frappe.db.get_value("Employee", employee, "status") == "Inactive":
|
||||
frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format(
|
||||
get_link_to_form("Employee", employee)), InactiveEmployeeStatusError)
|
||||
@@ -28,7 +28,8 @@ frappe.ui.form.on('Loan', {
|
||||
frm.set_query("loan_type", function () {
|
||||
return {
|
||||
"filters": {
|
||||
"docstatus": 1
|
||||
"docstatus": 1,
|
||||
"company": frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -14,6 +14,13 @@ frappe.ui.form.on('Loan Application', {
|
||||
refresh: function(frm) {
|
||||
frm.trigger("toggle_fields");
|
||||
frm.trigger("add_toolbar_buttons");
|
||||
frm.set_query('loan_type', () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
repayment_method: function(frm) {
|
||||
frm.doc.repayment_amount = frm.doc.repayment_periods = ""
|
||||
|
||||
@@ -83,7 +83,7 @@ frappe.ui.form.on("BOM", {
|
||||
|
||||
if (!frm.doc.__islocal && frm.doc.docstatus<2) {
|
||||
frm.add_custom_button(__("Update Cost"), function() {
|
||||
frm.events.update_cost(frm);
|
||||
frm.events.update_cost(frm, true);
|
||||
});
|
||||
frm.add_custom_button(__("Browse BOM"), function() {
|
||||
frappe.route_options = {
|
||||
@@ -318,14 +318,15 @@ frappe.ui.form.on("BOM", {
|
||||
})
|
||||
},
|
||||
|
||||
update_cost: function(frm) {
|
||||
update_cost: function(frm, save_doc=false) {
|
||||
return frappe.call({
|
||||
doc: frm.doc,
|
||||
method: "update_cost",
|
||||
freeze: true,
|
||||
args: {
|
||||
update_parent: true,
|
||||
from_child_bom:false
|
||||
save: save_doc,
|
||||
from_child_bom: false
|
||||
},
|
||||
callback: function(r) {
|
||||
refresh_field("items");
|
||||
|
||||
@@ -330,7 +330,7 @@ class BOM(WebsiteGenerator):
|
||||
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
|
||||
|
||||
if not from_child_bom:
|
||||
frappe.msgprint(_("Cost Updated"))
|
||||
frappe.msgprint(_("Cost Updated"), alert=True)
|
||||
|
||||
def update_parent_cost(self):
|
||||
if self.total_cost:
|
||||
@@ -748,7 +748,7 @@ def get_valuation_rate(args):
|
||||
if valuation_rate <= 0:
|
||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and valuation_rate > 0
|
||||
where item_code = %s and valuation_rate > 0 and is_cancelled = 0
|
||||
order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
|
||||
|
||||
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
|
||||
@@ -1069,13 +1069,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if barcodes:
|
||||
or_cond_filters["name"] = ("in", barcodes)
|
||||
|
||||
for cond in get_match_cond(doctype, as_condition=False):
|
||||
for key, value in cond.items():
|
||||
if key == doctype:
|
||||
key = "name"
|
||||
|
||||
query_filters[key] = ("in", value)
|
||||
|
||||
if filters and filters.get("item_code"):
|
||||
has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
|
||||
if not has_variants:
|
||||
@@ -1084,7 +1077,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters and filters.get("is_stock_item"):
|
||||
query_filters["is_stock_item"] = 1
|
||||
|
||||
return frappe.get_all("Item",
|
||||
return frappe.get_list("Item",
|
||||
fields = fields, filters=query_filters,
|
||||
or_filters = or_cond_filters, order_by=order_by,
|
||||
limit_start=start, limit_page_length=page_len, as_list=1)
|
||||
|
||||
@@ -192,11 +192,11 @@ class JobCard(Document):
|
||||
"completed_qty": args.get("completed_qty") or 0.0
|
||||
})
|
||||
elif args.get("start_time"):
|
||||
new_args = {
|
||||
new_args = frappe._dict({
|
||||
"from_time": get_datetime(args.get("start_time")),
|
||||
"operation": args.get("sub_operation"),
|
||||
"completed_qty": 0.0
|
||||
}
|
||||
})
|
||||
|
||||
if employees:
|
||||
for name in employees:
|
||||
|
||||
@@ -747,9 +747,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
|
||||
group by item_code, warehouse
|
||||
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
|
||||
|
||||
def get_warehouse_list(warehouses, warehouse_list=None):
|
||||
if not warehouse_list:
|
||||
warehouse_list = []
|
||||
def get_warehouse_list(warehouses):
|
||||
warehouse_list = []
|
||||
|
||||
if isinstance(warehouses, str):
|
||||
warehouses = json.loads(warehouses)
|
||||
@@ -761,23 +760,19 @@ def get_warehouse_list(warehouses, warehouse_list=None):
|
||||
else:
|
||||
warehouse_list.append(row.get("warehouse"))
|
||||
|
||||
return warehouse_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
|
||||
if isinstance(doc, str):
|
||||
doc = frappe._dict(json.loads(doc))
|
||||
|
||||
warehouse_list = []
|
||||
if warehouses:
|
||||
get_warehouse_list(warehouses, warehouse_list)
|
||||
|
||||
if warehouse_list:
|
||||
warehouses = list(set(warehouse_list))
|
||||
warehouses = list(set(get_warehouse_list(warehouses)))
|
||||
|
||||
if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses:
|
||||
warehouses.remove(doc.get("for_warehouse"))
|
||||
|
||||
warehouse_list = None
|
||||
|
||||
doc['mr_items'] = []
|
||||
|
||||
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
|
||||
|
||||
@@ -10,7 +10,7 @@ from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list
|
||||
|
||||
class TestProductionPlan(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@@ -251,6 +251,27 @@ class TestProductionPlan(unittest.TestCase):
|
||||
pln.cancel()
|
||||
frappe.delete_doc("Production Plan", pln.name)
|
||||
|
||||
def test_get_warehouse_list_group(self):
|
||||
"""Check if required warehouses are returned"""
|
||||
warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
|
||||
|
||||
warehouses = set(get_warehouse_list(warehouse_json))
|
||||
expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"}
|
||||
|
||||
missing_warehouse = expected_warehouses - warehouses
|
||||
|
||||
self.assertTrue(len(missing_warehouse) == 0,
|
||||
msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
|
||||
|
||||
def test_get_warehouse_list_single(self):
|
||||
warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
|
||||
|
||||
warehouses = set(get_warehouse_list(warehouse_json))
|
||||
expected_warehouses = {"_Test Scrap Warehouse - _TC", }
|
||||
|
||||
self.assertEqual(warehouses, expected_warehouses)
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"options": "Operation"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Time in mins",
|
||||
"fieldname": "time_in_mins",
|
||||
"fieldtype": "Float",
|
||||
@@ -38,7 +39,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-07 18:09:18.005578",
|
||||
"modified": "2021-07-15 16:39:41.635362",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Sub Operation",
|
||||
|
||||
@@ -487,21 +487,20 @@ class WorkOrder(Document):
|
||||
return
|
||||
|
||||
operations = []
|
||||
if not self.use_multi_level_bom:
|
||||
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
||||
operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
|
||||
else:
|
||||
|
||||
if self.use_multi_level_bom:
|
||||
bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
|
||||
bom_traversal = list(reversed(bom_tree.level_order_traversal()))
|
||||
bom_traversal.append(bom_tree) # add operation on top level item last
|
||||
bom_traversal = reversed(bom_tree.level_order_traversal())
|
||||
|
||||
for d in bom_traversal:
|
||||
if d.is_bom:
|
||||
operations.extend(_get_operations(d.name, qty=d.exploded_qty))
|
||||
for node in bom_traversal:
|
||||
if node.is_bom:
|
||||
operations.extend(_get_operations(node.name, qty=node.exploded_qty))
|
||||
|
||||
for correct_index, operation in enumerate(operations, start=1):
|
||||
operation.idx = correct_index
|
||||
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
||||
operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
|
||||
|
||||
for correct_index, operation in enumerate(operations, start=1):
|
||||
operation.idx = correct_index
|
||||
|
||||
self.set('operations', operations)
|
||||
self.calculate_time()
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"razorpay_details_section",
|
||||
"subscription_id",
|
||||
"customer_id",
|
||||
"subscription_activated",
|
||||
"subscription_status",
|
||||
"column_break_21",
|
||||
"subscription_start",
|
||||
"subscription_end"
|
||||
@@ -151,12 +151,6 @@
|
||||
"fieldname": "column_break_21",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "subscription_activated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Subscription Activated"
|
||||
},
|
||||
{
|
||||
"fieldname": "subscription_start",
|
||||
"fieldtype": "Date",
|
||||
@@ -166,11 +160,17 @@
|
||||
"fieldname": "subscription_end",
|
||||
"fieldtype": "Date",
|
||||
"label": "Subscription End"
|
||||
},
|
||||
{
|
||||
"fieldname": "subscription_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Subscription Status",
|
||||
"options": "\nActive\nHalted"
|
||||
}
|
||||
],
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-11-09 12:12:10.174647",
|
||||
"modified": "2021-07-11 14:27:26.368039",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Member",
|
||||
|
||||
@@ -84,7 +84,9 @@ def create_member(user_details):
|
||||
"email_id": user_details.email,
|
||||
"pan_number": user_details.pan or None,
|
||||
"membership_type": user_details.plan_id,
|
||||
"subscription_id": user_details.subscription_id or None
|
||||
"customer_id": user_details.customer_id or None,
|
||||
"subscription_id": user_details.subscription_id or None,
|
||||
"subscription_status": user_details.subscription_status or ""
|
||||
})
|
||||
|
||||
member.insert(ignore_permissions=True)
|
||||
|
||||
@@ -196,11 +196,14 @@ def make_invoice(membership, member, plan, settings):
|
||||
return invoice
|
||||
|
||||
|
||||
def get_member_based_on_subscription(subscription_id, email):
|
||||
members = frappe.get_all("Member", filters={
|
||||
"subscription_id": subscription_id,
|
||||
"email_id": email
|
||||
}, order_by="creation desc")
|
||||
def get_member_based_on_subscription(subscription_id, email=None, customer_id=None):
|
||||
filters = {"subscription_id": subscription_id}
|
||||
if email:
|
||||
filters.update({"email_id": email})
|
||||
if customer_id:
|
||||
filters.update({"customer_id": customer_id})
|
||||
|
||||
members = frappe.get_all("Member", filters=filters, order_by="creation desc")
|
||||
|
||||
try:
|
||||
return frappe.get_doc("Member", members[0]["name"])
|
||||
@@ -209,8 +212,6 @@ def get_member_based_on_subscription(subscription_id, email):
|
||||
|
||||
|
||||
def verify_signature(data, endpoint="Membership"):
|
||||
if frappe.flags.in_test or os.environ.get("CI"):
|
||||
return True
|
||||
signature = frappe.request.headers.get("X-Razorpay-Signature")
|
||||
|
||||
settings = frappe.get_doc("Non Profit Settings")
|
||||
@@ -225,16 +226,7 @@ def verify_signature(data, endpoint="Membership"):
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def trigger_razorpay_subscription(*args, **kwargs):
|
||||
data = frappe.request.get_data(as_text=True)
|
||||
try:
|
||||
verify_signature(data)
|
||||
except Exception as e:
|
||||
log = frappe.log_error(e, "Membership Webhook Verification Error")
|
||||
notify_failure(log)
|
||||
return { "status": "Failed", "reason": e}
|
||||
|
||||
if isinstance(data, six.string_types):
|
||||
data = json.loads(data)
|
||||
data = frappe._dict(data)
|
||||
data = process_request_data(data)
|
||||
|
||||
subscription = data.payload.get("subscription", {}).get("entity", {})
|
||||
subscription = frappe._dict(subscription)
|
||||
@@ -281,7 +273,7 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
# Update membership values
|
||||
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
|
||||
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
|
||||
member.subscription_activated = 1
|
||||
member.subscription_status = "Active"
|
||||
member.flags.ignore_mandatory = True
|
||||
member.save()
|
||||
|
||||
@@ -294,9 +286,67 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id)
|
||||
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
|
||||
notify_failure(log)
|
||||
return { "status": "Failed", "reason": e}
|
||||
return {"status": "Failed", "reason": e}
|
||||
|
||||
return { "status": "Success" }
|
||||
return {"status": "Success"}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def update_halted_razorpay_subscription(*args, **kwargs):
|
||||
"""
|
||||
When all retries have been exhausted, Razorpay moves the subscription to the halted state.
|
||||
The customer has to manually retry the charge or change the card linked to the subscription,
|
||||
for the subscription to move back to the active state.
|
||||
"""
|
||||
if frappe.request:
|
||||
data = frappe.request.get_data(as_text=True)
|
||||
data = process_request_data(data)
|
||||
elif frappe.flags.in_test:
|
||||
data = kwargs.get("data")
|
||||
data = frappe._dict(data)
|
||||
else:
|
||||
return
|
||||
|
||||
if not data.event == "subscription.halted":
|
||||
return
|
||||
|
||||
subscription = data.payload.get("subscription", {}).get("entity", {})
|
||||
subscription = frappe._dict(subscription)
|
||||
|
||||
try:
|
||||
member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id)
|
||||
if not member:
|
||||
frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id))
|
||||
|
||||
member.subscription_status = "Halted"
|
||||
member.flags.ignore_mandatory = True
|
||||
member.save()
|
||||
|
||||
if subscription.get("notes"):
|
||||
member = get_additional_notes(member, subscription)
|
||||
|
||||
except Exception as e:
|
||||
message = "{0}\n\n{1}".format(e, frappe.get_traceback())
|
||||
log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name))
|
||||
notify_failure(log)
|
||||
return {"status": "Failed", "reason": e}
|
||||
|
||||
return {"status": "Success"}
|
||||
|
||||
|
||||
def process_request_data(data):
|
||||
try:
|
||||
verify_signature(data)
|
||||
except Exception as e:
|
||||
log = frappe.log_error(e, "Membership Webhook Verification Error")
|
||||
notify_failure(log)
|
||||
return {"status": "Failed", "reason": e}
|
||||
|
||||
if isinstance(data, six.string_types):
|
||||
data = json.loads(data)
|
||||
data = frappe._dict(data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_company_for_memberships():
|
||||
@@ -362,4 +412,4 @@ def set_expired_status():
|
||||
`tabMembership` SET `status` = 'Expired'
|
||||
WHERE
|
||||
`status` not in ('Cancelled') AND `to_date` < %s
|
||||
""", (nowdate()))
|
||||
""", (nowdate()))
|
||||
|
||||
@@ -6,6 +6,7 @@ import unittest
|
||||
import frappe
|
||||
import erpnext
|
||||
from erpnext.non_profit.doctype.member.member import create_member
|
||||
from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription
|
||||
from frappe.utils import nowdate, add_months
|
||||
|
||||
class TestMembership(unittest.TestCase):
|
||||
@@ -13,11 +14,16 @@ class TestMembership(unittest.TestCase):
|
||||
plan = setup_membership()
|
||||
|
||||
# make test member
|
||||
self.member_doc = create_member(frappe._dict({
|
||||
'fullname': "_Test_Member",
|
||||
'email': "_test_member_erpnext@example.com",
|
||||
'plan_id': plan.name
|
||||
}))
|
||||
self.member_doc = create_member(
|
||||
frappe._dict({
|
||||
"fullname": "_Test_Member",
|
||||
"email": "_test_member_erpnext@example.com",
|
||||
"plan_id": plan.name,
|
||||
"subscription_id": "sub_DEX6xcJ1HSW4CR",
|
||||
"customer_id": "cust_C0WlbKhp3aLA7W",
|
||||
"subscription_status": "Active"
|
||||
})
|
||||
)
|
||||
self.member_doc.make_customer_and_link()
|
||||
self.member = self.member_doc.name
|
||||
|
||||
@@ -51,6 +57,20 @@ class TestMembership(unittest.TestCase):
|
||||
"to_date": add_months(nowdate(), 3),
|
||||
})
|
||||
|
||||
def test_halted_memberships(self):
|
||||
make_membership(self.member, {
|
||||
"from_date": add_months(nowdate(), 2),
|
||||
"to_date": add_months(nowdate(), 3)
|
||||
})
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active")
|
||||
payload = get_subscription_payload()
|
||||
update_halted_razorpay_subscription(data=payload)
|
||||
self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def set_config(key, value):
|
||||
frappe.db.set_value("Non Profit Settings", None, key, value)
|
||||
|
||||
@@ -115,4 +135,28 @@ def setup_membership():
|
||||
else:
|
||||
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
|
||||
|
||||
return plan
|
||||
return plan
|
||||
|
||||
def get_subscription_payload():
|
||||
return {
|
||||
"entity": "event",
|
||||
"account_id": "acc_BFQ7uQEaa7j2z7",
|
||||
"event": "subscription.halted",
|
||||
"contains": [
|
||||
"subscription"
|
||||
],
|
||||
"payload": {
|
||||
"subscription": {
|
||||
"entity": {
|
||||
"id": "sub_DEX6xcJ1HSW4CR",
|
||||
"entity": "subscription",
|
||||
"plan_id": "_rzpy_test_milythm",
|
||||
"customer_id": "cust_C0WlbKhp3aLA7W",
|
||||
"status": "halted",
|
||||
"notes": {
|
||||
"Important": "Notes for Internal Reference"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,3 +292,6 @@ erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
|
||||
erpnext.patches.v13_0.update_job_card_details
|
||||
erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
||||
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
||||
erpnext.patches.v13_0.update_subscription_status_in_memberships
|
||||
erpnext.patches.v13_0.update_export_type_for_gst
|
||||
erpnext.patches.v13_0.update_tds_check_field #3
|
||||
|
||||
@@ -10,6 +10,7 @@ def execute():
|
||||
if not frappe.db.has_column('Work Order', 'has_batch_no'):
|
||||
return
|
||||
|
||||
frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings')
|
||||
if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
|
||||
return
|
||||
|
||||
@@ -107,4 +108,4 @@ def repost_future_sle_and_gle(doc):
|
||||
"company": doc.company
|
||||
})
|
||||
|
||||
create_repost_item_valuation_entry(args)
|
||||
create_repost_item_valuation_entry(args)
|
||||
|
||||
24
erpnext/patches/v13_0/update_export_type_for_gst.py
Normal file
24
erpnext/patches/v13_0/update_export_type_for_gst.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
# Update custom fields
|
||||
fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'})
|
||||
if fieldname:
|
||||
frappe.db.set_value('Custom Field', fieldname, 'default', '')
|
||||
|
||||
fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'})
|
||||
if fieldname:
|
||||
frappe.db.set_value('Custom Field', fieldname, 'default', '')
|
||||
|
||||
# Update Customer/Supplier Masters
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export')
|
||||
""")
|
||||
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas')
|
||||
""")
|
||||
@@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
if frappe.db.exists('DocType', 'Member'):
|
||||
frappe.reload_doc('Non Profit', 'doctype', 'Member')
|
||||
|
||||
if frappe.db.has_column('Member', 'subscription_activated'):
|
||||
frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1')
|
||||
frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated')
|
||||
9
erpnext/patches/v13_0/update_tds_check_field.py
Normal file
9
erpnext/patches/v13_0/update_tds_check_field.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
if frappe.db.has_table("Tax Withholding Category") \
|
||||
and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"):
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0
|
||||
WHERE round_off_tax_amount IS NULL
|
||||
""")
|
||||
@@ -7,6 +7,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _, bold
|
||||
from frappe.utils import getdate, date_diff, comma_and, formatdate
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class AdditionalSalary(Document):
|
||||
def on_submit(self):
|
||||
@@ -19,6 +20,7 @@ class AdditionalSalary(Document):
|
||||
self.update_employee_referral(cancel=True)
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_dates()
|
||||
self.validate_salary_structure()
|
||||
self.validate_recurring_additional_salary_overlap()
|
||||
@@ -110,11 +112,11 @@ class AdditionalSalary(Document):
|
||||
no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
|
||||
return amount_per_day * no_of_days
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_additional_salaries(employee, start_date, end_date, component_type):
|
||||
additional_salary_list = frappe.db.sql("""
|
||||
select name, salary_component as component, type, amount,
|
||||
overwrite_salary_structure_amount as overwrite,
|
||||
deduct_full_tax_on_selected_payroll_date
|
||||
select name, salary_component as component, type, amount, overwrite_salary_structure_amount as overwrite,
|
||||
deduct_full_tax_on_selected_payroll_date, is_recurring
|
||||
from `tabAdditional Salary`
|
||||
where employee=%(employee)s
|
||||
and docstatus = 1
|
||||
|
||||
@@ -9,10 +9,11 @@ from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt
|
||||
from frappe.model.document import Document
|
||||
from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
||||
from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount
|
||||
from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee
|
||||
|
||||
class EmployeeBenefitApplication(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_duplicate_on_payroll_period()
|
||||
if not self.max_benefits:
|
||||
self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period)
|
||||
|
||||
@@ -8,12 +8,13 @@ from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.model.document import Document
|
||||
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits
|
||||
from erpnext.hr.utils import get_previous_claimed_amount
|
||||
from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee
|
||||
from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
||||
|
||||
class EmployeeBenefitClaim(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
max_benefits = get_max_benefits(self.employee, self.claim_date)
|
||||
if not max_benefits or max_benefits <= 0:
|
||||
frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee))
|
||||
|
||||
@@ -6,9 +6,11 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class EmployeeIncentive(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_salary_structure()
|
||||
|
||||
def validate_salary_structure(self):
|
||||
|
||||
@@ -8,11 +8,12 @@ from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
|
||||
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
|
||||
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
|
||||
|
||||
class EmployeeTaxExemptionDeclaration(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
validate_tax_declaration(self.declarations)
|
||||
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
|
||||
self.set_total_declared_amount()
|
||||
|
||||
@@ -7,11 +7,12 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
|
||||
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
|
||||
calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period
|
||||
|
||||
class EmployeeTaxExemptionProofSubmission(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
validate_tax_declaration(self.tax_exemption_proofs)
|
||||
self.set_total_actual_amount()
|
||||
self.set_total_exemption_amount()
|
||||
|
||||
@@ -7,11 +7,10 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import getdate
|
||||
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
class RetentionBonus(Document):
|
||||
def validate(self):
|
||||
if frappe.get_value('Employee', self.employee, 'status') != 'Active':
|
||||
frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees'))
|
||||
validate_active_employee(self.employee)
|
||||
if getdate(self.bonus_payment_date) < getdate():
|
||||
frappe.throw(_('Bonus Payment Date cannot be a past date'))
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"year_to_date",
|
||||
"section_break_5",
|
||||
"additional_salary",
|
||||
"is_recurring_additional_salary",
|
||||
"statistical_component",
|
||||
"depends_on_payment_days",
|
||||
"exempted_from_income_tax",
|
||||
@@ -235,11 +236,19 @@
|
||||
"label": "Year To Date",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='earnings' && doc.additional_salary",
|
||||
"fieldname": "is_recurring_additional_salary",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Recurring Additional Salary",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-14 13:39:15.847158",
|
||||
"modified": "2021-03-14 13:39:15.847158",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Detail",
|
||||
|
||||
@@ -7,18 +7,19 @@ import datetime, math
|
||||
|
||||
from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
|
||||
from frappe import msgprint, _
|
||||
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries
|
||||
from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period
|
||||
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
|
||||
from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
|
||||
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
from six import iteritems
|
||||
|
||||
class SalarySlip(TransactionBase):
|
||||
@@ -39,6 +40,7 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
def validate(self):
|
||||
self.status = self.get_status()
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_dates()
|
||||
self.check_existing()
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
@@ -616,7 +618,8 @@ class SalarySlip(TransactionBase):
|
||||
get_salary_component_data(additional_salary.component),
|
||||
additional_salary.amount,
|
||||
component_type,
|
||||
additional_salary
|
||||
additional_salary,
|
||||
is_recurring = additional_salary.is_recurring
|
||||
)
|
||||
|
||||
def add_tax_components(self, payroll_period):
|
||||
@@ -637,7 +640,7 @@ class SalarySlip(TransactionBase):
|
||||
tax_row = get_salary_component_data(d)
|
||||
self.update_component_row(tax_row, tax_amount, "deductions")
|
||||
|
||||
def update_component_row(self, component_data, amount, component_type, additional_salary=None):
|
||||
def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0):
|
||||
component_row = None
|
||||
for d in self.get(component_type):
|
||||
if d.salary_component != component_data.salary_component:
|
||||
@@ -678,6 +681,7 @@ class SalarySlip(TransactionBase):
|
||||
component_row.set('abbr', abbr)
|
||||
|
||||
if additional_salary:
|
||||
component_row.is_recurring_additional_salary = is_recurring
|
||||
component_row.default_amount = 0
|
||||
component_row.additional_amount = amount
|
||||
component_row.additional_salary = additional_salary.name
|
||||
@@ -711,6 +715,7 @@ class SalarySlip(TransactionBase):
|
||||
# get remaining numbers of sub-period (period for which one salary is processed)
|
||||
remaining_sub_periods = get_period_factor(self.employee,
|
||||
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
|
||||
|
||||
# get taxable_earnings, paid_taxes for previous period
|
||||
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date,
|
||||
self.start_date, tax_slab.allow_tax_exemption)
|
||||
@@ -870,8 +875,16 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
if earning.is_tax_applicable:
|
||||
if additional_amount:
|
||||
taxable_earnings += (amount - additional_amount)
|
||||
additional_income += additional_amount
|
||||
if not earning.is_recurring_additional_salary:
|
||||
taxable_earnings += (amount - additional_amount)
|
||||
additional_income += additional_amount
|
||||
else:
|
||||
to_date = frappe.db.get_value("Additional Salary", earning.additional_salary, 'to_date')
|
||||
period = (getdate(to_date).month - getdate(self.start_date).month) + 1
|
||||
if period > 0:
|
||||
taxable_earnings += (amount - additional_amount) * period
|
||||
additional_income += additional_amount * period
|
||||
|
||||
if earning.deduct_full_tax_on_selected_payroll_date:
|
||||
additional_income_with_full_tax += additional_amount
|
||||
continue
|
||||
@@ -1091,6 +1104,7 @@ class SalarySlip(TransactionBase):
|
||||
"applicant": self.employee,
|
||||
"docstatus": 1,
|
||||
"repay_from_salary": 1,
|
||||
"company": self.company
|
||||
})
|
||||
|
||||
def make_loan_repayment_entry(self):
|
||||
|
||||
@@ -482,14 +482,19 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
|
||||
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
|
||||
|
||||
|
||||
employee = frappe.db.get_value("Employee", {"user_id": user})
|
||||
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
|
||||
employee = frappe.db.get_value("Employee",
|
||||
{
|
||||
"user_id": user
|
||||
},
|
||||
["name", "company", "employee_name"],
|
||||
as_dict=True)
|
||||
|
||||
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee.name, company=employee.company)
|
||||
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
|
||||
|
||||
if not salary_slip_name:
|
||||
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee)
|
||||
salary_slip.employee_name = frappe.get_value("Employee",
|
||||
{"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
|
||||
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee.name)
|
||||
salary_slip.employee_name = employee.employee_name
|
||||
salary_slip.payroll_frequency = payroll_frequency
|
||||
salary_slip.posting_date = nowdate()
|
||||
salary_slip.insert()
|
||||
|
||||
@@ -119,26 +119,25 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None,
|
||||
if test_tax:
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
|
||||
|
||||
if not frappe.db.exists('Salary Structure', salary_structure):
|
||||
details = {
|
||||
"doctype": "Salary Structure",
|
||||
"name": salary_structure,
|
||||
"company": company or erpnext.get_default_company(),
|
||||
"earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"payroll_frequency": payroll_frequency,
|
||||
"payment_account": get_random("Account", filters={'account_currency': currency}),
|
||||
"currency": currency
|
||||
}
|
||||
if other_details and isinstance(other_details, dict):
|
||||
details.update(other_details)
|
||||
salary_structure_doc = frappe.get_doc(details)
|
||||
salary_structure_doc.insert()
|
||||
if not dont_submit:
|
||||
salary_structure_doc.submit()
|
||||
if frappe.db.exists("Salary Structure", salary_structure):
|
||||
frappe.db.delete("Salary Structure", salary_structure)
|
||||
|
||||
else:
|
||||
salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure)
|
||||
details = {
|
||||
"doctype": "Salary Structure",
|
||||
"name": salary_structure,
|
||||
"company": company or erpnext.get_default_company(),
|
||||
"earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"payroll_frequency": payroll_frequency,
|
||||
"payment_account": get_random("Account", filters={'account_currency': currency}),
|
||||
"currency": currency
|
||||
}
|
||||
if other_details and isinstance(other_details, dict):
|
||||
details.update(other_details)
|
||||
salary_structure_doc = frappe.get_doc(details)
|
||||
salary_structure_doc.insert()
|
||||
if not dont_submit:
|
||||
salary_structure_doc.submit()
|
||||
|
||||
filters = {'employee':employee, 'docstatus': 1}
|
||||
if not from_date and payroll_period:
|
||||
|
||||
@@ -101,7 +101,7 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None):
|
||||
return html
|
||||
|
||||
def set_item_group_filters(field_filters):
|
||||
if 'item_group' in field_filters:
|
||||
if field_filters is not None and 'item_group' in field_filters:
|
||||
field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]
|
||||
|
||||
|
||||
|
||||
@@ -15,12 +15,15 @@ from erpnext.manufacturing.doctype.workstation.workstation import (check_if_with
|
||||
WorkstationHolidayError)
|
||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
class OverWorkLoggedError(frappe.ValidationError): pass
|
||||
|
||||
class Timesheet(Document):
|
||||
def validate(self):
|
||||
if self.employee:
|
||||
validate_active_employee(self.employee)
|
||||
self.set_employee_name()
|
||||
self.set_status()
|
||||
self.validate_dates()
|
||||
|
||||
16
erpnext/public/js/contact.js
Normal file
16
erpnext/public/js/contact.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
frappe.ui.form.on("Contact", {
|
||||
refresh(frm) {
|
||||
frm.set_query('link_doctype', "links", function() {
|
||||
return {
|
||||
query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes",
|
||||
filters: {
|
||||
fieldtype: ["in", ["HTML", "Text Editor"]],
|
||||
fieldname: ["in", ["contact_html", "company_description"]],
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.refresh_field("links");
|
||||
}
|
||||
});
|
||||
@@ -65,7 +65,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
this.frm.refresh_fields();
|
||||
},
|
||||
|
||||
calculate_discount_amount: function(){
|
||||
calculate_discount_amount: function() {
|
||||
if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) {
|
||||
this.calculate_item_values();
|
||||
this.calculate_net_total();
|
||||
@@ -75,18 +75,15 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
},
|
||||
|
||||
_calculate_taxes_and_totals: function() {
|
||||
frappe.run_serially([
|
||||
() => this.validate_conversion_rate(),
|
||||
() => this.calculate_item_values(),
|
||||
() => this.update_item_tax_map(),
|
||||
() => this.initialize_taxes(),
|
||||
() => this.determine_exclusive_rate(),
|
||||
() => this.calculate_net_total(),
|
||||
() => this.calculate_taxes(),
|
||||
() => this.manipulate_grand_total_for_inclusive_tax(),
|
||||
() => this.calculate_totals(),
|
||||
() => this._cleanup()
|
||||
]);
|
||||
this.validate_conversion_rate();
|
||||
this.calculate_item_values();
|
||||
this.initialize_taxes();
|
||||
this.determine_exclusive_rate();
|
||||
this.calculate_net_total();
|
||||
this.calculate_taxes();
|
||||
this.manipulate_grand_total_for_inclusive_tax();
|
||||
this.calculate_totals();
|
||||
this._cleanup();
|
||||
},
|
||||
|
||||
validate_conversion_rate: function() {
|
||||
@@ -107,7 +104,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
},
|
||||
|
||||
calculate_item_values: function() {
|
||||
var me = this;
|
||||
let me = this;
|
||||
if (!this.discount_amount_applied) {
|
||||
$.each(this.frm.doc["items"] || [], function(i, item) {
|
||||
frappe.model.round_floats_in(item);
|
||||
@@ -268,46 +265,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
|
||||
},
|
||||
|
||||
update_item_tax_map: function() {
|
||||
let me = this;
|
||||
let item_codes = [];
|
||||
let item_rates = {};
|
||||
let item_tax_templates = {};
|
||||
|
||||
$.each(this.frm.doc.items || [], function(i, item) {
|
||||
if (item.item_code) {
|
||||
// Use combination of name and item code in case same item is added multiple times
|
||||
item_codes.push([item.item_code, item.name]);
|
||||
item_rates[item.name] = item.net_rate;
|
||||
item_tax_templates[item.name] = item.item_tax_template;
|
||||
}
|
||||
});
|
||||
|
||||
if (item_codes.length) {
|
||||
return this.frm.call({
|
||||
method: "erpnext.stock.get_item_details.get_item_tax_info",
|
||||
args: {
|
||||
company: me.frm.doc.company,
|
||||
tax_category: cstr(me.frm.doc.tax_category),
|
||||
item_codes: item_codes,
|
||||
item_rates: item_rates,
|
||||
item_tax_templates: item_tax_templates
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
$.each(me.frm.doc.items || [], function(i, item) {
|
||||
if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
|
||||
item.item_tax_template = r.message[item.name].item_tax_template;
|
||||
item.item_tax_rate = r.message[item.name].item_tax_rate;
|
||||
me.add_taxes_from_item_tax_template(item.item_tax_rate);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
add_taxes_from_item_tax_template: function(item_tax_map) {
|
||||
let me = this;
|
||||
|
||||
@@ -632,8 +589,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail);
|
||||
});
|
||||
}
|
||||
|
||||
this.frm.refresh_fields();
|
||||
},
|
||||
|
||||
set_discount_amount: function() {
|
||||
|
||||
@@ -826,9 +826,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
|
||||
frappe.run_serially([
|
||||
() => me.frm.script_manager.trigger("currency"),
|
||||
() => me.update_item_tax_map(),
|
||||
() => me.apply_default_taxes(),
|
||||
() => me.apply_pricing_rule(),
|
||||
() => me.calculate_taxes_and_totals()
|
||||
() => me.apply_pricing_rule()
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1787,6 +1787,46 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
]);
|
||||
},
|
||||
|
||||
update_item_tax_map: function() {
|
||||
let me = this;
|
||||
let item_codes = [];
|
||||
let item_rates = {};
|
||||
let item_tax_templates = {};
|
||||
|
||||
$.each(this.frm.doc.items || [], function(i, item) {
|
||||
if (item.item_code) {
|
||||
// Use combination of name and item code in case same item is added multiple times
|
||||
item_codes.push([item.item_code, item.name]);
|
||||
item_rates[item.name] = item.net_rate;
|
||||
item_tax_templates[item.name] = item.item_tax_template;
|
||||
}
|
||||
});
|
||||
|
||||
if (item_codes.length) {
|
||||
return this.frm.call({
|
||||
method: "erpnext.stock.get_item_details.get_item_tax_info",
|
||||
args: {
|
||||
company: me.frm.doc.company,
|
||||
tax_category: cstr(me.frm.doc.tax_category),
|
||||
item_codes: item_codes,
|
||||
item_rates: item_rates,
|
||||
item_tax_templates: item_tax_templates
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
$.each(me.frm.doc.items || [], function(i, item) {
|
||||
if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
|
||||
item.item_tax_template = r.message[item.name].item_tax_template;
|
||||
item.item_tax_rate = r.message[item.name].item_tax_rate;
|
||||
me.add_taxes_from_item_tax_template(item.item_tax_rate);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
item_tax_template: function(doc, cdt, cdn) {
|
||||
var me = this;
|
||||
if(me.frm.updating_party_details) return;
|
||||
|
||||
@@ -54,7 +54,7 @@ frappe.help.help_links["permission-manager"] = [
|
||||
|
||||
frappe.help.help_links["Form/System Settings"] = [
|
||||
{
|
||||
label: "Naming Series",
|
||||
label: "System Settings",
|
||||
url: docsUrl + "user/manual/en/setting-up/settings/system-settings",
|
||||
},
|
||||
];
|
||||
@@ -206,7 +206,7 @@ frappe.help.help_links["Form/PayPal Settings"] = [
|
||||
label: "PayPal Settings",
|
||||
url:
|
||||
docsUrl +
|
||||
"user/manual/en/setting-up/integrations/paypal-integration",
|
||||
"user/manual/en/erpnext_integration/paypal-integration",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -215,14 +215,14 @@ frappe.help.help_links["Form/Razorpay Settings"] = [
|
||||
label: "Razorpay Settings",
|
||||
url:
|
||||
docsUrl +
|
||||
"user/manual/en/setting-up/integrations/razorpay-integration",
|
||||
"user/manual/en/erpnext_integration/razorpay-integration",
|
||||
},
|
||||
];
|
||||
|
||||
frappe.help.help_links["Form/Dropbox Settings"] = [
|
||||
{
|
||||
label: "Dropbox Settings",
|
||||
url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup",
|
||||
url: docsUrl + "user/manual/en/erpnext_integration/dropbox-backup",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -230,7 +230,7 @@ frappe.help.help_links["Form/LDAP Settings"] = [
|
||||
{
|
||||
label: "LDAP Settings",
|
||||
url:
|
||||
docsUrl + "user/manual/en/setting-up/integrations/ldap-integration",
|
||||
docsUrl + "user/manual/en/erpnext_integration/ldap-integration",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -239,7 +239,7 @@ frappe.help.help_links["Form/Stripe Settings"] = [
|
||||
label: "Stripe Settings",
|
||||
url:
|
||||
docsUrl +
|
||||
"user/manual/en/setting-up/integrations/stripe-integration",
|
||||
"user/manual/en/erpnext_integration/stripe-integration",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -991,7 +991,7 @@ frappe.help.help_links["Form/BOM"] = [
|
||||
label: "Nested BOM Structure",
|
||||
url:
|
||||
docsUrl +
|
||||
"user/manual/en/manufacturing/articles/nested-bom-structure",
|
||||
"user/manual/en/manufacturing/articles/managing-multi-level-bom",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
frappe.provide('frappe.ui.form');
|
||||
|
||||
frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
|
||||
init: function(doctype, after_insert) {
|
||||
init: function(doctype, after_insert, init_callback, doc, force) {
|
||||
this._super(doctype, after_insert, init_callback, doc, force);
|
||||
this.skip_redirect_on_error = true;
|
||||
this._super(doctype, after_insert);
|
||||
},
|
||||
|
||||
render_dialog: function() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
frappe.ui.form.on('E Invoice Settings', {
|
||||
refresh(frm) {
|
||||
const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
|
||||
const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing';
|
||||
frm.dashboard.set_headline(
|
||||
__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
|
||||
);
|
||||
|
||||
@@ -214,9 +214,8 @@ class GSTR3BReport(Document):
|
||||
|
||||
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))
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
|
||||
self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
|
||||
|
||||
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
|
||||
self.is_nil_exempt.append(d.item_code)
|
||||
@@ -281,9 +280,15 @@ class GSTR3BReport(Document):
|
||||
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"):
|
||||
if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \
|
||||
== "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas":
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||
else:
|
||||
for item in items.keys():
|
||||
if item in self.is_nil_exempt + self.is_non_gst and \
|
||||
item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []):
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, [])
|
||||
self.items_based_on_tax_rate[invoice][0].append(item)
|
||||
|
||||
def set_outward_taxable_supplies(self):
|
||||
inter_state_supply_details = {}
|
||||
@@ -322,6 +327,9 @@ class GSTR3BReport(Document):
|
||||
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)
|
||||
|
||||
if self.invoice_cess.get(inv):
|
||||
self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2)
|
||||
|
||||
self.set_inter_state_supply(inter_state_supply_details)
|
||||
|
||||
def set_supplies_liable_to_reverse_charge(self):
|
||||
|
||||
@@ -641,7 +641,6 @@ def make_custom_fields(update=True):
|
||||
'label': 'Export Type',
|
||||
'fieldtype': 'Select',
|
||||
'insert_after': 'gst_category',
|
||||
'default': 'Without Payment of Tax',
|
||||
'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
|
||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||
}
|
||||
@@ -660,7 +659,6 @@ def make_custom_fields(update=True):
|
||||
'label': 'Export Type',
|
||||
'fieldtype': 'Select',
|
||||
'insert_after': 'gst_category',
|
||||
'default': 'Without Payment of Tax',
|
||||
'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
|
||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||
}
|
||||
|
||||
@@ -217,9 +217,8 @@ class Gstr1Report(object):
|
||||
|
||||
for d in items:
|
||||
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 items
|
||||
if i.item_code == d.item_code and i.parent == d.parent))
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
|
||||
self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
|
||||
|
||||
item_tax_rate = {}
|
||||
|
||||
@@ -287,7 +286,8 @@ class Gstr1Report(object):
|
||||
# Build itemised tax for export invoices where tax table is blank
|
||||
for invoice, items in iteritems(self.invoice_items):
|
||||
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
|
||||
and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
|
||||
and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
|
||||
and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||
|
||||
def get_columns(self):
|
||||
|
||||
@@ -367,15 +367,16 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
`<div class="add-discount-field"></div>`
|
||||
);
|
||||
const me = this;
|
||||
const frm = me.events.get_frm();
|
||||
let discount = frm.doc.additional_discount_percentage;
|
||||
|
||||
this.discount_field = frappe.ui.form.make_control({
|
||||
df: {
|
||||
label: __('Discount'),
|
||||
fieldtype: 'Data',
|
||||
placeholder: __('Enter discount percentage.'),
|
||||
placeholder: ( discount ? discount + '%' : __('Enter discount percentage.') ),
|
||||
input_class: 'input-xs',
|
||||
onchange: function() {
|
||||
const frm = me.events.get_frm();
|
||||
if (flt(this.value) != 0) {
|
||||
frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value));
|
||||
me.hide_discount_control(this.value);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user