Merge branch 'develop' of https://github.com/frappe/erpnext into project-link-for-all-accounts

This commit is contained in:
Deepesh Garg 2020-07-06 12:37:28 +05:30
commit 92a03aa5db
179 changed files with 3346 additions and 1787 deletions

View File

@ -2,10 +2,11 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, cint, get_link_to_form from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, get_first_day, cint, get_link_to_form, rounded
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from frappe.email import sendmail_to_system_managers from frappe.email import sendmail_to_system_managers
from frappe.utils.background_jobs import enqueue from frappe.utils.background_jobs import enqueue
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
def validate_service_stop_date(doc): def validate_service_stop_date(doc):
''' Validates service_stop_date for Purchase Invoice and Sales Invoice ''' ''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
@ -109,6 +110,18 @@ def get_booking_dates(doc, item, posting_date=None):
order by posting_date desc limit 1 order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True) ''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
prev_gl_via_je = frappe.db.sql('''
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
WHERE p.name = c.parent and p.company=%s and c.account=%s
and c.reference_type=%s and c.reference_name=%s
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
if prev_gl_via_je:
if (not prev_gl_entry) or (prev_gl_entry and
prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date):
prev_gl_entry = prev_gl_via_je
if prev_gl_entry: if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1)) start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else: else:
@ -130,14 +143,48 @@ def get_booking_dates(doc, item, posting_date=None):
else: else:
return None, None, None return None, None, None
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency): def calculate_monthly_amount(doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency):
if doc.doctype == "Sales Invoice": amount, base_amount = 0, 0
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
deferred_account = "deferred_revenue_account"
else:
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
deferred_account = "deferred_expense_account"
if not last_gl_entry:
total_months = (item.service_end_date.year - item.service_start_date.year) * 12 + \
(item.service_end_date.month - item.service_start_date.month) + 1
prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) \
/ flt(date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date)))
actual_months = rounded(total_months * prorate_factor, 1)
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
if base_amount + already_booked_amount > item.base_net_amount:
base_amount = item.base_net_amount - already_booked_amount
if account_currency==doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount/actual_months, item.precision("net_amount"))
if amount + already_booked_amount_in_account_currency > item.net_amount:
amount = item.net_amount - already_booked_amount_in_account_currency
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
partial_month = flt(date_diff(end_date, start_date)) \
/ flt(date_diff(get_last_day(end_date), get_first_day(start_date)))
base_amount = rounded(partial_month, 1) * base_amount
amount = rounded(partial_month, 1) * amount
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
if account_currency==doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
return amount, base_amount
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
amount, base_amount = 0, 0 amount, base_amount = 0, 0
if not last_gl_entry: if not last_gl_entry:
base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount")) base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
@ -146,27 +193,55 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a
else: else:
amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount")) amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
else: else:
gl_entries_details = frappe.db.sql(''' already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
group by voucher_detail_no
'''.format(total_credit_debit, total_credit_debit_currency),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount")) base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
if account_currency==doc.company_currency: if account_currency==doc.company_currency:
amount = base_amount amount = base_amount
else: else:
already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")) amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
return amount, base_amount return amount, base_amount
def get_already_booked_amount(doc, item):
if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
deferred_account = "deferred_revenue_account"
else:
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
deferred_account = "deferred_expense_account"
gl_entries_details = frappe.db.sql('''
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
group by voucher_detail_no
'''.format(total_credit_debit, total_credit_debit_currency),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
journal_entry_details = frappe.db.sql('''
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
and p.docstatus < 2 group by reference_detail_no
'''.format(total_credit_debit, total_credit_debit_currency),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
if doc.currency == doc.company_currency:
already_booked_amount_in_account_currency = already_booked_amount
else:
already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
already_booked_amount_in_account_currency += journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
return already_booked_amount, already_booked_amount_in_account_currency
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): 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"
def _book_deferred_revenue_or_expense(item): 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
@ -181,23 +256,34 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
total_days = date_diff(item.service_end_date, item.service_start_date) + 1 total_days = date_diff(item.service_end_date, item.service_start_date) + 1
total_booking_days = date_diff(end_date, start_date) + 1 total_booking_days = date_diff(end_date, start_date) + 1
amount, base_amount = calculate_amount(doc, item, last_gl_entry, if book_deferred_entries_based_on == 'Months':
total_days, total_booking_days, account_currency) amount, base_amount = calculate_monthly_amount(doc, item, last_gl_entry,
start_date, end_date, total_days, total_booking_days, account_currency)
else:
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
total_days, total_booking_days, account_currency)
make_gl_entries(doc, credit_account, debit_account, against, if via_journal_entry:
amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process) 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)
else:
make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors # Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error: if frappe.flags.deferred_accounting_error:
return return
if getdate(end_date) < getdate(posting_date) and not last_gl_entry: if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense(item) _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
via_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_via_journal_entry'))
submit_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'submit_journal_entries'))
book_deferred_entries_based_on = frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_based_on')
for item in doc.get('items'): for item in doc.get('items'):
if item.get(enable_check): if item.get(enable_check):
_book_deferred_revenue_or_expense(item) _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
def process_deferred_accounting(posting_date=None): def process_deferred_accounting(posting_date=None):
''' Converts deferred income/expense into income/expense ''' Converts deferred income/expense into income/expense
@ -281,3 +367,83 @@ def send_mail(deferred_process):
and submit manually after resolving errors and submit manually after resolving errors
""").format(get_link_to_form('Process Deferred Accounting', deferred_process)) """).format(get_link_to_form('Process Deferred Accounting', deferred_process))
sendmail_to_system_managers(title, content) sendmail_to_system_managers(title, content)
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, item,
deferred_process=None, submit='No'):
if amount == 0: return
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.posting_date = posting_date
journal_entry.company = doc.company
journal_entry.voucher_type = 'Deferred Revenue' if doc.doctype == 'Sales Invoice' \
else 'Deferred Expense'
debit_entry = {
'account': credit_account,
'credit': base_amount,
'credit_in_account_currency': amount,
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
'party': against,
'account_currency': account_currency,
'reference_name': doc.name,
'reference_type': doc.doctype,
'reference_detail_no': item.name,
'cost_center': cost_center,
'project': project,
}
credit_entry = {
'account': debit_account,
'debit': base_amount,
'debit_in_account_currency': amount,
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
'party': against,
'account_currency': account_currency,
'reference_name': doc.name,
'reference_type': doc.doctype,
'reference_detail_no': item.name,
'cost_center': cost_center,
'project': project,
}
for dimension in get_accounting_dimensions():
debit_entry.update({
dimension: item.get(dimension)
})
credit_entry.update({
dimension: item.get(dimension)
})
journal_entry.append('accounts', debit_entry)
journal_entry.append('accounts', credit_entry)
try:
journal_entry.save()
if submit:
journal_entry.submit()
except:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(message=traceback)
frappe.flags.deferred_accounting_error = True
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == 'Sales Invoice':
credit_account, debit_account = frappe.db.get_value('Sales Invoice Item', {'name': voucher_detail_no},
['income_account', 'deferred_revenue_account'])
else:
credit_account, debit_account = frappe.db.get_value('Purchase Invoice Item', {'name': voucher_detail_no},
['deferred_expense_account', 'expense_account'])
if dr_or_cr == 'Debit':
return debit_account
else:
return credit_account

View File

@ -34,11 +34,15 @@
{ {
"fieldname": "properties", "fieldname": "properties",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"oldfieldtype": "Section Break" "oldfieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break0", "fieldname": "column_break0",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%" "width": "50%"
}, },
{ {
@ -49,7 +53,9 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "account_name", "oldfieldname": "account_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "account_number", "fieldname": "account_number",
@ -57,13 +63,17 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Account Number", "label": "Account Number",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "is_group", "fieldname": "is_group",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Group" "label": "Is Group",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "company", "fieldname": "company",
@ -75,7 +85,9 @@
"options": "Company", "options": "Company",
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "root_type", "fieldname": "root_type",
@ -83,7 +95,9 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Root Type", "label": "Root Type",
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity", "options": "\nAsset\nLiability\nIncome\nExpense\nEquity",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "report_type", "fieldname": "report_type",
@ -91,24 +105,32 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Report Type", "label": "Report Type",
"options": "\nBalance Sheet\nProfit and Loss", "options": "\nBalance Sheet\nProfit and Loss",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"depends_on": "eval:doc.is_group==0", "depends_on": "eval:doc.is_group==0",
"fieldname": "account_currency", "fieldname": "account_currency",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Currency", "label": "Currency",
"options": "Currency" "options": "Currency",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "inter_company_account", "fieldname": "inter_company_account",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Inter Company Account" "label": "Inter Company Account",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break1", "fieldname": "column_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%" "width": "50%"
}, },
{ {
@ -120,7 +142,9 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Account", "options": "Account",
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Setting Account Type helps in selecting this Account in transactions.", "description": "Setting Account Type helps in selecting this Account in transactions.",
@ -130,7 +154,9 @@
"label": "Account Type", "label": "Account Type",
"oldfieldname": "account_type", "oldfieldname": "account_type",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nTax\nTemporary" "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Rate at which this tax is applied", "description": "Rate at which this tax is applied",
@ -138,7 +164,9 @@
"fieldtype": "Float", "fieldtype": "Float",
"label": "Rate", "label": "Rate",
"oldfieldname": "tax_rate", "oldfieldname": "tax_rate",
"oldfieldtype": "Currency" "oldfieldtype": "Currency",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "If the account is frozen, entries are allowed to restricted users.", "description": "If the account is frozen, entries are allowed to restricted users.",
@ -147,13 +175,17 @@
"label": "Frozen", "label": "Frozen",
"oldfieldname": "freeze_account", "oldfieldname": "freeze_account",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "No\nYes" "options": "No\nYes",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "balance_must_be", "fieldname": "balance_must_be",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Balance must be", "label": "Balance must be",
"options": "\nDebit\nCredit" "options": "\nDebit\nCredit",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "lft", "fieldname": "lft",
@ -162,7 +194,9 @@
"label": "Lft", "label": "Lft",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "rgt", "fieldname": "rgt",
@ -171,7 +205,9 @@
"label": "Rgt", "label": "Rgt",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "old_parent", "fieldname": "old_parent",
@ -179,27 +215,33 @@
"hidden": 1, "hidden": 1,
"label": "Old Parent", "label": "Old Parent",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)", "depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)",
"fieldname": "include_in_gross", "fieldname": "include_in_gross",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Include in gross" "label": "Include in gross",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "disabled", "fieldname": "disabled",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable" "label": "Disable",
"show_days": 1,
"show_seconds": 1
} }
], ],
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-03-18 17:57:52.063233", "modified": "2020-06-11 15:15:54.338622",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account", "name": "Account",

View File

@ -21,7 +21,12 @@
"book_asset_depreciation_entry_automatically", "book_asset_depreciation_entry_automatically",
"add_taxes_from_item_tax_template", "add_taxes_from_item_tax_template",
"automatically_fetch_payment_terms", "automatically_fetch_payment_terms",
"deferred_accounting_settings_section",
"automatically_process_deferred_accounting_entry", "automatically_process_deferred_accounting_entry",
"book_deferred_entries_based_on",
"column_break_18",
"book_deferred_entries_via_journal_entry",
"submit_journal_entries",
"print_settings", "print_settings",
"show_inclusive_tax_in_print", "show_inclusive_tax_in_print",
"column_break_12", "column_break_12",
@ -182,13 +187,45 @@
"fieldname": "automatically_process_deferred_accounting_entry", "fieldname": "automatically_process_deferred_accounting_entry",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Automatically Process Deferred Accounting Entry" "label": "Automatically Process Deferred Accounting Entry"
},
{
"fieldname": "deferred_accounting_settings_section",
"fieldtype": "Section Break",
"label": "Deferred Accounting Settings"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If this is unchecked direct GL Entries will be created to book Deferred Revenue/Expense",
"fieldname": "book_deferred_entries_via_journal_entry",
"fieldtype": "Check",
"label": "Book Deferred Entries Via Journal Entry"
},
{
"default": "0",
"depends_on": "eval:doc.book_deferred_entries_via_journal_entry",
"description": "If this is unchecked Journal Entries will be saved in a Draft state and will have to be submitted manually",
"fieldname": "submit_journal_entries",
"fieldtype": "Check",
"label": "Submit Journal Entries"
},
{
"default": "Days",
"description": "If \"Months\" is selected then fixed amount will be booked as deferred revenue or expense for each month irrespective of number of days in a month. Will be prorated if deferred revenue or expense is not booked for an entire month.",
"fieldname": "book_deferred_entries_based_on",
"fieldtype": "Select",
"label": "Book Deferred Entries Based On",
"options": "Days\nMonths"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2019-12-19 16:58:17.395595", "modified": "2020-06-22 20:13:26.043092",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@ -71,8 +71,13 @@ frappe.ui.form.on('Cost Center', {
"label": "Cost Center Number", "label": "Cost Center Number",
"fieldname": "cost_center_number", "fieldname": "cost_center_number",
"fieldtype": "Data", "fieldtype": "Data",
"reqd": 1,
"default": frm.doc.cost_center_number "default": frm.doc.cost_center_number
},
{
"label": __("Merge with existing"),
"fieldname": "merge",
"fieldtype": "Check",
"default": 0
} }
], ],
primary_action: function() { primary_action: function() {
@ -87,8 +92,9 @@ frappe.ui.form.on('Cost Center', {
args: { args: {
docname: frm.doc.name, docname: frm.doc.name,
cost_center_name: data.cost_center_name, cost_center_name: data.cost_center_name,
cost_center_number: data.cost_center_number, cost_center_number: cstr(data.cost_center_number),
company: frm.doc.company company: frm.doc.company,
merge: data.merge
}, },
callback: function(r) { callback: function(r) {
frappe.dom.unfreeze(); frappe.dom.unfreeze();

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe import _ from frappe import _
from frappe.utils import flt, fmt_money, getdate, formatdate from frappe.utils import flt, fmt_money, getdate, formatdate, cint
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.naming import set_name_from_naming_options from frappe.model.naming import set_name_from_naming_options
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
@ -128,10 +128,17 @@ class GLEntry(Document):
return self.cost_center_company[self.cost_center] return self.cost_center_company[self.cost_center]
def _check_is_group():
return cint(frappe.get_cached_value('Cost Center', self.cost_center, 'is_group'))
if self.cost_center and _get_cost_center_company() != self.company: if self.cost_center and _get_cost_center_company() != self.company:
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}") frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) .format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if self.cost_center and _check_is_group():
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
def validate_party(self): def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party) validate_party_frozen_disabled(self.party_type, self.party)

View File

@ -188,14 +188,15 @@ frappe.ui.form.on('Invoice Discounting', {
}, },
show_general_ledger: (frm) => { show_general_ledger: (frm) => {
if(frm.doc.docstatus===1) { if(frm.doc.docstatus > 0) {
cur_frm.add_custom_button(__('Accounting Ledger'), function() { cur_frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
from_date: frm.doc.posting_date, from_date: frm.doc.posting_date,
to_date: frm.doc.posting_date, to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
company: frm.doc.company, company: frm.doc.company,
group_by: "Group by Voucher (Consolidated)" group_by: "Group by Voucher (Consolidated)",
show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __("View")); }, __("View"));

View File

@ -13,15 +13,16 @@ frappe.ui.form.on("Journal Entry", {
refresh: function(frm) { refresh: function(frm) {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
if(frm.doc.docstatus==1) { if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() { frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = { frappe.route_options = {
"voucher_no": frm.doc.name, "voucher_no": frm.doc.name,
"from_date": frm.doc.posting_date, "from_date": frm.doc.posting_date,
"to_date": frm.doc.posting_date, "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'),
"company": frm.doc.company, "company": frm.doc.company,
"finance_book": frm.doc.finance_book, "finance_book": frm.doc.finance_book,
"group_by_voucher": 0 "group_by": '',
"show_cancelled_entries": frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __('View')); }, __('View'));

View File

@ -83,7 +83,7 @@
"label": "Entry Type", "label": "Entry Type",
"oldfieldname": "voucher_type", "oldfieldname": "voucher_type",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation", "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation\nDeferred Revenue\nDeferred Expense",
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1
}, },

View File

@ -10,6 +10,7 @@ from erpnext.accounts.utils import get_balance_on, get_account_currency
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
from six import string_types, iteritems from six import string_types, iteritems
@ -265,7 +266,10 @@ class JournalEntry(AccountsController):
# set totals # set totals
if not d.reference_name in self.reference_totals: if not d.reference_name in self.reference_totals:
self.reference_totals[d.reference_name] = 0.0 self.reference_totals[d.reference_name] = 0.0
self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
if self.voucher_type not in ('Deferred Revenue', 'Deferred Expense'):
self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
self.reference_types[d.reference_name] = d.reference_type self.reference_types[d.reference_name] = d.reference_type
self.reference_accounts[d.reference_name] = d.account self.reference_accounts[d.reference_name] = d.account
@ -277,10 +281,16 @@ class JournalEntry(AccountsController):
# check if party and account match # check if party and account match
if d.reference_type in ("Sales Invoice", "Purchase Invoice"): if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
if d.reference_type == "Sales Invoice": if self.voucher_type in ('Deferred Revenue', 'Deferred Expense') and d.reference_detail_no:
party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1] debit_or_credit = 'Debit' if d.debit else 'Credit'
party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no,
debit_or_credit)
else: else:
party_account = against_voucher[1] if d.reference_type == "Sales Invoice":
party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
else:
party_account = against_voucher[1]
if (against_voucher[0] != d.party or party_account != d.account): if (against_voucher[0] != 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],
@ -513,14 +523,20 @@ class JournalEntry(AccountsController):
"against_voucher_type": d.reference_type, "against_voucher_type": d.reference_type,
"against_voucher": d.reference_name, "against_voucher": d.reference_name,
"remarks": remarks, "remarks": remarks,
"voucher_detail_no": d.reference_detail_no,
"cost_center": d.cost_center, "cost_center": d.cost_center,
"project": d.project, "project": d.project,
"finance_book": self.finance_book "finance_book": self.finance_book
}, item=d) }, item=d)
) )
if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'):
update_outstanding = 'No'
else:
update_outstanding = 'Yes'
if gl_map: if gl_map:
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj) make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
def get_balance(self): def get_balance(self):
if not self.get('accounts'): if not self.get('accounts'):

View File

@ -33,6 +33,7 @@
"reference_type", "reference_type",
"reference_name", "reference_name",
"reference_due_date", "reference_due_date",
"reference_detail_no",
"col_break3", "col_break3",
"is_advance", "is_advance",
"user_remark", "user_remark",
@ -268,12 +269,18 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Bank Account", "label": "Bank Account",
"options": "Bank Account" "options": "Bank Account"
},
{
"fieldname": "reference_detail_no",
"fieldtype": "Data",
"hidden": 1,
"label": "Reference Detail No"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-06-18 14:06:54.833738", "modified": "2020-06-24 14:06:54.833738",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry Account", "name": "Journal Entry Account",

View File

@ -68,6 +68,9 @@ class OpeningInvoiceCreationTool(Document):
if not self.company: if not self.company:
frappe.throw(_("Please select the Company")) frappe.throw(_("Please select the Company"))
company_details = frappe.get_cached_value('Company', self.company,
["default_currency", "default_letter_head"], as_dict=1) or {}
for row in self.invoices: for row in self.invoices:
if not row.qty: if not row.qty:
row.qty = 1.0 row.qty = 1.0
@ -99,6 +102,12 @@ class OpeningInvoiceCreationTool(Document):
if not args: if not args:
continue continue
if company_details:
args.update({
"currency": company_details.get("default_currency"),
"letter_head": company_details.get("default_letter_head")
})
doc = frappe.get_doc(args).insert() doc = frappe.get_doc(args).insert()
doc.submit() doc.submit()
names.append(doc.name) names.append(doc.name)
@ -172,8 +181,7 @@ class OpeningInvoiceCreationTool(Document):
"due_date": row.due_date, "due_date": row.due_date,
"posting_date": row.posting_date, "posting_date": row.posting_date,
frappe.scrub(party_type): row.party, frappe.scrub(party_type): row.party,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
"currency": frappe.get_cached_value('Company', self.company, "default_currency")
}) })
accounting_dimension = get_accounting_dimensions() accounting_dimension = get_accounting_dimensions()

View File

@ -172,8 +172,8 @@ frappe.ui.form.on('Payment Entry', {
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency); frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
frm.toggle_display("base_received_amount", ( frm.toggle_display("base_received_amount", (
frm.doc.paid_to_account_currency != company_currency frm.doc.paid_to_account_currency != company_currency
&& frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency && frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency
&& frm.doc.base_paid_amount != frm.doc.base_received_amount && frm.doc.base_paid_amount != frm.doc.base_received_amount
)); ));
@ -234,14 +234,15 @@ frappe.ui.form.on('Payment Entry', {
}, },
show_general_ledger: function(frm) { show_general_ledger: function(frm) {
if(frm.doc.docstatus==1) { if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() { frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = { frappe.route_options = {
"voucher_no": frm.doc.name, "voucher_no": frm.doc.name,
"from_date": frm.doc.posting_date, "from_date": frm.doc.posting_date,
"to_date": frm.doc.posting_date, "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'),
"company": frm.doc.company, "company": frm.doc.company,
group_by: "" "group_by": "",
"show_cancelled_entries": frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, "fa fa-table"); }, "fa fa-table");

View File

@ -25,9 +25,10 @@ frappe.ui.form.on('Period Closing Voucher', {
frappe.route_options = { frappe.route_options = {
"voucher_no": frm.doc.name, "voucher_no": frm.doc.name,
"from_date": frm.doc.posting_date, "from_date": frm.doc.posting_date,
"to_date": frm.doc.posting_date, "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'),
"company": frm.doc.company, "company": frm.doc.company,
group_by_voucher: 0 "group_by": "",
"show_cancelled_entries": frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, "fa fa-table"); }, "fa fa-table");

View File

@ -17,6 +17,8 @@ from six import string_types
apply_on_dict = {"Item Code": "items", apply_on_dict = {"Item Code": "items",
"Item Group": "item_groups", "Brand": "brands"} "Item Group": "item_groups", "Brand": "brands"}
other_fields = ["other_item_code", "other_item_group", "other_brand"]
class PricingRule(Document): class PricingRule(Document):
def validate(self): def validate(self):
self.validate_mandatory() self.validate_mandatory()
@ -47,6 +49,13 @@ class PricingRule(Document):
if tocheck and not self.get(tocheck): if tocheck and not self.get(tocheck):
throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError) throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError)
if self.apply_rule_on_other:
o_field = 'other_' + frappe.scrub(self.apply_rule_on_other)
if not self.get(o_field) and o_field in other_fields:
frappe.throw(_("For the 'Apply Rule On Other' condition the field {0} is mandatory")
.format(frappe.bold(self.apply_rule_on_other)))
if self.price_or_product_discount == 'Price' and not self.rate_or_discount: if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError) throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
@ -80,13 +89,27 @@ class PricingRule(Document):
for f in options: for f in options:
if not f: continue if not f: continue
f = frappe.scrub(f) scrubbed_f = frappe.scrub(f)
if f!=fieldname:
self.set(f, None) if logic_field == 'apply_on':
apply_on_f = apply_on_dict.get(f, f)
else:
apply_on_f = scrubbed_f
if scrubbed_f != fieldname:
self.set(apply_on_f, None)
if self.mixed_conditions and self.get("same_item"): if self.mixed_conditions and self.get("same_item"):
self.same_item = 0 self.same_item = 0
apply_rule_on_other = frappe.scrub(self.apply_rule_on_other or "")
cleanup_other_fields = (other_fields if not apply_rule_on_other
else [o_field for o_field in other_fields if o_field != 'other_' + apply_rule_on_other])
for other_field in cleanup_other_fields:
self.set(other_field, None)
def validate_rate_or_discount(self): def validate_rate_or_discount(self):
for field in ["Rate"]: for field in ["Rate"]:
if flt(self.get(frappe.scrub(field))) < 0: if flt(self.get(frappe.scrub(field))) < 0:

View File

@ -10,6 +10,18 @@ frappe.ui.form.on('Process Deferred Accounting', {
} }
}; };
}); });
if (frm.doc.company) {
frm.set_query("account", function() {
return {
filters: {
'company': frm.doc.company,
'root_type': 'Liability',
'is_group': 0
}
};
});
}
}, },
validate: function() { validate: function() {

View File

@ -238,6 +238,12 @@ class PurchaseInvoice(BuyingController):
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 (not item.from_warehouse):
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
frappe.msgprint(_('''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.expense_account = warehouse_account[item.warehouse]["account"] item.expense_account = warehouse_account[item.warehouse]["account"]
else: else:
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not # check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
@ -247,10 +253,21 @@ class PurchaseInvoice(BuyingController):
(item.purchase_receipt, stock_not_billed_account)) (item.purchase_receipt, stock_not_billed_account))
if negative_expense_booked_in_pr: if negative_expense_booked_in_pr:
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because
expense is booked against this account in Purchase Receipt {2}'''.format(
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))))
item.expense_account = stock_not_billed_account item.expense_account = stock_not_billed_account
else: else:
# If no purchase receipt present then book expense in 'Stock Received But Not Billed' # If no purchase receipt present then book expense in 'Stock Received But Not Billed'
# This is done in cases when Purchase Invoice is created before Purchase Receipt # This is done in cases when Purchase Invoice is created before Purchase Receipt
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} as no Purchase
Receipt is created against Item {2}. This is done to handle accounting for cases
when Purchase Receipt is created after Purchase Invoice'''.format(
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))))
item.expense_account = stock_not_billed_account item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category): elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
@ -574,6 +591,20 @@ class PurchaseInvoice(BuyingController):
else: else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
if auto_accounting_for_non_stock_items:
service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
if item.purchase_receipt:
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0,
'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail,
'account':service_received_but_not_billed_account}, ['name'])
if expense_booked_in_pr:
expense_account = service_received_but_not_billed_account
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": expense_account, "account": expense_account,
"against": self.supplier, "against": self.supplier,

