Merge branch 'develop' into maint_sch_link_fix
This commit is contained in:
commit
d39ffeeef0
42
.github/labeler.yml
vendored
42
.github/labeler.yml
vendored
@ -1,53 +1,53 @@
|
|||||||
accounts:
|
accounts:
|
||||||
- 'erpnext/accounts/*'
|
- erpnext/accounts/*
|
||||||
- 'erpnext/controllers/accounts_controller.py'
|
- erpnext/controllers/accounts_controller.py
|
||||||
- 'erpnext/controllers/taxes_and_totals.py'
|
- erpnext/controllers/taxes_and_totals.py
|
||||||
|
|
||||||
stock:
|
stock:
|
||||||
- 'erpnext/stock/*'
|
- erpnext/stock/*
|
||||||
- 'erpnext/controllers/stock_controller.py'
|
- erpnext/controllers/stock_controller.py
|
||||||
- 'erpnext/controllers/item_variant.py'
|
- erpnext/controllers/item_variant.py
|
||||||
|
|
||||||
assets:
|
assets:
|
||||||
- 'erpnext/assets/*'
|
- erpnext/assets/*
|
||||||
|
|
||||||
regional:
|
regional:
|
||||||
- 'erpnext/regional/*'
|
- erpnext/regional/*
|
||||||
|
|
||||||
selling:
|
selling:
|
||||||
- 'erpnext/selling/*'
|
- erpnext/selling/*
|
||||||
- 'erpnext/controllers/selling_controller.py'
|
- erpnext/controllers/selling_controller.py
|
||||||
|
|
||||||
buying:
|
buying:
|
||||||
- 'erpnext/buying/*'
|
- erpnext/buying/*
|
||||||
- 'erpnext/controllers/buying_controller.py'
|
- erpnext/controllers/buying_controller.py
|
||||||
|
|
||||||
support:
|
support:
|
||||||
- 'erpnext/support/*'
|
- erpnext/support/*
|
||||||
|
|
||||||
POS:
|
POS:
|
||||||
- 'pos*'
|
- pos*
|
||||||
|
|
||||||
ecommerce:
|
ecommerce:
|
||||||
- 'erpnext/e_commerce/*'
|
- erpnext/e_commerce/*
|
||||||
|
|
||||||
maintenance:
|
maintenance:
|
||||||
- 'erpnext/maintenance/*'
|
- erpnext/maintenance/*
|
||||||
|
|
||||||
manufacturing:
|
manufacturing:
|
||||||
- 'erpnext/manufacturing/*'
|
- erpnext/manufacturing/*
|
||||||
|
|
||||||
crm:
|
crm:
|
||||||
- 'erpnext/crm/*'
|
- erpnext/crm/*
|
||||||
|
|
||||||
HR:
|
HR:
|
||||||
- 'erpnext/hr/*'
|
- erpnext/hr/*
|
||||||
|
|
||||||
payroll:
|
payroll:
|
||||||
- 'erpnext/payroll*'
|
- erpnext/payroll*
|
||||||
|
|
||||||
projects:
|
projects:
|
||||||
- 'erpnext/projects/*'
|
- erpnext/projects/*
|
||||||
|
|
||||||
# Any python files modifed but no test files modified
|
# Any python files modifed but no test files modified
|
||||||
needs-tests:
|
needs-tests:
|
||||||
|
@ -254,11 +254,13 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
|||||||
enable_check = "enable_deferred_revenue" \
|
enable_check = "enable_deferred_revenue" \
|
||||||
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
|
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
|
||||||
|
|
||||||
|
accounts_frozen_upto = frappe.get_cached_value('Accounts Settings', 'None', 'acc_frozen_upto')
|
||||||
|
|
||||||
def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
|
def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
|
||||||
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
|
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
|
||||||
if not (start_date and end_date): return
|
if not (start_date and end_date): return
|
||||||
|
|
||||||
account_currency = get_account_currency(item.expense_account)
|
account_currency = get_account_currency(item.expense_account or item.income_account)
|
||||||
if doc.doctype == "Sales Invoice":
|
if doc.doctype == "Sales Invoice":
|
||||||
against, project = doc.customer, doc.project
|
against, project = doc.customer, doc.project
|
||||||
credit_account, debit_account = item.income_account, item.deferred_revenue_account
|
credit_account, debit_account = item.income_account, item.deferred_revenue_account
|
||||||
@ -279,6 +281,10 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
|||||||
if not amount:
|
if not amount:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# check if books nor frozen till endate:
|
||||||
|
if getdate(end_date) >= getdate(accounts_frozen_upto):
|
||||||
|
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
|
||||||
|
|
||||||
if via_journal_entry:
|
if via_journal_entry:
|
||||||
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
|
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
|
||||||
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
|
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
|
||||||
@ -406,8 +412,6 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
|
|||||||
'account': credit_account,
|
'account': credit_account,
|
||||||
'credit': base_amount,
|
'credit': base_amount,
|
||||||
'credit_in_account_currency': amount,
|
'credit_in_account_currency': amount,
|
||||||
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
|
|
||||||
'party': against,
|
|
||||||
'account_currency': account_currency,
|
'account_currency': account_currency,
|
||||||
'reference_name': doc.name,
|
'reference_name': doc.name,
|
||||||
'reference_type': doc.doctype,
|
'reference_type': doc.doctype,
|
||||||
@ -420,8 +424,6 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
|
|||||||
'account': debit_account,
|
'account': debit_account,
|
||||||
'debit': base_amount,
|
'debit': base_amount,
|
||||||
'debit_in_account_currency': amount,
|
'debit_in_account_currency': amount,
|
||||||
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
|
|
||||||
'party': against,
|
|
||||||
'account_currency': account_currency,
|
'account_currency': account_currency,
|
||||||
'reference_name': doc.name,
|
'reference_name': doc.name,
|
||||||
'reference_type': doc.doctype,
|
'reference_type': doc.doctype,
|
||||||
|
@ -407,13 +407,14 @@ class JournalEntry(AccountsController):
|
|||||||
debit_or_credit = 'Debit' if d.debit else 'Credit'
|
debit_or_credit = 'Debit' if d.debit else 'Credit'
|
||||||
party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no,
|
party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no,
|
||||||
debit_or_credit)
|
debit_or_credit)
|
||||||
|
against_voucher = ['', against_voucher[1]]
|
||||||
else:
|
else:
|
||||||
if d.reference_type == "Sales Invoice":
|
if d.reference_type == "Sales Invoice":
|
||||||
party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
|
party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
|
||||||
else:
|
else:
|
||||||
party_account = against_voucher[1]
|
party_account = against_voucher[1]
|
||||||
|
|
||||||
if (against_voucher[0] != d.party or party_account != d.account):
|
if (against_voucher[0] != cstr(d.party) or party_account != d.account):
|
||||||
frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
|
frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
|
||||||
.format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
|
.format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
|
||||||
d.reference_type, d.reference_name))
|
d.reference_type, d.reference_name))
|
||||||
@ -478,6 +479,15 @@ class JournalEntry(AccountsController):
|
|||||||
|
|
||||||
def set_against_account(self):
|
def set_against_account(self):
|
||||||
accounts_debited, accounts_credited = [], []
|
accounts_debited, accounts_credited = [], []
|
||||||
|
if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'):
|
||||||
|
for d in self.get('accounts'):
|
||||||
|
if d.reference_type == 'Sales Invoice':
|
||||||
|
field = 'customer'
|
||||||
|
else:
|
||||||
|
field = 'supplier'
|
||||||
|
|
||||||
|
d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
|
||||||
|
else:
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
|
if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
|
||||||
if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
|
if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
|
||||||
|
@ -1781,47 +1781,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
|
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
|
||||||
|
|
||||||
def test_deferred_revenue_post_account_freeze_upto_by_admin(self):
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
|
|
||||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
|
|
||||||
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
|
|
||||||
|
|
||||||
deferred_account = create_account(account_name="Deferred Revenue",
|
|
||||||
parent_account="Current Liabilities - _TC", company="_Test Company")
|
|
||||||
|
|
||||||
item = create_item("_Test Item for Deferred Accounting")
|
|
||||||
item.enable_deferred_revenue = 1
|
|
||||||
item.deferred_revenue_account = deferred_account
|
|
||||||
item.no_of_months = 12
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True)
|
|
||||||
si.items[0].enable_deferred_revenue = 1
|
|
||||||
si.items[0].service_start_date = "2019-01-10"
|
|
||||||
si.items[0].service_end_date = "2019-03-15"
|
|
||||||
si.items[0].deferred_revenue_account = deferred_account
|
|
||||||
si.save()
|
|
||||||
si.submit()
|
|
||||||
|
|
||||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
|
|
||||||
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager')
|
|
||||||
|
|
||||||
pda1 = frappe.get_doc(dict(
|
|
||||||
doctype='Process Deferred Accounting',
|
|
||||||
posting_date=nowdate(),
|
|
||||||
start_date="2019-01-01",
|
|
||||||
end_date="2019-03-31",
|
|
||||||
type="Income",
|
|
||||||
company="_Test Company"
|
|
||||||
))
|
|
||||||
|
|
||||||
pda1.insert()
|
|
||||||
self.assertRaises(frappe.ValidationError, pda1.submit)
|
|
||||||
|
|
||||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
|
|
||||||
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
|
|
||||||
|
|
||||||
def test_fixed_deferred_revenue(self):
|
def test_fixed_deferred_revenue(self):
|
||||||
deferred_account = create_account(account_name="Deferred Revenue",
|
deferred_account = create_account(account_name="Deferred Revenue",
|
||||||
parent_account="Current Liabilities - _TC", company="_Test Company")
|
parent_account="Current Liabilities - _TC", company="_Test Company")
|
||||||
@ -2482,6 +2441,74 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
|
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
|
||||||
|
|
||||||
|
def test_multi_currency_deferred_revenue_via_journal_entry(self):
|
||||||
|
deferred_account = create_account(account_name="Deferred Revenue",
|
||||||
|
parent_account="Current Liabilities - _TC", company="_Test Company")
|
||||||
|
|
||||||
|
acc_settings = frappe.get_single('Accounts Settings')
|
||||||
|
acc_settings.book_deferred_entries_via_journal_entry = 1
|
||||||
|
acc_settings.submit_journal_entries = 1
|
||||||
|
acc_settings.save()
|
||||||
|
|
||||||
|
item = create_item("_Test Item for Deferred Accounting")
|
||||||
|
item.enable_deferred_expense = 1
|
||||||
|
item.deferred_revenue_account = deferred_account
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(customer='_Test Customer USD', currency='USD',
|
||||||
|
item=item.name, qty=1, rate=100, conversion_rate=60, do_not_save=True)
|
||||||
|
|
||||||
|
si.set_posting_time = 1
|
||||||
|
si.posting_date = '2019-01-01'
|
||||||
|
si.debit_to = '_Test Receivable USD - _TC'
|
||||||
|
si.items[0].enable_deferred_revenue = 1
|
||||||
|
si.items[0].service_start_date = "2019-01-01"
|
||||||
|
si.items[0].service_end_date = "2019-03-30"
|
||||||
|
si.items[0].deferred_expense_account = deferred_account
|
||||||
|
si.save()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
|
||||||
|
|
||||||
|
pda1 = frappe.get_doc(dict(
|
||||||
|
doctype='Process Deferred Accounting',
|
||||||
|
posting_date=nowdate(),
|
||||||
|
start_date="2019-01-01",
|
||||||
|
end_date="2019-03-31",
|
||||||
|
type="Income",
|
||||||
|
company="_Test Company"
|
||||||
|
))
|
||||||
|
|
||||||
|
pda1.insert()
|
||||||
|
pda1.submit()
|
||||||
|
|
||||||
|
expected_gle = [
|
||||||
|
["Sales - _TC", 0.0, 2089.89, "2019-01-28"],
|
||||||
|
[deferred_account, 2089.89, 0.0, "2019-01-28"],
|
||||||
|
["Sales - _TC", 0.0, 1887.64, "2019-02-28"],
|
||||||
|
[deferred_account, 1887.64, 0.0, "2019-02-28"],
|
||||||
|
["Sales - _TC", 0.0, 2022.47, "2019-03-15"],
|
||||||
|
[deferred_account, 2022.47, 0.0, "2019-03-15"]
|
||||||
|
]
|
||||||
|
|
||||||
|
gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
||||||
|
from `tabGL Entry`
|
||||||
|
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
|
||||||
|
order by posting_date asc, account asc""", (si.items[0].name, si.posting_date), as_dict=1)
|
||||||
|
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
|
self.assertEqual(expected_gle[i][1], gle.credit)
|
||||||
|
self.assertEqual(expected_gle[i][2], gle.debit)
|
||||||
|
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||||
|
|
||||||
|
acc_settings = frappe.get_single('Accounts Settings')
|
||||||
|
acc_settings.book_deferred_entries_via_journal_entry = 0
|
||||||
|
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||||
|
acc_settings.save()
|
||||||
|
|
||||||
|
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
|
||||||
|
|
||||||
def get_sales_invoice_for_e_invoice():
|
def get_sales_invoice_for_e_invoice():
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
si.naming_series = 'INV-2020-.#####'
|
si.naming_series = 'INV-2020-.#####'
|
||||||
|
@ -28,14 +28,14 @@
|
|||||||
{
|
{
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "single_threshold",
|
"fieldname": "single_threshold",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Single Transaction Threshold"
|
"label": "Single Transaction Threshold"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "cumulative_threshold",
|
"fieldname": "cumulative_threshold",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Cumulative Transaction Threshold"
|
"label": "Cumulative Transaction Threshold"
|
||||||
},
|
},
|
||||||
@ -59,7 +59,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-31 11:42:12.213977",
|
"modified": "2022-01-13 12:04:42.904263",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Tax Withholding Rate",
|
"name": "Tax Withholding Rate",
|
||||||
@ -68,5 +68,6 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -185,8 +185,6 @@ class AccountsController(TransactionBase):
|
|||||||
frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
|
frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
|
||||||
elif getdate(self.posting_date) > getdate(d.service_end_date):
|
elif getdate(self.posting_date) > getdate(d.service_end_date):
|
||||||
frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
|
frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
|
||||||
elif getdate(self.posting_date) > getdate(d.service_start_date):
|
|
||||||
frappe.throw(_("Row #{0}: Service Start Date cannot be before Invoice Posting Date").format(d.idx))
|
|
||||||
|
|
||||||
def validate_invoice_documents_schedule(self):
|
def validate_invoice_documents_schedule(self):
|
||||||
self.validate_payment_schedule_dates()
|
self.validate_payment_schedule_dates()
|
||||||
|
@ -1,294 +1,108 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "HR-LPR-.YYYY.-.#####",
|
"autoname": "HR-LPR-.YYYY.-.#####",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-04-13 15:20:52.864288",
|
"creation": "2018-04-13 15:20:52.864288",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"from_date",
|
||||||
|
"to_date",
|
||||||
|
"is_active",
|
||||||
|
"column_break_3",
|
||||||
|
"company",
|
||||||
|
"optional_holiday_list"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"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": "From Date",
|
"label": "From Date",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"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": 1,
|
|
||||||
"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": "to_date",
|
"fieldname": "to_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"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": "To Date",
|
"label": "To Date",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"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": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "is_active",
|
"fieldname": "is_active",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Is Active"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Is Active",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Company",
|
"label": "Company",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "optional_holiday_list",
|
"fieldname": "optional_holiday_list",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Holiday List for Optional Leave",
|
"label": "Holiday List for Optional Leave",
|
||||||
"length": 0,
|
"options": "Holiday List"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Holiday List",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"links": [],
|
||||||
"hide_heading": 0,
|
"modified": "2022-01-13 13:28:12.951025",
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2019-05-30 16:15:43.305502",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Leave Period",
|
"name": "Leave Period",
|
||||||
"name_case": "",
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "HR Manager",
|
"role": "HR Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "HR User",
|
"role": "HR User",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"search_fields": "from_date, to_date, company",
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"states": [],
|
||||||
"track_seen": 0,
|
"track_changes": 1
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
@ -113,10 +113,11 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-01 17:54:01.014509",
|
"modified": "2022-01-13 13:37:11.218882",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Leave Policy Assignment",
|
"name": "Leave Policy Assignment",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -164,5 +165,7 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "employee_name",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -48,7 +48,16 @@ frappe.listview_settings['Leave Policy Assignment'] = {
|
|||||||
if (cur_dialog.fields_dict.leave_period.value) {
|
if (cur_dialog.fields_dict.leave_period.value) {
|
||||||
me.set_effective_date();
|
me.set_effective_date();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
get_query() {
|
||||||
|
let filters = {"is_active": 1};
|
||||||
|
if (cur_dialog.fields_dict.company.value)
|
||||||
|
filters["company"] = cur_dialog.fields_dict.company.value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: filters
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldtype: "Column Break"
|
fieldtype: "Column Break"
|
||||||
|
@ -279,7 +279,7 @@ erpnext.patches.v13_0.add_custom_field_for_south_africa #2
|
|||||||
erpnext.patches.v13_0.update_recipient_email_digest
|
erpnext.patches.v13_0.update_recipient_email_digest
|
||||||
erpnext.patches.v13_0.shopify_deprecation_warning
|
erpnext.patches.v13_0.shopify_deprecation_warning
|
||||||
erpnext.patches.v13_0.remove_bad_selling_defaults
|
erpnext.patches.v13_0.remove_bad_selling_defaults
|
||||||
erpnext.patches.v13_0.trim_whitespace_from_serial_nos
|
erpnext.patches.v13_0.trim_whitespace_from_serial_nos # 16-01-2022
|
||||||
erpnext.patches.v13_0.migrate_stripe_api
|
erpnext.patches.v13_0.migrate_stripe_api
|
||||||
erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
|
erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
|
||||||
erpnext.patches.v13_0.einvoicing_deprecation_warning
|
erpnext.patches.v13_0.einvoicing_deprecation_warning
|
||||||
@ -313,8 +313,8 @@ erpnext.patches.v14_0.delete_healthcare_doctypes
|
|||||||
erpnext.patches.v13_0.update_category_in_ltds_certificate
|
erpnext.patches.v13_0.update_category_in_ltds_certificate
|
||||||
erpnext.patches.v13_0.create_pan_field_for_india #2
|
erpnext.patches.v13_0.create_pan_field_for_india #2
|
||||||
erpnext.patches.v14_0.delete_hub_doctypes
|
erpnext.patches.v14_0.delete_hub_doctypes
|
||||||
erpnext.patches.v13_0.create_ksa_vat_custom_fields
|
|
||||||
erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
|
erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
|
||||||
|
erpnext.patches.v13_0.create_ksa_vat_custom_fields # 07-01-2022
|
||||||
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
||||||
erpnext.patches.v14_0.migrate_crm_settings
|
erpnext.patches.v14_0.migrate_crm_settings
|
||||||
erpnext.patches.v13_0.rename_ksa_qr_field
|
erpnext.patches.v13_0.rename_ksa_qr_field
|
||||||
@ -326,3 +326,4 @@ erpnext.patches.v14_0.set_payroll_cost_centers
|
|||||||
erpnext.patches.v13_0.agriculture_deprecation_warning
|
erpnext.patches.v13_0.agriculture_deprecation_warning
|
||||||
erpnext.patches.v14_0.delete_agriculture_doctypes
|
erpnext.patches.v14_0.delete_agriculture_doctypes
|
||||||
erpnext.patches.v13_0.update_exchange_rate_settings
|
erpnext.patches.v13_0.update_exchange_rate_settings
|
||||||
|
erpnext.patches.v14_0.rearrange_company_fields
|
||||||
|
@ -9,13 +9,15 @@ def execute():
|
|||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where
|
where
|
||||||
is_cancelled = 0
|
is_cancelled = 0
|
||||||
and (serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s)
|
and ( serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s
|
||||||
|
or serial_no = %s )
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
" %", # leading whitespace
|
" %", # leading whitespace
|
||||||
"% ", # trailing whitespace
|
"% ", # trailing whitespace
|
||||||
"%\n %", # leading whitespace on newline
|
"%\n %", # leading whitespace on newline
|
||||||
"% \n%", # trailing whitespace on newline
|
"% \n%", # trailing whitespace on newline
|
||||||
|
"\n", # just new line
|
||||||
),
|
),
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
@ -47,3 +47,18 @@ def execute():
|
|||||||
frappe.delete_doc("DocType", doctype, ignore_missing=True)
|
frappe.delete_doc("DocType", doctype, ignore_missing=True)
|
||||||
|
|
||||||
frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True)
|
frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True)
|
||||||
|
|
||||||
|
custom_fields = {
|
||||||
|
'Sales Invoice': ['patient', 'patient_name', 'ref_practitioner'],
|
||||||
|
'Sales Invoice Item': ['reference_dt', 'reference_dn'],
|
||||||
|
'Stock Entry': ['inpatient_medication_entry'],
|
||||||
|
'Stock Entry Detail': ['patient', 'inpatient_medication_entry_child'],
|
||||||
|
}
|
||||||
|
for doc, fields in custom_fields.items():
|
||||||
|
filters = {
|
||||||
|
'dt': doc,
|
||||||
|
'fieldname': ['in', fields]
|
||||||
|
}
|
||||||
|
records = frappe.get_all('Custom Field', filters=filters, pluck='name')
|
||||||
|
for record in records:
|
||||||
|
frappe.delete_doc('Custom Field', record, ignore_missing=True, force=True)
|
||||||
|
31
erpnext/patches/v14_0/rearrange_company_fields.py
Normal file
31
erpnext/patches/v14_0/rearrange_company_fields.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc('setup', 'doctype', 'company')
|
||||||
|
|
||||||
|
custom_fields = {
|
||||||
|
'Company': [
|
||||||
|
dict(fieldname='hra_section', label='HRA Settings',
|
||||||
|
fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1),
|
||||||
|
dict(fieldname='basic_component', label='Basic Component',
|
||||||
|
fieldtype='Link', options='Salary Component', insert_after='hra_section'),
|
||||||
|
dict(fieldname='hra_component', label='HRA Component',
|
||||||
|
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
|
||||||
|
dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
|
||||||
|
dict(fieldname='arrear_component', label='Arrear Component',
|
||||||
|
fieldtype='Link', options='Salary Component', insert_after='hra_column_break'),
|
||||||
|
dict(fieldname='non_profit_section', label='Non Profit Settings',
|
||||||
|
fieldtype='Section Break', insert_after='arrear_component', collapsible=1),
|
||||||
|
dict(fieldname='company_80g_number', label='80G Number',
|
||||||
|
fieldtype='Data', insert_after='non_profit_section'),
|
||||||
|
dict(fieldname='with_effect_from', label='80G With Effect From',
|
||||||
|
fieldtype='Date', insert_after='company_80g_number'),
|
||||||
|
dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
|
||||||
|
dict(fieldname='pan_details', label='PAN Number',
|
||||||
|
fieldtype='Data', insert_after='non_profit_column_break')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
create_custom_fields(custom_fields, update=True)
|
@ -61,6 +61,8 @@ class PayrollEntry(Document):
|
|||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
|
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
|
||||||
where payroll_entry=%s """, (self.name)))
|
where payroll_entry=%s """, (self.name)))
|
||||||
|
self.db_set("salary_slips_created", 0)
|
||||||
|
self.db_set("salary_slips_submitted", 0)
|
||||||
|
|
||||||
def get_emp_list(self):
|
def get_emp_list(self):
|
||||||
"""
|
"""
|
||||||
|
@ -567,16 +567,16 @@ def get_custom_fields():
|
|||||||
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
|
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
|
||||||
dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
|
dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
|
||||||
dict(fieldname='arrear_component', label='Arrear Component',
|
dict(fieldname='arrear_component', label='Arrear Component',
|
||||||
fieldtype='Link', options='Salary Component', insert_after='hra_component'),
|
fieldtype='Link', options='Salary Component', insert_after='hra_column_break'),
|
||||||
dict(fieldname='non_profit_section', label='Non Profit Settings',
|
dict(fieldname='non_profit_section', label='Non Profit Settings',
|
||||||
fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1),
|
fieldtype='Section Break', insert_after='arrear_component', collapsible=1),
|
||||||
dict(fieldname='company_80g_number', label='80G Number',
|
dict(fieldname='company_80g_number', label='80G Number',
|
||||||
fieldtype='Data', insert_after='non_profit_section'),
|
fieldtype='Data', insert_after='non_profit_section'),
|
||||||
dict(fieldname='with_effect_from', label='80G With Effect From',
|
dict(fieldname='with_effect_from', label='80G With Effect From',
|
||||||
fieldtype='Date', insert_after='company_80g_number'),
|
fieldtype='Date', insert_after='company_80g_number'),
|
||||||
dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
|
dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
|
||||||
dict(fieldname='pan_details', label='PAN Number',
|
dict(fieldname='pan_details', label='PAN Number',
|
||||||
fieldtype='Data', insert_after='with_effect_from')
|
fieldtype='Data', insert_after='non_profit_column_break')
|
||||||
],
|
],
|
||||||
'Employee Tax Exemption Declaration':[
|
'Employee Tax Exemption Declaration':[
|
||||||
dict(fieldname='hra_section', label='HRA Exemption',
|
dict(fieldname='hra_section', label='HRA Exemption',
|
||||||
|
@ -53,7 +53,8 @@ frappe.query_reports["GSTR-1"] = {
|
|||||||
{ "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") },
|
{ "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") },
|
||||||
{ "value": "CDNR-UNREG", "label": __("Credit/Debit Notes (Unregistered) - 9B") },
|
{ "value": "CDNR-UNREG", "label": __("Credit/Debit Notes (Unregistered) - 9B") },
|
||||||
{ "value": "EXPORT", "label": __("Export Invoice - 6A") },
|
{ "value": "EXPORT", "label": __("Export Invoice - 6A") },
|
||||||
{ "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") }
|
{ "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") },
|
||||||
|
{ "value": "NIL Rated", "label": __("NIL RATED/EXEMPTED Invoices") }
|
||||||
],
|
],
|
||||||
"default": "B2B"
|
"default": "B2B"
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,8 @@ class Gstr1Report(object):
|
|||||||
port_code,
|
port_code,
|
||||||
shipping_bill_number,
|
shipping_bill_number,
|
||||||
shipping_bill_date,
|
shipping_bill_date,
|
||||||
reason_for_issuing_document
|
reason_for_issuing_document,
|
||||||
|
company_gstin
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -62,6 +63,8 @@ class Gstr1Report(object):
|
|||||||
self.get_b2c_data()
|
self.get_b2c_data()
|
||||||
elif self.filters.get("type_of_business") == "Advances":
|
elif self.filters.get("type_of_business") == "Advances":
|
||||||
self.get_advance_data()
|
self.get_advance_data()
|
||||||
|
elif self.filters.get("type_of_business") == "NIL Rated":
|
||||||
|
self.get_nil_rated_invoices()
|
||||||
elif self.invoices:
|
elif self.invoices:
|
||||||
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||||
invoice_details = self.invoices.get(inv)
|
invoice_details = self.invoices.get(inv)
|
||||||
@ -91,6 +94,57 @@ class Gstr1Report(object):
|
|||||||
row= [key[0], key[1], value[0], value[1]]
|
row= [key[0], key[1], value[0], value[1]]
|
||||||
self.data.append(row)
|
self.data.append(row)
|
||||||
|
|
||||||
|
def get_nil_rated_invoices(self):
|
||||||
|
nil_exempt_output = [
|
||||||
|
{
|
||||||
|
"description": "Inter-State supplies to registered persons",
|
||||||
|
"nil_rated": 0.0,
|
||||||
|
"exempted": 0.0,
|
||||||
|
"non_gst": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Intra-State supplies to registered persons",
|
||||||
|
"nil_rated": 0.0,
|
||||||
|
"exempted": 0.0,
|
||||||
|
"non_gst": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Inter-State supplies to unregistered persons",
|
||||||
|
"nil_rated": 0.0,
|
||||||
|
"exempted": 0.0,
|
||||||
|
"non_gst": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Intra-State supplies to unregistered persons",
|
||||||
|
"nil_rated": 0.0,
|
||||||
|
"exempted": 0.0,
|
||||||
|
"non_gst": 0.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for invoice, details in self.nil_exempt_non_gst.items():
|
||||||
|
invoice_detail = self.invoices.get(invoice)
|
||||||
|
if invoice_detail.get('gst_category') in ("Registered Regular", "Deemed Export", "SEZ"):
|
||||||
|
if is_inter_state(invoice_detail):
|
||||||
|
nil_exempt_output[0]["nil_rated"] += details[0]
|
||||||
|
nil_exempt_output[0]["exempted"] += details[1]
|
||||||
|
nil_exempt_output[0]["non_gst"] += details[2]
|
||||||
|
else:
|
||||||
|
nil_exempt_output[1]["nil_rated"] += details[0]
|
||||||
|
nil_exempt_output[1]["exempted"] += details[1]
|
||||||
|
nil_exempt_output[1]["non_gst"] += details[2]
|
||||||
|
else:
|
||||||
|
if is_inter_state(invoice_detail):
|
||||||
|
nil_exempt_output[2]["nil_rated"] += details[0]
|
||||||
|
nil_exempt_output[2]["exempted"] += details[1]
|
||||||
|
nil_exempt_output[2]["non_gst"] += details[2]
|
||||||
|
else:
|
||||||
|
nil_exempt_output[3]["nil_rated"] += details[0]
|
||||||
|
nil_exempt_output[3]["exempted"] += details[1]
|
||||||
|
nil_exempt_output[3]["non_gst"] += details[2]
|
||||||
|
|
||||||
|
self.data = nil_exempt_output
|
||||||
|
|
||||||
def get_b2c_data(self):
|
def get_b2c_data(self):
|
||||||
b2cs_output = {}
|
b2cs_output = {}
|
||||||
|
|
||||||
@ -240,10 +294,11 @@ class Gstr1Report(object):
|
|||||||
def get_invoice_items(self):
|
def get_invoice_items(self):
|
||||||
self.invoice_items = frappe._dict()
|
self.invoice_items = frappe._dict()
|
||||||
self.item_tax_rate = frappe._dict()
|
self.item_tax_rate = frappe._dict()
|
||||||
|
self.nil_exempt_non_gst = {}
|
||||||
|
|
||||||
items = frappe.db.sql("""
|
items = frappe.db.sql("""
|
||||||
select item_code, parent, taxable_value, base_net_amount, item_tax_rate
|
select item_code, parent, taxable_value, base_net_amount, item_tax_rate, is_nil_exempt,
|
||||||
from `tab%s Item`
|
is_non_gst from `tab%s Item`
|
||||||
where parent in (%s)
|
where parent in (%s)
|
||||||
""" % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
""" % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
||||||
|
|
||||||
@ -260,6 +315,16 @@ class Gstr1Report(object):
|
|||||||
tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, [])
|
tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, [])
|
||||||
tax_rate_dict.append(rate)
|
tax_rate_dict.append(rate)
|
||||||
|
|
||||||
|
if d.is_nil_exempt:
|
||||||
|
self.nil_exempt_non_gst.setdefault(d.parent, [0.0, 0.0, 0.0])
|
||||||
|
if item_tax_rate:
|
||||||
|
self.nil_exempt_non_gst[d.parent][0] += d.get('taxable_value', 0)
|
||||||
|
else:
|
||||||
|
self.nil_exempt_non_gst[d.parent][1] += d.get('taxable_value', 0)
|
||||||
|
elif d.is_non_gst:
|
||||||
|
self.nil_exempt_non_gst.setdefault(d.parent, [0.0, 0.0, 0.0])
|
||||||
|
self.nil_exempt_non_gst[d.parent][2] += d.get('taxable_value', 0)
|
||||||
|
|
||||||
def get_items_based_on_tax_rate(self):
|
def get_items_based_on_tax_rate(self):
|
||||||
self.tax_details = frappe.db.sql("""
|
self.tax_details = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
@ -322,6 +387,10 @@ class Gstr1Report(object):
|
|||||||
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):
|
||||||
|
self.other_columns = []
|
||||||
|
self.tax_columns = []
|
||||||
|
|
||||||
|
if self.filters.get("type_of_business") != "NIL Rated":
|
||||||
self.tax_columns = [
|
self.tax_columns = [
|
||||||
{
|
{
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
@ -336,7 +405,6 @@ class Gstr1Report(object):
|
|||||||
"width": 100
|
"width": 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
self.other_columns = []
|
|
||||||
|
|
||||||
if self.filters.get("type_of_business") == "B2B":
|
if self.filters.get("type_of_business") == "B2B":
|
||||||
self.invoice_columns = [
|
self.invoice_columns = [
|
||||||
@ -705,6 +773,33 @@ class Gstr1Report(object):
|
|||||||
"width": 100
|
"width": 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
elif self.filters.get("type_of_business") == "NIL Rated":
|
||||||
|
self.invoice_columns = [
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"label": "Description",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 420
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "nil_rated",
|
||||||
|
"label": "Nil Rated",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "exempted",
|
||||||
|
"label": "Exempted",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "non_gst",
|
||||||
|
"label": "Non GST",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
self.columns = self.invoice_columns + self.tax_columns + self.other_columns
|
self.columns = self.invoice_columns + self.tax_columns + self.other_columns
|
||||||
|
|
||||||
@ -768,6 +863,11 @@ def get_json(filters, report_name, data):
|
|||||||
out = get_advances_json(res, gstin)
|
out = get_advances_json(res, gstin)
|
||||||
gst_json["at"] = out
|
gst_json["at"] = out
|
||||||
|
|
||||||
|
elif filters["type_of_business"] == "NIL Rated":
|
||||||
|
res = report_data[:-1]
|
||||||
|
out = get_exempted_json(res)
|
||||||
|
gst_json["nil"] = out
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'report_name': report_name,
|
'report_name': report_name,
|
||||||
'report_type': filters['type_of_business'],
|
'report_type': filters['type_of_business'],
|
||||||
@ -980,6 +1080,36 @@ def get_cdnr_unreg_json(res, gstin):
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def get_exempted_json(data):
|
||||||
|
out = {
|
||||||
|
"inv": [
|
||||||
|
{
|
||||||
|
"sply_ty": "INTRB2B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sply_ty": "INTRAB2B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sply_ty": "INTRB2C"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sply_ty": "INTRAB2C"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v in enumerate(data):
|
||||||
|
if data[i].get('nil_rated'):
|
||||||
|
out['inv'][i]['nil_amt'] = data[i]['nil_rated']
|
||||||
|
|
||||||
|
if data[i].get('exempted'):
|
||||||
|
out['inv'][i]['expt_amt'] = data[i]['exempted']
|
||||||
|
|
||||||
|
if data[i].get('non_gst'):
|
||||||
|
out['inv'][i]['ngsup_amt'] = data[i]['non_gst']
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
def get_invoice_type_for_cdnr(row):
|
def get_invoice_type_for_cdnr(row):
|
||||||
if row.get('gst_category') == 'SEZ':
|
if row.get('gst_category') == 'SEZ':
|
||||||
if row.get('export_type') == 'WPAY':
|
if row.get('export_type') == 'WPAY':
|
||||||
@ -1064,3 +1194,9 @@ def download_json_file():
|
|||||||
frappe.response['filecontent'] = data['data']
|
frappe.response['filecontent'] = data['data']
|
||||||
frappe.response['content_type'] = 'application/json'
|
frappe.response['content_type'] = 'application/json'
|
||||||
frappe.response['type'] = 'download'
|
frappe.response['type'] = 'download'
|
||||||
|
|
||||||
|
def is_inter_state(invoice_detail):
|
||||||
|
if invoice_detail.place_of_supply.split("-")[0] != invoice_detail.company_gstin[:2]:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
@ -3,12 +3,10 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.permissions import add_permission, update_permission_property
|
from frappe.permissions import add_permission, update_permission_property
|
||||||
from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields
|
|
||||||
from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting
|
from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting
|
||||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
|
|
||||||
def setup(company=None, patch=True):
|
def setup(company=None, patch=True):
|
||||||
uae_custom_fields()
|
|
||||||
add_print_formats()
|
add_print_formats()
|
||||||
add_permissions()
|
add_permissions()
|
||||||
make_custom_fields()
|
make_custom_fields()
|
||||||
@ -40,38 +38,67 @@ def make_custom_fields():
|
|||||||
- Company Name in Arabic
|
- Company Name in Arabic
|
||||||
- Address in Arabic
|
- Address in Arabic
|
||||||
"""
|
"""
|
||||||
|
is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
|
||||||
|
fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description',
|
||||||
|
print_hide=1)
|
||||||
|
|
||||||
|
is_exempt = dict(fieldname='is_exempt', label='Is Exempt',
|
||||||
|
fieldtype='Check', fetch_from='item_code.is_exempt', insert_after='is_zero_rated',
|
||||||
|
print_hide=1)
|
||||||
|
|
||||||
|
purchase_invoice_fields = [
|
||||||
|
dict(fieldname='company_trn', label='Company TRN',
|
||||||
|
fieldtype='Read Only', insert_after='shipping_address',
|
||||||
|
fetch_from='company.tax_id', print_hide=1),
|
||||||
|
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
|
||||||
|
fieldtype='Read Only', insert_after='supplier_name',
|
||||||
|
fetch_from='supplier.supplier_name_in_arabic', print_hide=1)
|
||||||
|
]
|
||||||
|
|
||||||
|
sales_invoice_fields = [
|
||||||
|
dict(fieldname='company_trn', label='Company TRN',
|
||||||
|
fieldtype='Read Only', insert_after='company_address',
|
||||||
|
fetch_from='company.tax_id', print_hide=1),
|
||||||
|
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
|
||||||
|
fieldtype='Read Only', insert_after='customer_name',
|
||||||
|
fetch_from='customer.customer_name_in_arabic', print_hide=1),
|
||||||
|
dict(fieldname='ksa_einv_qr', label='KSA E-Invoicing QR',
|
||||||
|
fieldtype='Attach Image', read_only=1, no_copy=1, hidden=1)
|
||||||
|
]
|
||||||
|
|
||||||
custom_fields = {
|
custom_fields = {
|
||||||
'Sales Invoice': [
|
'Item': [is_zero_rated, is_exempt],
|
||||||
dict(
|
'Customer': [
|
||||||
fieldname='ksa_einv_qr',
|
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
|
||||||
label='KSA E-Invoicing QR',
|
fieldtype='Data', insert_after='customer_name'),
|
||||||
fieldtype='Attach Image',
|
|
||||||
read_only=1, no_copy=1, hidden=1
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
'POS Invoice': [
|
'Supplier': [
|
||||||
dict(
|
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
|
||||||
fieldname='ksa_einv_qr',
|
fieldtype='Data', insert_after='supplier_name'),
|
||||||
label='KSA E-Invoicing QR',
|
|
||||||
fieldtype='Attach Image',
|
|
||||||
read_only=1, no_copy=1, hidden=1
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
|
'Purchase Invoice': purchase_invoice_fields,
|
||||||
|
'Purchase Order': purchase_invoice_fields,
|
||||||
|
'Purchase Receipt': purchase_invoice_fields,
|
||||||
|
'Sales Invoice': sales_invoice_fields,
|
||||||
|
'POS Invoice': sales_invoice_fields,
|
||||||
|
'Sales Order': sales_invoice_fields,
|
||||||
|
'Delivery Note': sales_invoice_fields,
|
||||||
|
'Sales Invoice Item': [is_zero_rated, is_exempt],
|
||||||
|
'POS Invoice Item': [is_zero_rated, is_exempt],
|
||||||
|
'Purchase Invoice Item': [is_zero_rated, is_exempt],
|
||||||
|
'Sales Order Item': [is_zero_rated, is_exempt],
|
||||||
|
'Delivery Note Item': [is_zero_rated, is_exempt],
|
||||||
|
'Quotation Item': [is_zero_rated, is_exempt],
|
||||||
|
'Purchase Order Item': [is_zero_rated, is_exempt],
|
||||||
|
'Purchase Receipt Item': [is_zero_rated, is_exempt],
|
||||||
|
'Supplier Quotation Item': [is_zero_rated, is_exempt],
|
||||||
'Address': [
|
'Address': [
|
||||||
dict(
|
dict(fieldname='address_in_arabic', label='Address in Arabic',
|
||||||
fieldname='address_in_arabic',
|
fieldtype='Data',insert_after='address_line2')
|
||||||
label='Address in Arabic',
|
|
||||||
fieldtype='Data',
|
|
||||||
insert_after='address_line2'
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
'Company': [
|
'Company': [
|
||||||
dict(
|
dict(fieldname='company_name_in_arabic', label='Company Name In Arabic',
|
||||||
fieldname='company_name_in_arabic',
|
fieldtype='Data', insert_after='company_name')
|
||||||
label='Company Name In Arabic',
|
|
||||||
fieldtype='Data',
|
|
||||||
insert_after='company_name'
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +213,9 @@ erpnext.company.setup_queries = function(frm) {
|
|||||||
["default_payroll_payable_account", {"root_type": "Liability"}],
|
["default_payroll_payable_account", {"root_type": "Liability"}],
|
||||||
["round_off_account", {"root_type": "Expense"}],
|
["round_off_account", {"root_type": "Expense"}],
|
||||||
["write_off_account", {"root_type": "Expense"}],
|
["write_off_account", {"root_type": "Expense"}],
|
||||||
|
["default_deferred_expense_account", {}],
|
||||||
|
["default_deferred_revenue_account", {}],
|
||||||
|
["default_expense_claim_payable_account", {}],
|
||||||
["default_discount_account", {}],
|
["default_discount_account", {}],
|
||||||
["discount_allowed_account", {"root_type": "Expense"}],
|
["discount_allowed_account", {"root_type": "Expense"}],
|
||||||
["discount_received_account", {"root_type": "Income"}],
|
["discount_received_account", {"root_type": "Income"}],
|
||||||
|
@ -292,6 +292,7 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
|
|||||||
join `tabStock Ledger Entry` ignore index (item_code, warehouse)
|
join `tabStock Ledger Entry` ignore index (item_code, warehouse)
|
||||||
on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
|
on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
|
||||||
where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
|
where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
|
||||||
|
and `tabStock Ledger Entry`.is_cancelled = 0
|
||||||
and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
|
and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
|
||||||
group by batch_id
|
group by batch_id
|
||||||
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
|
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
|
||||||
|
@ -402,10 +402,16 @@ def update_serial_nos(sle, item_det):
|
|||||||
def get_auto_serial_nos(serial_no_series, qty):
|
def get_auto_serial_nos(serial_no_series, qty):
|
||||||
serial_nos = []
|
serial_nos = []
|
||||||
for i in range(cint(qty)):
|
for i in range(cint(qty)):
|
||||||
serial_nos.append(make_autoname(serial_no_series, "Serial No"))
|
serial_nos.append(get_new_serial_number(serial_no_series))
|
||||||
|
|
||||||
return "\n".join(serial_nos)
|
return "\n".join(serial_nos)
|
||||||
|
|
||||||
|
def get_new_serial_number(series):
|
||||||
|
sr_no = make_autoname(series, "Serial No")
|
||||||
|
if frappe.db.exists("Serial No", sr_no):
|
||||||
|
sr_no = get_new_serial_number(series)
|
||||||
|
return sr_no
|
||||||
|
|
||||||
def auto_make_serial_nos(args):
|
def auto_make_serial_nos(args):
|
||||||
serial_nos = get_serial_nos(args.get('serial_no'))
|
serial_nos = get_serial_nos(args.get('serial_no'))
|
||||||
created_numbers = []
|
created_numbers = []
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
@ -176,6 +177,24 @@ class TestSerialNo(ERPNextTestCase):
|
|||||||
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
|
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
|
||||||
self.assertEqual(sn_doc.purchase_document_no, se.name)
|
self.assertEqual(sn_doc.purchase_document_no, se.name)
|
||||||
|
|
||||||
|
def test_auto_creation_of_serial_no(self):
|
||||||
|
"""
|
||||||
|
Test if auto created Serial No excludes existing serial numbers
|
||||||
|
"""
|
||||||
|
item_code = make_item("_Test Auto Serial Item ", {
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "XYZ.###"
|
||||||
|
}).item_code
|
||||||
|
|
||||||
|
# Reserve XYZ005
|
||||||
|
pr_1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no="XYZ005")
|
||||||
|
# XYZ005 is already used and will throw an error if used again
|
||||||
|
pr_2 = make_purchase_receipt(item_code=item_code, qty=10)
|
||||||
|
|
||||||
|
self.assertEqual(get_serial_nos(pr_1.get("items")[0].serial_no)[0], "XYZ005")
|
||||||
|
for serial_no in get_serial_nos(pr_2.get("items")[0].serial_no):
|
||||||
|
self.assertNotEqual(serial_no, "XYZ005")
|
||||||
|
|
||||||
def test_serial_no_sanitation(self):
|
def test_serial_no_sanitation(self):
|
||||||
"Test if Serial No input is sanitised before entering the DB."
|
"Test if Serial No input is sanitised before entering the DB."
|
||||||
item_code = "_Test Serialized Item"
|
item_code = "_Test Serialized Item"
|
||||||
|
@ -86,8 +86,11 @@ class StockEntry(StockController):
|
|||||||
self.validate_warehouse()
|
self.validate_warehouse()
|
||||||
self.validate_work_order()
|
self.validate_work_order()
|
||||||
self.validate_bom()
|
self.validate_bom()
|
||||||
|
|
||||||
|
if self.purpose in ("Manufacture", "Repack"):
|
||||||
self.mark_finished_and_scrap_items()
|
self.mark_finished_and_scrap_items()
|
||||||
self.validate_finished_goods()
|
self.validate_finished_goods()
|
||||||
|
|
||||||
self.validate_with_material_request()
|
self.validate_with_material_request()
|
||||||
self.validate_batch()
|
self.validate_batch()
|
||||||
self.validate_inspection()
|
self.validate_inspection()
|
||||||
@ -706,7 +709,6 @@ class StockEntry(StockController):
|
|||||||
validate_bom_no(item_code, d.bom_no)
|
validate_bom_no(item_code, d.bom_no)
|
||||||
|
|
||||||
def mark_finished_and_scrap_items(self):
|
def mark_finished_and_scrap_items(self):
|
||||||
if self.purpose in ("Repack", "Manufacture"):
|
|
||||||
if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
|
if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -738,9 +740,9 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
def validate_finished_goods(self):
|
def validate_finished_goods(self):
|
||||||
"""
|
"""
|
||||||
1. Check if FG exists
|
1. Check if FG exists (mfg, repack)
|
||||||
2. Check if Multiple FG Items are present
|
2. Check if Multiple FG Items are present (mfg)
|
||||||
3. Check FG Item and Qty against WO if present
|
3. Check FG Item and Qty against WO if present (mfg)
|
||||||
"""
|
"""
|
||||||
production_item, wo_qty, finished_items = None, 0, []
|
production_item, wo_qty, finished_items = None, 0, []
|
||||||
|
|
||||||
@ -753,8 +755,9 @@ class StockEntry(StockController):
|
|||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if d.is_finished_item:
|
if d.is_finished_item:
|
||||||
if not self.work_order:
|
if not self.work_order:
|
||||||
|
# Independent MFG Entry/ Repack Entry, no WO to match against
|
||||||
finished_items.append(d.item_code)
|
finished_items.append(d.item_code)
|
||||||
continue # Independent Manufacture Entry, no WO to match against
|
continue
|
||||||
|
|
||||||
if d.item_code != production_item:
|
if d.item_code != production_item:
|
||||||
frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
|
frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
|
||||||
@ -767,19 +770,17 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
finished_items.append(d.item_code)
|
finished_items.append(d.item_code)
|
||||||
|
|
||||||
if len(set(finished_items)) > 1:
|
|
||||||
frappe.throw(
|
|
||||||
msg=_("Multiple items cannot be marked as finished item"),
|
|
||||||
title=_("Note"),
|
|
||||||
exc=FinishedGoodError
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.purpose == "Manufacture":
|
|
||||||
if not finished_items:
|
if not finished_items:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
|
msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
|
||||||
title=_("Missing Finished Good"),
|
title=_("Missing Finished Good"), exc=FinishedGoodError
|
||||||
exc=FinishedGoodError
|
)
|
||||||
|
|
||||||
|
if self.purpose == "Manufacture":
|
||||||
|
if len(set(finished_items)) > 1:
|
||||||
|
frappe.throw(
|
||||||
|
msg=_("Multiple items cannot be marked as finished item"),
|
||||||
|
title=_("Note"), exc=FinishedGoodError
|
||||||
)
|
)
|
||||||
|
|
||||||
allowance_percentage = flt(
|
allowance_percentage = flt(
|
||||||
|
@ -226,9 +226,47 @@ class TestStockEntry(ERPNextTestCase):
|
|||||||
|
|
||||||
mtn.cancel()
|
mtn.cancel()
|
||||||
|
|
||||||
def test_repack_no_change_in_valuation(self):
|
def test_repack_multiple_fg(self):
|
||||||
company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
|
"Test `is_finished_item` for one item repacked into two items."
|
||||||
|
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100)
|
||||||
|
|
||||||
|
repack = frappe.copy_doc(test_records[3])
|
||||||
|
repack.posting_date = nowdate()
|
||||||
|
repack.posting_time = nowtime()
|
||||||
|
|
||||||
|
repack.items[0].qty = 100.0
|
||||||
|
repack.items[0].transfer_qty = 100.0
|
||||||
|
repack.items[1].qty = 50.0
|
||||||
|
|
||||||
|
repack.append("items", {
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"doctype": "Stock Entry Detail",
|
||||||
|
"expense_account": "Stock Adjustment - _TC",
|
||||||
|
"basic_rate": 150,
|
||||||
|
"item_code": "_Test Item 2",
|
||||||
|
"parentfield": "items",
|
||||||
|
"qty": 50.0,
|
||||||
|
"stock_uom": "_Test UOM",
|
||||||
|
"t_warehouse": "_Test Warehouse - _TC",
|
||||||
|
"transfer_qty": 50.0,
|
||||||
|
"uom": "_Test UOM"
|
||||||
|
})
|
||||||
|
repack.set_stock_entry_type()
|
||||||
|
repack.insert()
|
||||||
|
|
||||||
|
self.assertEqual(repack.items[1].is_finished_item, 1)
|
||||||
|
self.assertEqual(repack.items[2].is_finished_item, 1)
|
||||||
|
|
||||||
|
repack.items[1].is_finished_item = 0
|
||||||
|
repack.items[2].is_finished_item = 0
|
||||||
|
|
||||||
|
# must raise error if 0 fg in repack entry
|
||||||
|
self.assertRaises(FinishedGoodError, repack.validate_finished_goods)
|
||||||
|
|
||||||
|
repack.delete() # teardown
|
||||||
|
|
||||||
|
def test_repack_no_change_in_valuation(self):
|
||||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
|
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
|
||||||
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
|
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
|
||||||
qty=50, basic_rate=100)
|
qty=50, basic_rate=100)
|
||||||
|
@ -55,7 +55,8 @@ def get_stock_ledger_entries(filters):
|
|||||||
return frappe.db.sql("""select item_code, batch_no, warehouse,
|
return frappe.db.sql("""select item_code, batch_no, warehouse,
|
||||||
posting_date, actual_qty
|
posting_date, actual_qty
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
|
where is_cancelled = 0
|
||||||
|
and docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
|
||||||
conditions, as_dict=1)
|
conditions, as_dict=1)
|
||||||
|
|
||||||
def get_item_warehouse_batch_map(filters, float_precision):
|
def get_item_warehouse_batch_map(filters, float_precision):
|
||||||
|
@ -91,7 +91,7 @@ def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDLis
|
|||||||
voucher_nos = [fe.get('voucher_no') for fe in filtered_entries]
|
voucher_nos = [fe.get('voucher_no') for fe in filtered_entries]
|
||||||
svd_list = frappe.get_list(
|
svd_list = frappe.get_list(
|
||||||
'Stock Ledger Entry', fields=['item_code','stock_value_difference'],
|
'Stock Ledger Entry', fields=['item_code','stock_value_difference'],
|
||||||
filters=[('voucher_no', 'in', voucher_nos)]
|
filters=[('voucher_no', 'in', voucher_nos), ("is_cancelled", "=", 0)]
|
||||||
)
|
)
|
||||||
assign_item_groups_to_svd_list(svd_list)
|
assign_item_groups_to_svd_list(svd_list)
|
||||||
return svd_list
|
return svd_list
|
||||||
|
@ -76,6 +76,7 @@ def get_consumed_items(condition):
|
|||||||
on sle.voucher_no = se.name
|
on sle.voucher_no = se.name
|
||||||
where
|
where
|
||||||
actual_qty < 0
|
actual_qty < 0
|
||||||
|
and is_cancelled = 0
|
||||||
and voucher_type not in ('Delivery Note', 'Sales Invoice')
|
and voucher_type not in ('Delivery Note', 'Sales Invoice')
|
||||||
%s
|
%s
|
||||||
group by item_code""" % condition, as_dict=1)
|
group by item_code""" % condition, as_dict=1)
|
||||||
|
@ -105,6 +105,7 @@ def get_args_for_future_sle(row):
|
|||||||
|
|
||||||
def validate_serial_no(sle):
|
def validate_serial_no(sle):
|
||||||
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 sn in get_serial_nos(sle.serial_no):
|
for sn in get_serial_nos(sle.serial_no):
|
||||||
args = copy.deepcopy(sle)
|
args = copy.deepcopy(sle)
|
||||||
args.serial_no = sn
|
args.serial_no = sn
|
||||||
@ -423,6 +424,8 @@ class update_entries_after(object):
|
|||||||
return sorted(entries_to_fix, key=lambda k: k['timestamp'])
|
return sorted(entries_to_fix, key=lambda k: k['timestamp'])
|
||||||
|
|
||||||
def process_sle(self, sle):
|
def process_sle(self, sle):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
# previous sle data for this warehouse
|
# previous sle data for this warehouse
|
||||||
self.wh_data = self.data[sle.warehouse]
|
self.wh_data = self.data[sle.warehouse]
|
||||||
|
|
||||||
@ -437,7 +440,7 @@ class update_entries_after(object):
|
|||||||
if not self.args.get("sle_id"):
|
if not self.args.get("sle_id"):
|
||||||
self.get_dynamic_incoming_outgoing_rate(sle)
|
self.get_dynamic_incoming_outgoing_rate(sle)
|
||||||
|
|
||||||
if sle.serial_no:
|
if get_serial_nos(sle.serial_no):
|
||||||
self.get_serialized_values(sle)
|
self.get_serialized_values(sle)
|
||||||
self.wh_data.qty_after_transaction += flt(sle.actual_qty)
|
self.wh_data.qty_after_transaction += flt(sle.actual_qty)
|
||||||
if sle.voucher_type == "Stock Reconciliation":
|
if sle.voucher_type == "Stock Reconciliation":
|
||||||
@ -449,8 +452,9 @@ class update_entries_after(object):
|
|||||||
# assert
|
# assert
|
||||||
self.wh_data.valuation_rate = sle.valuation_rate
|
self.wh_data.valuation_rate = sle.valuation_rate
|
||||||
self.wh_data.qty_after_transaction = sle.qty_after_transaction
|
self.wh_data.qty_after_transaction = sle.qty_after_transaction
|
||||||
self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]]
|
|
||||||
self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
|
self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
|
||||||
|
if self.valuation_method != "Moving Average":
|
||||||
|
self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]]
|
||||||
else:
|
else:
|
||||||
if self.valuation_method == "Moving Average":
|
if self.valuation_method == "Moving Average":
|
||||||
self.get_moving_average_values(sle)
|
self.get_moving_average_values(sle)
|
||||||
@ -646,6 +650,7 @@ class update_entries_after(object):
|
|||||||
where
|
where
|
||||||
company = %s
|
company = %s
|
||||||
and actual_qty > 0
|
and actual_qty > 0
|
||||||
|
and is_cancelled = 0
|
||||||
and (serial_no = %s
|
and (serial_no = %s
|
||||||
or serial_no like %s
|
or serial_no like %s
|
||||||
or serial_no like %s
|
or serial_no like %s
|
||||||
@ -901,6 +906,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
|
|||||||
item_code = %s
|
item_code = %s
|
||||||
AND warehouse = %s
|
AND warehouse = %s
|
||||||
AND valuation_rate >= 0
|
AND valuation_rate >= 0
|
||||||
|
AND is_cancelled = 0
|
||||||
AND NOT (voucher_no = %s AND voucher_type = %s)
|
AND NOT (voucher_no = %s AND voucher_type = %s)
|
||||||
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
|
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
|
||||||
|
|
||||||
@ -911,6 +917,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
|
|||||||
where
|
where
|
||||||
item_code = %s
|
item_code = %s
|
||||||
AND valuation_rate > 0
|
AND valuation_rate > 0
|
||||||
|
AND is_cancelled = 0
|
||||||
AND NOT(voucher_no = %s AND voucher_type = %s)
|
AND NOT(voucher_no = %s AND voucher_type = %s)
|
||||||
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type))
|
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type))
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@ frappe.ui.form.on('Service Level Agreement', {
|
|||||||
filters: [
|
filters: [
|
||||||
['DocType', 'issingle', '=', 0],
|
['DocType', 'issingle', '=', 0],
|
||||||
['DocType', 'istable', '=', 0],
|
['DocType', 'istable', '=', 0],
|
||||||
|
['DocType', 'is_submittable', '=', 0],
|
||||||
['DocType', 'name', 'not in', invalid_doctypes],
|
['DocType', 'name', 'not in', invalid_doctypes],
|
||||||
['DocType', 'module', 'not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
|
['DocType', 'module', 'not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
|
||||||
]
|
]
|
||||||
|
@ -29,6 +29,7 @@ from erpnext.support.doctype.issue.issue import get_holidays
|
|||||||
|
|
||||||
class ServiceLevelAgreement(Document):
|
class ServiceLevelAgreement(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_selected_doctype()
|
||||||
self.validate_doc()
|
self.validate_doc()
|
||||||
self.validate_status_field()
|
self.validate_status_field()
|
||||||
self.check_priorities()
|
self.check_priorities()
|
||||||
@ -106,6 +107,23 @@ class ServiceLevelAgreement(Document):
|
|||||||
frappe.throw(_("Service Level Agreement for {0} {1} already exists.").format(
|
frappe.throw(_("Service Level Agreement for {0} {1} already exists.").format(
|
||||||
frappe.bold(self.entity_type), frappe.bold(self.entity)))
|
frappe.bold(self.entity_type), frappe.bold(self.entity)))
|
||||||
|
|
||||||
|
def validate_selected_doctype(self):
|
||||||
|
invalid_doctypes = list(frappe.model.core_doctypes_list)
|
||||||
|
invalid_doctypes.extend(['Cost Center', 'Company'])
|
||||||
|
valid_document_types = frappe.get_all('DocType', {
|
||||||
|
'issingle': 0,
|
||||||
|
'istable': 0,
|
||||||
|
'is_submittable': 0,
|
||||||
|
'name': ['not in', invalid_doctypes],
|
||||||
|
'module': ['not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
|
||||||
|
}, pluck="name")
|
||||||
|
|
||||||
|
if self.document_type not in valid_document_types:
|
||||||
|
frappe.throw(
|
||||||
|
msg=_("Please select valid document type."),
|
||||||
|
title=_("Invalid Document Type")
|
||||||
|
)
|
||||||
|
|
||||||
def validate_status_field(self):
|
def validate_status_field(self):
|
||||||
meta = frappe.get_meta(self.document_type)
|
meta = frappe.get_meta(self.document_type)
|
||||||
if not meta.get_field("status"):
|
if not meta.get_field("status"):
|
||||||
@ -247,8 +265,14 @@ def get_active_service_level_agreement_for(doc):
|
|||||||
]
|
]
|
||||||
|
|
||||||
customer = doc.get('customer')
|
customer = doc.get('customer')
|
||||||
|
if customer:
|
||||||
|
or_filters.extend([
|
||||||
|
["Service Level Agreement", "entity", "in", [customer] + get_customer_group(customer) + get_customer_territory(customer)],
|
||||||
|
["Service Level Agreement", "entity_type", "is", "not set"]
|
||||||
|
])
|
||||||
|
else:
|
||||||
or_filters.append(
|
or_filters.append(
|
||||||
["Service Level Agreement", "entity", "in", [customer] + get_customer_group(customer) + get_customer_territory(customer)]
|
["Service Level Agreement", "entity_type", "is", "not set"]
|
||||||
)
|
)
|
||||||
|
|
||||||
default_sla_filter = filters + [["Service Level Agreement", "default_service_level_agreement", "=", 1]]
|
default_sla_filter = filters + [["Service Level Agreement", "default_service_level_agreement", "=", 1]]
|
||||||
@ -361,11 +385,18 @@ def apply(doc, method=None):
|
|||||||
sla = get_active_service_level_agreement_for(doc)
|
sla = get_active_service_level_agreement_for(doc)
|
||||||
|
|
||||||
if not sla:
|
if not sla:
|
||||||
|
remove_sla_if_applied(doc)
|
||||||
return
|
return
|
||||||
|
|
||||||
process_sla(doc, sla)
|
process_sla(doc, sla)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_sla_if_applied(doc):
|
||||||
|
doc.service_level_agreement = None
|
||||||
|
doc.response_by = None
|
||||||
|
doc.resolution_by = None
|
||||||
|
|
||||||
|
|
||||||
def process_sla(doc, sla):
|
def process_sla(doc, sla):
|
||||||
|
|
||||||
if not doc.creation:
|
if not doc.creation:
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
def get_data():
|
|
||||||
return {
|
|
||||||
'fieldname': 'service_level_agreement',
|
|
||||||
'transactions': [
|
|
||||||
{
|
|
||||||
'label': _('Issue'),
|
|
||||||
'items': ['Issue']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -244,6 +244,13 @@ class TestServiceLevelAgreement(unittest.TestCase):
|
|||||||
applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement')
|
applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement')
|
||||||
self.assertEqual(applied_sla, lead_sla.name)
|
self.assertEqual(applied_sla, lead_sla.name)
|
||||||
|
|
||||||
|
# check if SLA is removed if condition fails
|
||||||
|
lead.reload()
|
||||||
|
lead.source = None
|
||||||
|
lead.save()
|
||||||
|
applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement')
|
||||||
|
self.assertFalse(applied_sla)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for d in frappe.get_all("Service Level Agreement"):
|
for d in frappe.get_all("Service Level Agreement"):
|
||||||
frappe.delete_doc("Service Level Agreement", d.name, force=1)
|
frappe.delete_doc("Service Level Agreement", d.name, force=1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user