fix: resolved conflicts in patches.txt
This commit is contained in:
commit
3bcbc86291
3
.github/workflows/backport.yml
vendored
3
.github/workflows/backport.yml
vendored
@ -8,11 +8,12 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Actions
|
- name: Checkout Actions
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: "ankush/backport"
|
repository: "frappe/backport"
|
||||||
path: ./actions
|
path: ./actions
|
||||||
ref: develop
|
ref: develop
|
||||||
- name: Install Actions
|
- name: Install Actions
|
||||||
|
1
.github/workflows/docs-checker.yml
vendored
1
.github/workflows/docs-checker.yml
vendored
@ -6,6 +6,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup Environment'
|
- name: 'Setup Environment'
|
||||||
|
1
.github/workflows/patch.yml
vendored
1
.github/workflows/patch.yml
vendored
@ -5,6 +5,7 @@ on: [pull_request, workflow_dispatch]
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
|
timeout-minutes: 60
|
||||||
|
|
||||||
name: Patch Test
|
name: Patch Test
|
||||||
|
|
||||||
|
1
.github/workflows/server-tests.yml
vendored
1
.github/workflows/server-tests.yml
vendored
@ -9,6 +9,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
|
timeout-minutes: 60
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
1
.github/workflows/ui-tests.yml
vendored
1
.github/workflows/ui-tests.yml
vendored
@ -7,6 +7,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
|
timeout-minutes: 60
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '13.7.1'
|
__version__ = '13.8.0'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
@ -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()
|
||||||
|
@ -27,6 +27,9 @@ class ExchangeRateRevaluation(Document):
|
|||||||
if not (self.company and self.posting_date):
|
if not (self.company and self.posting_date):
|
||||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.ignore_linked_doctypes = ('GL Entry')
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def check_journal_entry_condition(self):
|
def check_journal_entry_condition(self):
|
||||||
total_debit = frappe.db.get_value("Journal Entry Account", {
|
total_debit = frappe.db.get_value("Journal Entry Account", {
|
||||||
@ -99,10 +102,12 @@ class ExchangeRateRevaluation(Document):
|
|||||||
sum(debit) - sum(credit) as balance
|
sum(debit) - sum(credit) as balance
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where account in (%s)
|
where account in (%s)
|
||||||
group by account, party_type, party
|
and posting_date <= %s
|
||||||
|
and is_cancelled = 0
|
||||||
|
group by account, NULLIF(party_type,''), NULLIF(party,'')
|
||||||
having sum(debit) != sum(credit)
|
having sum(debit) != sum(credit)
|
||||||
order by account
|
order by account
|
||||||
""" % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
|
""" % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
|
||||||
|
|
||||||
return account_details
|
return account_details
|
||||||
|
|
||||||
@ -143,9 +148,9 @@ class ExchangeRateRevaluation(Document):
|
|||||||
"party_type": d.get("party_type"),
|
"party_type": d.get("party_type"),
|
||||||
"party": d.get("party"),
|
"party": d.get("party"),
|
||||||
"account_currency": d.get("account_currency"),
|
"account_currency": d.get("account_currency"),
|
||||||
"balance": d.get("balance_in_account_currency"),
|
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
|
||||||
dr_or_cr: abs(d.get("balance_in_account_currency")),
|
dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
|
||||||
"exchange_rate":d.get("new_exchange_rate"),
|
"exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
|
||||||
"reference_type": "Exchange Rate Revaluation",
|
"reference_type": "Exchange Rate Revaluation",
|
||||||
"reference_name": self.name,
|
"reference_name": self.name,
|
||||||
})
|
})
|
||||||
@ -154,9 +159,9 @@ class ExchangeRateRevaluation(Document):
|
|||||||
"party_type": d.get("party_type"),
|
"party_type": d.get("party_type"),
|
||||||
"party": d.get("party"),
|
"party": d.get("party"),
|
||||||
"account_currency": d.get("account_currency"),
|
"account_currency": d.get("account_currency"),
|
||||||
"balance": d.get("balance_in_account_currency"),
|
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
|
||||||
reverse_dr_or_cr: abs(d.get("balance_in_account_currency")),
|
reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
|
||||||
"exchange_rate": d.get("current_exchange_rate"),
|
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
|
||||||
"reference_type": "Exchange Rate Revaluation",
|
"reference_type": "Exchange Rate Revaluation",
|
||||||
"reference_name": self.name
|
"reference_name": self.name
|
||||||
})
|
})
|
||||||
@ -185,9 +190,9 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
|
|||||||
|
|
||||||
account_details = {}
|
account_details = {}
|
||||||
company_currency = erpnext.get_company_currency(company)
|
company_currency = erpnext.get_company_currency(company)
|
||||||
balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False)
|
balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
|
||||||
if balance:
|
if balance:
|
||||||
balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party)
|
balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
|
||||||
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
|
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
|
||||||
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
|
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
|
||||||
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
|
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
|
||||||
|
@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
jv.flags.ignore_mandatory = True
|
||||||
jv.submit()
|
jv.submit()
|
@ -15,6 +15,7 @@ from erpnext.healthcare.doctype.lab_test_template.lab_test_template import make_
|
|||||||
class TestPricingRule(unittest.TestCase):
|
class TestPricingRule(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
delete_existing_pricing_rules()
|
delete_existing_pricing_rules()
|
||||||
|
setup_pricing_rule_data()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
delete_existing_pricing_rules()
|
delete_existing_pricing_rules()
|
||||||
@ -554,6 +555,8 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
for doc in [si, si1]:
|
for doc in [si, si1]:
|
||||||
doc.delete()
|
doc.delete()
|
||||||
|
|
||||||
|
test_dependencies = ["Campaign"]
|
||||||
|
|
||||||
def make_pricing_rule(**args):
|
def make_pricing_rule(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
@ -600,6 +603,13 @@ def make_pricing_rule(**args):
|
|||||||
if args.get(applicable_for):
|
if args.get(applicable_for):
|
||||||
doc.db_set(applicable_for, args.get(applicable_for))
|
doc.db_set(applicable_for, args.get(applicable_for))
|
||||||
|
|
||||||
|
def setup_pricing_rule_data():
|
||||||
|
if not frappe.db.exists('Campaign', '_Test Campaign'):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Campaign',
|
||||||
|
'campaign_name': '_Test Campaign',
|
||||||
|
'name': '_Test Campaign'
|
||||||
|
}).insert()
|
||||||
|
|
||||||
def delete_existing_pricing_rules():
|
def delete_existing_pricing_rules():
|
||||||
for doctype in ["Pricing Rule", "Pricing Rule Item Code",
|
for doctype in ["Pricing Rule", "Pricing Rule Item Code",
|
||||||
|
@ -168,7 +168,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
|||||||
frappe.throw(_("Invalid {0}").format(args.get(field)))
|
frappe.throw(_("Invalid {0}").format(args.get(field)))
|
||||||
|
|
||||||
parent_groups = frappe.db.sql_list("""select name from `tab%s`
|
parent_groups = frappe.db.sql_list("""select name from `tab%s`
|
||||||
where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||||
|
|
||||||
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||||
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||||
|
@ -27,6 +27,8 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
|
|||||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
|
||||||
|
|
||||||
|
class WarehouseMissingError(frappe.ValidationError): pass
|
||||||
|
|
||||||
form_grid_templates = {
|
form_grid_templates = {
|
||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
}
|
}
|
||||||
@ -207,8 +209,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if self.update_stock and for_validate:
|
if self.update_stock and for_validate:
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if not d.warehouse:
|
if not d.warehouse:
|
||||||
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
|
frappe.throw(_("Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}").
|
||||||
format(d.idx, d.item_code, self.company))
|
format(d.idx, d.item_code, self.company), exc=WarehouseMissingError)
|
||||||
|
|
||||||
super(PurchaseInvoice, self).validate_warehouse()
|
super(PurchaseInvoice, self).validate_warehouse()
|
||||||
|
|
||||||
@ -246,7 +248,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
and (not item.po_detail or
|
and (not item.po_detail or
|
||||||
not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")):
|
not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")):
|
||||||
|
|
||||||
if self.update_stock and (not item.from_warehouse):
|
if self.update_stock and item.warehouse and (not item.from_warehouse):
|
||||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||||
msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
|
msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
|
||||||
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||||
@ -657,7 +659,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
"account": self.get_company_default("exchange_gain_loss_account"),
|
"account": self.get_company_default("exchange_gain_loss_account"),
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"credit": discrepancy_caused_by_exchange_rate_difference,
|
"credit": discrepancy_caused_by_exchange_rate_difference,
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
@ -1193,7 +1195,7 @@ def get_purchase_document_details(doc):
|
|||||||
purchase_receipts_or_invoices.append(item.get(doc_reference))
|
purchase_receipts_or_invoices.append(item.get(doc_reference))
|
||||||
if item.get(items_reference):
|
if item.get(items_reference):
|
||||||
items.append(item.get(items_reference))
|
items.append(item.get(items_reference))
|
||||||
|
|
||||||
exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in',
|
exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in',
|
||||||
purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1))
|
purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1))
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
pi.conversion_rate = 80
|
pi.conversion_rate = 80
|
||||||
|
|
||||||
pi.insert()
|
pi.insert()
|
||||||
pi.submit()
|
pi.submit()
|
||||||
|
|
||||||
# Get exchnage gain and loss account
|
# Get exchnage gain and loss account
|
||||||
exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
|
exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
|
||||||
@ -978,7 +978,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
unlink_enabled = frappe.db.get_value(
|
unlink_enabled = frappe.db.get_value(
|
||||||
"Accounts Settings", "Accounts Settings",
|
"Accounts Settings", "Accounts Settings",
|
||||||
"unlink_payment_on_cancel_of_invoice")
|
"unlink_payment_on_cancel_of_invoice")
|
||||||
|
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Accounts Settings", "Accounts Settings",
|
"Accounts Settings", "Accounts Settings",
|
||||||
"unlink_payment_on_cancel_of_invoice", 1)
|
"unlink_payment_on_cancel_of_invoice", 1)
|
||||||
@ -1018,8 +1018,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
expected_gle = [
|
expected_gle = [
|
||||||
["_Test Account Cost for Goods Sold - _TC", 37500.0],
|
["_Test Account Cost for Goods Sold - _TC", 37500.0],
|
||||||
["_Test Payable USD - _TC", -40000.0],
|
["_Test Payable USD - _TC", -35000.0],
|
||||||
["Exchange Gain/Loss - _TC", 2500.0]
|
["Exchange Gain/Loss - _TC", -2500.0]
|
||||||
]
|
]
|
||||||
|
|
||||||
gl_entries = frappe.db.sql("""
|
gl_entries = frappe.db.sql("""
|
||||||
@ -1027,7 +1027,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
where voucher_no=%s
|
where voucher_no=%s
|
||||||
group by account
|
group by account
|
||||||
order by account asc""", (pi.name), as_dict=1)
|
order by account asc""", (pi.name), as_dict=1)
|
||||||
|
|
||||||
for i, gle in enumerate(gl_entries):
|
for i, gle in enumerate(gl_entries):
|
||||||
self.assertEqual(expected_gle[i][0], gle.account)
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
self.assertEqual(expected_gle[i][1], gle.balance)
|
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||||
@ -1049,8 +1049,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
expected_gle = [
|
expected_gle = [
|
||||||
["_Test Account Cost for Goods Sold - _TC", 36500.0],
|
["_Test Account Cost for Goods Sold - _TC", 36500.0],
|
||||||
["_Test Payable USD - _TC", -38000.0],
|
["_Test Payable USD - _TC", -35000.0],
|
||||||
["Exchange Gain/Loss - _TC", 1500.0]
|
["Exchange Gain/Loss - _TC", -1500.0]
|
||||||
]
|
]
|
||||||
|
|
||||||
gl_entries = frappe.db.sql("""
|
gl_entries = frappe.db.sql("""
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe, erpnext
|
||||||
|
|
||||||
import unittest, copy, time
|
import unittest, copy, time
|
||||||
from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months
|
from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months
|
||||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import WarehouseMissingError
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
|
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
|
||||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||||
@ -1073,7 +1074,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
def test_gle_made_when_asset_is_returned(self):
|
def test_gle_made_when_asset_is_returned(self):
|
||||||
create_asset_data()
|
create_asset_data()
|
||||||
asset = create_asset(item_code="Macbook Pro")
|
asset = create_asset(item_code="Macbook Pro")
|
||||||
|
|
||||||
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
|
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
|
||||||
return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
|
return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
|
||||||
|
|
||||||
@ -1081,7 +1082,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
|
# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
|
||||||
loss_for_si = frappe.get_all(
|
loss_for_si = frappe.get_all(
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
filters = {
|
filters = {
|
||||||
"voucher_no": si.name,
|
"voucher_no": si.name,
|
||||||
"account": disposal_account
|
"account": disposal_account
|
||||||
@ -1090,7 +1091,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
loss_for_return_si = frappe.get_all(
|
loss_for_return_si = frappe.get_all(
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
filters = {
|
filters = {
|
||||||
"voucher_no": return_si.name,
|
"voucher_no": return_si.name,
|
||||||
"account": disposal_account
|
"account": disposal_account
|
||||||
@ -1836,6 +1837,89 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(target_doc.company, "_Test Company 1")
|
self.assertEqual(target_doc.company, "_Test Company 1")
|
||||||
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
||||||
|
|
||||||
|
def test_inter_company_transaction_without_default_warehouse(self):
|
||||||
|
"Check mapping (expense account) of inter company SI to PI in absence of default warehouse."
|
||||||
|
# setup
|
||||||
|
old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
|
||||||
|
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||||
|
|
||||||
|
old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled('_Test Company 1')
|
||||||
|
frappe.local.enable_perpetual_inventory['_Test Company 1'] = 1
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", '_Test Company 1', "stock_received_but_not_billed", "Stock Received But Not Billed - _TC1")
|
||||||
|
frappe.db.set_value("Company", '_Test Company 1', "expenses_included_in_valuation", "Expenses Included In Valuation - _TC1")
|
||||||
|
|
||||||
|
|
||||||
|
if not frappe.db.exists("Customer", "_Test Internal Customer"):
|
||||||
|
customer = frappe.get_doc({
|
||||||
|
"customer_group": "_Test Customer Group",
|
||||||
|
"customer_name": "_Test Internal Customer",
|
||||||
|
"customer_type": "Individual",
|
||||||
|
"doctype": "Customer",
|
||||||
|
"territory": "_Test Territory",
|
||||||
|
"is_internal_customer": 1,
|
||||||
|
"represents_company": "_Test Company 1"
|
||||||
|
})
|
||||||
|
|
||||||
|
customer.append("companies", {
|
||||||
|
"company": "Wind Power LLC"
|
||||||
|
})
|
||||||
|
|
||||||
|
customer.insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
|
||||||
|
supplier = frappe.get_doc({
|
||||||
|
"supplier_group": "_Test Supplier Group",
|
||||||
|
"supplier_name": "_Test Internal Supplier",
|
||||||
|
"doctype": "Supplier",
|
||||||
|
"is_internal_supplier": 1,
|
||||||
|
"represents_company": "Wind Power LLC"
|
||||||
|
})
|
||||||
|
|
||||||
|
supplier.append("companies", {
|
||||||
|
"company": "_Test Company 1"
|
||||||
|
})
|
||||||
|
|
||||||
|
supplier.insert()
|
||||||
|
|
||||||
|
# begin test
|
||||||
|
si = create_sales_invoice(
|
||||||
|
company = "Wind Power LLC",
|
||||||
|
customer = "_Test Internal Customer",
|
||||||
|
debit_to = "Debtors - WP",
|
||||||
|
warehouse = "Stores - WP",
|
||||||
|
income_account = "Sales - WP",
|
||||||
|
expense_account = "Cost of Goods Sold - WP",
|
||||||
|
cost_center = "Main - WP",
|
||||||
|
currency = "USD",
|
||||||
|
update_stock = 1,
|
||||||
|
do_not_save = 1
|
||||||
|
)
|
||||||
|
si.selling_price_list = "_Test Price List Rest of the World"
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
|
||||||
|
|
||||||
|
# in absence of warehouse Stock Received But Not Billed is set as expense account while mapping
|
||||||
|
# mapping is not obstructed
|
||||||
|
self.assertIsNone(target_doc.items[0].warehouse)
|
||||||
|
self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1")
|
||||||
|
|
||||||
|
target_doc.items[0].update({"cost_center": "Main - _TC1"})
|
||||||
|
|
||||||
|
# missing warehouse is validated on save, after mapping
|
||||||
|
self.assertRaises(WarehouseMissingError, target_doc.save)
|
||||||
|
|
||||||
|
target_doc.items[0].update({"warehouse": "Stores - _TC1"})
|
||||||
|
target_doc.save()
|
||||||
|
|
||||||
|
# after warehouse is set, linked account or default inventory account is set
|
||||||
|
self.assertEqual(target_doc.items[0].expense_account, 'Stock In Hand - _TC1')
|
||||||
|
|
||||||
|
# tear down
|
||||||
|
frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory
|
||||||
|
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
|
||||||
|
|
||||||
def test_internal_transfer_gl_entry(self):
|
def test_internal_transfer_gl_entry(self):
|
||||||
## Create internal transfer account
|
## Create internal transfer account
|
||||||
account = create_account(account_name="Unrealized Profit",
|
account = create_account(account_name="Unrealized Profit",
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
"base_tax_amount",
|
"base_tax_amount",
|
||||||
"base_total",
|
"base_total",
|
||||||
"base_tax_amount_after_discount_amount",
|
"base_tax_amount_after_discount_amount",
|
||||||
"item_wise_tax_detail"
|
"item_wise_tax_detail",
|
||||||
|
"dont_recompute_tax"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -200,13 +201,22 @@
|
|||||||
"fieldname": "included_in_paid_amount",
|
"fieldname": "included_in_paid_amount",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Considered In Paid Amount"
|
"label": "Considered In Paid Amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "dont_recompute_tax",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Dont Recompute tax",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-06-14 01:44:36.899147",
|
"modified": "2021-07-27 12:40:59.051803",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Taxes and Charges",
|
"name": "Sales Taxes and Charges",
|
||||||
|
@ -1,263 +1,151 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"allow_import": 1,
|
||||||
"allow_import": 1,
|
"allow_rename": 1,
|
||||||
"allow_rename": 1,
|
"autoname": "Prompt",
|
||||||
"autoname": "Prompt",
|
"creation": "2018-04-13 18:42:06.431683",
|
||||||
"beta": 0,
|
"doctype": "DocType",
|
||||||
"creation": "2018-04-13 18:42:06.431683",
|
"editable_grid": 1,
|
||||||
"custom": 0,
|
"engine": "InnoDB",
|
||||||
"docstatus": 0,
|
"field_order": [
|
||||||
"doctype": "DocType",
|
"category_details_section",
|
||||||
"document_type": "",
|
"category_name",
|
||||||
"editable_grid": 1,
|
"round_off_tax_amount",
|
||||||
"engine": "InnoDB",
|
"column_break_2",
|
||||||
|
"consider_party_ledger_amount",
|
||||||
|
"tax_on_excess_amount",
|
||||||
|
"section_break_8",
|
||||||
|
"rates",
|
||||||
|
"section_break_7",
|
||||||
|
"accounts"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "category_name",
|
"fieldname": "category_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Category Name",
|
"label": "Category Name",
|
||||||
"length": 0,
|
"show_days": 1,
|
||||||
"no_copy": 0,
|
"show_seconds": 1
|
||||||
"permlevel": 0,
|
},
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Tax Withholding Rates",
|
"label": "Tax Withholding Rates",
|
||||||
"length": 0,
|
"show_days": 1,
|
||||||
"no_copy": 0,
|
"show_seconds": 1
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "rates",
|
"fieldname": "rates",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Rates",
|
"label": "Rates",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Tax Withholding Rate",
|
"options": "Tax Withholding Rate",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"show_days": 1,
|
||||||
"set_only_once": 0,
|
"show_seconds": 1
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "section_break_7",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break",
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_7",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Account Details",
|
"label": "Account Details",
|
||||||
"length": 0,
|
"show_days": 1,
|
||||||
"no_copy": 0,
|
"show_seconds": 1
|
||||||
"permlevel": 0,
|
},
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "accounts",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Table",
|
||||||
"allow_on_submit": 0,
|
"label": "Accounts",
|
||||||
"bold": 0,
|
"options": "Tax Withholding Account",
|
||||||
"collapsible": 0,
|
"reqd": 1,
|
||||||
"columns": 0,
|
"show_days": 1,
|
||||||
"fieldname": "accounts",
|
"show_seconds": 1
|
||||||
"fieldtype": "Table",
|
},
|
||||||
"hidden": 0,
|
{
|
||||||
"ignore_user_permissions": 0,
|
"fieldname": "category_details_section",
|
||||||
"ignore_xss_filter": 0,
|
"fieldtype": "Section Break",
|
||||||
"in_filter": 0,
|
"label": "Category Details",
|
||||||
"in_global_search": 0,
|
"show_days": 1,
|
||||||
"in_list_view": 0,
|
"show_seconds": 1
|
||||||
"in_standard_filter": 0,
|
},
|
||||||
"label": "Accounts",
|
{
|
||||||
"length": 0,
|
"fieldname": "column_break_2",
|
||||||
"no_copy": 0,
|
"fieldtype": "Column Break",
|
||||||
"options": "Tax Withholding Account",
|
"show_days": 1,
|
||||||
"permlevel": 0,
|
"show_seconds": 1
|
||||||
"precision": "",
|
},
|
||||||
"print_hide": 0,
|
{
|
||||||
"print_hide_if_no_value": 0,
|
"default": "0",
|
||||||
"read_only": 0,
|
"description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
|
||||||
"remember_last_selected_value": 0,
|
"fieldname": "consider_party_ledger_amount",
|
||||||
"report_hide": 0,
|
"fieldtype": "Check",
|
||||||
"reqd": 1,
|
"label": "Consider Entire Party Ledger Amount",
|
||||||
"search_index": 0,
|
"show_days": 1,
|
||||||
"set_only_once": 0,
|
"show_seconds": 1
|
||||||
"translatable": 0,
|
},
|
||||||
"unique": 0
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Tax will be withheld only for amount exceeding the cumulative threshold",
|
||||||
|
"fieldname": "tax_on_excess_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Only Deduct Tax On Excess Amount ",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Checking this will round off the tax amount to the nearest integer",
|
||||||
|
"fieldname": "round_off_tax_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Round Off Tax Amount",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
"links": [],
|
||||||
"hide_toolbar": 0,
|
"modified": "2021-07-27 21:47:34.396071",
|
||||||
"idx": 0,
|
"modified_by": "Administrator",
|
||||||
"image_view": 0,
|
"module": "Accounts",
|
||||||
"in_create": 0,
|
"name": "Tax Withholding Category",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-07-17 22:53:26.193179",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Tax Withholding Category",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"export": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 0,
|
"role": "System Manager",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"export": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 0,
|
"role": "Accounts Manager",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"export": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 0,
|
"role": "Accounts User",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"sort_field": "modified",
|
||||||
"read_only": 0,
|
"sort_order": "DESC",
|
||||||
"read_only_onload": 0,
|
"track_changes": 1
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ 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 frappe.utils import flt, getdate
|
from frappe.utils import flt, getdate, cint
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
class TaxWithholdingCategory(Document):
|
class TaxWithholdingCategory(Document):
|
||||||
@ -86,7 +86,10 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
|
|||||||
"rate": tax_rate_detail.tax_withholding_rate,
|
"rate": tax_rate_detail.tax_withholding_rate,
|
||||||
"threshold": tax_rate_detail.single_threshold,
|
"threshold": tax_rate_detail.single_threshold,
|
||||||
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
|
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
|
||||||
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
|
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
|
||||||
|
"consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
|
||||||
|
"tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
|
||||||
|
"round_off_tax_amount": tax_withholding.round_off_tax_amount
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_tax_withholding_rates(tax_withholding, fiscal_year):
|
def get_tax_withholding_rates(tax_withholding, fiscal_year):
|
||||||
@ -145,6 +148,7 @@ def get_lower_deduction_certificate(fiscal_year, pan_no):
|
|||||||
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
|
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
|
||||||
fiscal_year = fiscal_year_details[0]
|
fiscal_year = fiscal_year_details[0]
|
||||||
|
|
||||||
|
|
||||||
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||||
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||||
taxable_vouchers = vouchers + advance_vouchers
|
taxable_vouchers = vouchers + advance_vouchers
|
||||||
@ -235,10 +239,18 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
|
|||||||
|
|
||||||
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
|
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
|
||||||
tds_amount = 0
|
tds_amount = 0
|
||||||
|
invoice_filters = {
|
||||||
|
'name': ('in', vouchers),
|
||||||
|
'docstatus': 1
|
||||||
|
}
|
||||||
|
|
||||||
supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
|
field = 'sum(net_total)'
|
||||||
'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
|
|
||||||
}, 'sum(net_total)') or 0.0
|
if not cint(tax_details.consider_party_ledger_amount):
|
||||||
|
invoice_filters.update({'apply_tds': 1})
|
||||||
|
field = 'sum(grand_total)'
|
||||||
|
|
||||||
|
supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
|
||||||
|
|
||||||
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
|
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
|
||||||
'parent': ('in', vouchers), 'docstatus': 1,
|
'parent': ('in', vouchers), 'docstatus': 1,
|
||||||
@ -255,6 +267,13 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
|||||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||||
|
|
||||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||||
|
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount):
|
||||||
|
# Get net total again as TDS is calculated on net total
|
||||||
|
# Grand is used to just check for threshold breach
|
||||||
|
net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0
|
||||||
|
net_total += inv.net_total
|
||||||
|
supp_credit_amt = net_total - cumulative_threshold
|
||||||
|
|
||||||
if ldc and is_valid_certificate(
|
if ldc and is_valid_certificate(
|
||||||
ldc.valid_from, ldc.valid_upto,
|
ldc.valid_from, ldc.valid_upto,
|
||||||
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
|
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
|
||||||
@ -263,6 +282,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
|||||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||||
else:
|
else:
|
||||||
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
|
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
|
||||||
|
|
||||||
|
if cint(tax_details.round_off_tax_amount):
|
||||||
|
tds_amount = round(tds_amount)
|
||||||
|
|
||||||
return tds_amount
|
return tds_amount
|
||||||
|
|
||||||
|
@ -87,6 +87,31 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
|||||||
for d in invoices:
|
for d in invoices:
|
||||||
d.cancel()
|
d.cancel()
|
||||||
|
|
||||||
|
def test_tax_withholding_category_checks(self):
|
||||||
|
invoices = []
|
||||||
|
frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
|
||||||
|
|
||||||
|
# First Invoice with no tds check
|
||||||
|
pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True)
|
||||||
|
pi.apply_tds = 0
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
invoices.append(pi)
|
||||||
|
|
||||||
|
# Second Invoice will apply TDS checked
|
||||||
|
pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000)
|
||||||
|
pi1.submit()
|
||||||
|
invoices.append(pi1)
|
||||||
|
|
||||||
|
# Cumulative threshold is 30000
|
||||||
|
# Threshold calculation should be on both the invoices
|
||||||
|
# TDS should be applied only on 1000
|
||||||
|
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
|
||||||
|
|
||||||
|
for d in invoices:
|
||||||
|
d.cancel()
|
||||||
|
|
||||||
|
|
||||||
def test_cumulative_threshold_tcs(self):
|
def test_cumulative_threshold_tcs(self):
|
||||||
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
|
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
|
||||||
invoices = []
|
invoices = []
|
||||||
@ -195,7 +220,7 @@ def create_sales_invoice(**args):
|
|||||||
|
|
||||||
def create_records():
|
def create_records():
|
||||||
# create a new suppliers
|
# create a new suppliers
|
||||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
|
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']:
|
||||||
if frappe.db.exists('Supplier', name):
|
if frappe.db.exists('Supplier', name):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -311,3 +336,23 @@ def create_tax_with_holding_category():
|
|||||||
'account': 'TDS - _TC'
|
'account': 'TDS - _TC'
|
||||||
}]
|
}]
|
||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
|
||||||
|
frappe.get_doc({
|
||||||
|
"doctype": "Tax Withholding Category",
|
||||||
|
"name": "New TDS Category",
|
||||||
|
"category_name": "New TDS Category",
|
||||||
|
"round_off_tax_amount": 1,
|
||||||
|
"consider_party_ledger_amount": 1,
|
||||||
|
"tax_on_excess_amount": 1,
|
||||||
|
"rates": [{
|
||||||
|
'fiscal_year': fiscal_year,
|
||||||
|
'tax_withholding_rate': 10,
|
||||||
|
'single_threshold': 0,
|
||||||
|
'cumulative_threshold': 30000
|
||||||
|
}],
|
||||||
|
"accounts": [{
|
||||||
|
'company': '_Test Company',
|
||||||
|
'account': 'TDS - _TC'
|
||||||
|
}]
|
||||||
|
}).insert()
|
||||||
|
@ -99,7 +99,6 @@ class ReceivablePayableReport(object):
|
|||||||
voucher_no = gle.voucher_no,
|
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'
|
||||||
|
@ -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)
|
||||||
|
@ -566,10 +566,10 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
|
|||||||
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
|
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_company_default(company, fieldname):
|
def get_company_default(company, fieldname, ignore_validation=False):
|
||||||
value = frappe.get_cached_value('Company', company, fieldname)
|
value = frappe.get_cached_value('Company', company, fieldname)
|
||||||
|
|
||||||
if not value:
|
if not ignore_validation and not value:
|
||||||
throw(_("Please set default {0} in Company {1}")
|
throw(_("Please set default {0} in Company {1}")
|
||||||
.format(frappe.get_meta("Company").get_label(fieldname), company))
|
.format(frappe.get_meta("Company").get_label(fieldname), company))
|
||||||
|
|
||||||
@ -966,7 +966,7 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
|
|||||||
for e in existing_gle:
|
for e in existing_gle:
|
||||||
if entry.account == e.account:
|
if entry.account == e.account:
|
||||||
account_existed = True
|
account_existed = True
|
||||||
if (entry.account == e.account and entry.against_account == e.against_account
|
if (entry.account == e.account
|
||||||
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
|
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
|
||||||
and ( flt(entry.debit, precision) != flt(e.debit, precision) or
|
and ( flt(entry.debit, precision) != flt(e.debit, precision) or
|
||||||
flt(entry.credit, precision) != flt(e.credit, precision))):
|
flt(entry.credit, precision) != flt(e.credit, precision))):
|
||||||
|
39
erpnext/change_log/v13/v13_8_0.md
Normal file
39
erpnext/change_log/v13/v13_8_0.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Version 13.8.0 Release Notes
|
||||||
|
|
||||||
|
### Features & Enhancements
|
||||||
|
- Report to show COGS by item groups ([#26222](https://github.com/frappe/erpnext/pull/26222))
|
||||||
|
- Enhancements in TDS ([#26677](https://github.com/frappe/erpnext/pull/26677))
|
||||||
|
- API Endpoint to update halted Razorpay subscriptions ([#26564](https://github.com/frappe/erpnext/pull/26564))
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Incorrect bom name ([#26600](https://github.com/frappe/erpnext/pull/26600))
|
||||||
|
- Exchange rate revaluation posting date and precision fixes ([#26651](https://github.com/frappe/erpnext/pull/26651))
|
||||||
|
- POS item cart dom updates ([#26460](https://github.com/frappe/erpnext/pull/26460))
|
||||||
|
- General Ledger report not working with filter group by ([#26439](https://github.com/frappe/erpnext/pull/26438))
|
||||||
|
- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206))
|
||||||
|
- Validation check for batch for stock reconciliation type in stock entry ([#26487](https://github.com/frappe/erpnext/pull/26487))
|
||||||
|
- Improved UX for additional discount field ([#26502](https://github.com/frappe/erpnext/pull/26502))
|
||||||
|
- Add missing cess amount in GSTR-3B report ([#26644](https://github.com/frappe/erpnext/pull/26644))
|
||||||
|
- Optimized code for reposting item valuation ([#26431](https://github.com/frappe/erpnext/pull/26431))
|
||||||
|
- FG item not fetched in manufacture entry ([#26508](https://github.com/frappe/erpnext/pull/26508))
|
||||||
|
- Errors on parallel requests creation of company for India ([#26420](https://github.com/frappe/erpnext/pull/26420))
|
||||||
|
- Incorrect valuation rate calculation in gross profit report ([#26558](https://github.com/frappe/erpnext/pull/26558))
|
||||||
|
- Empty "against account" in Purchase Receipt GLE ([#26712](https://github.com/frappe/erpnext/pull/26712))
|
||||||
|
- Remove cancelled entries from Stock and Account Value comparison report ([#26721](https://github.com/frappe/erpnext/pull/26721))
|
||||||
|
- Remove manual permission checking ([#26691](https://github.com/frappe/erpnext/pull/26691))
|
||||||
|
- Delete child docs when parent doc is deleted ([#26518](https://github.com/frappe/erpnext/pull/26518))
|
||||||
|
- GST Reports timeout issue ([#26646](https://github.com/frappe/erpnext/pull/26646))
|
||||||
|
- Parent condition in pricing rules ([#26727](https://github.com/frappe/erpnext/pull/26727))
|
||||||
|
- Added Company filters for Loan ([#26294](https://github.com/frappe/erpnext/pull/26294))
|
||||||
|
- Incorrect discount amount on amended document ([#26292](https://github.com/frappe/erpnext/pull/26292))
|
||||||
|
- Exchange gain loss not set for advances linked with invoices ([#26436](https://github.com/frappe/erpnext/pull/26436))
|
||||||
|
- Unallocated amount in Payment Entry after taxes ([#26412](https://github.com/frappe/erpnext/pull/26412))
|
||||||
|
- Wrong operation time in Work Order ([#26613](https://github.com/frappe/erpnext/pull/26613))
|
||||||
|
- Serial No and Batch validation ([#26614](https://github.com/frappe/erpnext/pull/26614))
|
||||||
|
- Gl Entries for exchange gain loss ([#26734](https://github.com/frappe/erpnext/pull/26734))
|
||||||
|
- TDS computation summary shows cancelled invoices ([#26485](https://github.com/frappe/erpnext/pull/26485))
|
||||||
|
- Price List rate not fetched for return sales invoice fixed ([#26560](https://github.com/frappe/erpnext/pull/26560))
|
||||||
|
- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576))
|
||||||
|
- Ignore mandatory fields while creating payment reconciliation Journal Entry ([#26643](https://github.com/frappe/erpnext/pull/26643))
|
||||||
|
- Unable to download GSTR-1 json ([#26418](https://github.com/frappe/erpnext/pull/26418))
|
||||||
|
- Paging buttons not working on item group portal page ([#26498](https://github.com/frappe/erpnext/pull/26498))
|
@ -674,19 +674,24 @@ class AccountsController(TransactionBase):
|
|||||||
if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
|
if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
|
||||||
for d in self.get("advances"):
|
for d in self.get("advances"):
|
||||||
if d.exchange_gain_loss:
|
if d.exchange_gain_loss:
|
||||||
party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
|
is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
|
||||||
party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
|
party = self.supplier if is_purchase_invoice else self.customer
|
||||||
party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
|
party_account = self.credit_to if is_purchase_invoice else self.debit_to
|
||||||
|
party_type = "Supplier" if is_purchase_invoice else "Customer"
|
||||||
|
|
||||||
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
|
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
|
||||||
|
if not gain_loss_account:
|
||||||
|
frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
|
||||||
|
.format(self.get('company')))
|
||||||
account_currency = get_account_currency(gain_loss_account)
|
account_currency = get_account_currency(gain_loss_account)
|
||||||
if account_currency != self.company_currency:
|
if account_currency != self.company_currency:
|
||||||
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
|
frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
|
||||||
|
|
||||||
# for purchase
|
# for purchase
|
||||||
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
|
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
|
||||||
# just reverse for sales?
|
if not is_purchase_invoice:
|
||||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
# just reverse for sales?
|
||||||
|
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||||
|
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
@ -904,9 +909,9 @@ class AccountsController(TransactionBase):
|
|||||||
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
|
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
|
||||||
.format(item.item_code, item.idx, max_allowed_amt))
|
.format(item.item_code, item.idx, max_allowed_amt))
|
||||||
|
|
||||||
def get_company_default(self, fieldname):
|
def get_company_default(self, fieldname, ignore_validation=False):
|
||||||
from erpnext.accounts.utils import get_company_default
|
from erpnext.accounts.utils import get_company_default
|
||||||
return get_company_default(self.company, fieldname)
|
return get_company_default(self.company, fieldname, ignore_validation=ignore_validation)
|
||||||
|
|
||||||
def get_stock_items(self):
|
def get_stock_items(self):
|
||||||
stock_items = []
|
stock_items = []
|
||||||
@ -1112,8 +1117,11 @@ class AccountsController(TransactionBase):
|
|||||||
for d in self.get("payment_schedule"):
|
for d in self.get("payment_schedule"):
|
||||||
if d.invoice_portion:
|
if d.invoice_portion:
|
||||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
|
||||||
d.outstanding = d.payment_amount
|
d.outstanding = d.payment_amount
|
||||||
|
elif not d.invoice_portion:
|
||||||
|
d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
|
||||||
|
|
||||||
|
|
||||||
def set_due_date(self):
|
def set_due_date(self):
|
||||||
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
|
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
|
||||||
|
127
erpnext/controllers/employee_boarding_controller.py
Normal file
127
erpnext/controllers/employee_boarding_controller.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.desk.form import assign_to
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import flt, unique
|
||||||
|
|
||||||
|
class EmployeeBoardingController(Document):
|
||||||
|
'''
|
||||||
|
Create the project and the task for the boarding process
|
||||||
|
Assign to the concerned person and roles as per the onboarding/separation template
|
||||||
|
'''
|
||||||
|
def validate(self):
|
||||||
|
# remove the task if linked before submitting the form
|
||||||
|
if self.amended_from:
|
||||||
|
for activity in self.activities:
|
||||||
|
activity.task = ''
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
# create the project for the given employee onboarding
|
||||||
|
project_name = _(self.doctype) + ' : '
|
||||||
|
if self.doctype == 'Employee Onboarding':
|
||||||
|
project_name += self.job_applicant
|
||||||
|
else:
|
||||||
|
project_name += self.employee
|
||||||
|
|
||||||
|
project = frappe.get_doc({
|
||||||
|
'doctype': 'Project',
|
||||||
|
'project_name': project_name,
|
||||||
|
'expected_start_date': self.date_of_joining if self.doctype == 'Employee Onboarding' else self.resignation_letter_date,
|
||||||
|
'department': self.department,
|
||||||
|
'company': self.company
|
||||||
|
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||||
|
|
||||||
|
self.db_set('project', project.name)
|
||||||
|
self.db_set('boarding_status', 'Pending')
|
||||||
|
self.reload()
|
||||||
|
self.create_task_and_notify_user()
|
||||||
|
|
||||||
|
def create_task_and_notify_user(self):
|
||||||
|
# create the task for the given project and assign to the concerned person
|
||||||
|
for activity in self.activities:
|
||||||
|
if activity.task:
|
||||||
|
continue
|
||||||
|
|
||||||
|
task = frappe.get_doc({
|
||||||
|
'doctype': 'Task',
|
||||||
|
'project': self.project,
|
||||||
|
'subject': activity.activity_name + ' : ' + self.employee_name,
|
||||||
|
'description': activity.description,
|
||||||
|
'department': self.department,
|
||||||
|
'company': self.company,
|
||||||
|
'task_weight': activity.task_weight
|
||||||
|
}).insert(ignore_permissions=True)
|
||||||
|
activity.db_set('task', task.name)
|
||||||
|
|
||||||
|
users = [activity.user] if activity.user else []
|
||||||
|
if activity.role:
|
||||||
|
user_list = frappe.db.sql_list('''
|
||||||
|
SELECT
|
||||||
|
DISTINCT(has_role.parent)
|
||||||
|
FROM
|
||||||
|
`tabHas Role` has_role
|
||||||
|
LEFT JOIN `tabUser` user
|
||||||
|
ON has_role.parent = user.name
|
||||||
|
WHERE
|
||||||
|
has_role.parenttype = 'User'
|
||||||
|
AND user.enabled = 1
|
||||||
|
AND has_role.role = %s
|
||||||
|
''', activity.role)
|
||||||
|
users = unique(users + user_list)
|
||||||
|
|
||||||
|
if 'Administrator' in users:
|
||||||
|
users.remove('Administrator')
|
||||||
|
|
||||||
|
# assign the task the users
|
||||||
|
if users:
|
||||||
|
self.assign_task_to_users(task, users)
|
||||||
|
|
||||||
|
def assign_task_to_users(self, task, users):
|
||||||
|
for user in users:
|
||||||
|
args = {
|
||||||
|
'assign_to': [user],
|
||||||
|
'doctype': task.doctype,
|
||||||
|
'name': task.name,
|
||||||
|
'description': task.description or task.subject,
|
||||||
|
'notify': self.notify_users_by_email
|
||||||
|
}
|
||||||
|
assign_to.add(args)
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
# delete task project
|
||||||
|
for task in frappe.get_all('Task', filters={'project': self.project}):
|
||||||
|
frappe.delete_doc('Task', task.name, force=1)
|
||||||
|
frappe.delete_doc('Project', self.project, force=1)
|
||||||
|
self.db_set('project', '')
|
||||||
|
for activity in self.activities:
|
||||||
|
activity.db_set('task', '')
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_onboarding_details(parent, parenttype):
|
||||||
|
return frappe.get_all('Employee Boarding Activity',
|
||||||
|
fields=['activity_name', 'role', 'user', 'required_for_employee_creation', 'description', 'task_weight'],
|
||||||
|
filters={'parent': parent, 'parenttype': parenttype},
|
||||||
|
order_by= 'idx')
|
||||||
|
|
||||||
|
|
||||||
|
def update_employee_boarding_status(project):
|
||||||
|
employee_onboarding = frappe.db.exists('Employee Onboarding', {'project': project.name})
|
||||||
|
employee_separation = frappe.db.exists('Employee Separation', {'project': project.name})
|
||||||
|
|
||||||
|
if not (employee_onboarding or employee_separation):
|
||||||
|
return
|
||||||
|
|
||||||
|
status = 'Pending'
|
||||||
|
if flt(project.percent_complete) > 0.0 and flt(project.percent_complete) < 100.0:
|
||||||
|
status = 'In Process'
|
||||||
|
elif flt(project.percent_complete) == 100.0:
|
||||||
|
status = 'Completed'
|
||||||
|
|
||||||
|
if employee_onboarding:
|
||||||
|
frappe.db.set_value('Employee Onboarding', employee_onboarding, 'boarding_status', status)
|
||||||
|
elif employee_separation:
|
||||||
|
frappe.db.set_value('Employee Separation', employee_separation, 'boarding_status', status)
|
@ -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")
|
||||||
|
@ -152,7 +152,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
validate_taxes_and_charges(tax)
|
validate_taxes_and_charges(tax)
|
||||||
validate_inclusive_tax(tax, self.doc)
|
validate_inclusive_tax(tax, self.doc)
|
||||||
|
|
||||||
if not self.doc.get('is_consolidated'):
|
if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
|
||||||
tax.item_wise_tax_detail = {}
|
tax.item_wise_tax_detail = {}
|
||||||
|
|
||||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||||
@ -347,7 +347,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
elif tax.charge_type == "On Item Quantity":
|
elif tax.charge_type == "On Item Quantity":
|
||||||
current_tax_amount = tax_rate * item.qty
|
current_tax_amount = tax_rate * item.qty
|
||||||
|
|
||||||
if not self.doc.get("is_consolidated"):
|
if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
|
||||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||||
|
|
||||||
return current_tax_amount
|
return current_tax_amount
|
||||||
@ -455,7 +455,8 @@ class calculate_taxes_and_totals(object):
|
|||||||
def _cleanup(self):
|
def _cleanup(self):
|
||||||
if not self.doc.get('is_consolidated'):
|
if not self.doc.get('is_consolidated'):
|
||||||
for tax in self.doc.get("taxes"):
|
for tax in self.doc.get("taxes"):
|
||||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
if not tax.get("dont_recompute_tax"):
|
||||||
|
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||||
|
|
||||||
def set_discount_amount(self):
|
def set_discount_amount(self):
|
||||||
if self.doc.additional_discount_percentage:
|
if self.doc.additional_discount_percentage:
|
||||||
|
0
erpnext/crm/doctype/campaign/__init__.py
Normal file
0
erpnext/crm/doctype/campaign/__init__.py
Normal file
17
erpnext/crm/doctype/campaign/campaign.js
Normal file
17
erpnext/crm/doctype/campaign/campaign.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Campaign', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
erpnext.toggle_naming_series();
|
||||||
|
|
||||||
|
if (frm.doc.__islocal) {
|
||||||
|
frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
|
||||||
|
} else {
|
||||||
|
cur_frm.add_custom_button(__("View Leads"), function() {
|
||||||
|
frappe.route_options = {"source": "Campaign", "campaign_name": frm.doc.name};
|
||||||
|
frappe.set_route("List", "Lead");
|
||||||
|
}, "fa fa-list", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
@ -39,17 +40,9 @@
|
|||||||
"set_only_once": 1
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "description",
|
"fieldname": "campaign_schedules_section",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Section Break",
|
||||||
"in_list_view": 1,
|
"label": "Campaign Schedules"
|
||||||
"label": "Description",
|
|
||||||
"oldfieldname": "description",
|
|
||||||
"oldfieldtype": "Text",
|
|
||||||
"width": "300px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "description_section",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "campaign_schedules",
|
"fieldname": "campaign_schedules",
|
||||||
@ -58,16 +51,25 @@
|
|||||||
"options": "Campaign Email Schedule"
|
"options": "Campaign Email Schedule"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "campaign_schedules_section",
|
"fieldname": "description_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"label": "Campaign Schedules"
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Description",
|
||||||
|
"oldfieldname": "description",
|
||||||
|
"oldfieldtype": "Text",
|
||||||
|
"width": "300px"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-bullhorn",
|
"icon": "fa fa-bullhorn",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2019-07-22 12:03:39.832342",
|
"links": [],
|
||||||
|
"modified": "2021-06-30 18:05:06.412712",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "CRM",
|
||||||
"name": "Campaign",
|
"name": "Campaign",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
@ -1,9 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.naming import set_name_by_naming_series
|
from frappe.model.naming import set_name_by_naming_series
|
||||||
|
|
8
erpnext/crm/doctype/campaign/test_campaign.py
Normal file
8
erpnext/crm/doctype/campaign/test_campaign.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestCampaign(unittest.TestCase):
|
||||||
|
pass
|
@ -34,11 +34,14 @@ def enroll_student(source_name):
|
|||||||
}
|
}
|
||||||
}}, ignore_permissions=True)
|
}}, ignore_permissions=True)
|
||||||
student.save()
|
student.save()
|
||||||
|
|
||||||
|
student_applicant = frappe.db.get_value("Student Applicant", source_name,
|
||||||
|
["student_category", "program"], as_dict=True)
|
||||||
program_enrollment = frappe.new_doc("Program Enrollment")
|
program_enrollment = frappe.new_doc("Program Enrollment")
|
||||||
program_enrollment.student = student.name
|
program_enrollment.student = student.name
|
||||||
program_enrollment.student_category = student.student_category
|
program_enrollment.student_category = student_applicant.student_category
|
||||||
program_enrollment.student_name = student.title
|
program_enrollment.student_name = student.title
|
||||||
program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
|
program_enrollment.program = student_applicant.program
|
||||||
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
|
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
|
||||||
return program_enrollment
|
return program_enrollment
|
||||||
|
|
||||||
|
@ -1,195 +1,68 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"creation": "2016-06-10 03:29:02.539914",
|
||||||
"allow_import": 0,
|
"doctype": "DocType",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"beta": 0,
|
"engine": "InnoDB",
|
||||||
"creation": "2016-06-10 03:29:02.539914",
|
"field_order": [
|
||||||
"custom": 0,
|
"student_applicant",
|
||||||
"docstatus": 0,
|
"student",
|
||||||
"doctype": "DocType",
|
"student_name",
|
||||||
"document_type": "",
|
"column_break_3",
|
||||||
"editable_grid": 1,
|
"student_batch_name",
|
||||||
|
"student_category"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "student_applicant",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Student Applicant",
|
||||||
"columns": 0,
|
"options": "Student Applicant"
|
||||||
"depends_on": "",
|
},
|
||||||
"fieldname": "student_applicant",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Student Applicant",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Student Applicant",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "student",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Student",
|
||||||
"columns": 0,
|
"options": "Student"
|
||||||
"depends_on": "",
|
},
|
||||||
"fieldname": "student",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Student",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Student",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_3",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Column Break"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "student_name",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Data",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Student Name",
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"fieldname": "student_name",
|
},
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Student Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "student_batch_name",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Student Batch Name",
|
||||||
"columns": 0,
|
"options": "Student Batch Name"
|
||||||
"fieldname": "student_batch_name",
|
},
|
||||||
"fieldtype": "Link",
|
{
|
||||||
"hidden": 0,
|
"fieldname": "student_category",
|
||||||
"ignore_user_permissions": 0,
|
"fieldtype": "Link",
|
||||||
"ignore_xss_filter": 0,
|
"label": "Student Category",
|
||||||
"in_filter": 0,
|
"options": "Student Category",
|
||||||
"in_global_search": 0,
|
"read_only": 1
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Student Batch Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Student Batch Name",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"istable": 1,
|
||||||
"hide_heading": 0,
|
"links": [],
|
||||||
"hide_toolbar": 0,
|
"modified": "2021-07-29 18:19:54.471594",
|
||||||
"idx": 0,
|
"modified_by": "Administrator",
|
||||||
"image_view": 0,
|
"module": "Education",
|
||||||
"in_create": 0,
|
"name": "Program Enrollment Tool Student",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
"permissions": [],
|
||||||
"istable": 1,
|
"quick_entry": 1,
|
||||||
"max_attachments": 0,
|
"restrict_to_domain": "Education",
|
||||||
"modified": "2018-01-02 12:03:53.890741",
|
"sort_field": "modified",
|
||||||
"modified_by": "Administrator",
|
"sort_order": "DESC"
|
||||||
"module": "Education",
|
|
||||||
"name": "Program Enrollment Tool Student",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"restrict_to_domain": "Education",
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -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,11 +19,11 @@ 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):
|
||||||
self.ignore_linked_doctypes = ('GL Entry')
|
self.ignore_linked_doctypes = ('GL Entry')
|
||||||
self.set_status()
|
|
||||||
|
|
||||||
def set_status(self):
|
def set_status(self):
|
||||||
if self.docstatus == 0:
|
if self.docstatus == 0:
|
||||||
@ -183,9 +184,9 @@ def make_return_entry(employee, company, employee_advance_name, return_amount,
|
|||||||
bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
||||||
if not bank_cash_account:
|
if not bank_cash_account:
|
||||||
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
|
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
|
||||||
|
|
||||||
advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
|
advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
|
||||||
|
|
||||||
je = frappe.new_doc('Journal Entry')
|
je = frappe.new_doc('Journal Entry')
|
||||||
je.posting_date = nowdate()
|
je.posting_date = nowdate()
|
||||||
je.voucher_type = get_voucher_type(mode_of_payment)
|
je.voucher_type = get_voucher_type(mode_of_payment)
|
||||||
@ -229,4 +230,4 @@ def get_voucher_type(mode_of_payment=None):
|
|||||||
if mode_of_payment_type == "Bank":
|
if mode_of_payment_type == "Bank":
|
||||||
voucher_type = "Bank Entry"
|
voucher_type = "Bank Entry"
|
||||||
|
|
||||||
return voucher_type
|
return voucher_type
|
||||||
|
@ -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()
|
||||||
|
|
||||||
@ -122,7 +124,7 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
|
|||||||
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
||||||
"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
|
"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
|
||||||
Zero is returned for all invalid cases.
|
Zero is returned for all invalid cases.
|
||||||
|
|
||||||
:param logs: The List of 'Employee Checkin'.
|
:param logs: The List of 'Employee Checkin'.
|
||||||
:param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin'
|
:param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin'
|
||||||
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
|
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
|
||||||
|
@ -50,28 +50,13 @@ frappe.ui.form.on('Employee Onboarding', {
|
|||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
}
|
}
|
||||||
if (frm.doc.docstatus === 1 && frm.doc.project) {
|
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.hr.utils.get_boarding_status",
|
|
||||||
args: {
|
|
||||||
"project": frm.doc.project
|
|
||||||
},
|
|
||||||
callback: function(r) {
|
|
||||||
if (r.message) {
|
|
||||||
frm.set_value('boarding_status', r.message);
|
|
||||||
}
|
|
||||||
refresh_field("boarding_status");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
employee_onboarding_template: function(frm) {
|
employee_onboarding_template: function(frm) {
|
||||||
frm.set_value("activities" ,"");
|
frm.set_value("activities" ,"");
|
||||||
if (frm.doc.employee_onboarding_template) {
|
if (frm.doc.employee_onboarding_template) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.hr.utils.get_onboarding_details",
|
method: "erpnext.controllers.employee_boarding_controller.get_onboarding_details",
|
||||||
args: {
|
args: {
|
||||||
"parent": frm.doc.employee_onboarding_template,
|
"parent": frm.doc.employee_onboarding_template,
|
||||||
"parenttype": "Employee Onboarding Template"
|
"parenttype": "Employee Onboarding Template"
|
||||||
|
@ -30,18 +30,14 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Job Applicant",
|
"label": "Job Applicant",
|
||||||
"options": "Job Applicant",
|
"options": "Job Applicant",
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "job_offer",
|
"fieldname": "job_offer",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Job Offer",
|
"label": "Job Offer",
|
||||||
"options": "Job Offer",
|
"options": "Job Offer",
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "job_applicant.applicant_name",
|
"fetch_from": "job_applicant.applicant_name",
|
||||||
@ -49,116 +45,90 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Employee Name",
|
"label": "Employee Name",
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "employee",
|
"fieldname": "employee",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Employee",
|
"label": "Employee",
|
||||||
"options": "Employee",
|
"options": "Employee",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "date_of_joining",
|
"fieldname": "date_of_joining",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Date of Joining",
|
"label": "Date of Joining"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
|
"default": "Pending",
|
||||||
"fieldname": "boarding_status",
|
"fieldname": "boarding_status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "\nPending\nIn Process\nCompleted",
|
"options": "Pending\nIn Process\nCompleted",
|
||||||
"show_days": 1,
|
"read_only": 1
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "notify_users_by_email",
|
"fieldname": "notify_users_by_email",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Notify users by email",
|
"label": "Notify users by email"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_7",
|
"fieldname": "column_break_7",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "employee_onboarding_template",
|
"fieldname": "employee_onboarding_template",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Employee Onboarding Template",
|
"label": "Employee Onboarding Template",
|
||||||
"options": "Employee Onboarding Template",
|
"options": "Employee Onboarding Template"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company",
|
"options": "Company"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "department",
|
"fieldname": "department",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Department",
|
"label": "Department",
|
||||||
"options": "Department",
|
"options": "Department"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "designation",
|
"fieldname": "designation",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Designation",
|
"label": "Designation",
|
||||||
"options": "Designation",
|
"options": "Designation"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "employee_grade",
|
"fieldname": "employee_grade",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Employee Grade",
|
"label": "Employee Grade",
|
||||||
"options": "Employee Grade",
|
"options": "Employee Grade"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Project",
|
"label": "Project",
|
||||||
"options": "Project",
|
"options": "Project",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "table_for_activity",
|
"fieldname": "table_for_activity",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"fieldname": "activities",
|
"fieldname": "activities",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Activities",
|
"label": "Activities",
|
||||||
"options": "Employee Boarding Activity",
|
"options": "Employee Boarding Activity"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
@ -167,14 +137,12 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Employee Onboarding",
|
"options": "Employee Onboarding",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-25 15:22:24.923835",
|
"modified": "2021-06-03 18:01:51.097927",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee Onboarding",
|
"name": "Employee Onboarding",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from erpnext.hr.utils import EmployeeBoardingController
|
from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
class IncompleteTaskError(frappe.ValidationError): pass
|
class IncompleteTaskError(frappe.ValidationError): pass
|
||||||
@ -16,9 +16,9 @@ class EmployeeOnboarding(EmployeeBoardingController):
|
|||||||
self.validate_duplicate_employee_onboarding()
|
self.validate_duplicate_employee_onboarding()
|
||||||
|
|
||||||
def validate_duplicate_employee_onboarding(self):
|
def validate_duplicate_employee_onboarding(self):
|
||||||
emp_onboarding = frappe.db.exists("Employee Onboarding",{"job_applicant": self.job_applicant})
|
emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant})
|
||||||
if emp_onboarding and emp_onboarding != self.name:
|
if emp_onboarding and emp_onboarding != self.name:
|
||||||
frappe.throw(_("Employee Onboarding: {0} is already for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
|
frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
|
||||||
|
|
||||||
def validate_employee_creation(self):
|
def validate_employee_creation(self):
|
||||||
if self.docstatus != 1:
|
if self.docstatus != 1:
|
||||||
@ -30,7 +30,7 @@ class EmployeeOnboarding(EmployeeBoardingController):
|
|||||||
else:
|
else:
|
||||||
task_status = frappe.db.get_value("Task", activity.task, "status")
|
task_status = frappe.db.get_value("Task", activity.task, "status")
|
||||||
if task_status not in ["Completed", "Cancelled"]:
|
if task_status not in ["Completed", "Cancelled"]:
|
||||||
frappe.throw(_("All the mandatory Task for employee creation hasn't been done yet."), IncompleteTaskError)
|
frappe.throw(_("All the mandatory tasks for employee creation are not completed yet."), IncompleteTaskError)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
super(EmployeeOnboarding, self).on_submit()
|
super(EmployeeOnboarding, self).on_submit()
|
||||||
|
@ -11,39 +11,26 @@ from erpnext.hr.doctype.employee_onboarding.employee_onboarding import Incomplet
|
|||||||
from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer
|
from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer
|
||||||
|
|
||||||
class TestEmployeeOnboarding(unittest.TestCase):
|
class TestEmployeeOnboarding(unittest.TestCase):
|
||||||
def test_employee_onboarding_incomplete_task(self):
|
def setUp(self):
|
||||||
if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
|
if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
|
||||||
frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
|
frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
|
||||||
_set_up()
|
|
||||||
applicant = get_job_applicant()
|
|
||||||
|
|
||||||
job_offer = create_job_offer(job_applicant=applicant.name)
|
project = "Employee Onboarding : Test Researcher - test@researcher.com"
|
||||||
job_offer.submit()
|
frappe.db.sql("delete from tabProject where name=%s", project)
|
||||||
|
frappe.db.sql("delete from tabTask where project=%s", project)
|
||||||
|
|
||||||
onboarding = frappe.new_doc('Employee Onboarding')
|
def test_employee_onboarding_incomplete_task(self):
|
||||||
onboarding.job_applicant = applicant.name
|
onboarding = create_employee_onboarding()
|
||||||
onboarding.job_offer = job_offer.name
|
|
||||||
onboarding.company = '_Test Company'
|
|
||||||
onboarding.designation = 'Researcher'
|
|
||||||
onboarding.append('activities', {
|
|
||||||
'activity_name': 'Assign ID Card',
|
|
||||||
'role': 'HR User',
|
|
||||||
'required_for_employee_creation': 1
|
|
||||||
})
|
|
||||||
onboarding.append('activities', {
|
|
||||||
'activity_name': 'Assign a laptop',
|
|
||||||
'role': 'HR User'
|
|
||||||
})
|
|
||||||
onboarding.status = 'Pending'
|
|
||||||
onboarding.insert()
|
|
||||||
onboarding.submit()
|
|
||||||
|
|
||||||
project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
|
project_name = frappe.db.get_value('Project', onboarding.project, 'project_name')
|
||||||
self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
|
self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
|
||||||
|
|
||||||
# don't allow making employee if onboarding is not complete
|
# don't allow making employee if onboarding is not complete
|
||||||
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
|
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
|
||||||
|
|
||||||
|
# boarding status
|
||||||
|
self.assertEqual(onboarding.boarding_status, 'Pending')
|
||||||
|
|
||||||
# complete the task
|
# complete the task
|
||||||
project = frappe.get_doc('Project', onboarding.project)
|
project = frappe.get_doc('Project', onboarding.project)
|
||||||
for task in frappe.get_all('Task', dict(project=project.name)):
|
for task in frappe.get_all('Task', dict(project=project.name)):
|
||||||
@ -51,6 +38,10 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
|||||||
task.status = 'Completed'
|
task.status = 'Completed'
|
||||||
task.save()
|
task.save()
|
||||||
|
|
||||||
|
# boarding status
|
||||||
|
onboarding.reload()
|
||||||
|
self.assertEqual(onboarding.boarding_status, 'Completed')
|
||||||
|
|
||||||
# make employee
|
# make employee
|
||||||
onboarding.reload()
|
onboarding.reload()
|
||||||
employee = make_employee(onboarding.name)
|
employee = make_employee(onboarding.name)
|
||||||
@ -61,6 +52,13 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
|||||||
employee.insert()
|
employee.insert()
|
||||||
self.assertEqual(employee.employee_name, 'Test Researcher')
|
self.assertEqual(employee.employee_name, 'Test Researcher')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for entry in frappe.get_all('Employee Onboarding'):
|
||||||
|
doc = frappe.get_doc('Employee Onboarding', entry.name)
|
||||||
|
doc.cancel()
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
|
||||||
def get_job_applicant():
|
def get_job_applicant():
|
||||||
if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'):
|
if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'):
|
||||||
return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com')
|
return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com')
|
||||||
@ -72,10 +70,35 @@ def get_job_applicant():
|
|||||||
applicant.insert()
|
applicant.insert()
|
||||||
return applicant
|
return applicant
|
||||||
|
|
||||||
def _set_up():
|
def get_job_offer(applicant_name):
|
||||||
for doctype in ["Employee Onboarding"]:
|
job_offer = frappe.db.exists('Job Offer', {'job_applicant': applicant_name})
|
||||||
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
|
if job_offer:
|
||||||
|
return frappe.get_doc('Job Offer', job_offer)
|
||||||
|
|
||||||
project = "Employee Onboarding : Test Researcher - test@researcher.com"
|
job_offer = create_job_offer(job_applicant=applicant_name)
|
||||||
frappe.db.sql("delete from tabProject where name=%s", project)
|
job_offer.submit()
|
||||||
frappe.db.sql("delete from tabTask where project=%s", project)
|
return job_offer
|
||||||
|
|
||||||
|
def create_employee_onboarding():
|
||||||
|
applicant = get_job_applicant()
|
||||||
|
job_offer = get_job_offer(applicant.name)
|
||||||
|
|
||||||
|
onboarding = frappe.new_doc('Employee Onboarding')
|
||||||
|
onboarding.job_applicant = applicant.name
|
||||||
|
onboarding.job_offer = job_offer.name
|
||||||
|
onboarding.company = '_Test Company'
|
||||||
|
onboarding.designation = 'Researcher'
|
||||||
|
onboarding.append('activities', {
|
||||||
|
'activity_name': 'Assign ID Card',
|
||||||
|
'role': 'HR User',
|
||||||
|
'required_for_employee_creation': 1
|
||||||
|
})
|
||||||
|
onboarding.append('activities', {
|
||||||
|
'activity_name': 'Assign a laptop',
|
||||||
|
'role': 'HR User'
|
||||||
|
})
|
||||||
|
onboarding.status = 'Pending'
|
||||||
|
onboarding.insert()
|
||||||
|
onboarding.submit()
|
||||||
|
|
||||||
|
return onboarding
|
@ -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()
|
||||||
|
|
||||||
|
@ -23,27 +23,13 @@ frappe.ui.form.on('Employee Separation', {
|
|||||||
frappe.set_route('List', 'Task', {project: frm.doc.project});
|
frappe.set_route('List', 'Task', {project: frm.doc.project});
|
||||||
},__("View"));
|
},__("View"));
|
||||||
}
|
}
|
||||||
if (frm.doc.docstatus === 1 && frm.doc.project) {
|
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.hr.utils.get_boarding_status",
|
|
||||||
args: {
|
|
||||||
"project": frm.doc.project
|
|
||||||
},
|
|
||||||
callback: function(r) {
|
|
||||||
if (r.message) {
|
|
||||||
frm.set_value('boarding_status', r.message);
|
|
||||||
}
|
|
||||||
refresh_field("boarding_status");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
employee_separation_template: function(frm) {
|
employee_separation_template: function(frm) {
|
||||||
frm.set_value("activities" ,"");
|
frm.set_value("activities" ,"");
|
||||||
if (frm.doc.employee_separation_template) {
|
if (frm.doc.employee_separation_template) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.hr.utils.get_onboarding_details",
|
method: "erpnext.controllers.employee_boarding_controller.get_onboarding_details",
|
||||||
args: {
|
args: {
|
||||||
"parent": frm.doc.employee_separation_template,
|
"parent": frm.doc.employee_separation_template,
|
||||||
"parenttype": "Employee Separation Template"
|
"parenttype": "Employee Separation Template"
|
||||||
|
@ -50,11 +50,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
|
"default": "Pending",
|
||||||
"fieldname": "boarding_status",
|
"fieldname": "boarding_status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "\nPending\nIn Process\nCompleted",
|
"options": "Pending\nIn Process\nCompleted",
|
||||||
"reqd": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
@ -147,7 +148,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-28 15:58:36.020196",
|
"modified": "2021-06-03 18:02:54.007313",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee Separation",
|
"name": "Employee Separation",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from erpnext.hr.utils import EmployeeBoardingController
|
from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
|
||||||
|
|
||||||
class EmployeeSeparation(EmployeeBoardingController):
|
class EmployeeSeparation(EmployeeBoardingController):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
@ -6,21 +6,43 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
test_dependencies = ["Employee Onboarding"]
|
test_dependencies = ['Employee Onboarding']
|
||||||
|
|
||||||
class TestEmployeeSeparation(unittest.TestCase):
|
class TestEmployeeSeparation(unittest.TestCase):
|
||||||
def test_employee_separation(self):
|
def test_employee_separation(self):
|
||||||
employee = frappe.db.get_value("Employee", {"status": "Active"})
|
separation = create_employee_separation()
|
||||||
separation = frappe.new_doc('Employee Separation')
|
|
||||||
separation.employee = employee
|
|
||||||
separation.company = '_Test Company'
|
|
||||||
separation.append('activities', {
|
|
||||||
'activity_name': 'Deactivate Employee',
|
|
||||||
'role': 'HR User'
|
|
||||||
})
|
|
||||||
separation.boarding_status = 'Pending'
|
|
||||||
separation.insert()
|
|
||||||
separation.submit()
|
|
||||||
self.assertEqual(separation.docstatus, 1)
|
self.assertEqual(separation.docstatus, 1)
|
||||||
|
self.assertEqual(separation.boarding_status, 'Pending')
|
||||||
|
|
||||||
|
project = frappe.get_doc('Project', separation.project)
|
||||||
|
project.percent_complete_method = 'Manual'
|
||||||
|
project.status = 'Completed'
|
||||||
|
project.save()
|
||||||
|
|
||||||
|
separation.reload()
|
||||||
|
self.assertEqual(separation.boarding_status, 'Completed')
|
||||||
|
|
||||||
separation.cancel()
|
separation.cancel()
|
||||||
self.assertEqual(separation.project, "")
|
self.assertEqual(separation.project, '')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for entry in frappe.get_all('Employee Separation'):
|
||||||
|
doc = frappe.get_doc('Employee Separation', entry.name)
|
||||||
|
if doc.docstatus == 1:
|
||||||
|
doc.cancel()
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
def create_employee_separation():
|
||||||
|
employee = frappe.db.get_value('Employee', {'status': 'Active'})
|
||||||
|
separation = frappe.new_doc('Employee Separation')
|
||||||
|
separation.employee = employee
|
||||||
|
separation.company = '_Test Company'
|
||||||
|
separation.append('activities', {
|
||||||
|
'activity_name': 'Deactivate Employee',
|
||||||
|
'role': 'HR User'
|
||||||
|
})
|
||||||
|
separation.boarding_status = 'Pending'
|
||||||
|
separation.insert()
|
||||||
|
separation.submit()
|
||||||
|
return separation
|
@ -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()
|
||||||
@ -35,8 +36,8 @@ class ExpenseClaim(AccountsController):
|
|||||||
if self.task and not self.project:
|
if self.task and not self.project:
|
||||||
self.project = frappe.db.get_value("Task", self.task, "project")
|
self.project = frappe.db.get_value("Task", self.task, "project")
|
||||||
|
|
||||||
def set_status(self):
|
def set_status(self, update=False):
|
||||||
self.status = {
|
status = {
|
||||||
"0": "Draft",
|
"0": "Draft",
|
||||||
"1": "Submitted",
|
"1": "Submitted",
|
||||||
"2": "Cancelled"
|
"2": "Cancelled"
|
||||||
@ -44,14 +45,18 @@ class ExpenseClaim(AccountsController):
|
|||||||
|
|
||||||
paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount)
|
paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount)
|
||||||
precision = self.precision("grand_total")
|
precision = self.precision("grand_total")
|
||||||
if (self.is_paid or (flt(self.total_sanctioned_amount) > 0
|
if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1
|
||||||
and flt(self.grand_total, precision) == flt(paid_amount, precision))) \
|
and flt(self.grand_total, precision) == flt(paid_amount, precision))) and self.approval_status == 'Approved':
|
||||||
and self.docstatus == 1 and self.approval_status == 'Approved':
|
status = "Paid"
|
||||||
self.status = "Paid"
|
|
||||||
elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
|
elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
|
||||||
self.status = "Unpaid"
|
status = "Unpaid"
|
||||||
elif self.docstatus == 1 and self.approval_status == 'Rejected':
|
elif self.docstatus == 1 and self.approval_status == 'Rejected':
|
||||||
self.status = 'Rejected'
|
status = 'Rejected'
|
||||||
|
|
||||||
|
if update:
|
||||||
|
self.db_set("status", status)
|
||||||
|
else:
|
||||||
|
self.status = status
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
share_doc_with_approver(self, self.expense_approver)
|
share_doc_with_approver(self, self.expense_approver)
|
||||||
@ -74,7 +79,7 @@ class ExpenseClaim(AccountsController):
|
|||||||
if self.is_paid:
|
if self.is_paid:
|
||||||
update_reimbursed_amount(self)
|
update_reimbursed_amount(self)
|
||||||
|
|
||||||
self.set_status()
|
self.set_status(update=True)
|
||||||
self.update_claimed_amount_in_employee_advance()
|
self.update_claimed_amount_in_employee_advance()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@ -86,7 +91,6 @@ class ExpenseClaim(AccountsController):
|
|||||||
if self.is_paid:
|
if self.is_paid:
|
||||||
update_reimbursed_amount(self)
|
update_reimbursed_amount(self)
|
||||||
|
|
||||||
self.set_status()
|
|
||||||
self.update_claimed_amount_in_employee_advance()
|
self.update_claimed_amount_in_employee_advance()
|
||||||
|
|
||||||
def update_claimed_amount_in_employee_advance(self):
|
def update_claimed_amount_in_employee_advance(self):
|
||||||
|
@ -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,128 +3,15 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
class EmployeeBoardingController(Document):
|
|
||||||
'''
|
|
||||||
Create the project and the task for the boarding process
|
|
||||||
Assign to the concerned person and roles as per the onboarding/separation template
|
|
||||||
'''
|
|
||||||
def validate(self):
|
|
||||||
# remove the task if linked before submitting the form
|
|
||||||
if self.amended_from:
|
|
||||||
for activity in self.activities:
|
|
||||||
activity.task = ''
|
|
||||||
|
|
||||||
def on_submit(self):
|
|
||||||
# create the project for the given employee onboarding
|
|
||||||
project_name = _(self.doctype) + " : "
|
|
||||||
if self.doctype == "Employee Onboarding":
|
|
||||||
project_name += self.job_applicant
|
|
||||||
else:
|
|
||||||
project_name += self.employee
|
|
||||||
|
|
||||||
project = frappe.get_doc({
|
|
||||||
"doctype": "Project",
|
|
||||||
"project_name": project_name,
|
|
||||||
"expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
|
|
||||||
"department": self.department,
|
|
||||||
"company": self.company
|
|
||||||
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
|
||||||
|
|
||||||
self.db_set("project", project.name)
|
|
||||||
self.db_set("boarding_status", "Pending")
|
|
||||||
self.reload()
|
|
||||||
self.create_task_and_notify_user()
|
|
||||||
|
|
||||||
def create_task_and_notify_user(self):
|
|
||||||
# create the task for the given project and assign to the concerned person
|
|
||||||
for activity in self.activities:
|
|
||||||
if activity.task:
|
|
||||||
continue
|
|
||||||
|
|
||||||
task = frappe.get_doc({
|
|
||||||
"doctype": "Task",
|
|
||||||
"project": self.project,
|
|
||||||
"subject": activity.activity_name + " : " + self.employee_name,
|
|
||||||
"description": activity.description,
|
|
||||||
"department": self.department,
|
|
||||||
"company": self.company,
|
|
||||||
"task_weight": activity.task_weight
|
|
||||||
}).insert(ignore_permissions=True)
|
|
||||||
activity.db_set("task", task.name)
|
|
||||||
|
|
||||||
users = [activity.user] if activity.user else []
|
|
||||||
if activity.role:
|
|
||||||
user_list = frappe.db.sql_list('''
|
|
||||||
SELECT
|
|
||||||
DISTINCT(has_role.parent)
|
|
||||||
FROM
|
|
||||||
`tabHas Role` has_role
|
|
||||||
LEFT JOIN `tabUser` user
|
|
||||||
ON has_role.parent = user.name
|
|
||||||
WHERE
|
|
||||||
has_role.parenttype = 'User'
|
|
||||||
AND user.enabled = 1
|
|
||||||
AND has_role.role = %s
|
|
||||||
''', activity.role)
|
|
||||||
users = unique(users + user_list)
|
|
||||||
|
|
||||||
if "Administrator" in users:
|
|
||||||
users.remove("Administrator")
|
|
||||||
|
|
||||||
# assign the task the users
|
|
||||||
if users:
|
|
||||||
self.assign_task_to_users(task, users)
|
|
||||||
|
|
||||||
def assign_task_to_users(self, task, users):
|
|
||||||
for user in users:
|
|
||||||
args = {
|
|
||||||
'assign_to': [user],
|
|
||||||
'doctype': task.doctype,
|
|
||||||
'name': task.name,
|
|
||||||
'description': task.description or task.subject,
|
|
||||||
'notify': self.notify_users_by_email
|
|
||||||
}
|
|
||||||
assign_to.add(args)
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
# delete task project
|
|
||||||
for task in frappe.get_all("Task", filters={"project": self.project}):
|
|
||||||
frappe.delete_doc("Task", task.name, force=1)
|
|
||||||
frappe.delete_doc("Project", self.project, force=1)
|
|
||||||
self.db_set('project', '')
|
|
||||||
for activity in self.activities:
|
|
||||||
activity.db_set("task", "")
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_onboarding_details(parent, parenttype):
|
|
||||||
return frappe.get_all("Employee Boarding Activity",
|
|
||||||
fields=["activity_name", "role", "user", "required_for_employee_creation", "description", "task_weight"],
|
|
||||||
filters={"parent": parent, "parenttype": parenttype},
|
|
||||||
order_by= "idx")
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_boarding_status(project):
|
|
||||||
status = 'Pending'
|
|
||||||
if project:
|
|
||||||
doc = frappe.get_doc('Project', project)
|
|
||||||
if flt(doc.percent_complete) > 0.0 and flt(doc.percent_complete) < 100.0:
|
|
||||||
status = 'In Process'
|
|
||||||
elif flt(doc.percent_complete) == 100.0:
|
|
||||||
status = 'Completed'
|
|
||||||
return status
|
|
||||||
|
|
||||||
def set_employee_name(doc):
|
def set_employee_name(doc):
|
||||||
if doc.employee and not doc.employee_name:
|
if doc.employee and not doc.employee_name:
|
||||||
doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name")
|
doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name")
|
||||||
@ -522,3 +409,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)
|
@ -14,7 +14,7 @@ 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", () => {
|
frm.set_query('loan_type', () => {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
company: frm.doc.company
|
company: frm.doc.company
|
||||||
|
@ -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
|
||||||
@ -1069,13 +1069,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
if barcodes:
|
if barcodes:
|
||||||
or_cond_filters["name"] = ("in", barcodes)
|
or_cond_filters["name"] = ("in", barcodes)
|
||||||
|
|
||||||
for cond in get_match_cond(doctype, as_condition=False):
|
|
||||||
for key, value in cond.items():
|
|
||||||
if key == doctype:
|
|
||||||
key = "name"
|
|
||||||
|
|
||||||
query_filters[key] = ("in", value)
|
|
||||||
|
|
||||||
if filters and filters.get("item_code"):
|
if filters and filters.get("item_code"):
|
||||||
has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
|
has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
|
||||||
if not has_variants:
|
if not has_variants:
|
||||||
@ -1084,7 +1077,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
if filters and filters.get("is_stock_item"):
|
if filters and filters.get("is_stock_item"):
|
||||||
query_filters["is_stock_item"] = 1
|
query_filters["is_stock_item"] = 1
|
||||||
|
|
||||||
return frappe.get_all("Item",
|
return frappe.get_list("Item",
|
||||||
fields = fields, filters=query_filters,
|
fields = fields, filters=query_filters,
|
||||||
or_filters = or_cond_filters, order_by=order_by,
|
or_filters = or_cond_filters, order_by=order_by,
|
||||||
limit_start=start, limit_page_length=page_len, as_list=1)
|
limit_start=start, limit_page_length=page_len, as_list=1)
|
||||||
|
@ -192,11 +192,11 @@ class JobCard(Document):
|
|||||||
"completed_qty": args.get("completed_qty") or 0.0
|
"completed_qty": args.get("completed_qty") or 0.0
|
||||||
})
|
})
|
||||||
elif args.get("start_time"):
|
elif args.get("start_time"):
|
||||||
new_args = {
|
new_args = frappe._dict({
|
||||||
"from_time": get_datetime(args.get("start_time")),
|
"from_time": get_datetime(args.get("start_time")),
|
||||||
"operation": args.get("sub_operation"),
|
"operation": args.get("sub_operation"),
|
||||||
"completed_qty": 0.0
|
"completed_qty": 0.0
|
||||||
}
|
})
|
||||||
|
|
||||||
if employees:
|
if employees:
|
||||||
for name in employees:
|
for name in employees:
|
||||||
|
@ -297,4 +297,6 @@ 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
|
erpnext.patches.v13_0.update_subscription_status_in_memberships
|
||||||
erpnext.patches.v13_0.update_amt_in_work_order_required_items
|
erpnext.patches.v13_0.update_amt_in_work_order_required_items
|
||||||
|
erpnext.patches.v13_0.update_export_type_for_gst
|
||||||
|
erpnext.patches.v13_0.update_tds_check_field #3
|
||||||
erpnext.patches.v13_0.add_custom_field_for_south_africa
|
erpnext.patches.v13_0.add_custom_field_for_south_africa
|
||||||
|
@ -10,6 +10,7 @@ def execute():
|
|||||||
if not frappe.db.has_column('Work Order', 'has_batch_no'):
|
if not frappe.db.has_column('Work Order', 'has_batch_no'):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings')
|
||||||
if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
|
if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -107,4 +108,4 @@ def repost_future_sle_and_gle(doc):
|
|||||||
"company": doc.company
|
"company": doc.company
|
||||||
})
|
})
|
||||||
|
|
||||||
create_repost_item_valuation_entry(args)
|
create_repost_item_valuation_entry(args)
|
||||||
|
@ -37,7 +37,7 @@ def execute():
|
|||||||
|
|
||||||
if frappe.db.exists('DocType', 'Opportunity'):
|
if frappe.db.exists('DocType', 'Opportunity'):
|
||||||
opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc')
|
opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc')
|
||||||
frappe.reload_doc('crm', 'doctype', 'opportunity')
|
frappe.reload_doctype('Opportunity', force=True)
|
||||||
rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
|
rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
|
||||||
|
|
||||||
# change fieldtype to duration
|
# change fieldtype to duration
|
||||||
|
24
erpnext/patches/v13_0/update_export_type_for_gst.py
Normal file
24
erpnext/patches/v13_0/update_export_type_for_gst.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||||
|
if not company:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update custom fields
|
||||||
|
fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'})
|
||||||
|
if fieldname:
|
||||||
|
frappe.db.set_value('Custom Field', fieldname, 'default', '')
|
||||||
|
|
||||||
|
fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'})
|
||||||
|
if fieldname:
|
||||||
|
frappe.db.set_value('Custom Field', fieldname, 'default', '')
|
||||||
|
|
||||||
|
# Update Customer/Supplier Masters
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export')
|
||||||
|
""")
|
||||||
|
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas')
|
||||||
|
""")
|
9
erpnext/patches/v13_0/update_tds_check_field.py
Normal file
9
erpnext/patches/v13_0/update_tds_check_field.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if frappe.db.has_table("Tax Withholding Category") \
|
||||||
|
and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"):
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0
|
||||||
|
WHERE round_off_tax_amount IS NULL
|
||||||
|
""")
|
@ -7,6 +7,7 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.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:
|
||||||
@ -675,6 +678,7 @@ class SalarySlip(TransactionBase):
|
|||||||
component_row.set(attr, component_data.get(attr))
|
component_row.set(attr, component_data.get(attr))
|
||||||
|
|
||||||
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
|
||||||
@ -708,6 +712,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)
|
||||||
@ -867,8 +872,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
|
||||||
|
@ -129,7 +129,7 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None,
|
|||||||
"earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test 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"]),
|
"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||||
"payroll_frequency": payroll_frequency,
|
"payroll_frequency": payroll_frequency,
|
||||||
"payment_account": get_random("Account", filters={"account_currency": currency}),
|
"payment_account": get_random("Account", filters={'account_currency': currency}),
|
||||||
"currency": currency
|
"currency": currency
|
||||||
}
|
}
|
||||||
if other_details and isinstance(other_details, dict):
|
if other_details and isinstance(other_details, dict):
|
||||||
|
@ -14,6 +14,7 @@ from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_e
|
|||||||
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list
|
from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list
|
||||||
|
from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status
|
||||||
|
|
||||||
class Project(Document):
|
class Project(Document):
|
||||||
def get_feed(self):
|
def get_feed(self):
|
||||||
@ -37,6 +38,7 @@ class Project(Document):
|
|||||||
self.send_welcome_email()
|
self.send_welcome_email()
|
||||||
self.update_costing()
|
self.update_costing()
|
||||||
self.update_percent_complete()
|
self.update_percent_complete()
|
||||||
|
update_employee_boarding_status(self)
|
||||||
|
|
||||||
def copy_from_template(self):
|
def copy_from_template(self):
|
||||||
'''
|
'''
|
||||||
@ -132,6 +134,7 @@ class Project(Document):
|
|||||||
def update_project(self):
|
def update_project(self):
|
||||||
'''Called externally by Task'''
|
'''Called externally by Task'''
|
||||||
self.update_percent_complete()
|
self.update_percent_complete()
|
||||||
|
update_employee_boarding_status(self)
|
||||||
self.update_costing()
|
self.update_costing()
|
||||||
self.db_update()
|
self.db_update()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -16,7 +16,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
|||||||
doctype: "Bank Transaction",
|
doctype: "Bank Transaction",
|
||||||
filters: { name: this.bank_transaction_name },
|
filters: { name: this.bank_transaction_name },
|
||||||
fieldname: [
|
fieldname: [
|
||||||
"date",
|
"date as reference_date",
|
||||||
"deposit",
|
"deposit",
|
||||||
"withdrawal",
|
"withdrawal",
|
||||||
"currency",
|
"currency",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('E Invoice Settings', {
|
frappe.ui.form.on('E Invoice Settings', {
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
|
const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing';
|
||||||
frm.dashboard.set_headline(
|
frm.dashboard.set_headline(
|
||||||
__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
|
__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
|
||||||
);
|
);
|
||||||
|
@ -280,9 +280,15 @@ class GSTR3BReport(Document):
|
|||||||
if self.get('invoice_items'):
|
if self.get('invoice_items'):
|
||||||
# Build itemised tax for export invoices, nil and exempted where tax table is blank
|
# Build itemised tax for export invoices, nil and exempted where tax table is blank
|
||||||
for invoice, items in iteritems(self.invoice_items):
|
for invoice, items in iteritems(self.invoice_items):
|
||||||
if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')
|
if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \
|
||||||
== "Without Payment of Tax"):
|
== "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas":
|
||||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||||
|
else:
|
||||||
|
for item in items.keys():
|
||||||
|
if item in self.is_nil_exempt + self.is_non_gst and \
|
||||||
|
item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []):
|
||||||
|
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, [])
|
||||||
|
self.items_based_on_tax_rate[invoice][0].append(item)
|
||||||
|
|
||||||
def set_outward_taxable_supplies(self):
|
def set_outward_taxable_supplies(self):
|
||||||
inter_state_supply_details = {}
|
inter_state_supply_details = {}
|
||||||
|
@ -641,7 +641,6 @@ def make_custom_fields(update=True):
|
|||||||
'label': 'Export Type',
|
'label': 'Export Type',
|
||||||
'fieldtype': 'Select',
|
'fieldtype': 'Select',
|
||||||
'insert_after': 'gst_category',
|
'insert_after': 'gst_category',
|
||||||
'default': 'Without Payment of Tax',
|
|
||||||
'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
|
'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
|
||||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||||
}
|
}
|
||||||
@ -660,7 +659,6 @@ def make_custom_fields(update=True):
|
|||||||
'label': 'Export Type',
|
'label': 'Export Type',
|
||||||
'fieldtype': 'Select',
|
'fieldtype': 'Select',
|
||||||
'insert_after': 'gst_category',
|
'insert_after': 'gst_category',
|
||||||
'default': 'Without Payment of Tax',
|
|
||||||
'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
|
'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
|
||||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,8 @@ class Gstr1Report(object):
|
|||||||
# Build itemised tax for export invoices where tax table is blank
|
# Build itemised tax for export invoices where tax table is blank
|
||||||
for invoice, items in iteritems(self.invoice_items):
|
for invoice, items in iteritems(self.invoice_items):
|
||||||
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
|
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
|
||||||
and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
|
and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
|
||||||
|
and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
|
||||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||||
|
|
||||||
def get_columns(self):
|
def get_columns(self):
|
||||||
|
@ -1 +0,0 @@
|
|||||||
Sales campaign / promotion, like special discount, exhibition, newsletter etc.
|
|
@ -1 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
@ -1,15 +0,0 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
// License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on("Campaign", "refresh", function(frm) {
|
|
||||||
erpnext.toggle_naming_series();
|
|
||||||
if(frm.doc.__islocal) {
|
|
||||||
frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
cur_frm.add_custom_button(__("View Leads"), function() {
|
|
||||||
frappe.route_options = {"source": "Campaign","campaign_name": frm.doc.name}
|
|
||||||
frappe.set_route("List", "Lead");
|
|
||||||
}, "fa fa-list", true);
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,17 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
from frappe import _
|
|
||||||
|
|
||||||
def get_data():
|
|
||||||
return {
|
|
||||||
'fieldname': 'campaign_name',
|
|
||||||
'transactions': [
|
|
||||||
{
|
|
||||||
'label': _('Email Campaigns'),
|
|
||||||
'items': ['Email Campaign']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'label': _('Social Media Campaigns'),
|
|
||||||
'items': ['Social Media Post']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
test_records = frappe.get_test_records('Campaign')
|
|
@ -1,10 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"campaign_name": "_Test Campaign",
|
|
||||||
"doctype": "Campaign"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"campaign_name": "_Test Campaign 1",
|
|
||||||
"doctype": "Campaign"
|
|
||||||
}
|
|
||||||
]
|
|
@ -62,12 +62,12 @@ class TestCurrencyExchange(unittest.TestCase):
|
|||||||
|
|
||||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
|
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
|
||||||
self.assertEqual(exchange_rate, 62.9)
|
self.assertEqual(exchange_rate, 62.9)
|
||||||
|
|
||||||
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
|
# Exchange rate as on 15th Dec, 2015
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling")
|
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling")
|
||||||
self.assertFalse(exchange_rate == 60)
|
self.assertFalse(exchange_rate == 60)
|
||||||
self.assertEqual(flt(exchange_rate, 3), 66.894)
|
self.assertEqual(flt(exchange_rate, 3), 66.999)
|
||||||
|
|
||||||
def test_exchange_rate_strict(self):
|
def test_exchange_rate_strict(self):
|
||||||
# strict currency settings
|
# strict currency settings
|
||||||
@ -77,28 +77,17 @@ class TestCurrencyExchange(unittest.TestCase):
|
|||||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying")
|
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying")
|
||||||
self.assertEqual(exchange_rate, 60.0)
|
self.assertEqual(exchange_rate, 60.0)
|
||||||
|
|
||||||
# Will fetch from fixer.io
|
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
|
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
|
||||||
self.assertEqual(flt(exchange_rate, 3), 67.79)
|
self.assertEqual(flt(exchange_rate, 3), 67.235)
|
||||||
|
|
||||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
|
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
|
||||||
self.assertEqual(exchange_rate, 62.9)
|
self.assertEqual(exchange_rate, 62.9)
|
||||||
|
|
||||||
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
|
# Exchange rate as on 15th Dec, 2015
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying")
|
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying")
|
||||||
self.assertEqual(flt(exchange_rate, 3), 66.894)
|
self.assertEqual(flt(exchange_rate, 3), 66.999)
|
||||||
|
|
||||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10", "for_selling")
|
|
||||||
self.assertEqual(exchange_rate, 65.1)
|
|
||||||
|
|
||||||
# NGN is not available on fixer.io so these should return 0
|
|
||||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09", "for_selling")
|
|
||||||
self.assertEqual(exchange_rate, 0)
|
|
||||||
|
|
||||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11", "for_selling")
|
|
||||||
self.assertEqual(exchange_rate, 0)
|
|
||||||
|
|
||||||
def test_exchange_rate_strict_switched(self):
|
def test_exchange_rate_strict_switched(self):
|
||||||
# Start with allow_stale is True
|
# Start with allow_stale is True
|
||||||
@ -111,4 +100,4 @@ class TestCurrencyExchange(unittest.TestCase):
|
|||||||
# Will fetch from fixer.io
|
# Will fetch from fixer.io
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
|
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
|
||||||
self.assertEqual(flt(exchange_rate, 3), 67.79)
|
self.assertEqual(flt(exchange_rate, 3), 67.235)
|
||||||
|
@ -18,7 +18,7 @@ class TransactionDeletionRecord(Document):
|
|||||||
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. "),
|
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"))
|
title=_("Not Allowed"))
|
||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
@ -31,7 +31,7 @@ class TransactionDeletionRecord(Document):
|
|||||||
clear_notifications()
|
clear_notifications()
|
||||||
self.delete_company_transactions()
|
self.delete_company_transactions()
|
||||||
|
|
||||||
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()
|
||||||
for doctype in doctypes_to_be_ignored_list:
|
for doctype in doctypes_to_be_ignored_list:
|
||||||
self.append('doctypes_to_be_ignored', {
|
self.append('doctypes_to_be_ignored', {
|
||||||
@ -74,7 +74,7 @@ class TransactionDeletionRecord(Document):
|
|||||||
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
|
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
|
||||||
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
|
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
|
||||||
|
|
||||||
tables = self.get_all_child_doctypes()
|
tables = self.get_all_child_doctypes()
|
||||||
for docfield in docfields:
|
for docfield in docfields:
|
||||||
if docfield['parent'] != self.doctype:
|
if docfield['parent'] != self.doctype:
|
||||||
no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
|
no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
|
||||||
@ -90,7 +90,7 @@ class TransactionDeletionRecord(Document):
|
|||||||
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
||||||
if naming_series:
|
if naming_series:
|
||||||
if '#' in naming_series:
|
if '#' in naming_series:
|
||||||
self.update_naming_series(naming_series, docfield['parent'])
|
self.update_naming_series(naming_series, docfield['parent'])
|
||||||
|
|
||||||
def get_doctypes_to_be_ignored_list(self):
|
def get_doctypes_to_be_ignored_list(self):
|
||||||
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
||||||
@ -101,9 +101,9 @@ class TransactionDeletionRecord(Document):
|
|||||||
return doctypes_to_be_ignored_list
|
return doctypes_to_be_ignored_list
|
||||||
|
|
||||||
def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
|
def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
|
||||||
docfields = frappe.get_all('DocField',
|
docfields = frappe.get_all('DocField',
|
||||||
filters = {
|
filters = {
|
||||||
'fieldtype': 'Link',
|
'fieldtype': 'Link',
|
||||||
'options': 'Company',
|
'options': 'Company',
|
||||||
'parent': ['not in', doctypes_to_be_ignored_list]},
|
'parent': ['not in', doctypes_to_be_ignored_list]},
|
||||||
fields=['parent', 'fieldname'])
|
fields=['parent', 'fieldname'])
|
||||||
@ -121,7 +121,7 @@ class TransactionDeletionRecord(Document):
|
|||||||
self.append('doctypes', {
|
self.append('doctypes', {
|
||||||
'doctype_name' : doctype,
|
'doctype_name' : doctype,
|
||||||
'no_of_docs' : no_of_docs
|
'no_of_docs' : no_of_docs
|
||||||
})
|
})
|
||||||
|
|
||||||
def delete_child_tables(self, doctype, company_fieldname):
|
def delete_child_tables(self, doctype, company_fieldname):
|
||||||
parent_docs_to_be_deleted = frappe.get_all(doctype, {
|
parent_docs_to_be_deleted = frappe.get_all(doctype, {
|
||||||
@ -129,7 +129,7 @@ class TransactionDeletionRecord(Document):
|
|||||||
}, pluck = 'name')
|
}, pluck = 'name')
|
||||||
|
|
||||||
child_tables = frappe.get_all('DocField', filters = {
|
child_tables = frappe.get_all('DocField', filters = {
|
||||||
'fieldtype': 'Table',
|
'fieldtype': 'Table',
|
||||||
'parent': doctype
|
'parent': doctype
|
||||||
}, pluck = 'options')
|
}, pluck = 'options')
|
||||||
|
|
||||||
|
@ -93,21 +93,21 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
cache = frappe.cache()
|
cache = frappe.cache()
|
||||||
key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date,from_currency, to_currency)
|
key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency)
|
||||||
value = cache.get(key)
|
value = cache.get(key)
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
import requests
|
import requests
|
||||||
api_url = "https://frankfurter.app/{0}".format(transaction_date)
|
api_url = "https://api.exchangerate.host/convert"
|
||||||
response = requests.get(api_url, params={
|
response = requests.get(api_url, params={
|
||||||
"base": from_currency,
|
"date": transaction_date,
|
||||||
"symbols": to_currency
|
"from": from_currency,
|
||||||
|
"to": to_currency
|
||||||
})
|
})
|
||||||
# expire in 6 hours
|
# expire in 6 hours
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
value = response.json()["rates"][to_currency]
|
value = response.json()["result"]
|
||||||
|
cache.setex(name=key, time=21600, value=flt(value))
|
||||||
cache.set_value(key, value, expires_in_sec=6 * 60 * 60)
|
|
||||||
return flt(value)
|
return flt(value)
|
||||||
except:
|
except:
|
||||||
frappe.log_error(title="Get Exchange Rate")
|
frappe.log_error(title="Get Exchange Rate")
|
||||||
|
@ -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
|
||||||
|
@ -162,8 +162,15 @@ class MaterialRequest(BuyingController):
|
|||||||
from `tabStock Entry Detail` where material_request = %s
|
from `tabStock Entry Detail` where material_request = %s
|
||||||
and material_request_item = %s and docstatus = 1""",
|
and material_request_item = %s and docstatus = 1""",
|
||||||
(self.name, d.name))[0][0])
|
(self.name, d.name))[0][0])
|
||||||
|
mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance')
|
||||||
|
|
||||||
if d.ordered_qty and d.ordered_qty > d.stock_qty:
|
if mr_qty_allowance:
|
||||||
|
allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100))
|
||||||
|
if d.ordered_qty and d.ordered_qty > allowed_qty:
|
||||||
|
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
|
||||||
|
cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code))
|
||||||
|
|
||||||
|
elif d.ordered_qty and d.ordered_qty > d.stock_qty:
|
||||||
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
|
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
|
||||||
cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))
|
cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))
|
||||||
|
|
||||||
|
@ -329,6 +329,58 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
|
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
|
||||||
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
|
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
|
||||||
|
|
||||||
|
def test_over_transfer_qty_allowance(self):
|
||||||
|
mr = frappe.new_doc('Material Request')
|
||||||
|
mr.company = "_Test Company"
|
||||||
|
mr.scheduled_date = today()
|
||||||
|
mr.append('items',{
|
||||||
|
"item_code": "_Test FG Item",
|
||||||
|
"item_name": "_Test FG Item",
|
||||||
|
"qty": 10,
|
||||||
|
"schedule_date": today(),
|
||||||
|
"uom": "_Test UOM 1",
|
||||||
|
"warehouse": "_Test Warehouse - _TC"
|
||||||
|
})
|
||||||
|
|
||||||
|
mr.material_request_type = "Material Transfer"
|
||||||
|
mr.insert()
|
||||||
|
mr.submit()
|
||||||
|
|
||||||
|
frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20)
|
||||||
|
|
||||||
|
# map a stock entry
|
||||||
|
|
||||||
|
se_doc = make_stock_entry(mr.name)
|
||||||
|
se_doc.update({
|
||||||
|
"posting_date": today(),
|
||||||
|
"posting_time": "00:00",
|
||||||
|
})
|
||||||
|
se_doc.get("items")[0].update({
|
||||||
|
"qty": 13,
|
||||||
|
"transfer_qty": 12.0,
|
||||||
|
"s_warehouse": "_Test Warehouse - _TC",
|
||||||
|
"t_warehouse": "_Test Warehouse 1 - _TC",
|
||||||
|
"basic_rate": 1.0
|
||||||
|
})
|
||||||
|
|
||||||
|
# make available the qty in _Test Warehouse 1 before transfer
|
||||||
|
sr = frappe.new_doc("Stock Reconciliation")
|
||||||
|
sr.company = "_Test Company"
|
||||||
|
sr.purpose = "Opening Stock"
|
||||||
|
sr.append('items', {
|
||||||
|
"item_code": "_Test FG Item",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 20,
|
||||||
|
"valuation_rate": 0.01
|
||||||
|
})
|
||||||
|
sr.insert()
|
||||||
|
sr.submit()
|
||||||
|
se = frappe.copy_doc(se_doc)
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(frappe.ValidationError)
|
||||||
|
se.items[0].qty = 12
|
||||||
|
se.submit()
|
||||||
|
|
||||||
def test_completed_qty_for_over_transfer(self):
|
def test_completed_qty_for_over_transfer(self):
|
||||||
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
||||||
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
||||||
|
@ -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
|
||||||
|
@ -352,7 +352,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
if self.is_return or flt(d.item_tax_amount):
|
if self.is_return or flt(d.item_tax_amount):
|
||||||
loss_account = expenses_included_in_valuation
|
loss_account = expenses_included_in_valuation
|
||||||
else:
|
else:
|
||||||
loss_account = self.get_company_default("default_expense_account")
|
loss_account = self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb
|
||||||
|
|
||||||
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
|
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
|
||||||
|
|
||||||
@ -436,7 +436,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
"debit": debit,
|
"debit": debit,
|
||||||
"credit": credit,
|
"credit": credit,
|
||||||
"against_account": against_account,
|
"against": against_account,
|
||||||
"remarks": remarks,
|
"remarks": remarks,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,10 +336,13 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
se3.cancel()
|
se3.cancel()
|
||||||
po.reload()
|
po.reload()
|
||||||
pr2.load_from_db()
|
pr2.load_from_db()
|
||||||
pr2.cancel()
|
|
||||||
|
|
||||||
po.load_from_db()
|
if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry',
|
||||||
po.cancel()
|
{'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'):
|
||||||
|
pr2.cancel()
|
||||||
|
|
||||||
|
po.load_from_db()
|
||||||
|
po.cancel()
|
||||||
|
|
||||||
def test_serial_no_supplier(self):
|
def test_serial_no_supplier(self):
|
||||||
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
|
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
|
||||||
@ -1044,7 +1047,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
'account': srbnb_account,
|
'account': srbnb_account,
|
||||||
'voucher_detail_no': pr.items[1].name
|
'voucher_detail_no': pr.items[1].name
|
||||||
}, pluck="name")
|
}, pluck="name")
|
||||||
|
|
||||||
# check if the entries are not merged into one
|
# check if the entries are not merged into one
|
||||||
# seperate entries should be made since voucher_detail_no is different
|
# seperate entries should be made since voucher_detail_no is different
|
||||||
self.assertEqual(len(item_one_gl_entry), 1)
|
self.assertEqual(len(item_one_gl_entry), 1)
|
||||||
@ -1055,13 +1058,13 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
def test_purchase_receipt_with_exchange_rate_difference(self):
|
def test_purchase_receipt_with_exchange_rate_difference(self):
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice
|
||||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt
|
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt
|
||||||
|
|
||||||
pi = create_purchase_invoice(company="_Test Company with perpetual inventory",
|
pi = create_purchase_invoice(company="_Test Company with perpetual inventory",
|
||||||
cost_center = "Main - TCP1",
|
cost_center = "Main - TCP1",
|
||||||
warehouse = "Stores - TCP1",
|
warehouse = "Stores - TCP1",
|
||||||
expense_account ="_Test Account Cost for Goods Sold - TCP1",
|
expense_account ="_Test Account Cost for Goods Sold - TCP1",
|
||||||
currency = "USD", conversion_rate = 70)
|
currency = "USD", conversion_rate = 70)
|
||||||
|
|
||||||
pr = create_purchase_receipt(pi.name)
|
pr = create_purchase_receipt(pi.name)
|
||||||
pr.conversion_rate = 80
|
pr.conversion_rate = 80
|
||||||
pr.items[0].purchase_invoice = pi.name
|
pr.items[0].purchase_invoice = pi.name
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user