View File

@ -7,15 +7,16 @@ import unittest
import frappe, erpnext import frappe, erpnext
import frappe.model import frappe.model
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from frappe.utils import cint, flt, today, nowdate, add_days from frappe.utils import cint, flt, today, nowdate, add_days, getdate
import frappe.defaults import frappe.defaults
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
test_records as pr_test_records, make_purchase_receipt, get_taxes test_records as pr_test_records, make_purchase_receipt, get_taxes
from erpnext.controllers.accounts_controller import get_payment_terms from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.exceptions import InvalidCurrency from erpnext.exceptions import InvalidCurrency
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.projects.doctype.project.test_project import make_project from erpnext.projects.doctype.project.test_project import make_project
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
from erpnext.stock.doctype.item.test_item import create_item
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
test_ignore = ["Serial No"] test_ignore = ["Serial No"]
@ -897,6 +898,68 @@ class TestPurchaseInvoice(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project) self.assertEqual(expected_values[gle.account]["project"], gle.project)
def test_deferred_expense_via_journal_entry(self):
deferred_account = create_account(account_name="Deferred Expense",
parent_account="Current Assets - _TC", company="_Test Company")
acc_settings = frappe.get_doc('Accounts Settings', '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_expense_account = deferred_account
item.save()
pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
pi.set_posting_time = 1
pi.posting_date = '2019-03-15'
pi.items[0].enable_deferred_expense = 1
pi.items[0].service_start_date = "2019-01-10"
pi.items[0].service_end_date = "2019-03-15"
pi.items[0].deferred_expense_account = deferred_account
pi.save()
pi.submit()
pda1 = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date=nowdate(),
start_date="2019-01-01",
end_date="2019-03-31",
type="Expense",
company="_Test Company"
))
pda1.insert()
pda1.submit()
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 0.0, 33.85, "2019-01-31"],
[deferred_account, 33.85, 0.0, "2019-01-31"],
["_Test Account Cost for Goods Sold - _TC", 0.0, 43.08, "2019-02-28"],
[deferred_account, 43.08, 0.0, "2019-02-28"],
["_Test Account Cost for Goods Sold - _TC", 0.0, 23.07, "2019-03-15"],
[deferred_account, 23.07, 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""", (pi.items[0].name, pi.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_doc('Accounts Settings', 'Accounts Settings')
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save()
def unlink_payment_on_cancel_of_invoice(enable=1): def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.unlink_payment_on_cancellation_of_invoice = enable accounts_settings.unlink_payment_on_cancellation_of_invoice = enable

File diff suppressed because it is too large Load Diff

View File

@ -1747,8 +1747,6 @@ class TestSalesInvoice(unittest.TestCase):
si.save() si.save()
si.submit() si.submit()
from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
pda1 = frappe.get_doc(dict( pda1 = frappe.get_doc(dict(
doctype='Process Deferred Accounting', doctype='Process Deferred Accounting',
posting_date=nowdate(), posting_date=nowdate(),
@ -1772,6 +1770,55 @@ 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_fixed_deferred_revenue(self):
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
acc_settings.book_deferred_entries_based_on = 'Months'
acc_settings.save()
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-16", rate=50000, do_not_submit=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-16"
si.items[0].service_end_date = "2019-03-31"
si.items[0].deferred_revenue_account = deferred_account
si.save()
si.submit()
pda1 = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date='2019-03-31',
start_date="2019-01-01",
end_date="2019-03-31",
type="Income",
company="_Test Company"
))
pda1.insert()
pda1.submit()
expected_gle = [
[deferred_account, 10000.0, 0.0, "2019-01-31"],
["Sales - _TC", 0.0, 10000.0, "2019-01-31"],
[deferred_account, 20000.0, 0.0, "2019-02-28"],
["Sales - _TC", 0.0, 20000.0, "2019-02-28"],
[deferred_account, 20000.0, 0.0, "2019-03-31"],
["Sales - _TC", 0.0, 20000.0, "2019-03-31"]
]
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
acc_settings.book_deferred_entries_based_on = 'Days'
acc_settings.save()
def test_inter_company_transaction(self): def test_inter_company_transaction(self):
if not frappe.db.exists("Customer", "_Test Internal Customer"): if not frappe.db.exists("Customer", "_Test Internal Customer"):

View File

@ -61,7 +61,7 @@ def make_sales_invoice():
debit_to = 'Debtors - _TC2', debit_to = 'Debtors - _TC2',
income_account = 'Sales - _TC2', income_account = 'Sales - _TC2',
expense_account = 'Cost of Goods Sold - _TC2', expense_account = 'Cost of Goods Sold - _TC2',
cost_center = '_Test Company 2 - _TC2') cost_center = 'Main - _TC2')

View File

@ -63,7 +63,7 @@ def make_sales_invoice():
debit_to = 'Debtors - _TC2', debit_to = 'Debtors - _TC2',
income_account = 'Sales - _TC2', income_account = 'Sales - _TC2',
expense_account = 'Cost of Goods Sold - _TC2', expense_account = 'Cost of Goods Sold - _TC2',
cost_center = '_Test Company 2 - _TC2', cost_center = 'Main - _TC2',
do_not_save=1) do_not_save=1)
si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30)) si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30))
@ -83,14 +83,14 @@ def make_payment(docname):
def make_credit_note(docname): def make_credit_note(docname):
create_sales_invoice(company="_Test Company 2", create_sales_invoice(company="_Test Company 2",
customer = '_Test Customer 2', customer = '_Test Customer 2',
currency = 'EUR', currency = 'EUR',
qty = -1, qty = -1,
warehouse = 'Finished Goods - _TC2', warehouse = 'Finished Goods - _TC2',
debit_to = 'Debtors - _TC2', debit_to = 'Debtors - _TC2',
income_account = 'Sales - _TC2', income_account = 'Sales - _TC2',
expense_account = 'Cost of Goods Sold - _TC2', expense_account = 'Cost of Goods Sold - _TC2',
cost_center = '_Test Company 2 - _TC2', cost_center = 'Main - _TC2',
is_return = 1, is_return = 1,
return_against = docname) return_against = docname)

View File

@ -133,7 +133,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
acc = frappe.get_doc("Account", account) acc = frappe.get_doc("Account", account)
try: try:
year_start_date = get_fiscal_year(date, verbose=0)[1] year_start_date = get_fiscal_year(date, company=company, verbose=0)[1]
except FiscalYearError: except FiscalYearError:
if getdate(date) > getdate(nowdate()): if getdate(date) > getdate(nowdate()):
# if fiscal year not found and the date is greater than today # if fiscal year not found and the date is greater than today
@ -785,10 +785,10 @@ def get_children(doctype, parent, company, is_root=False):
company_currency = frappe.get_cached_value('Company', company, "default_currency") company_currency = frappe.get_cached_value('Company', company, "default_currency")
for each in acc: for each in acc:
each["company_currency"] = company_currency each["company_currency"] = company_currency
each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False)) each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False, company=company))
if each.account_currency != company_currency: if each.account_currency != company_currency:
each["balance_in_account_currency"] = flt(get_balance_on(each.get("value"))) each["balance_in_account_currency"] = flt(get_balance_on(each.get("value"), company=company))
return acc return acc
@ -835,7 +835,7 @@ def create_payment_gateway_account(gateway):
pass pass
@frappe.whitelist() @frappe.whitelist()
def update_cost_center(docname, cost_center_name, cost_center_number, company): def update_cost_center(docname, cost_center_name, cost_center_number, company, merge):
''' '''
Renames the document by adding the number as a prefix to the current name and updates Renames the document by adding the number as a prefix to the current name and updates
all transaction where it was present. all transaction where it was present.
@ -851,7 +851,7 @@ def update_cost_center(docname, cost_center_name, cost_center_number, company):
new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company) new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company)
if docname != new_name: if docname != new_name:
frappe.rename_doc("Cost Center", docname, new_name, force=1) frappe.rename_doc("Cost Center", docname, new_name, force=1, merge=merge)
return new_name return new_name
def validate_field_number(doctype_name, docname, number_value, company, field_name): def validate_field_number(doctype_name, docname, number_value, company, field_name):

View File

@ -58,7 +58,7 @@
"type": "Report" "type": "Report"
}, },
{ {
"label": "Assets Dashboard", "label": "Dashboard",
"link_to": "Asset", "link_to": "Asset",
"type": "Dashboard" "type": "Dashboard"
} }

View File

@ -408,6 +408,8 @@ class Asset(AccountsController):
row.expected_value_after_useful_life = asset_value_after_full_schedule row.expected_value_after_useful_life = asset_value_after_full_schedule
def validate_cancellation(self): def validate_cancellation(self):
if self.status in ("In Maintenance", "Out of Order"):
frappe.throw(_("There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset."))
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):
frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status)) frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status))

View File

@ -33,7 +33,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Other Reports", "label": "Other Reports",
"links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -60,7 +60,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Buying", "label": "Buying",
"modified": "2020-05-28 13:32:49.960574", "modified": "2020-06-29 19:30:24.983050",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying", "name": "Buying",

View File

@ -166,7 +166,8 @@ frappe.ui.form.on("Request for Quotation",{
{ "fieldtype": "Select", "label": __("Supplier"), { "fieldtype": "Select", "label": __("Supplier"),
"fieldname": "supplier", "fieldname": "supplier",
"options": doc.suppliers.map(d => d.supplier), "options": doc.suppliers.map(d => d.supplier),
"reqd": 1 }, "reqd": 1,
"default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" },
{ "fieldtype": "Button", "label": __('Create Supplier Quotation'), { "fieldtype": "Button", "label": __('Create Supplier Quotation'),
"fieldname": "make_supplier_quotation", "cssClass": "btn-primary" }, "fieldname": "make_supplier_quotation", "cssClass": "btn-primary" },
] ]

View File

@ -51,7 +51,7 @@ class RequestforQuotation(BuyingController):
def validate_email_id(self, args): def validate_email_id(self, args):
if not args.email_id: if not args.email_id:
frappe.throw(_("Row {0}: For supplier {0} Email Address is required to send email").format(args.idx, args.supplier)) frappe.throw(_("Row {0}: For Supplier {0}, Email Address is Required to Send Email").format(args.idx, args.supplier))
def on_submit(self): def on_submit(self):
frappe.db.set(self, 'status', 'Submitted') frappe.db.set(self, 'status', 'Submitted')
@ -154,7 +154,7 @@ class RequestforQuotation(BuyingController):
sender=sender,attachments = attachments, send_email=True, sender=sender,attachments = attachments, send_email=True,
doctype=self.doctype, name=self.name)["name"] doctype=self.doctype, name=self.name)["name"]
frappe.msgprint(_("Email sent to supplier {0}").format(data.supplier)) frappe.msgprint(_("Email Sent to Supplier {0}").format(data.supplier))
def get_attachments(self): def get_attachments(self):
attachments = [d.name for d in get_attachments(self.doctype, self.name)] attachments = [d.name for d in get_attachments(self.doctype, self.name)]
@ -193,7 +193,7 @@ def send_supplier_emails(rfq_name):
def check_portal_enabled(reference_doctype): def check_portal_enabled(reference_doctype):
if not frappe.db.get_value('Portal Menu Item', if not frappe.db.get_value('Portal Menu Item',
{'reference_doctype': reference_doctype}, 'enabled'): {'reference_doctype': reference_doctype}, 'enabled'):
frappe.throw(_("Request for Quotation is disabled to access from portal, for more check portal settings.")) frappe.throw(_("The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings."))
def get_list_context(context=None): def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context from erpnext.controllers.website_list_for_contact import get_list_context
@ -259,7 +259,7 @@ def create_supplier_quotation(doc):
sq_doc.flags.ignore_permissions = True sq_doc.flags.ignore_permissions = True
sq_doc.run_method("set_missing_values") sq_doc.run_method("set_missing_values")
sq_doc.save() sq_doc.save()
frappe.msgprint(_("Supplier Quotation {0} created").format(sq_doc.name)) frappe.msgprint(_("Supplier Quotation {0} Created").format(sq_doc.name))
return sq_doc.name return sq_doc.name
except Exception: except Exception:
return None return None

View File

@ -20,6 +20,7 @@ from erpnext.exceptions import InvalidCurrency
from six import text_type from six import text_type
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
from erpnext.stock.get_item_details import get_item_warehouse from erpnext.stock.get_item_details import get_item_warehouse
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
@ -1301,6 +1302,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
parent.set_qty_as_per_stock_uom() parent.set_qty_as_per_stock_uom()
parent.calculate_taxes_and_totals() parent.calculate_taxes_and_totals()
if parent_doctype == "Sales Order": if parent_doctype == "Sales Order":
make_packing_list(parent)
parent.set_gross_profit() parent.set_gross_profit()
frappe.get_doc('Authorization Control').validate_approving_authority(parent.doctype, frappe.get_doc('Authorization Control').validate_approving_authority(parent.doctype,
parent.company, parent.base_grand_total) parent.company, parent.base_grand_total)

View File

@ -33,7 +33,7 @@ def validate_filters(filters):
frappe.throw(_("{0} is mandatory").format(f)) frappe.throw(_("{0} is mandatory").format(f))
if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")): if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")):
frappe.throw(_("Fiscal Year: {0} does not exists").format(filters.get("fiscal_year"))) frappe.throw(_("Fiscal Year {0} Does Not Exist").format(filters.get("fiscal_year")))
if filters.get("based_on") == filters.get("group_by"): if filters.get("based_on") == filters.get("group_by"):
frappe.throw(_("'Based On' and 'Group By' can not be same")) frappe.throw(_("'Based On' and 'Group By' can not be same"))

View File

@ -29,6 +29,9 @@
"requires_fulfilment", "requires_fulfilment",
"fulfilment_deadline", "fulfilment_deadline",
"fulfilment_terms", "fulfilment_terms",
"authorised_by_section",
"signee_company",
"signed_by_company",
"sb_references", "sb_references",
"document_type", "document_type",
"cb_links", "cb_links",
@ -223,10 +226,28 @@
"options": "Contract", "options": "Contract",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "signee_company",
"fieldtype": "Signature",
"label": "Signee (Company)"
},
{
"fieldname": "signed_by_company",
"fieldtype": "Link",
"label": "Signed By (Company)",
"options": "User",
"read_only": 1
},
{
"fieldname": "authorised_by_section",
"fieldtype": "Section Break",
"label": "Authorised By"
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-09-30 00:56:41.559681", "links": [],
"modified": "2020-03-30 06:56:07.257932",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Contract", "name": "Contract",

View File

@ -29,6 +29,9 @@ class Contract(Document):
self.update_contract_status() self.update_contract_status()
self.update_fulfilment_status() self.update_fulfilment_status()
def before_submit(self):
self.signed_by_company = frappe.session.user
def before_update_after_submit(self): def before_update_after_submit(self):
self.update_contract_status() self.update_contract_status()
self.update_fulfilment_status() self.update_fulfilment_status()

View File

@ -1,306 +1,106 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "allow_rename": 1,
"allow_import": 0, "autoname": "field:title",
"allow_rename": 0, "creation": "2018-04-16 06:44:48.791312",
"autoname": "field:title", "doctype": "DocType",
"beta": 0, "editable_grid": 1,
"creation": "2018-04-16 06:44:48.791312", "engine": "InnoDB",
"custom": 0, "field_order": [
"docstatus": 0, "title",
"doctype": "DocType", "contract_terms",
"document_type": "", "sb_fulfilment",
"editable_grid": 1, "requires_fulfilment",
"engine": "InnoDB", "fulfilment_terms"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "title",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "label": "Title",
"collapsible": 0, "unique": 1
"columns": 0, },
"fieldname": "title",
"fieldtype": "Data",
"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": "Title",
"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": "contract_terms",
"allow_on_submit": 0, "fieldtype": "Text Editor",
"bold": 0, "label": "Contract Terms and Conditions",
"collapsible": 0, "read_only": 1
"columns": 0, },
"fieldname": "sb_terms",
"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": "",
"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": "sb_fulfilment",
"allow_on_submit": 0, "fieldtype": "Section Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "contract_terms",
"fieldtype": "Text Editor",
"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": "Contract Terms and Conditions",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_on_submit": 0, "fieldname": "requires_fulfilment",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "label": "Requires Fulfilment"
"columns": 0, },
"fieldname": "sb_fulfilment",
"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": "",
"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, "depends_on": "eval:doc.requires_fulfilment==1",
"allow_on_submit": 0, "fieldname": "fulfilment_terms",
"bold": 0, "fieldtype": "Table",
"collapsible": 0, "label": "Fulfilment Terms and Conditions",
"columns": 0, "options": "Contract Template Fulfilment Terms"
"fieldname": "requires_fulfilment",
"fieldtype": "Check",
"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": "Requires Fulfilment",
"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,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.requires_fulfilment==1",
"fieldname": "fulfilment_terms",
"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": "Fulfilment Terms and Conditions",
"length": 0,
"no_copy": 0,
"options": "Contract Template Fulfilment Terms",
"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, "links": [],
"hide_heading": 0, "modified": "2020-06-03 00:24:58.179816",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "CRM",
"image_view": 0, "name": "Contract Template",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-04-17 07:36:05.217599",
"modified_by": "Administrator",
"module": "CRM",
"name": "Contract Template",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "System Manager",
"import": 0, "share": 1,
"permlevel": 0,
"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,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "Sales Manager",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "Purchase Manager",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "HR Manager",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"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
} }

View File

@ -96,6 +96,7 @@ frappe.ui.form.on("Opportunity", {
}); });
} else { } else {
frm.add_custom_button(__("Reopen"), function() { frm.add_custom_button(__("Reopen"), function() {
frm.set_value("lost_reasons",[])
frm.set_value("status", "Open"); frm.set_value("status", "Open");
frm.save(); frm.save();
}); });

View File

@ -55,14 +55,15 @@ frappe.ui.form.on("Fees", {
frm.set_df_property('posting_date', 'read_only', 1); frm.set_df_property('posting_date', 'read_only', 1);
frm.set_df_property('posting_time', 'read_only', 1); frm.set_df_property('posting_time', 'read_only', 1);
} }
if(frm.doc.docstatus===1) { if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Accounting Ledger'), function() { frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
from_date: frm.doc.posting_date, from_date: frm.doc.posting_date,
to_date: frm.doc.posting_date, to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
company: frm.doc.company, company: frm.doc.company,
group_by_voucher: false group_by: '',
show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __("View")); }, __("View"));

View File

@ -1,20 +1,21 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "creation": "2013-05-13 14:04:03",
"creation": "2013-05-13 14:04:03", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 3, "idx": 3,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2017-11-10 19:42:36.457449", "modified": "2020-06-24 17:16:40.251116",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Absent Student Report", "name": "Absent Student Report",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Student Attendance", "prepared_report": 0,
"report_name": "Absent Student Report", "ref_doctype": "Student Attendance",
"report_type": "Script Report", "report_name": "Absent Student Report",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Academics User" "role": "Academics User"

View File

@ -1,20 +1,21 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "creation": "2017-11-09 15:07:30.404428",
"creation": "2017-11-09 15:07:30.404428", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2017-11-28 18:35:44.903665", "modified": "2020-06-24 17:16:02.027410",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Assessment Plan Status", "name": "Assessment Plan Status",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Assessment Plan", "prepared_report": 0,
"report_name": "Assessment Plan Status", "ref_doctype": "Assessment Plan",
"report_type": "Script Report", "report_name": "Assessment Plan Status",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Academics User" "role": "Academics User"

View File

@ -1,24 +1,26 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "creation": "2017-05-05 14:46:13.776133",
"creation": "2017-05-05 14:46:13.776133", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2018-02-08 15:11:24.904628", "modified": "2020-06-24 17:15:15.477530",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Course wise Assessment Report", "name": "Course wise Assessment Report",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Assessment Result", "prepared_report": 0,
"report_name": "Course wise Assessment Report", "query": "",
"report_type": "Script Report", "ref_doctype": "Assessment Result",
"report_name": "Course wise Assessment Report",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Instructor" "role": "Instructor"
}, },
{ {
"role": "Education Manager" "role": "Education Manager"
} }

View File

@ -1,24 +1,25 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "creation": "2018-01-22 17:04:43.412054",
"creation": "2018-01-22 17:04:43.412054", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2019-02-08 15:11:35.339434", "modified": "2020-06-24 17:13:35.373756",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Final Assessment Grades", "name": "Final Assessment Grades",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Assessment Result", "prepared_report": 0,
"report_name": "Final Assessment Grades", "ref_doctype": "Assessment Result",
"report_type": "Script Report", "report_name": "Final Assessment Grades",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Instructor" "role": "Instructor"
}, },
{ {
"role": "Education Manager" "role": "Education Manager"
} }

View File

@ -1,24 +1,25 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "creation": "2017-03-27 17:47:16.831433",
"creation": "2017-03-27 17:47:16.831433", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2017-11-10 19:42:30.300729", "modified": "2020-06-24 17:16:50.639488",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Student and Guardian Contact Details", "name": "Student and Guardian Contact Details",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Program Enrollment", "prepared_report": 0,
"report_name": "Student and Guardian Contact Details", "ref_doctype": "Program Enrollment",
"report_type": "Script Report", "report_name": "Student and Guardian Contact Details",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Instructor" "role": "Instructor"
}, },
{ {
"role": "Academics User" "role": "Academics User"
} }

View File

@ -1,20 +1,21 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "creation": "2016-11-28 22:07:03.859124",
"creation": "2016-11-28 22:07:03.859124", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 2, "idx": 2,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2017-11-10 19:41:12.328346", "modified": "2020-06-24 17:16:59.823709",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Student Batch-Wise Attendance", "name": "Student Batch-Wise Attendance",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Student Attendance", "prepared_report": 0,
"report_name": "Student Batch-Wise Attendance", "ref_doctype": "Student Attendance",
"report_type": "Script Report", "report_name": "Student Batch-Wise Attendance",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Academics User" "role": "Academics User"

View File

@ -1,21 +1,22 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"creation": "2016-06-22 02:58:41.024538", "creation": "2016-06-22 02:58:41.024538",
"disabled": 0, "disable_prepared_report": 0,
"docstatus": 0, "disabled": 0,
"doctype": "Report", "docstatus": 0,
"idx": 3, "doctype": "Report",
"is_standard": "Yes", "idx": 3,
"modified": "2018-12-17 16:46:46.176620", "is_standard": "Yes",
"modified_by": "Administrator", "modified": "2020-06-24 17:14:39.452551",
"module": "Education", "modified_by": "Administrator",
"name": "Student Fee Collection", "module": "Education",
"owner": "Administrator", "name": "Student Fee Collection",
"prepared_report": 0, "owner": "Administrator",
"query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(grand_total) - sum(outstanding_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student", "prepared_report": 0,
"ref_doctype": "Fees", "query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(grand_total) - sum(outstanding_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student",
"report_name": "Student Fee Collection", "ref_doctype": "Fees",
"report_type": "Query Report", "report_name": "Student Fee Collection",
"report_type": "Query Report",
"roles": [ "roles": [
{ {
"role": "Academics User" "role": "Academics User"

View File

@ -1,20 +1,21 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "creation": "2013-05-13 14:04:03",
"creation": "2013-05-13 14:04:03", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 3, "idx": 3,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2017-11-10 19:42:43.376658", "modified": "2020-06-24 17:16:13.307053",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Student Monthly Attendance Sheet", "name": "Student Monthly Attendance Sheet",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Student Attendance", "prepared_report": 0,
"report_name": "Student Monthly Attendance Sheet", "ref_doctype": "Student Attendance",
"report_type": "Script Report", "report_name": "Student Monthly Attendance Sheet",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Academics User" "role": "Academics User"

View File

@ -0,0 +1,9 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('TaxJar Settings', {
is_sandbox: (frm) => {
frm.toggle_reqd("api_key", !frm.doc.is_sandbox);
frm.toggle_reqd("sandbox_api_key", frm.doc.is_sandbox);
}
});

View File

@ -0,0 +1,110 @@
{
"actions": [],
"creation": "2017-06-15 08:21:24.624315",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"is_sandbox",
"taxjar_calculate_tax",
"taxjar_create_transactions",
"credentials",
"api_key",
"cb_keys",
"sandbox_api_key",
"configuration",
"tax_account_head",
"configuration_cb",
"shipping_account_head"
],
"fields": [
{
"fieldname": "credentials",
"fieldtype": "Section Break",
"label": "Credentials"
},
{
"fieldname": "api_key",
"fieldtype": "Password",
"in_list_view": 1,
"label": "Live API Key",
"reqd": 1
},
{
"fieldname": "configuration",
"fieldtype": "Section Break",
"label": "Configuration"
},
{
"fieldname": "tax_account_head",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Tax Account Head",
"options": "Account",
"reqd": 1
},
{
"fieldname": "shipping_account_head",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Shipping Account Head",
"options": "Account",
"reqd": 1
},
{
"default": "0",
"fieldname": "is_sandbox",
"fieldtype": "Check",
"label": "Sandbox Mode"
},
{
"fieldname": "sandbox_api_key",
"fieldtype": "Password",
"label": "Sandbox API Key"
},
{
"fieldname": "configuration_cb",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "taxjar_create_transactions",
"fieldtype": "Check",
"label": "Create TaxJar Transaction"
},
{
"default": "0",
"fieldname": "taxjar_calculate_tax",
"fieldtype": "Check",
"label": "Enable Tax Calculation"
},
{
"fieldname": "cb_keys",
"fieldtype": "Column Break"
}
],
"issingle": 1,
"links": [],
"modified": "2020-04-30 04:38:03.311089",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "TaxJar Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class TaxJarSettings(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestTaxJarSettings(unittest.TestCase):
pass

View File

@ -0,0 +1,251 @@
import traceback
import pycountry
import taxjar
import frappe
from erpnext import get_default_company
from frappe import _
from frappe.contacts.doctype.address.address import get_company_address
TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head")
TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI",
"FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO",
"SE", "SI", "SK", "US"]
def get_client():
taxjar_settings = frappe.get_single("TaxJar Settings")
if not taxjar_settings.is_sandbox:
api_key = taxjar_settings.api_key and taxjar_settings.get_password("api_key")
api_url = taxjar.DEFAULT_API_URL
else:
api_key = taxjar_settings.sandbox_api_key and taxjar_settings.get_password("sandbox_api_key")
api_url = taxjar.SANDBOX_API_URL
if api_key and api_url:
return taxjar.Client(api_key=api_key, api_url=api_url)
def create_transaction(doc, method):
"""Create an order transaction in TaxJar"""
if not TAXJAR_CREATE_TRANSACTIONS:
return
client = get_client()
if not client:
return
sales_tax = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == TAX_ACCOUNT_HEAD])
if not sales_tax:
return
tax_dict = get_tax_data(doc)
if not tax_dict:
return
tax_dict['transaction_id'] = doc.name
tax_dict['transaction_date'] = frappe.utils.today()
tax_dict['sales_tax'] = sales_tax
tax_dict['amount'] = doc.total + tax_dict['shipping']
try:
client.create_order(tax_dict)
except taxjar.exceptions.TaxJarResponseError as err:
frappe.throw(_(sanitize_error_response(err)))
except Exception as ex:
print(traceback.format_exc(ex))
def delete_transaction(doc, method):
"""Delete an existing TaxJar order transaction"""
if not TAXJAR_CREATE_TRANSACTIONS:
return
client = get_client()
if not client:
return
client.delete_order(doc.name)
def get_tax_data(doc):
from_address = get_company_address_details(doc)
from_shipping_state = from_address.get("state")
from_country_code = frappe.db.get_value("Country", from_address.country, "code")
from_country_code = from_country_code.upper()
to_address = get_shipping_address_details(doc)
to_shipping_state = to_address.get("state")
to_country_code = frappe.db.get_value("Country", to_address.country, "code")
to_country_code = to_country_code.upper()
if to_country_code not in SUPPORTED_COUNTRY_CODES:
return
shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD])
if to_shipping_state is not None:
to_shipping_state = get_iso_3166_2_state_code(to_address)
tax_dict = {
'from_country': from_country_code,
'from_zip': from_address.pincode,
'from_state': from_shipping_state,
'from_city': from_address.city,
'from_street': from_address.address_line1,
'to_country': to_country_code,
'to_zip': to_address.pincode,
'to_city': to_address.city,
'to_street': to_address.address_line1,
'to_state': to_shipping_state,
'shipping': shipping,
'amount': doc.net_total
}
return tax_dict
def set_sales_tax(doc, method):
if not TAXJAR_CALCULATE_TAX:
return
if not doc.items:
return
# if the party is exempt from sales tax, then set all tax account heads to zero
sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
or frappe.db.has_column("Customer", "exempt_from_sales_tax") and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")
if sales_tax_exempted:
for tax in doc.taxes:
if tax.account_head == TAX_ACCOUNT_HEAD:
tax.tax_amount = 0
break
doc.run_method("calculate_taxes_and_totals")
return
tax_dict = get_tax_data(doc)
if not tax_dict:
# Remove existing tax rows if address is changed from a taxable state/country
setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
return
tax_data = validate_tax_request(tax_dict)
if tax_data is not None:
if not tax_data.amount_to_collect:
setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
elif tax_data.amount_to_collect > 0:
# Loop through tax rows for existing Sales Tax entry
# If none are found, add a row with the tax amount
for tax in doc.taxes:
if tax.account_head == TAX_ACCOUNT_HEAD:
tax.tax_amount = tax_data.amount_to_collect
doc.run_method("calculate_taxes_and_totals")
break
else:
doc.append("taxes", {
"charge_type": "Actual",
"description": "Sales Tax",
"account_head": TAX_ACCOUNT_HEAD,
"tax_amount": tax_data.amount_to_collect
})
doc.run_method("calculate_taxes_and_totals")
def validate_tax_request(tax_dict):
"""Return the sales tax that should be collected for a given order."""
client = get_client()
if not client:
return
try:
tax_data = client.tax_for_order(tax_dict)
except taxjar.exceptions.TaxJarResponseError as err:
frappe.throw(_(sanitize_error_response(err)))
else:
return tax_data
def get_company_address_details(doc):
"""Return default company address details"""
company_address = get_company_address(get_default_company()).company_address
if not company_address:
frappe.throw(_("Please set a default company address"))
company_address = frappe.get_doc("Address", company_address)
return company_address
def get_shipping_address_details(doc):
"""Return customer shipping address details"""
if doc.shipping_address_name:
shipping_address = frappe.get_doc("Address", doc.shipping_address_name)
else:
shipping_address = get_company_address_details(doc)
return shipping_address
def get_iso_3166_2_state_code(address):
country_code = frappe.db.get_value("Country", address.get("country"), "code")
error_message = _("""{0} is not a valid state! Check for typos or enter the ISO code for your state.""").format(address.get("state"))
state = address.get("state").upper().strip()
# The max length for ISO state codes is 3, excluding the country code
if len(state) <= 3:
# PyCountry returns state code as {country_code}-{state-code} (e.g. US-FL)
address_state = (country_code + "-" + state).upper()
states = pycountry.subdivisions.get(country_code=country_code.upper())
states = [pystate.code for pystate in states]
if address_state in states:
return state
frappe.throw(_(error_message))
else:
try:
lookup_state = pycountry.subdivisions.lookup(state)
except LookupError:
frappe.throw(_(error_message))
else:
return lookup_state.code.split('-')[1]
def sanitize_error_response(response):
response = response.full_response.get("detail")
response = response.replace("_", " ")
sanitized_responses = {
"to zip": "Zipcode",
"to city": "City",
"to state": "State",
"to country": "Country"
}
for k, v in sanitized_responses.items():
response = response.replace(k, v)
return response

View File

@ -13,7 +13,7 @@ source_link = "https://github.com/frappe/erpnext"
app_logo_url = '/assets/erpnext/images/erp-icon.svg' app_logo_url = '/assets/erpnext/images/erp-icon.svg'
develop_version = '12.x.x-develop' develop_version = '13.x.x-develop'
app_include_js = "assets/js/erpnext.min.js" app_include_js = "assets/js/erpnext.min.js"
app_include_css = "assets/css/erpnext.css" app_include_css = "assets/css/erpnext.css"
@ -234,8 +234,15 @@ doc_events = {
"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products" "validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
}, },
"Sales Invoice": { "Sales Invoice": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], "on_submit": [
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.regional.create_transaction_log",
"erpnext.regional.italy.utils.sales_invoice_on_submit",
"erpnext.erpnext_integrations.taxjar_integration.create_transaction"
],
"on_cancel": [
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
],
"on_trash": "erpnext.regional.check_deletion_permission" "on_trash": "erpnext.regional.check_deletion_permission"
}, },
"Purchase Invoice": { "Purchase Invoice": {
@ -261,6 +268,9 @@ doc_events = {
}, },
"Email Unsubscribe": { "Email Unsubscribe": {
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
},
('Quotation', 'Sales Order', 'Sales Invoice'): {
'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"]
} }
} }

View File

@ -22,6 +22,7 @@ class EmployeeAdvance(Document):
self.validate_employee_advance_account() self.validate_employee_advance_account()
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry')
self.set_status() self.set_status()
def set_status(self): def set_status(self):

View File

@ -213,12 +213,15 @@ frappe.ui.form.on("Expense Claim", {
refresh: function(frm) { refresh: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
if(frm.doc.docstatus === 1 && frm.doc.approval_status !== "Rejected") { if(frm.doc.docstatus > 0 && frm.doc.approval_status !== "Rejected") {
frm.add_custom_button(__('Accounting Ledger'), function() { frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
company: frm.doc.company, company: frm.doc.company,
group_by_voucher: false from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
group_by: '',
show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __("View")); }, __("View"));
@ -297,6 +300,11 @@ frappe.ui.form.on("Expense Claim", {
cost_center: function(frm) { cost_center: function(frm) {
frm.events.set_child_cost_center(frm); frm.events.set_child_cost_center(frm);
}, },
validate: function(frm) {
frm.events.set_child_cost_center(frm);
},
set_child_cost_center: function(frm){ set_child_cost_center: function(frm){
(frm.doc.expenses || []).forEach(function(d) { (frm.doc.expenses || []).forEach(function(d) {
if (!d.cost_center){ if (!d.cost_center){
@ -346,9 +354,6 @@ frappe.ui.form.on("Expense Claim", {
}); });
frappe.ui.form.on("Expense Claim Detail", { frappe.ui.form.on("Expense Claim Detail", {
expenses_add: function(frm, cdt, cdn) {
frm.events.set_child_cost_center(frm);
},
amount: function(frm, cdt, cdn) { amount: function(frm, cdt, cdn) {
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
frappe.model.set_value(cdt, cdn, 'sanctioned_amount', child.amount); frappe.model.set_value(cdt, cdn, 'sanctioned_amount', child.amount);

View File

@ -129,7 +129,7 @@ class ExpenseClaim(AccountsController):
"debit": data.sanctioned_amount, "debit": data.sanctioned_amount,
"debit_in_account_currency": data.sanctioned_amount, "debit_in_account_currency": data.sanctioned_amount,
"against": self.employee, "against": self.employee,
"cost_center": data.cost_center "cost_center": data.cost_center or self.cost_center
}, item=data) }, item=data)
) )

View File

@ -0,0 +1,15 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.listview_settings['Job Applicant'] = {
add_fields: ["company", "designation", "job_applicant", "status"],
get_indicator: function (doc) {
if (doc.status == "Accepted") {
return [__(doc.status), "green", "status,=," + doc.status];
} else if (["Open", "Replied"].includes(doc.status)) {
return [__(doc.status), "orange", "status,=," + doc.status];
} else if (["Hold", "Rejected"].includes(doc.status)) {
return [__(doc.status), "red", "status,=," + doc.status];
}
}
};

View File

@ -30,7 +30,6 @@
{ {
"fieldname": "job_applicant", "fieldname": "job_applicant",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Job Applicant", "label": "Job Applicant",
"options": "Job Applicant", "options": "Job Applicant",
"print_hide": 1, "print_hide": 1,
@ -161,7 +160,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2019-12-31 02:40:33.650728", "modified": "2020-06-25 00:56:24.756395",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Job Offer", "name": "Job Offer",

View File

@ -20,10 +20,9 @@ class JobOffer(Document):
staffing_plan = get_staffing_plan_detail(self.designation, self.company, self.offer_date) staffing_plan = get_staffing_plan_detail(self.designation, self.company, self.offer_date)
check_vacancies = frappe.get_single("HR Settings").check_vacancies check_vacancies = frappe.get_single("HR Settings").check_vacancies
if staffing_plan and check_vacancies: if staffing_plan and check_vacancies:
vacancies = frappe.db.get_value("Staffing Plan Detail", filters={"name": staffing_plan.name}, fieldname=['vacancies']) job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date)
job_offers = len(self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date)) if staffing_plan.vacancies - len(job_offers) <= 0:
if vacancies - job_offers <= 0: frappe.throw(_("There are no vacancies under staffing plan {0}").format(frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent))))
frappe.throw(_("There are no vacancies under staffing plan {0}").format(get_link_to_form("Staffing Plan", staffing_plan.parent)))
def on_change(self): def on_change(self):
update_job_applicant(self.status, self.job_applicant) update_job_applicant(self.status, self.job_applicant)
@ -33,7 +32,8 @@ class JobOffer(Document):
return frappe.get_all("Job Offer", filters={ return frappe.get_all("Job Offer", filters={
"offer_date": ['between', (from_date, to_date)], "offer_date": ['between', (from_date, to_date)],
"designation": self.designation, "designation": self.designation,
"company": self.company "company": self.company,
"docstatus": 1
}, fields=['name']) }, fields=['name'])
def update_job_applicant(status, job_applicant): def update_job_applicant(status, job_applicant):
@ -42,18 +42,22 @@ def update_job_applicant(status, job_applicant):
def get_staffing_plan_detail(designation, company, offer_date): def get_staffing_plan_detail(designation, company, offer_date):
detail = frappe.db.sql(""" detail = frappe.db.sql("""
SELECT spd.name as name, SELECT DISTINCT spd.parent,
sp.from_date as from_date, sp.from_date as from_date,
sp.to_date as to_date, sp.to_date as to_date,
sp.name as parent sp.name,
sum(spd.vacancies) as vacancies,
spd.designation
FROM `tabStaffing Plan Detail` spd, `tabStaffing Plan` sp FROM `tabStaffing Plan Detail` spd, `tabStaffing Plan` sp
WHERE WHERE
sp.docstatus=1 sp.docstatus=1
AND spd.designation=%s AND spd.designation=%s
AND sp.company=%s AND sp.company=%s
AND spd.parent = sp.name
AND %s between sp.from_date and sp.to_date AND %s between sp.from_date and sp.to_date
""", (designation, company, offer_date), as_dict=1) """, (designation, company, offer_date), as_dict=1)
return detail[0] if detail else None
return frappe._dict(detail[0]) if detail else None
@frappe.whitelist() @frappe.whitelist()
def make_employee(source_name, target_doc=None): def make_employee(source_name, target_doc=None):

View File

@ -0,0 +1,15 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.listview_settings['Job Offer'] = {
add_fields: ["company", "designation", "job_applicant", "status"],
get_indicator: function (doc) {
if (doc.status == "Accepted") {
return [__(doc.status), "green", "status,=," + doc.status];
} else if (doc.status == "Awaiting Response") {
return [__(doc.status), "orange", "status,=," + doc.status];
} else if (doc.status == "Rejected") {
return [__(doc.status), "red", "status,=," + doc.status];
}
}
};

View File

@ -1,4 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from frappe import _
def get_data(): def get_data():
return { return {
@ -8,13 +9,17 @@ def get_data():
}, },
'transactions': [ 'transactions': [
{ {
'items': ['Employee'] 'label': _('Employees'),
}, 'items': ['Employee', 'Employee Grade']
{
'items': ['Employee Grade']
}, },
{ {
'label': _('Leaves'),
'items': ['Leave Allocation'] 'items': ['Leave Allocation']
}, },
] ]
} }

View File

@ -45,15 +45,6 @@ frappe.ui.form.on('Loan', {
}); });
}) })
frm.set_query('loan_security_pledge', function(doc, cdt, cdn) {
return {
filters: {
applicant: frm.doc.applicant,
docstatus: 1,
loan_application: frm.doc.loan_application || ''
}
};
});
}, },
refresh: function (frm) { refresh: function (frm) {
@ -86,9 +77,6 @@ frappe.ui.form.on('Loan', {
frm.toggle_display("repayment_periods", s1 - frm.doc.is_term_loan); frm.toggle_display("repayment_periods", s1 - frm.doc.is_term_loan);
}, },
is_secured_loan: function(frm) {
frm.toggle_reqd("loan_security_pledge", frm.doc.is_secured_loan);
},
make_loan_disbursement: function (frm) { make_loan_disbursement: function (frm) {
frappe.call({ frappe.call({

View File

@ -25,15 +25,12 @@
"disbursement_date", "disbursement_date",
"disbursed_amount", "disbursed_amount",
"column_break_11", "column_break_11",
"maximum_loan_amount",
"is_term_loan", "is_term_loan",
"repayment_method", "repayment_method",
"repayment_periods", "repayment_periods",
"monthly_repayment_amount", "monthly_repayment_amount",
"repayment_start_date", "repayment_start_date",
"loan_security_details_section",
"loan_security_pledge",
"column_break_25",
"maximum_loan_value",
"account_info", "account_info",
"mode_of_payment", "mode_of_payment",
"payment_account", "payment_account",
@ -292,13 +289,8 @@
"default": "0", "default": "0",
"fieldname": "is_secured_loan", "fieldname": "is_secured_loan",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Secured Loan" "label": "Is Secured Loan",
}, "read_only": 1
{
"depends_on": "is_secured_loan",
"fieldname": "loan_security_details_section",
"fieldtype": "Section Break",
"label": "Loan Security Details"
}, },
{ {
"default": "0", "default": "0",
@ -324,12 +316,6 @@
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "loan_security_pledge",
"fieldtype": "Link",
"label": "Loan Security Pledge",
"options": "Loan Security Pledge"
},
{ {
"fieldname": "disbursed_amount", "fieldname": "disbursed_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
@ -338,21 +324,17 @@
"read_only": 1 "read_only": 1
}, },
{ {
"fetch_from": "loan_security_pledge.maximum_loan_value", "fetch_from": "loan_application.maximum_loan_amount",
"fieldname": "maximum_loan_value", "fieldname": "maximum_loan_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Maximum Loan Value", "label": "Maximum Loan Amount",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1 "read_only": 1
},
{
"fieldname": "column_break_25",
"fieldtype": "Column Break"
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-13 13:16:10.192624", "modified": "2020-07-02 20:46:40.128142",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan", "name": "Loan",

View File

@ -13,11 +13,9 @@ from erpnext.controllers.accounts_controller import AccountsController
class Loan(AccountsController): class Loan(AccountsController):
def validate(self): def validate(self):
self.set_loan_amount() self.set_loan_amount()
self.validate_loan_amount()
self.set_missing_fields() self.set_missing_fields()
self.validate_accounts() self.validate_accounts()
self.validate_loan_security_pledge()
self.validate_loan_amount()
self.check_sanctioned_amount_limit() self.check_sanctioned_amount_limit()
self.validate_repay_from_salary() self.validate_repay_from_salary()
@ -56,21 +54,6 @@ class Loan(AccountsController):
if self.repayment_method == "Repay Over Number of Periods": if self.repayment_method == "Repay Over Number of Periods":
self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods) self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
def validate_loan_security_pledge(self):
if self.is_secured_loan and not self.loan_security_pledge:
frappe.throw(_("Loan Security Pledge is mandatory for secured loan"))
if self.loan_security_pledge:
loan_security_details = frappe.db.get_value("Loan Security Pledge", self.loan_security_pledge,
['loan', 'company'], as_dict=1)
if loan_security_details.loan:
frappe.throw(_("Loan Security Pledge already pledged against loan {0}").format(loan_security_details.loan))
if loan_security_details.company != self.company:
frappe.throw(_("Loan Security Pledge Company and Loan Company must be same"))
def check_sanctioned_amount_limit(self): def check_sanctioned_amount_limit(self):
total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company) total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company) sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
@ -129,22 +112,29 @@ class Loan(AccountsController):
self.total_payment = self.loan_amount self.total_payment = self.loan_amount
def set_loan_amount(self): def set_loan_amount(self):
if self.loan_application and not self.loan_amount:
self.loan_amount = frappe.db.get_value('Loan Application', self.loan_application, 'loan_amount')
if not self.loan_amount and self.is_secured_loan and self.loan_security_pledge:
self.loan_amount = self.maximum_loan_value
def validate_loan_amount(self): def validate_loan_amount(self):
if self.is_secured_loan and self.loan_amount > self.maximum_loan_value: if self.maximum_loan_amount and self.loan_amount > self.maximum_loan_amount:
msg = _("Loan amount cannot be greater than {0}").format(self.maximum_loan_value) msg = _("Loan amount cannot be greater than {0}").format(self.maximum_loan_amount)
frappe.throw(msg) frappe.throw(msg)
if not self.loan_amount: if not self.loan_amount:
frappe.throw(_("Loan amount is mandatory")) frappe.throw(_("Loan amount is mandatory"))
def link_loan_security_pledge(self): def link_loan_security_pledge(self):
frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET if self.is_secured_loan:
loan = %s, status = 'Pledged', pledge_time = %s loan_security_pledge = frappe.db.get_value('Loan Security Pledge', {'loan_application': self.loan_application},
where name = %s """, (self.name, now_datetime(), self.loan_security_pledge)) 'name')
if loan_security_pledge:
frappe.db.set_value('Loan Security Pledge', loan_security_pledge, {
'loan': self.name,
'status': 'Pledged',
'pledge_time': now_datetime()
})
def unlink_loan_security_pledge(self): def unlink_loan_security_pledge(self):
frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET
@ -235,8 +225,10 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as
@frappe.whitelist() @frappe.whitelist()
def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1): def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1):
loan_security_pledge_details = frappe.db.sql(""" loan_security_pledge_details = frappe.db.sql("""
SELECT p.parent, p.loan_security, p.qty as qty FROM `tabLoan Security Pledge` lsp , `tabPledge` p SELECT p.loan_security, sum(p.qty) as qty
FROM `tabLoan Security Pledge` lsp , `tabPledge` p
WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1 WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1
GROUP BY p.loan_security
""",(loan), as_dict=1) """,(loan), as_dict=1)
unpledge_request = frappe.new_doc("Loan Security Unpledge") unpledge_request = frappe.new_doc("Loan Security Unpledge")

