Merge branch 'version-13-hotfix' into 'version-13-pre-release-13-8'
This commit is contained in:
commit
60a0b7fe77
@ -98,8 +98,6 @@ rules:
|
|||||||
languages: [python]
|
languages: [python]
|
||||||
severity: WARNING
|
severity: WARNING
|
||||||
paths:
|
paths:
|
||||||
exclude:
|
|
||||||
- test_*.py
|
|
||||||
include:
|
include:
|
||||||
- "*/**/doctype/*"
|
- "*/**/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().
|
dynamic content. Avoid it or use safe_eval().
|
||||||
languages: [python]
|
languages: [python]
|
||||||
severity: ERROR
|
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
|
name: Semgrep
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request: { }
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- version-13-hotfix
|
|
||||||
- version-13-pre-release
|
|
||||||
jobs:
|
jobs:
|
||||||
semgrep:
|
semgrep:
|
||||||
name: Frappe Linter
|
name: Frappe Linter
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup python3
|
- uses: returntocorp/semgrep-action@v1
|
||||||
uses: actions/setup-python@v2
|
env:
|
||||||
with:
|
SEMGREP_TIMEOUT: 120
|
||||||
python-version: 3.8
|
with:
|
||||||
|
config: >-
|
||||||
- name: Setup semgrep
|
r/python.lang.correctness
|
||||||
run: |
|
.github/helper/semgrep_rules
|
||||||
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
|
|
||||||
|
43
CODEOWNERS
43
CODEOWNERS
@ -3,16 +3,33 @@
|
|||||||
# These owners will be the default owners for everything in
|
# These owners will be the default owners for everything in
|
||||||
# the repo. Unless a later match takes precedence,
|
# the repo. Unless a later match takes precedence,
|
||||||
|
|
||||||
manufacturing/ @rohitwaghchaure @marination
|
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007
|
||||||
accounts/ @deepeshgarg007 @nextchamp-saqib
|
erpnext/assets/ @nextchamp-saqib @deepeshgarg007
|
||||||
loan_management/ @deepeshgarg007 @rohitwaghchaure
|
erpnext/erpnext_integrations/ @nextchamp-saqib
|
||||||
pos* @nextchamp-saqib @rohitwaghchaure
|
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
||||||
assets/ @nextchamp-saqib @deepeshgarg007
|
erpnext/regional @nextchamp-saqib @deepeshgarg007
|
||||||
stock/ @marination @rohitwaghchaure
|
erpnext/selling @nextchamp-saqib @deepeshgarg007
|
||||||
buying/ @marination @deepeshgarg007
|
erpnext/support/ @nextchamp-saqib @deepeshgarg007
|
||||||
hr/ @Anurag810 @rohitwaghchaure
|
pos* @nextchamp-saqib
|
||||||
projects/ @hrwX @nextchamp-saqib
|
|
||||||
support/ @hrwX @marination
|
erpnext/buying/ @marination @rohitwaghchaure @ankush
|
||||||
healthcare/ @ruchamahabal @marination
|
erpnext/e_commerce/ @marination
|
||||||
erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib
|
erpnext/maintenance/ @marination @rohitwaghchaure
|
||||||
requirements.txt @gavindsouza
|
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
|
||||||
|
@ -391,5 +391,5 @@ def set_default_accounts(company):
|
|||||||
})
|
})
|
||||||
|
|
||||||
company.save()
|
company.save()
|
||||||
install_country_fixtures(company.name)
|
install_country_fixtures(company.name, company.country)
|
||||||
company.create_default_tax_template()
|
company.create_default_tax_template()
|
||||||
|
@ -183,6 +183,13 @@ class PaymentEntry(AccountsController):
|
|||||||
d.reference_name, self.party_account_currency)
|
d.reference_name, self.party_account_currency)
|
||||||
|
|
||||||
for field, value in iteritems(ref_details):
|
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:
|
if field == 'exchange_rate' or not d.get(field) or force:
|
||||||
d.db_set(field, value)
|
d.db_set(field, value)
|
||||||
|
|
||||||
@ -685,8 +692,8 @@ class PaymentEntry(AccountsController):
|
|||||||
gl_entries.append(gle)
|
gl_entries.append(gle)
|
||||||
|
|
||||||
if self.unallocated_amount:
|
if self.unallocated_amount:
|
||||||
base_unallocated_amount = self.unallocated_amount * \
|
exchange_rate = self.get_exchange_rate()
|
||||||
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
|
base_unallocated_amount = (self.unallocated_amount * exchange_rate)
|
||||||
|
|
||||||
gle = party_gl_dict.copy()
|
gle = party_gl_dict.copy()
|
||||||
|
|
||||||
@ -835,9 +842,16 @@ class PaymentEntry(AccountsController):
|
|||||||
if account_details:
|
if account_details:
|
||||||
row.update(account_details)
|
row.update(account_details)
|
||||||
|
|
||||||
|
if not row.get('amount'):
|
||||||
|
# if no difference amount
|
||||||
|
return
|
||||||
|
|
||||||
self.append('deductions', row)
|
self.append('deductions', row)
|
||||||
self.set_unallocated_amount()
|
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):
|
def initialize_taxes(self):
|
||||||
for tax in self.get("taxes"):
|
for tax in self.get("taxes"):
|
||||||
validate_taxes_and_charges(tax)
|
validate_taxes_and_charges(tax)
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
"total_amount",
|
"total_amount",
|
||||||
"outstanding_amount",
|
"outstanding_amount",
|
||||||
"allocated_amount",
|
"allocated_amount",
|
||||||
"exchange_rate"
|
"exchange_rate",
|
||||||
|
"exchange_gain_loss"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -90,12 +91,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Payment Term",
|
"label": "Payment Term",
|
||||||
"options": "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,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-10 11:25:47.144392",
|
"modified": "2021-04-21 13:30:11.605388",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry Reference",
|
"name": "Payment Entry Reference",
|
||||||
|
@ -451,6 +451,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.get_asset_gl_entry(gl_entries)
|
self.get_asset_gl_entry(gl_entries)
|
||||||
|
|
||||||
self.make_tax_gl_entries(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.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
self.allocate_advance_taxes(gl_entries)
|
self.allocate_advance_taxes(gl_entries)
|
||||||
|
@ -953,6 +953,120 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||||
acc_settings.save()
|
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", -40000.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", -38000.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):
|
def test_purchase_invoice_advance_taxes(self):
|
||||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
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
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
@ -1,235 +1,127 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-03-08 15:36:46",
|
"creation": "2013-03-08 15:36:46",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"editable_grid": 1,
|
"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": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reference_type",
|
"fieldname": "reference_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Reference Type",
|
"label": "Reference Type",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "journal_voucher",
|
"oldfieldname": "journal_voucher",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "DocType",
|
"options": "DocType",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "180px",
|
"print_width": "180px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "180px"
|
"width": "180px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "reference_name",
|
"fieldname": "reference_name",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Reference Name",
|
"label": "Reference Name",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "reference_type",
|
"options": "reference_type",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "remarks",
|
"oldfieldname": "remarks",
|
||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "150px",
|
"print_width": "150px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reference_row",
|
"fieldname": "reference_row",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Reference Row",
|
"label": "Reference Row",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "jv_detail_no",
|
"oldfieldname": "jv_detail_no",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "80px",
|
"print_width": "80px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "80px"
|
"width": "80px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "col_break1",
|
"fieldname": "col_break1",
|
||||||
"fieldtype": "Column Break",
|
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "advance_amount",
|
"fieldname": "advance_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Advance Amount",
|
"label": "Advance Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "advance_amount",
|
"oldfieldname": "advance_amount",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "party_account_currency",
|
"options": "party_account_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "allocated_amount",
|
"fieldname": "allocated_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Allocated Amount",
|
"label": "Allocated Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "allocated_amount",
|
"oldfieldname": "allocated_amount",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "party_account_currency",
|
"options": "party_account_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"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,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"menu_index": 0,
|
"modified": "2021-04-20 16:26:53.820530",
|
||||||
"modified": "2016-08-26 02:30:54.407138",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Advance",
|
"name": "Purchase Invoice Advance",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC"
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -840,6 +840,7 @@ class SalesInvoice(SellingController):
|
|||||||
self.make_customer_gl_entry(gl_entries)
|
self.make_customer_gl_entry(gl_entries)
|
||||||
|
|
||||||
self.make_tax_gl_entries(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.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
self.allocate_advance_taxes(gl_entries)
|
self.allocate_advance_taxes(gl_entries)
|
||||||
|
@ -1,235 +1,128 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-02-22 01:27:41",
|
"creation": "2013-02-22 01:27:41",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"editable_grid": 1,
|
"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": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reference_type",
|
"fieldname": "reference_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Reference Type",
|
"label": "Reference Type",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "journal_voucher",
|
"oldfieldname": "journal_voucher",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "DocType",
|
"options": "DocType",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "250px",
|
"print_width": "250px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "250px"
|
"width": "250px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "reference_name",
|
"fieldname": "reference_name",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Reference Name",
|
"label": "Reference Name",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "reference_type",
|
"options": "reference_type",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "remarks",
|
"oldfieldname": "remarks",
|
||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "150px",
|
"print_width": "150px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reference_row",
|
"fieldname": "reference_row",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Reference Row",
|
"label": "Reference Row",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "jv_detail_no",
|
"oldfieldname": "jv_detail_no",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "120px",
|
"print_width": "120px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "120px"
|
"width": "120px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "col_break1",
|
"fieldname": "col_break1",
|
||||||
"fieldtype": "Column Break",
|
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "advance_amount",
|
"fieldname": "advance_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Advance amount",
|
"label": "Advance amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "advance_amount",
|
"oldfieldname": "advance_amount",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "party_account_currency",
|
"options": "party_account_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "120px",
|
"print_width": "120px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "120px"
|
"width": "120px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "allocated_amount",
|
"fieldname": "allocated_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Allocated amount",
|
"label": "Allocated amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "allocated_amount",
|
"oldfieldname": "allocated_amount",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "party_account_currency",
|
"options": "party_account_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "120px",
|
"print_width": "120px",
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"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,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"menu_index": 0,
|
"modified": "2021-06-04 20:25:49.832052",
|
||||||
"modified": "2016-08-26 02:36:10.718057",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Advance",
|
"name": "Sales Invoice Advance",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC"
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -99,7 +99,6 @@ class ReceivablePayableReport(object):
|
|||||||
voucher_no = gle.voucher_no,
|
voucher_no = gle.voucher_no,
|
||||||
party = gle.party,
|
party = gle.party,
|
||||||
posting_date = gle.posting_date,
|
posting_date = gle.posting_date,
|
||||||
remarks = gle.remarks,
|
|
||||||
account_currency = gle.account_currency,
|
account_currency = gle.account_currency,
|
||||||
invoiced = 0.0,
|
invoiced = 0.0,
|
||||||
paid = 0.0,
|
paid = 0.0,
|
||||||
@ -579,7 +578,7 @@ class ReceivablePayableReport(object):
|
|||||||
self.gl_entries = frappe.db.sql("""
|
self.gl_entries = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
|
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
|
from
|
||||||
`tabGL Entry`
|
`tabGL Entry`
|
||||||
where
|
where
|
||||||
@ -792,8 +791,6 @@ class ReceivablePayableReport(object):
|
|||||||
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
|
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
|
||||||
options='Supplier Group')
|
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):
|
def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
|
||||||
if not fieldname: fieldname = scrub(label)
|
if not fieldname: fieldname = scrub(label)
|
||||||
if fieldtype=='Currency': options='currency'
|
if fieldtype=='Currency': options='currency'
|
||||||
|
@ -55,9 +55,11 @@ def validate_filters(filters, account_details):
|
|||||||
if not account_details.get(account):
|
if not account_details.get(account):
|
||||||
frappe.throw(_("Account {0} does not exists").format(account))
|
frappe.throw(_("Account {0} does not exists").format(account))
|
||||||
|
|
||||||
if (filters.get("account") and filters.get("group_by") == _('Group by Account')
|
if (filters.get("account") and filters.get("group_by") == _('Group by Account')):
|
||||||
and account_details[filters.account].is_group == 0):
|
filters.account = frappe.parse_json(filters.get('account'))
|
||||||
frappe.throw(_("Can not filter based on Account, if grouped by 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")
|
if (filters.get("voucher_no")
|
||||||
and filters.get("group_by") in [_('Group by Voucher')]):
|
and filters.get("group_by") in [_('Group by Voucher')]):
|
||||||
|
@ -241,6 +241,7 @@ class GrossProfitGenerator(object):
|
|||||||
sle.voucher_detail_no == row.item_row:
|
sle.voucher_detail_no == row.item_row:
|
||||||
previous_stock_value = len(my_sle) > i+1 and \
|
previous_stock_value = len(my_sle) > i+1 and \
|
||||||
flt(my_sle[i+1].stock_value) or 0.0
|
flt(my_sle[i+1].stock_value) or 0.0
|
||||||
|
|
||||||
if previous_stock_value:
|
if previous_stock_value:
|
||||||
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
||||||
else:
|
else:
|
||||||
@ -335,7 +336,7 @@ class GrossProfitGenerator(object):
|
|||||||
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
||||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where company=%(company)s
|
where company=%(company)s and is_cancelled = 0
|
||||||
order by
|
order by
|
||||||
item_code desc, warehouse desc, posting_date desc,
|
item_code desc, warehouse desc, posting_date desc,
|
||||||
posting_time desc, creation desc""", self.filters, as_dict=True)
|
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,
|
"total_amount": d.grand_total,
|
||||||
"outstanding_amount": d.outstanding_amount,
|
"outstanding_amount": d.outstanding_amount,
|
||||||
"allocated_amount": d.allocated_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:
|
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()
|
payment_entry.set_amounts()
|
||||||
|
|
||||||
if d.difference_amount and d.difference_account:
|
if d.difference_amount and d.difference_account:
|
||||||
payment_entry.set_gain_or_loss(account_details={
|
account_details = {
|
||||||
'account': d.difference_account,
|
'account': d.difference_account,
|
||||||
'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company',
|
'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company',
|
||||||
payment_entry.company, "cost_center"),
|
payment_entry.company, "cost_center")
|
||||||
'amount': d.difference_amount
|
}
|
||||||
})
|
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:
|
if not do_not_save:
|
||||||
payment_entry.save(ignore_permissions=True)
|
payment_entry.save(ignore_permissions=True)
|
||||||
|
@ -124,6 +124,8 @@ class AccountsController(TransactionBase):
|
|||||||
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
|
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
|
||||||
self.set_advances()
|
self.set_advances()
|
||||||
|
|
||||||
|
self.set_advance_gain_or_loss()
|
||||||
|
|
||||||
if self.is_return:
|
if self.is_return:
|
||||||
self.validate_qty()
|
self.validate_qty()
|
||||||
else:
|
else:
|
||||||
@ -584,15 +586,18 @@ class AccountsController(TransactionBase):
|
|||||||
allocated_amount = min(amount - advance_allocated, d.amount)
|
allocated_amount = min(amount - advance_allocated, d.amount)
|
||||||
advance_allocated += flt(allocated_amount)
|
advance_allocated += flt(allocated_amount)
|
||||||
|
|
||||||
self.append("advances", {
|
advance_row = {
|
||||||
"doctype": self.doctype + " Advance",
|
"doctype": self.doctype + " Advance",
|
||||||
"reference_type": d.reference_type,
|
"reference_type": d.reference_type,
|
||||||
"reference_name": d.reference_name,
|
"reference_name": d.reference_name,
|
||||||
"reference_row": d.reference_row,
|
"reference_row": d.reference_row,
|
||||||
"remarks": d.remarks,
|
"remarks": d.remarks,
|
||||||
"advance_amount": flt(d.amount),
|
"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):
|
def get_advance_entries(self, include_unallocated=True):
|
||||||
if self.doctype == "Sales Invoice":
|
if self.doctype == "Sales Invoice":
|
||||||
@ -650,6 +655,66 @@ class AccountsController(TransactionBase):
|
|||||||
"Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.")
|
"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))
|
.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:
|
||||||
|
party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
|
||||||
|
party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
|
||||||
|
party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
|
||||||
|
|
||||||
|
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
|
||||||
|
account_currency = get_account_currency(gain_loss_account)
|
||||||
|
if account_currency != self.company_currency:
|
||||||
|
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
|
||||||
|
|
||||||
|
# for purchase
|
||||||
|
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
|
||||||
|
# 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):
|
def update_against_document_in_jv(self):
|
||||||
"""
|
"""
|
||||||
Links invoice and advance voucher:
|
Links invoice and advance voucher:
|
||||||
@ -690,7 +755,9 @@ class AccountsController(TransactionBase):
|
|||||||
if self.party_account_currency != self.company_currency else 1),
|
if self.party_account_currency != self.company_currency else 1),
|
||||||
'grand_total': (self.base_grand_total
|
'grand_total': (self.base_grand_total
|
||||||
if self.party_account_currency == self.company_currency else self.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)
|
lst.append(args)
|
||||||
|
|
||||||
@ -1289,6 +1356,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"
|
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"
|
currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
|
||||||
payment_type = "Receive" if party_type == "Customer" else "Pay"
|
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 = [], []
|
payment_entries_against_order, unallocated_payment_entries = [], []
|
||||||
limit_cond = "limit %s" % limit if limit else ""
|
limit_cond = "limit %s" % limit if limit else ""
|
||||||
|
|
||||||
@ -1305,27 +1374,28 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
|
|||||||
"Payment Entry" as reference_type, t1.name as reference_name,
|
"Payment Entry" as reference_type, t1.name as reference_name,
|
||||||
t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
|
t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
|
||||||
t2.reference_name as against_order, t1.posting_date,
|
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
|
from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
|
||||||
where
|
where
|
||||||
t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
|
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 t1.party_type = %s and t1.party = %s and t1.docstatus = 1
|
||||||
and t2.reference_doctype = %s {2}
|
and t2.reference_doctype = %s {2}
|
||||||
order by t1.posting_date {3}
|
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,
|
[party_account, payment_type, party_type, party,
|
||||||
order_doctype] + order_list, as_dict=1)
|
order_doctype] + order_list, as_dict=1)
|
||||||
|
|
||||||
if include_unallocated:
|
if include_unallocated:
|
||||||
unallocated_payment_entries = frappe.db.sql("""
|
unallocated_payment_entries = frappe.db.sql("""
|
||||||
select "Payment Entry" as reference_type, name as reference_name,
|
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`
|
from `tabPayment Entry`
|
||||||
where
|
where
|
||||||
{0} = %s and party_type = %s and party = %s and payment_type = %s
|
{0} = %s and party_type = %s and party = %s and payment_type = %s
|
||||||
and docstatus = 1 and unallocated_amount > 0
|
and docstatus = 1 and unallocated_amount > 0
|
||||||
order by posting_date {1}
|
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)
|
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
|
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
||||||
where
|
where
|
||||||
batch.disabled = 0
|
batch.disabled = 0
|
||||||
|
and sle.is_cancelled = 0
|
||||||
and sle.item_code = %(item_code)s
|
and sle.item_code = %(item_code)s
|
||||||
and sle.warehouse = %(warehouse)s
|
and sle.warehouse = %(warehouse)s
|
||||||
and (sle.batch_no like %(txt)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
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
|
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)
|
serial_nos = frappe.get_all("Serial No",
|
||||||
for serial_no_data in frappe.get_all("Serial No",
|
fields=["batch_no", "name", "warehouse"],
|
||||||
filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]):
|
filters={
|
||||||
if serial_no_data.batch_no != d.batch_no:
|
"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}")
|
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:
|
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")
|
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
|
||||||
|
@ -9,7 +9,7 @@ from frappe.utils import flt, getdate
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.model.document import Document
|
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):
|
class Appraisal(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -19,6 +19,7 @@ class Appraisal(Document):
|
|||||||
if not self.goals:
|
if not self.goals:
|
||||||
frappe.throw(_("Goals cannot be empty"))
|
frappe.throw(_("Goals cannot be empty"))
|
||||||
|
|
||||||
|
validate_active_employee(self.employee)
|
||||||
set_employee_name(self)
|
set_employee_name(self)
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_existing_appraisal()
|
self.validate_existing_appraisal()
|
||||||
|
@ -8,11 +8,13 @@ from frappe.utils import getdate, nowdate
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cstr, get_datetime, formatdate
|
from frappe.utils import cstr, get_datetime, formatdate
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
|
|
||||||
class Attendance(Document):
|
class Attendance(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
from erpnext.controllers.status_updater import validate_status
|
from erpnext.controllers.status_updater import validate_status
|
||||||
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
|
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_attendance_date()
|
self.validate_attendance_date()
|
||||||
self.validate_duplicate_record()
|
self.validate_duplicate_record()
|
||||||
self.validate_employee_status()
|
self.validate_employee_status()
|
||||||
|
@ -8,10 +8,11 @@ from frappe import _
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import date_diff, add_days, getdate
|
from frappe.utils import date_diff, add_days, getdate
|
||||||
from erpnext.hr.doctype.employee.employee import is_holiday
|
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):
|
class AttendanceRequest(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
validate_dates(self, self.from_date, self.to_date)
|
validate_dates(self, self.from_date, self.to_date)
|
||||||
if self.half_day:
|
if self.half_day:
|
||||||
if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date):
|
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 import _
|
||||||
from frappe.utils import date_diff, add_days, getdate, cint, format_date
|
from frappe.utils import date_diff, add_days, getdate, cint, format_date
|
||||||
from frappe.model.document import Document
|
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
|
get_holidays_for_employee, create_additional_leave_ledger_entry
|
||||||
|
|
||||||
class CompensatoryLeaveRequest(Document):
|
class CompensatoryLeaveRequest(Document):
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
validate_dates(self, self.work_from_date, self.work_end_date)
|
validate_dates(self, self.work_from_date, self.work_end_date)
|
||||||
if self.half_day:
|
if self.half_day:
|
||||||
if not self.half_day_date:
|
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 erpnext.utilities.transaction_base import delete_events
|
||||||
from frappe.utils.nestedset import NestedSet
|
from frappe.utils.nestedset import NestedSet
|
||||||
|
|
||||||
class EmployeeUserDisabledError(frappe.ValidationError): pass
|
class EmployeeUserDisabledError(frappe.ValidationError):
|
||||||
class EmployeeLeftValidationError(frappe.ValidationError): pass
|
pass
|
||||||
|
class InactiveEmployeeStatusError(frappe.ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
class Employee(NestedSet):
|
class Employee(NestedSet):
|
||||||
nsm_parent_field = 'reports_to'
|
nsm_parent_field = 'reports_to'
|
||||||
@ -196,7 +198,7 @@ class Employee(NestedSet):
|
|||||||
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
|
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
|
||||||
message += "</li></ul><br>"
|
message += "</li></ul><br>"
|
||||||
message += _("Please make sure the employees above report to another Active employee.")
|
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:
|
if not self.relieving_date:
|
||||||
throw(_("Please enter relieving date."))
|
throw(_("Please enter relieving date."))
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import frappe
|
|||||||
import erpnext
|
import erpnext
|
||||||
import unittest
|
import unittest
|
||||||
import frappe.utils
|
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')
|
test_records = frappe.get_test_records('Employee')
|
||||||
|
|
||||||
@ -45,10 +45,33 @@ class TestEmployee(unittest.TestCase):
|
|||||||
employee2_doc.save()
|
employee2_doc.save()
|
||||||
employee1_doc.reload()
|
employee1_doc.reload()
|
||||||
employee1_doc.status = 'Left'
|
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):
|
def make_employee(user, company=None, **kwargs):
|
||||||
""
|
|
||||||
if not frappe.db.get_value("User", user):
|
if not frappe.db.get_value("User", user):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "User",
|
"doctype": "User",
|
||||||
@ -80,4 +103,5 @@ def make_employee(user, company=None, **kwargs):
|
|||||||
employee.insert()
|
employee.insert()
|
||||||
return employee.name
|
return employee.name
|
||||||
else:
|
else:
|
||||||
|
frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active")
|
||||||
return frappe.get_value("Employee", {"employee_name":user}, "name")
|
return frappe.get_value("Employee", {"employee_name":user}, "name")
|
||||||
|
@ -8,6 +8,7 @@ from frappe import _
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
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):
|
class EmployeeAdvanceOverPayment(frappe.ValidationError):
|
||||||
pass
|
pass
|
||||||
@ -18,6 +19,7 @@ class EmployeeAdvance(Document):
|
|||||||
'make_payment_via_journal_entry')
|
'make_payment_via_journal_entry')
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
@ -9,9 +9,11 @@ from frappe.model.document import Document
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift
|
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):
|
class EmployeeCheckin(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_duplicate_log()
|
self.validate_duplicate_log()
|
||||||
self.fetch_shift()
|
self.fetch_shift()
|
||||||
|
|
||||||
|
@ -7,12 +7,11 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import getdate
|
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):
|
class EmployeePromotion(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if frappe.get_value("Employee", self.employee, "status") != "Active":
|
validate_active_employee(self.employee)
|
||||||
frappe.throw(_("Cannot promote Employee with status Left or Inactive"))
|
|
||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
if getdate(self.promotion_date) > getdate():
|
if getdate(self.promotion_date) > getdate():
|
||||||
|
@ -7,9 +7,11 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import get_link_to_form
|
from frappe.utils import get_link_to_form
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
|
|
||||||
class EmployeeReferral(Document):
|
class EmployeeReferral(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.referrer)
|
||||||
self.set_full_name()
|
self.set_full_name()
|
||||||
self.set_referral_bonus_payment_status()
|
self.set_referral_bonus_payment_status()
|
||||||
|
|
||||||
|
@ -10,10 +10,6 @@ from frappe.utils import getdate
|
|||||||
from erpnext.hr.utils import update_employee
|
from erpnext.hr.utils import update_employee
|
||||||
|
|
||||||
class EmployeeTransfer(Document):
|
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):
|
def before_submit(self):
|
||||||
if getdate(self.transfer_date) > getdate():
|
if getdate(self.transfer_date) > getdate():
|
||||||
frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
|
frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
|
||||||
|
@ -6,7 +6,7 @@ import frappe, erpnext
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import get_fullname, flt, cstr, get_link_to_form
|
from frappe.utils import get_fullname, flt, cstr, get_link_to_form
|
||||||
from frappe.model.document import Document
|
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.party import get_party_account
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
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')
|
'make_payment_via_journal_entry')
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_advances()
|
self.validate_advances()
|
||||||
self.validate_sanctioned_amount()
|
self.validate_sanctioned_amount()
|
||||||
self.calculate_total_amount()
|
self.calculate_total_amount()
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
|
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.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.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||||
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
|
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)
|
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
set_employee_name(self)
|
set_employee_name(self)
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_balance_leaves()
|
self.validate_balance_leaves()
|
||||||
|
@ -7,7 +7,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import getdate, nowdate, flt
|
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.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_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
|
||||||
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
|
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):
|
class LeaveEncashment(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
set_employee_name(self)
|
set_employee_name(self)
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.get_leave_details_for_encashment()
|
self.get_leave_details_for_encashment()
|
||||||
self.validate_salary_structure()
|
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 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.employee.employee import get_holiday_list_for_employee
|
||||||
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
class ShiftAssignment(Document):
|
class ShiftAssignment(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_overlapping_dates()
|
self.validate_overlapping_dates()
|
||||||
|
|
||||||
if self.end_date and self.end_date <= self.start_date:
|
if self.end_date and self.end_date <= self.start_date:
|
||||||
|
@ -7,12 +7,13 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import formatdate, getdate
|
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 OverlapError(frappe.ValidationError): pass
|
||||||
|
|
||||||
class ShiftRequest(Document):
|
class ShiftRequest(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_shift_request_overlap_dates()
|
self.validate_shift_request_overlap_dates()
|
||||||
self.validate_approver()
|
self.validate_approver()
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
|
|
||||||
class TravelRequest(Document):
|
class TravelRequest(Document):
|
||||||
pass
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
|
@ -3,13 +3,12 @@
|
|||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
import frappe
|
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 import _
|
||||||
from frappe.desk.form import assign_to
|
from frappe.desk.form import assign_to
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate,
|
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
|
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
|
Assign to the concerned person and roles as per the onboarding/separation template
|
||||||
'''
|
'''
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
# remove the task if linked before submitting the form
|
# remove the task if linked before submitting the form
|
||||||
if self.amended_from:
|
if self.amended_from:
|
||||||
for activity in self.activities:
|
for activity in self.activities:
|
||||||
@ -522,3 +522,8 @@ def share_doc_with_approver(doc, user):
|
|||||||
approver = approvers.get(doc.doctype)
|
approver = approvers.get(doc.doctype)
|
||||||
if doc_before_save.get(approver) != doc.get(approver):
|
if doc_before_save.get(approver) != doc.get(approver):
|
||||||
frappe.share.remove(doc.doctype, doc.name, doc_before_save.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 () {
|
frm.set_query("loan_type", function () {
|
||||||
return {
|
return {
|
||||||
"filters": {
|
"filters": {
|
||||||
"docstatus": 1
|
"docstatus": 1,
|
||||||
|
"company": frm.doc.company
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -14,6 +14,13 @@ frappe.ui.form.on('Loan Application', {
|
|||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frm.trigger("toggle_fields");
|
frm.trigger("toggle_fields");
|
||||||
frm.trigger("add_toolbar_buttons");
|
frm.trigger("add_toolbar_buttons");
|
||||||
|
frm.set_query('loan_type', () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
repayment_method: function(frm) {
|
repayment_method: function(frm) {
|
||||||
frm.doc.repayment_amount = frm.doc.repayment_periods = ""
|
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) {
|
if (!frm.doc.__islocal && frm.doc.docstatus<2) {
|
||||||
frm.add_custom_button(__("Update Cost"), function() {
|
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() {
|
frm.add_custom_button(__("Browse BOM"), function() {
|
||||||
frappe.route_options = {
|
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({
|
return frappe.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
method: "update_cost",
|
method: "update_cost",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
args: {
|
args: {
|
||||||
update_parent: true,
|
update_parent: true,
|
||||||
from_child_bom:false
|
save: save_doc,
|
||||||
|
from_child_bom: false
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
refresh_field("items");
|
refresh_field("items");
|
||||||
|
@ -330,7 +330,7 @@ class BOM(WebsiteGenerator):
|
|||||||
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
|
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
|
||||||
|
|
||||||
if not from_child_bom:
|
if not from_child_bom:
|
||||||
frappe.msgprint(_("Cost Updated"))
|
frappe.msgprint(_("Cost Updated"), alert=True)
|
||||||
|
|
||||||
def update_parent_cost(self):
|
def update_parent_cost(self):
|
||||||
if self.total_cost:
|
if self.total_cost:
|
||||||
@ -748,7 +748,7 @@ def get_valuation_rate(args):
|
|||||||
if valuation_rate <= 0:
|
if valuation_rate <= 0:
|
||||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||||
from `tabStock Ledger Entry`
|
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'])
|
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
|
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
|
||||||
|
@ -747,9 +747,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
|
|||||||
group by item_code, warehouse
|
group by item_code, warehouse
|
||||||
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
|
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
|
||||||
|
|
||||||
def get_warehouse_list(warehouses, warehouse_list=None):
|
def get_warehouse_list(warehouses):
|
||||||
if not warehouse_list:
|
warehouse_list = []
|
||||||
warehouse_list = []
|
|
||||||
|
|
||||||
if isinstance(warehouses, str):
|
if isinstance(warehouses, str):
|
||||||
warehouses = json.loads(warehouses)
|
warehouses = json.loads(warehouses)
|
||||||
@ -761,23 +760,19 @@ def get_warehouse_list(warehouses, warehouse_list=None):
|
|||||||
else:
|
else:
|
||||||
warehouse_list.append(row.get("warehouse"))
|
warehouse_list.append(row.get("warehouse"))
|
||||||
|
|
||||||
|
return warehouse_list
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
|
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
|
||||||
if isinstance(doc, str):
|
if isinstance(doc, str):
|
||||||
doc = frappe._dict(json.loads(doc))
|
doc = frappe._dict(json.loads(doc))
|
||||||
|
|
||||||
warehouse_list = []
|
|
||||||
if warehouses:
|
if warehouses:
|
||||||
get_warehouse_list(warehouses, warehouse_list)
|
warehouses = list(set(get_warehouse_list(warehouses)))
|
||||||
|
|
||||||
if warehouse_list:
|
|
||||||
warehouses = list(set(warehouse_list))
|
|
||||||
|
|
||||||
if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in 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"))
|
warehouses.remove(doc.get("for_warehouse"))
|
||||||
|
|
||||||
warehouse_list = None
|
|
||||||
|
|
||||||
doc['mr_items'] = []
|
doc['mr_items'] = []
|
||||||
|
|
||||||
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('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.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.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.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):
|
class TestProductionPlan(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -251,6 +251,27 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
pln.cancel()
|
pln.cancel()
|
||||||
frappe.delete_doc("Production Plan", pln.name)
|
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):
|
def create_production_plan(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"options": "Operation"
|
"options": "Operation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"description": "Time in mins",
|
"description": "Time in mins",
|
||||||
"fieldname": "time_in_mins",
|
"fieldname": "time_in_mins",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
@ -38,7 +39,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-07 18:09:18.005578",
|
"modified": "2021-07-15 16:39:41.635362",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Sub Operation",
|
"name": "Sub Operation",
|
||||||
|
@ -487,21 +487,20 @@ class WorkOrder(Document):
|
|||||||
return
|
return
|
||||||
|
|
||||||
operations = []
|
operations = []
|
||||||
if not self.use_multi_level_bom:
|
|
||||||
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
if self.use_multi_level_bom:
|
||||||
operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
|
|
||||||
else:
|
|
||||||
bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
|
bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
|
||||||
bom_traversal = list(reversed(bom_tree.level_order_traversal()))
|
bom_traversal = reversed(bom_tree.level_order_traversal())
|
||||||
bom_traversal.append(bom_tree) # add operation on top level item last
|
|
||||||
|
|
||||||
for d in bom_traversal:
|
for node in bom_traversal:
|
||||||
if d.is_bom:
|
if node.is_bom:
|
||||||
operations.extend(_get_operations(d.name, qty=d.exploded_qty))
|
operations.extend(_get_operations(node.name, qty=node.exploded_qty))
|
||||||
|
|
||||||
for correct_index, operation in enumerate(operations, start=1):
|
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
||||||
operation.idx = correct_index
|
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.set('operations', operations)
|
||||||
self.calculate_time()
|
self.calculate_time()
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"razorpay_details_section",
|
"razorpay_details_section",
|
||||||
"subscription_id",
|
"subscription_id",
|
||||||
"customer_id",
|
"customer_id",
|
||||||
"subscription_activated",
|
"subscription_status",
|
||||||
"column_break_21",
|
"column_break_21",
|
||||||
"subscription_start",
|
"subscription_start",
|
||||||
"subscription_end"
|
"subscription_end"
|
||||||
@ -151,12 +151,6 @@
|
|||||||
"fieldname": "column_break_21",
|
"fieldname": "column_break_21",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "subscription_activated",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Subscription Activated"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "subscription_start",
|
"fieldname": "subscription_start",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
@ -166,11 +160,17 @@
|
|||||||
"fieldname": "subscription_end",
|
"fieldname": "subscription_end",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Subscription End"
|
"label": "Subscription End"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "subscription_status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Subscription Status",
|
||||||
|
"options": "\nActive\nHalted"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-09 12:12:10.174647",
|
"modified": "2021-07-11 14:27:26.368039",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Non Profit",
|
"module": "Non Profit",
|
||||||
"name": "Member",
|
"name": "Member",
|
||||||
|
@ -84,7 +84,9 @@ def create_member(user_details):
|
|||||||
"email_id": user_details.email,
|
"email_id": user_details.email,
|
||||||
"pan_number": user_details.pan or None,
|
"pan_number": user_details.pan or None,
|
||||||
"membership_type": user_details.plan_id,
|
"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)
|
member.insert(ignore_permissions=True)
|
||||||
|
@ -196,11 +196,14 @@ def make_invoice(membership, member, plan, settings):
|
|||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
|
|
||||||
def get_member_based_on_subscription(subscription_id, email):
|
def get_member_based_on_subscription(subscription_id, email=None, customer_id=None):
|
||||||
members = frappe.get_all("Member", filters={
|
filters = {"subscription_id": subscription_id}
|
||||||
"subscription_id": subscription_id,
|
if email:
|
||||||
"email_id": email
|
filters.update({"email_id": email})
|
||||||
}, order_by="creation desc")
|
if customer_id:
|
||||||
|
filters.update({"customer_id": customer_id})
|
||||||
|
|
||||||
|
members = frappe.get_all("Member", filters=filters, order_by="creation desc")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return frappe.get_doc("Member", members[0]["name"])
|
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"):
|
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")
|
signature = frappe.request.headers.get("X-Razorpay-Signature")
|
||||||
|
|
||||||
settings = frappe.get_doc("Non Profit Settings")
|
settings = frappe.get_doc("Non Profit Settings")
|
||||||
@ -225,16 +226,7 @@ def verify_signature(data, endpoint="Membership"):
|
|||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def trigger_razorpay_subscription(*args, **kwargs):
|
def trigger_razorpay_subscription(*args, **kwargs):
|
||||||
data = frappe.request.get_data(as_text=True)
|
data = frappe.request.get_data(as_text=True)
|
||||||
try:
|
data = process_request_data(data)
|
||||||
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)
|
|
||||||
|
|
||||||
subscription = data.payload.get("subscription", {}).get("entity", {})
|
subscription = data.payload.get("subscription", {}).get("entity", {})
|
||||||
subscription = frappe._dict(subscription)
|
subscription = frappe._dict(subscription)
|
||||||
@ -281,7 +273,7 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
|||||||
# Update membership values
|
# Update membership values
|
||||||
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
|
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
|
||||||
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
|
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
|
||||||
member.subscription_activated = 1
|
member.subscription_status = "Active"
|
||||||
member.flags.ignore_mandatory = True
|
member.flags.ignore_mandatory = True
|
||||||
member.save()
|
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)
|
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))
|
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
|
||||||
notify_failure(log)
|
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():
|
def get_company_for_memberships():
|
||||||
|
@ -6,6 +6,7 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.non_profit.doctype.member.member import create_member
|
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
|
from frappe.utils import nowdate, add_months
|
||||||
|
|
||||||
class TestMembership(unittest.TestCase):
|
class TestMembership(unittest.TestCase):
|
||||||
@ -13,11 +14,16 @@ class TestMembership(unittest.TestCase):
|
|||||||
plan = setup_membership()
|
plan = setup_membership()
|
||||||
|
|
||||||
# make test member
|
# make test member
|
||||||
self.member_doc = create_member(frappe._dict({
|
self.member_doc = create_member(
|
||||||
'fullname': "_Test_Member",
|
frappe._dict({
|
||||||
'email': "_test_member_erpnext@example.com",
|
"fullname": "_Test_Member",
|
||||||
'plan_id': plan.name
|
"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_doc.make_customer_and_link()
|
||||||
self.member = self.member_doc.name
|
self.member = self.member_doc.name
|
||||||
|
|
||||||
@ -51,6 +57,20 @@ class TestMembership(unittest.TestCase):
|
|||||||
"to_date": add_months(nowdate(), 3),
|
"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):
|
def set_config(key, value):
|
||||||
frappe.db.set_value("Non Profit Settings", None, key, value)
|
frappe.db.set_value("Non Profit Settings", None, key, value)
|
||||||
|
|
||||||
@ -116,3 +136,27 @@ def setup_membership():
|
|||||||
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
|
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,4 @@ erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
|
|||||||
erpnext.patches.v13_0.update_job_card_details
|
erpnext.patches.v13_0.update_job_card_details
|
||||||
erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
||||||
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
||||||
|
erpnext.patches.v13_0.update_subscription_status_in_memberships
|
||||||
|
@ -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')
|
@ -7,6 +7,7 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.utils import getdate, date_diff, comma_and, formatdate
|
from frappe.utils import getdate, date_diff, comma_and, formatdate
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
|
|
||||||
class AdditionalSalary(Document):
|
class AdditionalSalary(Document):
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
@ -19,6 +20,7 @@ class AdditionalSalary(Document):
|
|||||||
self.update_employee_referral(cancel=True)
|
self.update_employee_referral(cancel=True)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_salary_structure()
|
self.validate_salary_structure()
|
||||||
self.validate_recurring_additional_salary_overlap()
|
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
|
no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
|
||||||
return amount_per_day * no_of_days
|
return amount_per_day * no_of_days
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_additional_salaries(employee, start_date, end_date, component_type):
|
def get_additional_salaries(employee, start_date, end_date, component_type):
|
||||||
additional_salary_list = frappe.db.sql("""
|
additional_salary_list = frappe.db.sql("""
|
||||||
select name, salary_component as component, type, amount,
|
select name, salary_component as component, type, amount, overwrite_salary_structure_amount as overwrite,
|
||||||
overwrite_salary_structure_amount as overwrite,
|
deduct_full_tax_on_selected_payroll_date, is_recurring
|
||||||
deduct_full_tax_on_selected_payroll_date
|
|
||||||
from `tabAdditional Salary`
|
from `tabAdditional Salary`
|
||||||
where employee=%(employee)s
|
where employee=%(employee)s
|
||||||
and docstatus = 1
|
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 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.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.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):
|
class EmployeeBenefitApplication(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_duplicate_on_payroll_period()
|
self.validate_duplicate_on_payroll_period()
|
||||||
if not self.max_benefits:
|
if not self.max_benefits:
|
||||||
self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period)
|
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.utils import flt
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits
|
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.payroll_period.payroll_period import get_payroll_period
|
||||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
||||||
|
|
||||||
class EmployeeBenefitClaim(Document):
|
class EmployeeBenefitClaim(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
max_benefits = get_max_benefits(self.employee, self.claim_date)
|
max_benefits = get_max_benefits(self.employee, self.claim_date)
|
||||||
if not max_benefits or max_benefits <= 0:
|
if not max_benefits or max_benefits <= 0:
|
||||||
frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee))
|
frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee))
|
||||||
|
@ -6,9 +6,11 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
|
|
||||||
class EmployeeIncentive(Document):
|
class EmployeeIncentive(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_salary_structure()
|
self.validate_salary_structure()
|
||||||
|
|
||||||
def validate_salary_structure(self):
|
def validate_salary_structure(self):
|
||||||
|
@ -8,11 +8,12 @@ from frappe.model.document import Document
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
from frappe.model.mapper import get_mapped_doc
|
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
|
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
|
||||||
|
|
||||||
class EmployeeTaxExemptionDeclaration(Document):
|
class EmployeeTaxExemptionDeclaration(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
validate_tax_declaration(self.declarations)
|
validate_tax_declaration(self.declarations)
|
||||||
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
|
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
|
||||||
self.set_total_declared_amount()
|
self.set_total_declared_amount()
|
||||||
|
@ -7,11 +7,12 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt
|
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
|
calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period
|
||||||
|
|
||||||
class EmployeeTaxExemptionProofSubmission(Document):
|
class EmployeeTaxExemptionProofSubmission(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
validate_active_employee(self.employee)
|
||||||
validate_tax_declaration(self.tax_exemption_proofs)
|
validate_tax_declaration(self.tax_exemption_proofs)
|
||||||
self.set_total_actual_amount()
|
self.set_total_actual_amount()
|
||||||
self.set_total_exemption_amount()
|
self.set_total_exemption_amount()
|
||||||
|
@ -7,11 +7,10 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
class RetentionBonus(Document):
|
class RetentionBonus(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if frappe.get_value('Employee', self.employee, 'status') != 'Active':
|
validate_active_employee(self.employee)
|
||||||
frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees'))
|
|
||||||
if getdate(self.bonus_payment_date) < getdate():
|
if getdate(self.bonus_payment_date) < getdate():
|
||||||
frappe.throw(_('Bonus Payment Date cannot be a past date'))
|
frappe.throw(_('Bonus Payment Date cannot be a past date'))
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"year_to_date",
|
"year_to_date",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"additional_salary",
|
"additional_salary",
|
||||||
|
"is_recurring_additional_salary",
|
||||||
"statistical_component",
|
"statistical_component",
|
||||||
"depends_on_payment_days",
|
"depends_on_payment_days",
|
||||||
"exempted_from_income_tax",
|
"exempted_from_income_tax",
|
||||||
@ -235,11 +236,19 @@
|
|||||||
"label": "Year To Date",
|
"label": "Year To Date",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
"read_only": 1
|
"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,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-14 13:39:15.847158",
|
"modified": "2021-03-14 13:39:15.847158",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Payroll",
|
"module": "Payroll",
|
||||||
"name": "Salary Detail",
|
"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.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.model.naming import make_autoname
|
||||||
|
from frappe.utils.background_jobs import enqueue
|
||||||
|
|
||||||
from frappe import msgprint, _
|
from frappe import msgprint, _
|
||||||
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates
|
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.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||||
from erpnext.utilities.transaction_base import TransactionBase
|
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.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.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_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.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.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
class SalarySlip(TransactionBase):
|
class SalarySlip(TransactionBase):
|
||||||
@ -39,6 +40,7 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.status = self.get_status()
|
self.status = self.get_status()
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.check_existing()
|
self.check_existing()
|
||||||
if not self.salary_slip_based_on_timesheet:
|
if not self.salary_slip_based_on_timesheet:
|
||||||
@ -616,7 +618,8 @@ class SalarySlip(TransactionBase):
|
|||||||
get_salary_component_data(additional_salary.component),
|
get_salary_component_data(additional_salary.component),
|
||||||
additional_salary.amount,
|
additional_salary.amount,
|
||||||
component_type,
|
component_type,
|
||||||
additional_salary
|
additional_salary,
|
||||||
|
is_recurring = additional_salary.is_recurring
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_tax_components(self, payroll_period):
|
def add_tax_components(self, payroll_period):
|
||||||
@ -637,7 +640,7 @@ class SalarySlip(TransactionBase):
|
|||||||
tax_row = get_salary_component_data(d)
|
tax_row = get_salary_component_data(d)
|
||||||
self.update_component_row(tax_row, tax_amount, "deductions")
|
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
|
component_row = None
|
||||||
for d in self.get(component_type):
|
for d in self.get(component_type):
|
||||||
if d.salary_component != component_data.salary_component:
|
if d.salary_component != component_data.salary_component:
|
||||||
@ -678,6 +681,7 @@ class SalarySlip(TransactionBase):
|
|||||||
component_row.set('abbr', abbr)
|
component_row.set('abbr', abbr)
|
||||||
|
|
||||||
if additional_salary:
|
if additional_salary:
|
||||||
|
component_row.is_recurring_additional_salary = is_recurring
|
||||||
component_row.default_amount = 0
|
component_row.default_amount = 0
|
||||||
component_row.additional_amount = amount
|
component_row.additional_amount = amount
|
||||||
component_row.additional_salary = additional_salary.name
|
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)
|
# get remaining numbers of sub-period (period for which one salary is processed)
|
||||||
remaining_sub_periods = get_period_factor(self.employee,
|
remaining_sub_periods = get_period_factor(self.employee,
|
||||||
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
|
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
|
||||||
|
|
||||||
# get taxable_earnings, paid_taxes for previous period
|
# get taxable_earnings, paid_taxes for previous period
|
||||||
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date,
|
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date,
|
||||||
self.start_date, tax_slab.allow_tax_exemption)
|
self.start_date, tax_slab.allow_tax_exemption)
|
||||||
@ -870,8 +875,16 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
if earning.is_tax_applicable:
|
if earning.is_tax_applicable:
|
||||||
if additional_amount:
|
if additional_amount:
|
||||||
taxable_earnings += (amount - additional_amount)
|
if not earning.is_recurring_additional_salary:
|
||||||
additional_income += additional_amount
|
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:
|
if earning.deduct_full_tax_on_selected_payroll_date:
|
||||||
additional_income_with_full_tax += additional_amount
|
additional_income_with_full_tax += additional_amount
|
||||||
continue
|
continue
|
||||||
@ -1091,6 +1104,7 @@ class SalarySlip(TransactionBase):
|
|||||||
"applicant": self.employee,
|
"applicant": self.employee,
|
||||||
"docstatus": 1,
|
"docstatus": 1,
|
||||||
"repay_from_salary": 1,
|
"repay_from_salary": 1,
|
||||||
|
"company": self.company
|
||||||
})
|
})
|
||||||
|
|
||||||
def make_loan_repayment_entry(self):
|
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"
|
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
|
||||||
|
|
||||||
|
|
||||||
employee = frappe.db.get_value("Employee", {"user_id": user})
|
employee = frappe.db.get_value("Employee",
|
||||||
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=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})})
|
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
|
||||||
|
|
||||||
if not salary_slip_name:
|
if not salary_slip_name:
|
||||||
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee)
|
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee.name)
|
||||||
salary_slip.employee_name = frappe.get_value("Employee",
|
salary_slip.employee_name = employee.employee_name
|
||||||
{"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
|
|
||||||
salary_slip.payroll_frequency = payroll_frequency
|
salary_slip.payroll_frequency = payroll_frequency
|
||||||
salary_slip.posting_date = nowdate()
|
salary_slip.posting_date = nowdate()
|
||||||
salary_slip.insert()
|
salary_slip.insert()
|
||||||
|
@ -119,26 +119,25 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None,
|
|||||||
if test_tax:
|
if test_tax:
|
||||||
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
|
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
|
||||||
|
|
||||||
if not frappe.db.exists('Salary Structure', salary_structure):
|
if frappe.db.exists("Salary Structure", salary_structure):
|
||||||
details = {
|
frappe.db.delete("Salary Structure", salary_structure)
|
||||||
"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()
|
|
||||||
|
|
||||||
else:
|
details = {
|
||||||
salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure)
|
"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}
|
filters = {'employee':employee, 'docstatus': 1}
|
||||||
if not from_date and payroll_period:
|
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
|
return html
|
||||||
|
|
||||||
def set_item_group_filters(field_filters):
|
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'])]
|
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)
|
WorkstationHolidayError)
|
||||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||||
from erpnext.setup.utils import get_exchange_rate
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
from erpnext.hr.utils import validate_active_employee
|
||||||
|
|
||||||
class OverlapError(frappe.ValidationError): pass
|
class OverlapError(frappe.ValidationError): pass
|
||||||
class OverWorkLoggedError(frappe.ValidationError): pass
|
class OverWorkLoggedError(frappe.ValidationError): pass
|
||||||
|
|
||||||
class Timesheet(Document):
|
class Timesheet(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
if self.employee:
|
||||||
|
validate_active_employee(self.employee)
|
||||||
self.set_employee_name()
|
self.set_employee_name()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
|
@ -54,7 +54,7 @@ frappe.help.help_links["permission-manager"] = [
|
|||||||
|
|
||||||
frappe.help.help_links["Form/System Settings"] = [
|
frappe.help.help_links["Form/System Settings"] = [
|
||||||
{
|
{
|
||||||
label: "Naming Series",
|
label: "System Settings",
|
||||||
url: docsUrl + "user/manual/en/setting-up/settings/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",
|
label: "PayPal Settings",
|
||||||
url:
|
url:
|
||||||
docsUrl +
|
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",
|
label: "Razorpay Settings",
|
||||||
url:
|
url:
|
||||||
docsUrl +
|
docsUrl +
|
||||||
"user/manual/en/setting-up/integrations/razorpay-integration",
|
"user/manual/en/erpnext_integration/razorpay-integration",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
frappe.help.help_links["Form/Dropbox Settings"] = [
|
frappe.help.help_links["Form/Dropbox Settings"] = [
|
||||||
{
|
{
|
||||||
label: "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",
|
label: "LDAP Settings",
|
||||||
url:
|
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",
|
label: "Stripe Settings",
|
||||||
url:
|
url:
|
||||||
docsUrl +
|
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",
|
label: "Nested BOM Structure",
|
||||||
url:
|
url:
|
||||||
docsUrl +
|
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.provide('frappe.ui.form');
|
||||||
|
|
||||||
frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
|
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.skip_redirect_on_error = true;
|
||||||
this._super(doctype, after_insert);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render_dialog: function() {
|
render_dialog: function() {
|
||||||
|
@ -367,15 +367,16 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
`<div class="add-discount-field"></div>`
|
`<div class="add-discount-field"></div>`
|
||||||
);
|
);
|
||||||
const me = this;
|
const me = this;
|
||||||
|
const frm = me.events.get_frm();
|
||||||
|
let discount = frm.doc.additional_discount_percentage;
|
||||||
|
|
||||||
this.discount_field = frappe.ui.form.make_control({
|
this.discount_field = frappe.ui.form.make_control({
|
||||||
df: {
|
df: {
|
||||||
label: __('Discount'),
|
label: __('Discount'),
|
||||||
fieldtype: 'Data',
|
fieldtype: 'Data',
|
||||||
placeholder: __('Enter discount percentage.'),
|
placeholder: ( discount ? discount + '%' : __('Enter discount percentage.') ),
|
||||||
input_class: 'input-xs',
|
input_class: 'input-xs',
|
||||||
onchange: function() {
|
onchange: function() {
|
||||||
const frm = me.events.get_frm();
|
|
||||||
if (flt(this.value) != 0) {
|
if (flt(this.value) != 0) {
|
||||||
frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value));
|
frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value));
|
||||||
me.hide_discount_control(this.value);
|
me.hide_discount_control(this.value);
|
||||||
|
@ -12,10 +12,14 @@ from frappe.desk.notifications import clear_notifications
|
|||||||
class TransactionDeletionRecord(Document):
|
class TransactionDeletionRecord(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
frappe.only_for('System Manager')
|
frappe.only_for('System Manager')
|
||||||
|
self.validate_doctypes_to_be_ignored()
|
||||||
|
|
||||||
|
def validate_doctypes_to_be_ignored(self):
|
||||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||||
for doctype in self.doctypes_to_be_ignored:
|
for doctype in self.doctypes_to_be_ignored:
|
||||||
if doctype.doctype_name not in doctypes_to_be_ignored_list:
|
if doctype.doctype_name not in doctypes_to_be_ignored_list:
|
||||||
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed"))
|
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."),
|
||||||
|
title=_("Not Allowed"))
|
||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
if not self.doctypes_to_be_ignored:
|
if not self.doctypes_to_be_ignored:
|
||||||
@ -23,54 +27,9 @@ class TransactionDeletionRecord(Document):
|
|||||||
|
|
||||||
self.delete_bins()
|
self.delete_bins()
|
||||||
self.delete_lead_addresses()
|
self.delete_lead_addresses()
|
||||||
|
self.reset_company_values()
|
||||||
company_obj = frappe.get_doc('Company', self.company)
|
|
||||||
# reset company values
|
|
||||||
company_obj.total_monthly_sales = 0
|
|
||||||
company_obj.sales_monthly_history = None
|
|
||||||
company_obj.save()
|
|
||||||
# Clear notification counts
|
|
||||||
clear_notifications()
|
clear_notifications()
|
||||||
|
self.delete_company_transactions()
|
||||||
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
|
||||||
tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
|
|
||||||
doctypes_to_be_ignored_list = singles
|
|
||||||
for doctype in self.doctypes_to_be_ignored:
|
|
||||||
doctypes_to_be_ignored_list.append(doctype.doctype_name)
|
|
||||||
|
|
||||||
docfields = frappe.get_all('DocField',
|
|
||||||
filters = {
|
|
||||||
'fieldtype': 'Link',
|
|
||||||
'options': 'Company',
|
|
||||||
'parent': ['not in', doctypes_to_be_ignored_list]},
|
|
||||||
fields=['parent', 'fieldname'])
|
|
||||||
|
|
||||||
for docfield in docfields:
|
|
||||||
if docfield['parent'] != self.doctype:
|
|
||||||
no_of_docs = frappe.db.count(docfield['parent'], {
|
|
||||||
docfield['fieldname'] : self.company
|
|
||||||
})
|
|
||||||
|
|
||||||
if no_of_docs > 0:
|
|
||||||
self.delete_version_log(docfield['parent'], docfield['fieldname'])
|
|
||||||
self.delete_communications(docfield['parent'], docfield['fieldname'])
|
|
||||||
|
|
||||||
# populate DocTypes table
|
|
||||||
if docfield['parent'] not in tables:
|
|
||||||
self.append('doctypes', {
|
|
||||||
'doctype_name' : docfield['parent'],
|
|
||||||
'no_of_docs' : no_of_docs
|
|
||||||
})
|
|
||||||
|
|
||||||
# delete the docs linked with the specified company
|
|
||||||
frappe.db.delete(docfield['parent'], {
|
|
||||||
docfield['fieldname'] : self.company
|
|
||||||
})
|
|
||||||
|
|
||||||
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
|
||||||
if naming_series:
|
|
||||||
if '#' in naming_series:
|
|
||||||
self.update_naming_series(naming_series, docfield['parent'])
|
|
||||||
|
|
||||||
def populate_doctypes_to_be_ignored_table(self):
|
def populate_doctypes_to_be_ignored_table(self):
|
||||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||||
@ -79,6 +38,111 @@ class TransactionDeletionRecord(Document):
|
|||||||
'doctype_name' : doctype
|
'doctype_name' : doctype
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def delete_bins(self):
|
||||||
|
frappe.db.sql("""delete from tabBin where warehouse in
|
||||||
|
(select name from tabWarehouse where company=%s)""", self.company)
|
||||||
|
|
||||||
|
def delete_lead_addresses(self):
|
||||||
|
"""Delete addresses to which leads are linked"""
|
||||||
|
leads = frappe.get_all('Lead', filters={'company': self.company})
|
||||||
|
leads = ["'%s'" % row.get("name") for row in leads]
|
||||||
|
addresses = []
|
||||||
|
if leads:
|
||||||
|
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
|
||||||
|
in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
if addresses:
|
||||||
|
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
|
||||||
|
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
||||||
|
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
||||||
|
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
|
||||||
|
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
def reset_company_values(self):
|
||||||
|
company_obj = frappe.get_doc('Company', self.company)
|
||||||
|
company_obj.total_monthly_sales = 0
|
||||||
|
company_obj.sales_monthly_history = None
|
||||||
|
company_obj.save()
|
||||||
|
|
||||||
|
def delete_company_transactions(self):
|
||||||
|
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
|
||||||
|
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
|
||||||
|
|
||||||
|
tables = self.get_all_child_doctypes()
|
||||||
|
for docfield in docfields:
|
||||||
|
if docfield['parent'] != self.doctype:
|
||||||
|
no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
|
||||||
|
|
||||||
|
if no_of_docs > 0:
|
||||||
|
self.delete_version_log(docfield['parent'], docfield['fieldname'])
|
||||||
|
self.delete_communications(docfield['parent'], docfield['fieldname'])
|
||||||
|
self.populate_doctypes_table(tables, docfield['parent'], no_of_docs)
|
||||||
|
|
||||||
|
self.delete_child_tables(docfield['parent'], docfield['fieldname'])
|
||||||
|
self.delete_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
|
||||||
|
|
||||||
|
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
||||||
|
if naming_series:
|
||||||
|
if '#' in naming_series:
|
||||||
|
self.update_naming_series(naming_series, docfield['parent'])
|
||||||
|
|
||||||
|
def get_doctypes_to_be_ignored_list(self):
|
||||||
|
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
||||||
|
doctypes_to_be_ignored_list = singles
|
||||||
|
for doctype in self.doctypes_to_be_ignored:
|
||||||
|
doctypes_to_be_ignored_list.append(doctype.doctype_name)
|
||||||
|
|
||||||
|
return doctypes_to_be_ignored_list
|
||||||
|
|
||||||
|
def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
|
||||||
|
docfields = frappe.get_all('DocField',
|
||||||
|
filters = {
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Company',
|
||||||
|
'parent': ['not in', doctypes_to_be_ignored_list]},
|
||||||
|
fields=['parent', 'fieldname'])
|
||||||
|
|
||||||
|
return docfields
|
||||||
|
|
||||||
|
def get_all_child_doctypes(self):
|
||||||
|
return frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
|
||||||
|
|
||||||
|
def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname):
|
||||||
|
return frappe.db.count(doctype, {company_fieldname : self.company})
|
||||||
|
|
||||||
|
def populate_doctypes_table(self, tables, doctype, no_of_docs):
|
||||||
|
if doctype not in tables:
|
||||||
|
self.append('doctypes', {
|
||||||
|
'doctype_name' : doctype,
|
||||||
|
'no_of_docs' : no_of_docs
|
||||||
|
})
|
||||||
|
|
||||||
|
def delete_child_tables(self, doctype, company_fieldname):
|
||||||
|
parent_docs_to_be_deleted = frappe.get_all(doctype, {
|
||||||
|
company_fieldname : self.company
|
||||||
|
}, pluck = 'name')
|
||||||
|
|
||||||
|
child_tables = frappe.get_all('DocField', filters = {
|
||||||
|
'fieldtype': 'Table',
|
||||||
|
'parent': doctype
|
||||||
|
}, pluck = 'options')
|
||||||
|
|
||||||
|
for table in child_tables:
|
||||||
|
frappe.db.delete(table, {
|
||||||
|
'parent': ['in', parent_docs_to_be_deleted]
|
||||||
|
})
|
||||||
|
|
||||||
|
def delete_docs_linked_with_specified_company(self, doctype, company_fieldname):
|
||||||
|
frappe.db.delete(doctype, {
|
||||||
|
company_fieldname : self.company
|
||||||
|
})
|
||||||
|
|
||||||
def update_naming_series(self, naming_series, doctype_name):
|
def update_naming_series(self, naming_series, doctype_name):
|
||||||
if '.' in naming_series:
|
if '.' in naming_series:
|
||||||
prefix, hashes = naming_series.rsplit('.', 1)
|
prefix, hashes = naming_series.rsplit('.', 1)
|
||||||
@ -107,32 +171,6 @@ class TransactionDeletionRecord(Document):
|
|||||||
|
|
||||||
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
|
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
|
||||||
|
|
||||||
def delete_bins(self):
|
|
||||||
frappe.db.sql("""delete from tabBin where warehouse in
|
|
||||||
(select name from tabWarehouse where company=%s)""", self.company)
|
|
||||||
|
|
||||||
def delete_lead_addresses(self):
|
|
||||||
"""Delete addresses to which leads are linked"""
|
|
||||||
leads = frappe.get_all('Lead', filters={'company': self.company})
|
|
||||||
leads = ["'%s'" % row.get("name") for row in leads]
|
|
||||||
addresses = []
|
|
||||||
if leads:
|
|
||||||
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
|
|
||||||
in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
if addresses:
|
|
||||||
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
|
||||||
|
|
||||||
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
|
|
||||||
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
|
||||||
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
|
||||||
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
|
|
||||||
|
|
||||||
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
|
|
||||||
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_doctypes_to_be_ignored():
|
def get_doctypes_to_be_ignored():
|
||||||
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
|
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
|
||||||
|
@ -162,19 +162,19 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=No
|
|||||||
|
|
||||||
out = float(frappe.db.sql("""select sum(actual_qty)
|
out = float(frappe.db.sql("""select sum(actual_qty)
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where warehouse=%s and batch_no=%s {0}""".format(cond),
|
where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond),
|
||||||
(warehouse, batch_no))[0][0] or 0)
|
(warehouse, batch_no))[0][0] or 0)
|
||||||
|
|
||||||
if batch_no and not warehouse:
|
if batch_no and not warehouse:
|
||||||
out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
|
out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where batch_no=%s
|
where is_cancelled = 0 and batch_no=%s
|
||||||
group by warehouse''', batch_no, as_dict=1)
|
group by warehouse''', batch_no, as_dict=1)
|
||||||
|
|
||||||
if not batch_no and item_code and warehouse:
|
if not batch_no and item_code and warehouse:
|
||||||
out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
|
out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where item_code = %s and warehouse=%s
|
where is_cancelled = 0 and item_code = %s and warehouse=%s
|
||||||
group by batch_no''', (item_code, warehouse), as_dict=1)
|
group by batch_no''', (item_code, warehouse), as_dict=1)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
@ -100,10 +100,11 @@ frappe.ui.form.on("Item", {
|
|||||||
|
|
||||||
frm.add_custom_button(__('Duplicate'), function() {
|
frm.add_custom_button(__('Duplicate'), function() {
|
||||||
var new_item = frappe.model.copy_doc(frm.doc);
|
var new_item = frappe.model.copy_doc(frm.doc);
|
||||||
if(new_item.item_name===new_item.item_code) {
|
// Duplicate item could have different name, causing "copy paste" error.
|
||||||
|
if (new_item.item_name===new_item.item_code) {
|
||||||
new_item.item_name = null;
|
new_item.item_name = null;
|
||||||
}
|
}
|
||||||
if(new_item.description===new_item.description) {
|
if (new_item.item_code===new_item.description || new_item.item_code===new_item.description) {
|
||||||
new_item.description = null;
|
new_item.description = null;
|
||||||
}
|
}
|
||||||
frappe.set_route('Form', 'Item', new_item.name);
|
frappe.set_route('Form', 'Item', new_item.name);
|
||||||
@ -186,8 +187,6 @@ frappe.ui.form.on("Item", {
|
|||||||
item_code: function(frm) {
|
item_code: function(frm) {
|
||||||
if(!frm.doc.item_name)
|
if(!frm.doc.item_name)
|
||||||
frm.set_value("item_name", frm.doc.item_code);
|
frm.set_value("item_name", frm.doc.item_code);
|
||||||
if(!frm.doc.description)
|
|
||||||
frm.set_value("description", frm.doc.item_code);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
is_stock_item: function(frm) {
|
is_stock_item: function(frm) {
|
||||||
|
@ -239,6 +239,7 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
|
|||||||
and sle.`item_code`=%(item_code)s
|
and sle.`item_code`=%(item_code)s
|
||||||
and sle.`company` = %(company)s
|
and sle.`company` = %(company)s
|
||||||
and batch.disabled = 0
|
and batch.disabled = 0
|
||||||
|
and sle.is_cancelled=0
|
||||||
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
|
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
|
||||||
{warehouse_condition}
|
{warehouse_condition}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
|
@ -1789,7 +1789,7 @@ def get_expired_batch_items():
|
|||||||
from `tabBatch` b, `tabStock Ledger Entry` sle
|
from `tabBatch` b, `tabStock Ledger Entry` sle
|
||||||
where b.expiry_date <= %s
|
where b.expiry_date <= %s
|
||||||
and b.expiry_date is not NULL
|
and b.expiry_date is not NULL
|
||||||
and b.batch_id = sle.batch_no
|
and b.batch_id = sle.batch_no and sle.is_cancelled = 0
|
||||||
group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1)
|
group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -60,7 +60,7 @@ class StockLedgerEntry(Document):
|
|||||||
if self.batch_no and not self.get("allow_negative_stock"):
|
if self.batch_no and not self.get("allow_negative_stock"):
|
||||||
batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty)
|
batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty)
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where warehouse=%s and item_code=%s and batch_no=%s""",
|
where is_cancelled =0 and warehouse=%s and item_code=%s and batch_no=%s""",
|
||||||
(self.warehouse, self.item_code, self.batch_no))[0][0])
|
(self.warehouse, self.item_code, self.batch_no))[0][0])
|
||||||
|
|
||||||
if batch_bal_after_transaction < 0:
|
if batch_bal_after_transaction < 0:
|
||||||
@ -152,7 +152,7 @@ class StockLedgerEntry(Document):
|
|||||||
last_transaction_time = frappe.db.sql("""
|
last_transaction_time = frappe.db.sql("""
|
||||||
select MAX(timestamp(posting_date, posting_time)) as posting_time
|
select MAX(timestamp(posting_date, posting_time)) as posting_time
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where docstatus = 1 and item_code = %s
|
where docstatus = 1 and is_cancelled = 0 and item_code = %s
|
||||||
and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
|
and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
|
||||||
|
|
||||||
cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
|
cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
|
||||||
|
@ -74,9 +74,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
|||||||
|
|
||||||
update_party_blanket_order(args, out)
|
update_party_blanket_order(args, out)
|
||||||
|
|
||||||
if not doc or cint(doc.get('is_return')) == 0:
|
|
||||||
# get price list rate only if the invoice is not a credit or debit note
|
get_price_list_rate(args, item, out)
|
||||||
get_price_list_rate(args, item, out)
|
|
||||||
|
|
||||||
if args.customer and cint(args.is_pos):
|
if args.customer and cint(args.is_pos):
|
||||||
out.update(get_pos_profile_item_details(args.company, args, update_data=True))
|
out.update(get_pos_profile_item_details(args.company, args, update_data=True))
|
||||||
|
0
erpnext/stock/report/cogs_by_item_group/__init__.py
Normal file
0
erpnext/stock/report/cogs_by_item_group/__init__.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
|
||||||
|
frappe.query_reports["COGS By Item Group"] = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
label: __("Company"),
|
||||||
|
fieldname: "company",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Company",
|
||||||
|
mandatory: true,
|
||||||
|
default: frappe.defaults.get_user_default("Company"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("From Date"),
|
||||||
|
fieldname: "from_date",
|
||||||
|
fieldtype: "Date",
|
||||||
|
mandatory: true,
|
||||||
|
default: frappe.datetime.year_start(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("To Date"),
|
||||||
|
fieldname: "to_date",
|
||||||
|
fieldtype: "Date",
|
||||||
|
mandatory: true,
|
||||||
|
default: frappe.datetime.get_today(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-06-02 18:59:19.830928",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-06-02 18:59:55.470621",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "COGS By Item Group",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "GL Entry",
|
||||||
|
"report_name": "COGS By Item Group",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Auditor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
188
erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
Normal file
188
erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
import datetime
|
||||||
|
from typing import Dict, List, Tuple, Union
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import date_diff
|
||||||
|
|
||||||
|
from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries
|
||||||
|
|
||||||
|
|
||||||
|
Filters = frappe._dict
|
||||||
|
Row = frappe._dict
|
||||||
|
Data = List[Row]
|
||||||
|
Columns = List[Dict[str, str]]
|
||||||
|
DateTime = Union[datetime.date, datetime.datetime]
|
||||||
|
FilteredEntries = List[Dict[str, Union[str, float, DateTime, None]]]
|
||||||
|
ItemGroupsDict = Dict[Tuple[int, int], Dict[str, Union[str, int]]]
|
||||||
|
SVDList = List[frappe._dict]
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters: Filters) -> Tuple[Columns, Data]:
|
||||||
|
update_filters_with_account(filters)
|
||||||
|
validate_filters(filters)
|
||||||
|
columns = get_columns()
|
||||||
|
data = get_data(filters)
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def update_filters_with_account(filters: Filters) -> None:
|
||||||
|
account = frappe.get_value("Company", filters.get("company"), "default_expense_account")
|
||||||
|
filters.update(dict(account=account))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_filters(filters: Filters) -> None:
|
||||||
|
if filters.from_date > filters.to_date:
|
||||||
|
frappe.throw(_("From Date must be before To Date"))
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns() -> Columns:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'label': 'Item Group',
|
||||||
|
'fieldname': 'item_group',
|
||||||
|
'fieldtype': 'Data',
|
||||||
|
'width': '200'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': 'COGS Debit',
|
||||||
|
'fieldname': 'cogs_debit',
|
||||||
|
'fieldtype': 'Currency',
|
||||||
|
'width': '200'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(filters: Filters) -> Data:
|
||||||
|
filtered_entries = get_filtered_entries(filters)
|
||||||
|
svd_list = get_stock_value_difference_list(filtered_entries)
|
||||||
|
leveled_dict = get_leveled_dict()
|
||||||
|
|
||||||
|
assign_self_values(leveled_dict, svd_list)
|
||||||
|
assign_agg_values(leveled_dict)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for item in leveled_dict.items():
|
||||||
|
i = item[1]
|
||||||
|
if i['agg_value'] == 0:
|
||||||
|
continue
|
||||||
|
data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level']))
|
||||||
|
if i['self_value'] < i['agg_value'] and i['self_value'] > 0:
|
||||||
|
data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1))
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_filtered_entries(filters: Filters) -> FilteredEntries:
|
||||||
|
gl_entries = get_gl_entries(filters, [])
|
||||||
|
filtered_entries = []
|
||||||
|
for entry in gl_entries:
|
||||||
|
posting_date = entry.get('posting_date')
|
||||||
|
from_date = filters.get('from_date')
|
||||||
|
if date_diff(from_date, posting_date) > 0:
|
||||||
|
continue
|
||||||
|
filtered_entries.append(entry)
|
||||||
|
return filtered_entries
|
||||||
|
|
||||||
|
|
||||||
|
def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDList:
|
||||||
|
voucher_nos = [fe.get('voucher_no') for fe in filtered_entries]
|
||||||
|
svd_list = frappe.get_list(
|
||||||
|
'Stock Ledger Entry', fields=['item_code','stock_value_difference'],
|
||||||
|
filters=[('voucher_no', 'in', voucher_nos)]
|
||||||
|
)
|
||||||
|
assign_item_groups_to_svd_list(svd_list)
|
||||||
|
return svd_list
|
||||||
|
|
||||||
|
|
||||||
|
def get_leveled_dict() -> OrderedDict:
|
||||||
|
item_groups_dict = get_item_groups_dict()
|
||||||
|
lr_list = sorted(item_groups_dict, key=lambda x : int(x[0]))
|
||||||
|
leveled_dict = OrderedDict()
|
||||||
|
current_level = 0
|
||||||
|
nesting_r = []
|
||||||
|
for l, r in lr_list:
|
||||||
|
while current_level > 0 and nesting_r[-1] < l:
|
||||||
|
nesting_r.pop()
|
||||||
|
current_level -= 1
|
||||||
|
|
||||||
|
leveled_dict[(l,r)] = {
|
||||||
|
'level' : current_level,
|
||||||
|
'name' : item_groups_dict[(l,r)]['name'],
|
||||||
|
'is_group' : item_groups_dict[(l,r)]['is_group']
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(r) - int(l) > 1:
|
||||||
|
current_level += 1
|
||||||
|
nesting_r.append(r)
|
||||||
|
|
||||||
|
update_leveled_dict(leveled_dict)
|
||||||
|
return leveled_dict
|
||||||
|
|
||||||
|
|
||||||
|
def assign_self_values(leveled_dict: OrderedDict, svd_list: SVDList) -> None:
|
||||||
|
key_dict = {v['name']:k for k, v in leveled_dict.items()}
|
||||||
|
for item in svd_list:
|
||||||
|
key = key_dict[item.get("item_group")]
|
||||||
|
leveled_dict[key]['self_value'] += -item.get("stock_value_difference")
|
||||||
|
|
||||||
|
|
||||||
|
def assign_agg_values(leveled_dict: OrderedDict) -> None:
|
||||||
|
keys = list(leveled_dict.keys())[::-1]
|
||||||
|
prev_level = leveled_dict[keys[-1]]['level']
|
||||||
|
accu = [0]
|
||||||
|
for k in keys[:-1]:
|
||||||
|
curr_level = leveled_dict[k]['level']
|
||||||
|
if curr_level == prev_level:
|
||||||
|
accu[-1] += leveled_dict[k]['self_value']
|
||||||
|
leveled_dict[k]['agg_value'] = leveled_dict[k]['self_value']
|
||||||
|
|
||||||
|
elif curr_level > prev_level:
|
||||||
|
accu.append(leveled_dict[k]['self_value'])
|
||||||
|
leveled_dict[k]['agg_value'] = accu[-1]
|
||||||
|
|
||||||
|
elif curr_level < prev_level:
|
||||||
|
accu[-1] += leveled_dict[k]['self_value']
|
||||||
|
leveled_dict[k]['agg_value'] = accu[-1]
|
||||||
|
|
||||||
|
prev_level = curr_level
|
||||||
|
|
||||||
|
# root node
|
||||||
|
rk = keys[-1]
|
||||||
|
leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value']
|
||||||
|
|
||||||
|
|
||||||
|
def get_row(name:str, value:float, is_bold:int, indent:int) -> Row:
|
||||||
|
item_group = name
|
||||||
|
if is_bold:
|
||||||
|
item_group = frappe.bold(item_group)
|
||||||
|
return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent)
|
||||||
|
|
||||||
|
|
||||||
|
def assign_item_groups_to_svd_list(svd_list: SVDList) -> None:
|
||||||
|
ig_map = get_item_groups_map(svd_list)
|
||||||
|
for item in svd_list:
|
||||||
|
item.item_group = ig_map[item.get("item_code")]
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]:
|
||||||
|
item_codes = set(i['item_code'] for i in svd_list)
|
||||||
|
ig_list = frappe.get_list(
|
||||||
|
'Item', fields=['item_code','item_group'],
|
||||||
|
filters=[('item_code', 'in', item_codes)]
|
||||||
|
)
|
||||||
|
return {i['item_code']:i['item_group'] for i in ig_list}
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_groups_dict() -> ItemGroupsDict:
|
||||||
|
item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt"))
|
||||||
|
return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']}
|
||||||
|
for i in item_groups_list}
|
||||||
|
|
||||||
|
|
||||||
|
def update_leveled_dict(leveled_dict: OrderedDict) -> None:
|
||||||
|
for k in leveled_dict:
|
||||||
|
leveled_dict[k].update({'self_value':0, 'agg_value':0})
|
@ -69,7 +69,7 @@ def get_consumed_details(filters):
|
|||||||
i.stock_uom, sle.actual_qty, sle.stock_value_difference,
|
i.stock_uom, sle.actual_qty, sle.stock_value_difference,
|
||||||
sle.voucher_no, sle.voucher_type
|
sle.voucher_no, sle.voucher_type
|
||||||
from `tabStock Ledger Entry` sle, `tabItem` i
|
from `tabStock Ledger Entry` sle, `tabItem` i
|
||||||
where sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
|
where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
|
||||||
consumed_details.setdefault(d.item_code, []).append(d)
|
consumed_details.setdefault(d.item_code, []).append(d)
|
||||||
|
|
||||||
return consumed_details
|
return consumed_details
|
||||||
|
@ -314,13 +314,16 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
|
|||||||
for row_idx, row in enumerate(result):
|
for row_idx, row in enumerate(result):
|
||||||
data = row.items() if is_dict_obj else enumerate(row)
|
data = row.items() if is_dict_obj else enumerate(row)
|
||||||
for key, value in data:
|
for key, value in data:
|
||||||
if key not in convertible_columns or not conversion_factors[row_idx-1]:
|
if key not in convertible_columns:
|
||||||
continue
|
continue
|
||||||
|
# If no conversion factor for the UOM, defaults to 1
|
||||||
|
if not conversion_factors[row_idx]:
|
||||||
|
conversion_factors[row_idx] = 1
|
||||||
|
|
||||||
if convertible_columns.get(key) == 'rate':
|
if convertible_columns.get(key) == 'rate':
|
||||||
new_value = flt(value) * conversion_factors[row_idx-1]
|
new_value = flt(value) * conversion_factors[row_idx]
|
||||||
else:
|
else:
|
||||||
new_value = flt(value) / conversion_factors[row_idx-1]
|
new_value = flt(value) / conversion_factors[row_idx]
|
||||||
|
|
||||||
if not is_dict_obj:
|
if not is_dict_obj:
|
||||||
row.insert(key+1, new_value)
|
row.insert(key+1, new_value)
|
||||||
|
Loading…
Reference in New Issue
Block a user