View File

@ -16,6 +16,7 @@ from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual
from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
class TestLoan(unittest.TestCase): class TestLoan(unittest.TestCase):
def setUp(self): def setUp(self):
@ -72,31 +73,31 @@ class TestLoan(unittest.TestCase):
self.assertEquals(loan.total_payment, 302712) self.assertEquals(loan.total_payment, 302712)
def test_loan_with_security(self): def test_loan_with_security(self):
pledges = []
pledges.append({ pledge = [{
"loan_security": "Test Security 1", "loan_security": "Test Security 1",
"qty": 4000.00, "qty": 4000.00,
"haircut": 50, }]
"loan_security_price": 500.00
})
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) loan_application = create_loan_application('_Test Company', self.applicant2,
'Stock Loan', pledge, "Repay Over Number of Periods", 12)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_security_pledge.name) create_pledge(loan_application)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods",
12, loan_application)
self.assertEquals(loan.loan_amount, 1000000) self.assertEquals(loan.loan_amount, 1000000)
def test_loan_disbursement(self): def test_loan_disbursement(self):
pledges = [] pledge = [{
pledges.append({
"loan_security": "Test Security 1", "loan_security": "Test Security 1",
"qty": 4000.00, "qty": 4000.00
"haircut": 50 }]
})
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) loan_application = create_loan_application('_Test Company', self.applicant2, 'Stock Loan', pledge, "Repay Over Number of Periods", 12)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_security_pledge.name) create_pledge(loan_application)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
self.assertEquals(loan.loan_amount, 1000000) self.assertEquals(loan.loan_amount, 1000000)
loan.submit() loan.submit()
@ -121,18 +122,15 @@ class TestLoan(unittest.TestCase):
self.assertTrue(gl_entries2) self.assertTrue(gl_entries2)
def test_regular_loan_repayment(self): def test_regular_loan_repayment(self):
pledges = [] pledge = [{
pledges.append({
"loan_security": "Test Security 1", "loan_security": "Test Security 1",
"qty": 4000.00, "qty": 4000.00
"haircut": 50 }]
})
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
create_pledge(loan_application)
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
posting_date=get_first_day(nowdate()))
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
loan.submit() loan.submit()
self.assertEquals(loan.loan_amount, 1000000) self.assertEquals(loan.loan_amount, 1000000)
@ -166,16 +164,15 @@ class TestLoan(unittest.TestCase):
penalty_amount - amounts[0], 2)) penalty_amount - amounts[0], 2))
def test_loan_closure_repayment(self): def test_loan_closure_repayment(self):
pledges = [] pledge = [{
pledges.append({
"loan_security": "Test Security 1", "loan_security": "Test Security 1",
"qty": 4000.00, "qty": 4000.00
"haircut": 50 }]
})
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name, create_pledge(loan_application)
posting_date=get_first_day(nowdate()))
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
loan.submit() loan.submit()
self.assertEquals(loan.loan_amount, 1000000) self.assertEquals(loan.loan_amount, 1000000)
@ -214,23 +211,21 @@ class TestLoan(unittest.TestCase):
self.assertEquals(loan.status, "Loan Closure Requested") self.assertEquals(loan.status, "Loan Closure Requested")
def test_loan_repayment_for_term_loan(self): def test_loan_repayment_for_term_loan(self):
pledges = [] pledges = [{
pledges.append({
"loan_security": "Test Security 2", "loan_security": "Test Security 2",
"qty": 4000.00, "qty": 4000.00
"haircut": 50 },
}) {
pledges.append({
"loan_security": "Test Security 1", "loan_security": "Test Security 1",
"qty": 2000.00, "qty": 2000.00
"haircut": 50 }]
})
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) loan_application = create_loan_application('_Test Company', self.applicant2, 'Stock Loan', pledges,
"Repay Over Number of Periods", 12)
create_pledge(loan_application)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application,
loan_security_pledge.name, posting_date=add_months(nowdate(), -1)) posting_date=add_months(nowdate(), -1))
loan.submit() loan.submit()
@ -250,16 +245,18 @@ class TestLoan(unittest.TestCase):
self.assertEquals(amounts[1], 78303.00) self.assertEquals(amounts[1], 78303.00)
def test_security_shortfall(self): def test_security_shortfall(self):
pledges = [] pledges = [{
pledges.append({
"loan_security": "Test Security 2", "loan_security": "Test Security 2",
"qty": 8000.00, "qty": 8000.00,
"haircut": 50, "haircut": 50,
}) }]
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) loan_application = create_loan_application('_Test Company', self.applicant2,
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_security_pledge.name) create_pledge(loan_application)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
loan.submit() loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount) make_loan_disbursement_entry(loan.name, loan.loan_amount)
@ -279,16 +276,15 @@ class TestLoan(unittest.TestCase):
where loan_security='Test Security 2'""") where loan_security='Test Security 2'""")
def test_loan_security_unpledge(self): def test_loan_security_unpledge(self):
pledges = [] pledge = [{
pledges.append({
"loan_security": "Test Security 1", "loan_security": "Test Security 1",
"qty": 4000.00, "qty": 4000.00
"haircut": 50 }]
})
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name, create_pledge(loan_application)
posting_date=get_first_day(nowdate()))
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
loan.submit() loan.submit()
self.assertEquals(loan.loan_amount, 1000000) self.assertEquals(loan.loan_amount, 1000000)
@ -446,12 +442,13 @@ def create_loan_security():
"haircut": 50.00, "haircut": 50.00,
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)
def create_loan_security_pledge(applicant, pledges): def create_loan_security_pledge(applicant, pledges, loan_application):
lsp = frappe.new_doc("Loan Security Pledge") lsp = frappe.new_doc("Loan Security Pledge")
lsp.applicant_type = 'Customer' lsp.applicant_type = 'Customer'
lsp.applicant = applicant lsp.applicant = applicant
lsp.company = "_Test Company" lsp.company = "_Test Company"
lsp.loan_application = loan_application
for pledge in pledges: for pledge in pledges:
lsp.append('securities', { lsp.append('securities', {
@ -510,6 +507,31 @@ def create_repayment_entry(loan, applicant, posting_date, payment_type, paid_amo
return lr return lr
def create_loan_application(company, applicant, loan_type, proposed_pledges, repayment_method=None,
repayment_periods=None, posting_date=None):
loan_application = frappe.new_doc('Loan Application')
loan_application.applicant_type = 'Customer'
loan_application.company = company
loan_application.applicant = applicant
loan_application.loan_type = loan_type
loan_application.posting_date = posting_date or nowdate()
loan_application.is_secured_loan = 1
if repayment_method:
loan_application.repayment_method = repayment_method
loan_application.repayment_periods = repayment_periods
for pledge in proposed_pledges:
loan_application.append('proposed_pledges', pledge)
loan_application.save()
loan_application.submit()
loan_application.status = 'Approved'
loan_application.save()
return loan_application.name
def create_loan(applicant, loan_type, loan_amount, repayment_method, repayment_periods, def create_loan(applicant, loan_type, loan_amount, repayment_method, repayment_periods,
repayment_start_date=None, posting_date=None): repayment_start_date=None, posting_date=None):
@ -531,14 +553,13 @@ def create_loan(applicant, loan_type, loan_amount, repayment_method, repayment_p
loan.save() loan.save()
return loan return loan
def create_loan_with_security(applicant, loan_type, repayment_method, repayment_periods, loan_security_pledge, def create_loan_with_security(applicant, loan_type, repayment_method, repayment_periods, loan_application, posting_date=None, repayment_start_date=None):
posting_date=None, repayment_start_date=None):
loan = frappe.get_doc({ loan = frappe.get_doc({
"doctype": "Loan", "doctype": "Loan",
"company": "_Test Company", "company": "_Test Company",
"applicant_type": "Customer", "applicant_type": "Customer",
"posting_date": posting_date or nowdate(), "posting_date": posting_date or nowdate(),
"loan_application": loan_application,
"applicant": applicant, "applicant": applicant,
"loan_type": loan_type, "loan_type": loan_type,
"is_term_loan": 1, "is_term_loan": 1,
@ -547,7 +568,6 @@ def create_loan_with_security(applicant, loan_type, repayment_method, repayment_
"repayment_periods": repayment_periods, "repayment_periods": repayment_periods,
"repayment_start_date": repayment_start_date or nowdate(), "repayment_start_date": repayment_start_date or nowdate(),
"mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'), "mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'),
"loan_security_pledge": loan_security_pledge,
"payment_account": 'Payment Account - _TC', "payment_account": 'Payment Account - _TC',
"loan_account": 'Loan Account - _TC', "loan_account": 'Loan Account - _TC',
"interest_income_account": 'Interest Income Account - _TC', "interest_income_account": 'Interest Income Account - _TC',
@ -558,19 +578,19 @@ def create_loan_with_security(applicant, loan_type, repayment_method, repayment_
return loan return loan
def create_demand_loan(applicant, loan_type, loan_security_pledge, posting_date=None): def create_demand_loan(applicant, loan_type, loan_application, posting_date=None):
loan = frappe.get_doc({ loan = frappe.get_doc({
"doctype": "Loan", "doctype": "Loan",
"company": "_Test Company", "company": "_Test Company",
"applicant_type": "Customer", "applicant_type": "Customer",
"posting_date": posting_date or nowdate(), "posting_date": posting_date or nowdate(),
'loan_application': loan_application,
"applicant": applicant, "applicant": applicant,
"loan_type": loan_type, "loan_type": loan_type,
"is_term_loan": 0, "is_term_loan": 0,
"is_secured_loan": 1, "is_secured_loan": 1,
"mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'), "mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'),
"loan_security_pledge": loan_security_pledge,
"payment_account": 'Payment Account - _TC', "payment_account": 'Payment Account - _TC',
"loan_account": 'Loan Account - _TC', "loan_account": 'Loan Account - _TC',
"interest_income_account": 'Interest Income Account - _TC', "interest_income_account": 'Interest Income Account - _TC',

View File

@ -103,10 +103,13 @@ class LoanApplication(Document):
if self.is_secured_loan and not self.proposed_pledges: if self.is_secured_loan and not self.proposed_pledges:
frappe.throw(_("Proposed Pledges are mandatory for secured Loans")) frappe.throw(_("Proposed Pledges are mandatory for secured Loans"))
if not self.loan_amount and self.is_secured_loan and self.proposed_pledges: if self.is_secured_loan and self.proposed_pledges:
self.loan_amount = 0 self.maximum_loan_amount = 0
for security in self.proposed_pledges: for security in self.proposed_pledges:
self.loan_amount += security.post_haircut_amount self.maximum_loan_amount += security.post_haircut_amount
if not self.loan_amount and self.is_secured_loan and self.proposed_pledges:
self.loan_amount = self.maximum_loan_amount
@frappe.whitelist() @frappe.whitelist()
def create_loan(source_name, target_doc=None, submit=0): def create_loan(source_name, target_doc=None, submit=0):
@ -116,7 +119,6 @@ def create_loan(source_name, target_doc=None, submit=0):
filters = {'name': source_doc.loan_type} filters = {'name': source_doc.loan_type}
)[0] )[0]
loan_security_pledge = frappe.db.get_value("Loan Security Pledge", {"loan_application": source_name}, 'name')
target_doc.mode_of_payment = account_details.mode_of_payment target_doc.mode_of_payment = account_details.mode_of_payment
target_doc.payment_account = account_details.payment_account target_doc.payment_account = account_details.payment_account
@ -124,9 +126,6 @@ def create_loan(source_name, target_doc=None, submit=0):
target_doc.interest_income_account = account_details.interest_income_account target_doc.interest_income_account = account_details.interest_income_account
target_doc.penalty_income_account = account_details.penalty_income_account target_doc.penalty_income_account = account_details.penalty_income_account
if loan_security_pledge:
target_doc.is_secured_loan = 1
target_doc.loan_security_pledge = loan_security_pledge
doclist = get_mapped_doc("Loan Application", source_name, { doclist = get_mapped_doc("Loan Application", source_name, {
"Loan Application": { "Loan Application": {

View File

@ -5,11 +5,12 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date) from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date)
from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_repayment_entry, from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_repayment_entry, create_loan_application,
make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_security_price) make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_security_price)
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
from erpnext.selling.doctype.customer.test_customer import get_customer_dict from erpnext.selling.doctype.customer.test_customer import get_customer_dict
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
class TestLoanDisbursement(unittest.TestCase): class TestLoanDisbursement(unittest.TestCase):
@ -31,18 +32,15 @@ class TestLoanDisbursement(unittest.TestCase):
self.applicant = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name') self.applicant = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
def test_loan_topup(self): def test_loan_topup(self):
pledges = [] pledge = [{
pledges.append({
"loan_security": "Test Security 1", "loan_security": "Test Security 1",
"qty": 4000.00, "qty": 4000.00
"haircut": 50, }]
"loan_security_price": 500.00
})
loan_security_pledge = create_loan_security_pledge(self.applicant, pledges) loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
create_pledge(loan_application)
loan = create_demand_loan(self.applicant, "Demand Loan", loan_security_pledge.name, loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
posting_date=get_first_day(nowdate()))
loan.submit() loan.submit()

View File

@ -6,10 +6,11 @@ import frappe
import unittest import unittest
from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date) from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date)
from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_loan_security_price, from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_loan_security_price,
make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan) make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_application)
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
from erpnext.selling.doctype.customer.test_customer import get_customer_dict from erpnext.selling.doctype.customer.test_customer import get_customer_dict
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
class TestLoanInterestAccrual(unittest.TestCase): class TestLoanInterestAccrual(unittest.TestCase):
def setUp(self): def setUp(self):
@ -29,17 +30,15 @@ class TestLoanInterestAccrual(unittest.TestCase):
self.applicant = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name') self.applicant = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
def test_loan_interest_accural(self): def test_loan_interest_accural(self):
pledges = [] pledge = [{
pledges.append({
"loan_security": "Test Security 1", "loan_security": "Test Security 1",
"qty": 4000.00, "qty": 4000.00
"haircut": 50, }]
"loan_security_price": 500.00
})
loan_security_pledge = create_loan_security_pledge(self.applicant, pledges) loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
create_pledge(loan_application)
loan = create_demand_loan(self.applicant, "Demand Loan", loan_security_pledge.name, loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
posting_date=get_first_day(nowdate())) posting_date=get_first_day(nowdate()))
loan.submit() loan.submit()

View File

@ -116,7 +116,7 @@ class LoanRepayment(AccountsController):
def allocate_amounts(self, paid_entries): def allocate_amounts(self, paid_entries):
self.set('repayment_details', []) self.set('repayment_details', [])
self.principal_amount_paid = 0 self.principal_amount_paid = 0
interest_paid = 0 interest_paid = self.amount_paid - self.penalty_amount
if self.amount_paid - self.penalty_amount > 0 and paid_entries: if self.amount_paid - self.penalty_amount > 0 and paid_entries:
interest_paid = self.amount_paid - self.penalty_amount interest_paid = self.amount_paid - self.penalty_amount

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "LS-.{applicant}.-.#####", "autoname": "LS-.{applicant}.-.#####",
"creation": "2019-08-29 18:48:51.371674", "creation": "2019-08-29 18:48:51.371674",
"doctype": "DocType", "doctype": "DocType",
@ -6,10 +7,10 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"loan_details_section", "loan_details_section",
"loan_application",
"loan",
"applicant_type", "applicant_type",
"applicant", "applicant",
"loan",
"loan_application",
"column_break_3", "column_break_3",
"company", "company",
"pledge_time", "pledge_time",
@ -55,15 +56,13 @@
"fieldname": "loan", "fieldname": "loan",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Loan", "label": "Loan",
"options": "Loan", "options": "Loan"
"read_only": 1
}, },
{ {
"fieldname": "loan_application", "fieldname": "loan_application",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Loan Application", "label": "Loan Application",
"options": "Loan Application", "options": "Loan Application"
"read_only": 1
}, },
{ {
"fieldname": "total_security_value", "fieldname": "total_security_value",
@ -133,7 +132,8 @@
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-10-10 13:22:53.297519", "links": [],
"modified": "2020-07-02 23:38:24.002382",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Security Pledge", "name": "Loan Security Pledge",

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "LM-LSP-.####", "autoname": "LM-LSP-.####",
"creation": "2019-09-03 18:20:31.382887", "creation": "2019-09-03 18:20:31.382887",
"doctype": "DocType", "doctype": "DocType",
@ -46,6 +47,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Loan Security Price", "label": "Loan Security Price",
"options": "Company:company:default_currency",
"reqd": 1 "reqd": 1
}, },
{ {
@ -79,7 +81,8 @@
"read_only": 1 "read_only": 1
} }
], ],
"modified": "2019-10-26 09:46:46.069667", "links": [],
"modified": "2020-06-11 03:41:33.900340",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Security Price", "name": "Loan Security Price",

View File

@ -19,7 +19,9 @@ def update_shortfall_status(loan, security_value):
return return
if security_value >= loan_security_shortfall.shortfall_amount: if security_value >= loan_security_shortfall.shortfall_amount:
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, "status", "Completed") frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, {
"status": "Completed",
"shortfall_value": loan_security_shortfall.shortfall_amount})
else: else:
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name,
"shortfall_amount", loan_security_shortfall.shortfall_amount - security_value) "shortfall_amount", loan_security_shortfall.shortfall_amount - security_value)

View File

@ -9,12 +9,15 @@ frappe.ui.form.on(cur_frm.doctype, {
} }
if (['Loan Disbursement', 'Loan Repayment', 'Loan Interest Accrual'].includes(frm.doc.doctype) if (['Loan Disbursement', 'Loan Repayment', 'Loan Interest Accrual'].includes(frm.doc.doctype)
&& frm.doc.docstatus == 1) { && frm.doc.docstatus > 0) {
frm.add_custom_button(__("Accounting Ledger"), function() { frm.add_custom_button(__("Accounting Ledger"), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
company: frm.doc.company company: frm.doc.company,
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");

View File

@ -192,7 +192,7 @@ def get_number_cards():
]), ]),
"function": "Count", "function": "Count",
"is_public": 1, "is_public": 1,
"label": _("Monthly Total Work Order"), "label": _("Monthly Total Work Orders"),
"show_percentage_stats": 1, "show_percentage_stats": 1,
"stats_time_interval": "Weekly" "stats_time_interval": "Weekly"
}, },
@ -207,7 +207,7 @@ def get_number_cards():
]), ]),
"function": "Count", "function": "Count",
"is_public": 1, "is_public": 1,
"label": _("Monthly Completed Work Order"), "label": _("Monthly Completed Work Orders"),
"show_percentage_stats": 1, "show_percentage_stats": 1,
"stats_time_interval": "Weekly" "stats_time_interval": "Weekly"
}, },
@ -221,7 +221,7 @@ def get_number_cards():
]), ]),
"function": "Count", "function": "Count",
"is_public": 1, "is_public": 1,
"label": _("Ongoing Job Card"), "label": _("Ongoing Job Cards"),
"show_percentage_stats": 1, "show_percentage_stats": 1,
"stats_time_interval": "Weekly" "stats_time_interval": "Weekly"
}, },
@ -235,7 +235,7 @@ def get_number_cards():
]), ]),
"function": "Count", "function": "Count",
"is_public": 1, "is_public": 1,
"label": _("Monthly Quality Inspection"), "label": _("Monthly Quality Inspections"),
"show_percentage_stats": 1, "show_percentage_stats": 1,
"stats_time_interval": "Weekly" "stats_time_interval": "Weekly"
}] }]

View File

@ -93,12 +93,6 @@
"stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}", "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}",
"type": "DocType" "type": "DocType"
}, },
{
"label": "Dashboard",
"link_to": "Manufacturing",
"restrict_to_domain": "Manufacturing",
"type": "Dashboard"
},
{ {
"label": "Forecasting", "label": "Forecasting",
"link_to": "Exponential Smoothing Forecasting", "link_to": "Exponential Smoothing Forecasting",
@ -119,6 +113,12 @@
"label": "Production Planning Report", "label": "Production Planning Report",
"link_to": "Production Planning Report", "link_to": "Production Planning Report",
"type": "Report" "type": "Report"
} },
{
"label": "Dashboard",
"link_to": "Manufacturing",
"restrict_to_domain": "Manufacturing",
"type": "Dashboard"
}
] ]
} }

View File

@ -98,11 +98,17 @@ class ProductionPlan(Document):
elif self.get_items_from == "Material Request": elif self.get_items_from == "Material Request":
self.get_mr_items() self.get_mr_items()
def get_so_mr_list(self, field, table):
"""Returns a list of Sales Orders or Material Requests from the respective tables"""
so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)]
return so_mr_list
def get_so_items(self): def get_so_items(self):
so_list = [d.sales_order for d in self.sales_orders if d.sales_order] # Check for empty table or empty rows
if not so_list: if not self.get("sales_orders") or not self.get_so_mr_list("sales_order", "sales_orders"):
msgprint(_("Please enter Sales Orders in the above table")) frappe.throw(_("Please fill the Sales Orders table"), title=_("Sales Orders Required"))
return []
so_list = self.get_so_mr_list("sales_order", "sales_orders")
item_condition = "" item_condition = ""
if self.item_code: if self.item_code:
@ -134,10 +140,11 @@ class ProductionPlan(Document):
self.calculate_total_planned_qty() self.calculate_total_planned_qty()
def get_mr_items(self): def get_mr_items(self):
mr_list = [d.material_request for d in self.material_requests if d.material_request] # Check for empty table or empty rows
if not mr_list: if not self.get("material_requests") or not self.get_so_mr_list("material_request", "material_requests"):
msgprint(_("Please enter Material Requests in the above table")) frappe.throw(_("Please fill the Material Requests table"), title=_("Material Requests Required"))
return []
mr_list = self.get_so_mr_list("material_request", "material_requests")
item_condition = "" item_condition = ""
if self.item_code: if self.item_code:
@ -628,16 +635,19 @@ def get_items_for_material_requests(doc, warehouses=None):
if warehouse_list: if warehouse_list:
warehouses = list(set(warehouse_list)) warehouses = list(set(warehouse_list))
if doc.get("for_warehouse") and doc.get("for_warehouse") in warehouses: if doc.get("for_warehouse") and doc.get("for_warehouse") in warehouses:
warehouses.remove(doc.get("for_warehouse")) warehouses.remove(doc.get("for_warehouse"))
warehouse_list = None warehouse_list = None
doc['mr_items'] = [] doc['mr_items'] = []
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
if not po_items: # Check for empty table or empty rows
frappe.throw(_("Items are required to pull the raw materials which is associated with it.")) if not po_items or not [row.get('item_code') for row in po_items if row.get('item_code')]:
frappe.throw(_("Items to Manufacture are required to pull the Raw Materials associated with it."),
title=_("Items Required"))
company = doc.get('company') company = doc.get('company')
ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')

View File

@ -19,7 +19,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing", "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"modified": "2020-05-19 12:51:42.744570", "modified": "2020-06-29 20:25:36.899106",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Manufacturing", "name": "Manufacturing",
@ -52,6 +52,5 @@
], ],
"subtitle": "Products, Raw Materials, BOM, Work Order and more.", "subtitle": "Products, Raw Materials, BOM, Work Order and more.",
"success_message": "Manufacturing module is all setup!", "success_message": "Manufacturing module is all setup!",
"title": "Let's Setup Manufacturing Module", "title": "Let's Set Up the Manufacturing Module"
"user_can_dismiss": 1 }
}

View File

@ -700,9 +700,11 @@ erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
erpnext.patches.v12_0.unhide_cost_center_field erpnext.patches.v12_0.unhide_cost_center_field
erpnext.patches.v13_0.update_sla_enhancements erpnext.patches.v13_0.update_sla_enhancements
erpnext.patches.v12_0.update_address_template_for_india erpnext.patches.v12_0.update_address_template_for_india
erpnext.patches.v13_0.update_deferred_settings
erpnext.patches.v12_0.set_multi_uom_in_rfq erpnext.patches.v12_0.set_multi_uom_in_rfq
erpnext.patches.v13_0.delete_old_sales_reports erpnext.patches.v13_0.delete_old_sales_reports
execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation")
erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
erpnext.patches.v13_0.check_is_income_tax_component erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
erpnext.patches.v12_0.add_taxjar_integration_field

View File

@ -0,0 +1,12 @@
from __future__ import unicode_literals
import frappe
from erpnext.regional.united_states.setup import make_custom_fields
def execute():
company = frappe.get_all('Company', filters={'country': 'United States'})
if not company:
return
make_custom_fields()

View File

@ -5,6 +5,8 @@ from erpnext.regional.united_states.setup import make_custom_fields
def execute(): def execute():
frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True) frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True)
frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True)
frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True)
company = frappe.get_all('Company', filters = {'country': 'United States'}) company = frappe.get_all('Company', filters = {'country': 'United States'})
if not company: if not company:

View File

@ -4,9 +4,28 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from erpnext.regional.india.setup import setup
def execute(): def execute():
frappe.reload_doc('Payroll', 'doctype', 'salary_structure')
doctypes = ['salary_component',
'Employee Tax Exemption Declaration',
'Employee Tax Exemption Proof Submission',
'Employee Tax Exemption Declaration Category',
'Employee Tax Exemption Proof Submission Detail'
]
for doctype in doctypes:
frappe.reload_doc('Payroll', 'doctype', doctype)
reports = ['Professional Tax Deductions', 'Provident Fund Deductions']
for report in reports:
frappe.reload_doc('Regional', 'Report', report)
frappe.reload_doc('Regional', 'Report', report)
if erpnext.get_region() == "India":
setup(patch=True)
if frappe.db.exists("Salary Component", "Income Tax"): if frappe.db.exists("Salary Component", "Income Tax"):
frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1) frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1)

View File

@ -0,0 +1,11 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.book_deferred_entries_based_on = 'Days'
accounts_settings.book_deferred_entries_via_journal_entry = 0
accounts_settings.submit_journal_entries = 0
accounts_settings.save()

View File

@ -163,7 +163,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 21:10:50.374063", "modified": "2020-06-22 21:10:50.374063",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Additional Salary", "name": "Additional Salary",

View File

@ -119,7 +119,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 22:58:31.271922", "modified": "2020-06-22 22:58:31.271922",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Benefit Application", "name": "Employee Benefit Application",

View File

@ -45,7 +45,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-05-27 23:45:00.519134", "modified": "2020-06-22 23:45:00.519134",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Benefit Application Detail", "name": "Employee Benefit Application Detail",

View File

@ -123,7 +123,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 23:01:50.791676", "modified": "2020-06-22 23:01:50.791676",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Benefit Claim", "name": "Employee Benefit Claim",

View File

@ -74,7 +74,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 22:42:51.209630", "modified": "2020-06-22 22:42:51.209630",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Incentive", "name": "Employee Incentive",

View File

@ -76,7 +76,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 22:55:17.604688", "modified": "2020-06-22 22:55:17.604688",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Other Income", "name": "Employee Other Income",

View File

@ -26,7 +26,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-05-27 23:16:47.472910", "modified": "2020-06-22 23:16:47.472910",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Tax Exemption Category", "name": "Employee Tax Exemption Category",

View File

@ -107,7 +107,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 22:49:43.829892", "modified": "2020-06-22 22:49:43.829892",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Tax Exemption Declaration", "name": "Employee Tax Exemption Declaration",

View File

@ -48,7 +48,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-05-27 23:41:03.638739", "modified": "2020-06-22 23:41:03.638739",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Tax Exemption Declaration Category", "name": "Employee Tax Exemption Declaration Category",

View File

@ -130,7 +130,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 22:53:10.412321", "modified": "2020-06-22 22:53:10.412321",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Tax Exemption Proof Submission", "name": "Employee Tax Exemption Proof Submission",

View File

@ -53,7 +53,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-05-27 23:37:08.265600", "modified": "2020-06-22 23:37:08.265600",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Tax Exemption Proof Submission Detail", "name": "Employee Tax Exemption Proof Submission Detail",

View File

@ -38,7 +38,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-05-27 23:18:08.254645", "modified": "2020-06-22 23:18:08.254645",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Tax Exemption Sub Category", "name": "Employee Tax Exemption Sub Category",

View File

@ -94,7 +94,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 20:27:13.425084", "modified": "2020-06-22 20:27:13.425084",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Income Tax Slab", "name": "Income Tax Slab",

View File

@ -62,7 +62,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-05-27 23:33:17.931912", "modified": "2020-06-22 23:33:17.931912",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Income Tax Slab Other Charges", "name": "Income Tax Slab Other Charges",

View File

@ -52,7 +52,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-05-27 23:25:13.779032", "modified": "2020-06-22 23:25:13.779032",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll Employee Detail", "name": "Payroll Employee Detail",

View File

@ -262,7 +262,7 @@
"icon": "fa fa-cog", "icon": "fa fa-cog",
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 20:06:06.953904", "modified": "2020-06-22 20:06:06.953904",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll Entry", "name": "Payroll Entry",

View File

@ -53,7 +53,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-05-27 20:12:32.684189", "modified": "2020-06-29 17:17:12.689089",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll Period", "name": "Payroll Period",
@ -96,6 +96,7 @@
"write": 1 "write": 1
} }
], ],
"quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1 "track_changes": 1

View File

@ -26,7 +26,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-05-27 23:30:15.943356", "modified": "2020-06-22 23:30:15.943356",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll Period Date", "name": "Payroll Period Date",

View File

@ -63,7 +63,7 @@
"description": "The fraction of daily wages to be paid for half-day attendance", "description": "The fraction of daily wages to be paid for half-day attendance",
"fieldname": "daily_wages_fraction_for_half_day", "fieldname": "daily_wages_fraction_for_half_day",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Daily Wages Fraction for Half Day", "label": "Fraction of Daily Salary for Half Day",
"show_days": 1, "show_days": 1,
"show_seconds": 1 "show_seconds": 1
}, },
@ -109,7 +109,7 @@
"icon": "fa fa-cog", "icon": "fa fa-cog",
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-06-05 12:35:34.861674", "modified": "2020-06-22 17:00:58.408030",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll Settings", "name": "Payroll Settings",

View File

@ -93,7 +93,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-27 22:42:05.251951", "modified": "2020-06-22 22:42:05.251951",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Retention Bonus", "name": "Retention Bonus",

View File

@ -245,7 +245,7 @@
], ],
"icon": "fa fa-flag", "icon": "fa fa-flag",
"links": [], "links": [],
"modified": "2020-06-01 15:39:20.826565", "modified": "2020-06-22 15:39:20.826565",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Salary Component", "name": "Salary Component",

View File

@ -211,7 +211,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-05-27 23:21:26.300951", "modified": "2020-06-22 23:21:26.300951",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Salary Detail", "name": "Salary Detail",

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