Merge branch 'develop' into sales-report-total-row

This commit is contained in:
Deepesh Garg 2020-05-06 10:48:21 +05:30 committed by GitHub
commit 88e6f72cad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
292 changed files with 517085 additions and 509738 deletions

View File

@ -2,9 +2,10 @@ 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 from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, cint, get_link_to_form
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
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 '''
@ -32,8 +33,20 @@ def validate_service_stop_date(doc):
if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name): if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx)) frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
def convert_deferred_expense_to_expense(start_date=None, end_date=None): def build_conditions(process_type, account, company):
conditions=''
deferred_account = "item.deferred_revenue_account" if process_type=="Income" else "item.deferred_expense_account"
if account:
conditions += "AND %s='%s'"%(deferred_account, account)
elif company:
conditions += "AND p.company='%s'"%(company)
return conditions
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=''):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date: if not start_date:
start_date = add_months(today(), -1) start_date = add_months(today(), -1)
if not end_date: if not end_date:
@ -41,18 +54,25 @@ def convert_deferred_expense_to_expense(start_date=None, end_date=None):
# check for the purchase invoice for which GL entries has to be done # check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list(''' invoices = frappe.db.sql_list('''
select distinct parent from `tabPurchase Invoice Item` select distinct item.parent
where service_start_date<=%s and service_end_date>=%s from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0 where item.service_start_date<=%s and item.service_end_date>=%s
''', (end_date, start_date)) and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
'''.format(conditions), (end_date, start_date)) #nosec
# For each invoice, book deferred expense # For each invoice, book deferred expense
for invoice in invoices: for invoice in invoices:
doc = frappe.get_doc("Purchase Invoice", invoice) doc = frappe.get_doc("Purchase Invoice", invoice)
book_deferred_income_or_expense(doc, end_date) book_deferred_income_or_expense(doc, deferred_process, end_date)
def convert_deferred_revenue_to_income(start_date=None, end_date=None): if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date: if not start_date:
start_date = add_months(today(), -1) start_date = add_months(today(), -1)
if not end_date: if not end_date:
@ -60,14 +80,20 @@ def convert_deferred_revenue_to_income(start_date=None, end_date=None):
# check for the sales invoice for which GL entries has to be done # check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list(''' invoices = frappe.db.sql_list('''
select distinct parent from `tabSales Invoice Item` select distinct item.parent
where service_start_date<=%s and service_end_date>=%s from `tabSales Invoice Item` item, `tabSales Invoice` p
and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0 where item.service_start_date<=%s and item.service_end_date>=%s
''', (end_date, start_date)) and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
'''.format(conditions), (end_date, start_date)) #nosec
for invoice in invoices: for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice) doc = frappe.get_doc("Sales Invoice", invoice)
book_deferred_income_or_expense(doc, end_date) book_deferred_income_or_expense(doc, deferred_process, end_date)
if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
def get_booking_dates(doc, item, posting_date=None): def get_booking_dates(doc, item, posting_date=None):
if not posting_date: if not posting_date:
@ -136,7 +162,7 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a
return amount, base_amount return amount, base_amount
def book_deferred_income_or_expense(doc, 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"
@ -159,7 +185,11 @@ def book_deferred_income_or_expense(doc, posting_date=None):
total_days, total_booking_days, account_currency) total_days, total_booking_days, account_currency)
make_gl_entries(doc, credit_account, debit_account, against, make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, end_date, project, account_currency, item.cost_center, item.name) amount, base_amount, end_date, project, account_currency, item.cost_center, item.name, deferred_process)
# 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:
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)
@ -169,8 +199,30 @@ def book_deferred_income_or_expense(doc, posting_date=None):
if item.get(enable_check): if item.get(enable_check):
_book_deferred_revenue_or_expense(item) _book_deferred_revenue_or_expense(item)
def process_deferred_accounting(posting_date=today()):
''' Converts deferred income/expense into income/expense
Executed via background jobs on every month end '''
if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')):
return
start_date = add_months(today(), -1)
end_date = add_days(today(), -1)
for record_type in ('Income', 'Expense'):
doc = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date=posting_date,
start_date=start_date,
end_date=end_date,
type=record_type
))
doc.insert()
doc.submit()
def make_gl_entries(doc, credit_account, debit_account, against, def make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no): amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no, deferred_process=None):
# GL Entry for crediting the amount in the deferred expense # GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
@ -186,7 +238,9 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"cost_center": cost_center, "cost_center": cost_center,
"voucher_detail_no": voucher_detail_no, "voucher_detail_no": voucher_detail_no,
'posting_date': posting_date, 'posting_date': posting_date,
'project': project 'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
}, account_currency) }, account_currency)
) )
# GL Entry to debit the amount from the expense # GL Entry to debit the amount from the expense
@ -199,7 +253,9 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"cost_center": cost_center, "cost_center": cost_center,
"voucher_detail_no": voucher_detail_no, "voucher_detail_no": voucher_detail_no,
'posting_date': posting_date, 'posting_date': posting_date,
'project': project 'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
}, account_currency) }, account_currency)
) )
@ -209,7 +265,16 @@ def make_gl_entries(doc, credit_account, debit_account, against,
frappe.db.commit() frappe.db.commit()
except: except:
frappe.db.rollback() frappe.db.rollback()
title = _("Error while processing deferred accounting for {0}").format(doc.name)
traceback = frappe.get_traceback() traceback = frappe.get_traceback()
frappe.log_error(message=traceback , title=title) frappe.log_error(message=traceback)
sendmail_to_system_managers(title, traceback)
frappe.flags.deferred_accounting_error = True
def send_mail(deferred_process):
title = _("Error while processing deferred accounting for {0}".format(deferred_process))
content = _("""
Deferred accounting failed for some invoices:
Please check Process Deferred Accounting {0}
and submit manually after resolving errors
""").format(get_link_to_form('Process Deferred Accounting', deferred_process))
sendmail_to_system_managers(title, content)

View File

@ -8,7 +8,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "General Ledger", "label": "General Ledger",
"links": "[\n {\n \"description\": \"Accounting journal entries.\",\n \"label\": \"Journal Entry\",\n \"name\": \"Journal Entry\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"General Ledger\",\n \"name\": \"General Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Customer Ledger Summary\",\n \"name\": \"Customer Ledger Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Supplier Ledger Summary\",\n \"name\": \"Supplier Ledger Summary\",\n \"type\": \"report\"\n }\n]" "links": "[\n {\n \"description\": \"Accounting journal entries.\",\n \"label\": \"Journal Entry\",\n \"name\": \"Journal Entry\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Make journal entries from a template.\",\n \"label\": \"Journal Entry Template\",\n \"name\": \"Journal Entry Template\",\n \"type\": \"doctype\"\n },\n \n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"General Ledger\",\n \"name\": \"General Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Customer Ledger Summary\",\n \"name\": \"Customer Ledger Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Supplier Ledger Summary\",\n \"name\": \"Supplier Ledger Summary\",\n \"type\": \"report\"\n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -47,8 +47,8 @@
}, },
{ {
"hidden": 0, "hidden": 0,
"label": "Banking and Payments", "links": "[\n {\n \"description\": \"Match non-linked Invoices and Payments.\",\n \"label\": \"Match Payments with Invoices\",\n \"name\": \"Payment Reconciliation\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update bank payment dates with journals.\",\n \"label\": \"Update Bank Clearance Dates\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Invoice Discounting\",\n \"name\": \"Invoice Discounting\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Clearance Summary\",\n \"name\": \"Bank Clearance Summary\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Bank Guarantee\",\n \"name\": \"Bank Guarantee\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup cheque dimensions for printing\",\n \"label\": \"Cheque Print Template\",\n \"name\": \"Cheque Print Template\",\n \"type\": \"doctype\"\n }\n]",
"links": "[\n {\n \"description\": \"Match non-linked Invoices and Payments.\",\n \"label\": \"Match Payments with Invoices\",\n \"name\": \"Payment Reconciliation\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update bank payment dates with journals.\",\n \"label\": \"Update Bank Transaction Dates\",\n \"name\": \"Bank Reconciliation\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Invoice Discounting\",\n \"name\": \"Invoice Discounting\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Clearance Summary\",\n \"name\": \"Bank Clearance Summary\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Bank Guarantee\",\n \"name\": \"Bank Guarantee\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup cheque dimensions for printing\",\n \"label\": \"Cheque Print Template\",\n \"name\": \"Cheque Print Template\",\n \"type\": \"doctype\"\n }\n]" "title": "Banking and Payments"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -103,7 +103,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Accounting", "label": "Accounting",
"modified": "2020-04-01 11:28:50.925719", "modified": "2020-04-29 12:17:34.844397",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "name": "Accounting",

View File

@ -206,12 +206,13 @@ def get_dimension_filters():
WHERE disabled = 0 WHERE disabled = 0
""", as_dict=1) """, as_dict=1)
default_dimensions = frappe.db.sql("""SELECT parent, company, default_dimension default_dimensions = frappe.db.sql("""SELECT p.fieldname, c.company, c.default_dimension
FROM `tabAccounting Dimension Detail`""", as_dict=1) FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
WHERE c.parent = p.name""", as_dict=1)
default_dimensions_map = {} default_dimensions_map = {}
for dimension in default_dimensions: for dimension in default_dimensions:
default_dimensions_map.setdefault(dimension['company'], {}) default_dimensions_map.setdefault(dimension.company, {})
default_dimensions_map[dimension['company']][dimension['parent']] = dimension['default_dimension'] default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
return dimension_filters, default_dimensions_map return dimension_filters, default_dimensions_map

View File

@ -42,7 +42,7 @@ class AccountingPeriod(Document):
def get_doctypes_for_closing(self): def get_doctypes_for_closing(self):
docs_for_closing = [] docs_for_closing = []
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \ doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
"Bank Reconciliation", "Asset", "Stock Entry"] "Bank Clearance", "Asset", "Stock Entry"]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes] closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes: for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype) docs_for_closing.append(closed_doctype)

View File

@ -1,210 +1,226 @@
{ {
"creation": "2013-06-24 15:49:57", "actions": [],
"description": "Settings for Accounts", "creation": "2013-06-24 15:49:57",
"doctype": "DocType", "description": "Settings for Accounts",
"document_type": "Other", "doctype": "DocType",
"editable_grid": 1, "document_type": "Other",
"engine": "InnoDB", "editable_grid": 1,
"field_order": [ "engine": "InnoDB",
"auto_accounting_for_stock", "field_order": [
"acc_frozen_upto", "auto_accounting_for_stock",
"frozen_accounts_modifier", "acc_frozen_upto",
"determine_address_tax_category_from", "frozen_accounts_modifier",
"over_billing_allowance", "determine_address_tax_category_from",
"column_break_4", "over_billing_allowance",
"credit_controller", "column_break_4",
"check_supplier_invoice_uniqueness", "credit_controller",
"make_payment_via_journal_entry", "check_supplier_invoice_uniqueness",
"unlink_payment_on_cancellation_of_invoice", "make_payment_via_journal_entry",
"unlink_advance_payment_on_cancelation_of_order", "unlink_payment_on_cancellation_of_invoice",
"book_asset_depreciation_entry_automatically", "unlink_advance_payment_on_cancelation_of_order",
"allow_cost_center_in_entry_of_bs_account", "book_asset_depreciation_entry_automatically",
"add_taxes_from_item_tax_template", "allow_cost_center_in_entry_of_bs_account",
"automatically_fetch_payment_terms", "add_taxes_from_item_tax_template",
"print_settings", "automatically_fetch_payment_terms",
"show_inclusive_tax_in_print", "automatically_process_deferred_accounting_entry",
"column_break_12", "print_settings",
"show_payment_schedule_in_print", "show_inclusive_tax_in_print",
"currency_exchange_section", "column_break_12",
"allow_stale", "show_payment_schedule_in_print",
"stale_days", "currency_exchange_section",
"report_settings_sb", "allow_stale",
"use_custom_cash_flow" "stale_days",
], "report_settings_sb",
"fields": [ "use_custom_cash_flow"
{ ],
"default": "1", "fields": [
"description": "If enabled, the system will post accounting entries for inventory automatically.", {
"fieldname": "auto_accounting_for_stock", "default": "1",
"fieldtype": "Check", "description": "If enabled, the system will post accounting entries for inventory automatically.",
"hidden": 1, "fieldname": "auto_accounting_for_stock",
"in_list_view": 1, "fieldtype": "Check",
"label": "Make Accounting Entry For Every Stock Movement" "hidden": 1,
}, "in_list_view": 1,
{ "label": "Make Accounting Entry For Every Stock Movement"
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", },
"fieldname": "acc_frozen_upto", {
"fieldtype": "Date", "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
"in_list_view": 1, "fieldname": "acc_frozen_upto",
"label": "Accounts Frozen Upto" "fieldtype": "Date",
}, "in_list_view": 1,
{ "label": "Accounts Frozen Upto"
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", },
"fieldname": "frozen_accounts_modifier", {
"fieldtype": "Link", "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
"in_list_view": 1, "fieldname": "frozen_accounts_modifier",
"label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", "fieldtype": "Link",
"options": "Role" "in_list_view": 1,
}, "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
{ "options": "Role"
"default": "Billing Address", },
"description": "Address used to determine Tax Category in transactions.", {
"fieldname": "determine_address_tax_category_from", "default": "Billing Address",
"fieldtype": "Select", "description": "Address used to determine Tax Category in transactions.",
"label": "Determine Address Tax Category From", "fieldname": "determine_address_tax_category_from",
"options": "Billing Address\nShipping Address" "fieldtype": "Select",
}, "label": "Determine Address Tax Category From",
{ "options": "Billing Address\nShipping Address"
"fieldname": "column_break_4", },
"fieldtype": "Column Break" {
}, "fieldname": "column_break_4",
{ "fieldtype": "Column Break"
"description": "Role that is allowed to submit transactions that exceed credit limits set.", },
"fieldname": "credit_controller", {
"fieldtype": "Link", "description": "Role that is allowed to submit transactions that exceed credit limits set.",
"in_list_view": 1, "fieldname": "credit_controller",
"label": "Credit Controller", "fieldtype": "Link",
"options": "Role" "in_list_view": 1,
}, "label": "Credit Controller",
{ "options": "Role"
"fieldname": "check_supplier_invoice_uniqueness", },
"fieldtype": "Check", {
"label": "Check Supplier Invoice Number Uniqueness" "default": "0",
}, "fieldname": "check_supplier_invoice_uniqueness",
{ "fieldtype": "Check",
"fieldname": "make_payment_via_journal_entry", "label": "Check Supplier Invoice Number Uniqueness"
"fieldtype": "Check", },
"label": "Make Payment via Journal Entry" {
}, "default": "0",
{ "fieldname": "make_payment_via_journal_entry",
"default": "1", "fieldtype": "Check",
"fieldname": "unlink_payment_on_cancellation_of_invoice", "label": "Make Payment via Journal Entry"
"fieldtype": "Check", },
"label": "Unlink Payment on Cancellation of Invoice" {
}, "default": "1",
{ "fieldname": "unlink_payment_on_cancellation_of_invoice",
"default": "1", "fieldtype": "Check",
"fieldname": "unlink_advance_payment_on_cancelation_of_order", "label": "Unlink Payment on Cancellation of Invoice"
"fieldtype": "Check", },
"label": "Unlink Advance Payment on Cancelation of Order" {
}, "default": "1",
{ "fieldname": "unlink_advance_payment_on_cancelation_of_order",
"default": "1", "fieldtype": "Check",
"fieldname": "book_asset_depreciation_entry_automatically", "label": "Unlink Advance Payment on Cancelation of Order"
"fieldtype": "Check", },
"label": "Book Asset Depreciation Entry Automatically" {
}, "default": "1",
{ "fieldname": "book_asset_depreciation_entry_automatically",
"fieldname": "allow_cost_center_in_entry_of_bs_account", "fieldtype": "Check",
"fieldtype": "Check", "label": "Book Asset Depreciation Entry Automatically"
"label": "Allow Cost Center In Entry of Balance Sheet Account" },
}, {
{ "default": "0",
"default": "1", "fieldname": "allow_cost_center_in_entry_of_bs_account",
"fieldname": "add_taxes_from_item_tax_template", "fieldtype": "Check",
"fieldtype": "Check", "label": "Allow Cost Center In Entry of Balance Sheet Account"
"label": "Automatically Add Taxes and Charges from Item Tax Template" },
}, {
{ "default": "1",
"fieldname": "print_settings", "fieldname": "add_taxes_from_item_tax_template",
"fieldtype": "Section Break", "fieldtype": "Check",
"label": "Print Settings" "label": "Automatically Add Taxes and Charges from Item Tax Template"
}, },
{ {
"fieldname": "show_inclusive_tax_in_print", "fieldname": "print_settings",
"fieldtype": "Check", "fieldtype": "Section Break",
"label": "Show Inclusive Tax In Print" "label": "Print Settings"
}, },
{ {
"fieldname": "column_break_12", "default": "0",
"fieldtype": "Column Break" "fieldname": "show_inclusive_tax_in_print",
}, "fieldtype": "Check",
{ "label": "Show Inclusive Tax In Print"
"fieldname": "show_payment_schedule_in_print", },
"fieldtype": "Check", {
"label": "Show Payment Schedule in Print" "fieldname": "column_break_12",
}, "fieldtype": "Column Break"
{ },
"fieldname": "currency_exchange_section", {
"fieldtype": "Section Break", "default": "0",
"label": "Currency Exchange Settings" "fieldname": "show_payment_schedule_in_print",
}, "fieldtype": "Check",
{ "label": "Show Payment Schedule in Print"
"default": "1", },
"fieldname": "allow_stale", {
"fieldtype": "Check", "fieldname": "currency_exchange_section",
"in_list_view": 1, "fieldtype": "Section Break",
"label": "Allow Stale Exchange Rates" "label": "Currency Exchange Settings"
}, },
{ {
"default": "1", "default": "1",
"depends_on": "eval:doc.allow_stale==0", "fieldname": "allow_stale",
"fieldname": "stale_days", "fieldtype": "Check",
"fieldtype": "Int", "in_list_view": 1,
"label": "Stale Days" "label": "Allow Stale Exchange Rates"
}, },
{ {
"fieldname": "report_settings_sb", "default": "1",
"fieldtype": "Section Break", "depends_on": "eval:doc.allow_stale==0",
"label": "Report Settings" "fieldname": "stale_days",
}, "fieldtype": "Int",
{ "label": "Stale Days"
"default": "0", },
"description": "Only select if you have setup Cash Flow Mapper documents", {
"fieldname": "use_custom_cash_flow", "fieldname": "report_settings_sb",
"fieldtype": "Check", "fieldtype": "Section Break",
"label": "Use Custom Cash Flow Format" "label": "Report Settings"
}, },
{ {
"fieldname": "automatically_fetch_payment_terms", "default": "0",
"fieldtype": "Check", "description": "Only select if you have setup Cash Flow Mapper documents",
"label": "Automatically Fetch Payment Terms" "fieldname": "use_custom_cash_flow",
}, "fieldtype": "Check",
{ "label": "Use Custom Cash Flow Format"
"description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", },
"fieldname": "over_billing_allowance", {
"fieldtype": "Currency", "default": "0",
"label": "Over Billing Allowance (%)" "fieldname": "automatically_fetch_payment_terms",
} "fieldtype": "Check",
], "label": "Automatically Fetch Payment Terms"
"icon": "icon-cog", },
"idx": 1, {
"issingle": 1, "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
"modified": "2019-07-04 18:20:55.789946", "fieldname": "over_billing_allowance",
"modified_by": "Administrator", "fieldtype": "Currency",
"module": "Accounts", "label": "Over Billing Allowance (%)"
"name": "Accounts Settings", },
"owner": "Administrator", {
"permissions": [ "default": "1",
{ "fieldname": "automatically_process_deferred_accounting_entry",
"create": 1, "fieldtype": "Check",
"email": 1, "label": "Automatically Process Deferred Accounting Entry"
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Sales User"
},
{
"read": 1,
"role": "Purchase User"
}
],
"quick_entry": 1,
"sort_order": "ASC",
"track_changes": 1
} }
],
"icon": "icon-cog",
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2019-12-19 16:58:17.395595",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Sales User"
},
{
"read": 1,
"role": "Purchase User"
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}

View File

@ -1,74 +1,32 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "creation": "2018-04-16 21:50:05.860195",
"allow_import": 0, "doctype": "DocType",
"allow_rename": 0, "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2018-04-16 21:50:05.860195", "field_order": [
"custom": 0, "company"
"docstatus": 0, ],
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "company",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Company",
"columns": 0, "options": "Company",
"default": "", "reqd": 1
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-05-01 12:32:34.044911",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Accounts",
"in_create": 0, "name": "Allowed To Transact With",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "modified",
"modified": "2018-04-20 14:00:46.014502", "sort_order": "DESC",
"modified_by": "Administrator", "track_changes": 1
"module": "Accounts",
"name": "Allowed To Transact With",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Bank Reconciliation", { frappe.ui.form.on("Bank Clearance", {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("account", "account_currency", "account_currency"); frm.add_fetch("account", "account_currency", "account_currency");
}, },

View File

@ -0,0 +1,130 @@
{
"allow_copy": 1,
"creation": "2013-01-10 16:34:05",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
"account",
"account_currency",
"from_date",
"to_date",
"column_break_5",
"bank_account",
"include_reconciled_entries",
"include_pos_transactions",
"get_payment_entries",
"section_break_10",
"payment_entries",
"update_clearance_date",
"total_amount"
],
"fields": [
{
"fetch_from": "bank_account.account",
"fetch_if_empty": 1,
"fieldname": "account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Account",
"options": "Account",
"reqd": 1
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"hidden": 1,
"label": "Account Currency",
"options": "Currency",
"print_hide": 1
},
{
"fieldname": "from_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "From Date",
"reqd": 1
},
{
"fieldname": "to_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "To Date",
"reqd": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"description": "Select the Bank Account to reconcile.",
"fieldname": "bank_account",
"fieldtype": "Link",
"label": "Bank Account",
"options": "Bank Account"
},
{
"default": "0",
"fieldname": "include_reconciled_entries",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Include Reconciled Entries"
},
{
"default": "0",
"fieldname": "include_pos_transactions",
"fieldtype": "Check",
"label": "Include POS Transactions"
},
{
"fieldname": "get_payment_entries",
"fieldtype": "Button",
"label": "Get Payment Entries"
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 1,
"fieldname": "payment_entries",
"fieldtype": "Table",
"label": "Payment Entries",
"options": "Bank Clearance Detail"
},
{
"fieldname": "update_clearance_date",
"fieldtype": "Button",
"label": "Update Clearance Date"
},
{
"fieldname": "total_amount",
"fieldtype": "Currency",
"label": "Total Amount",
"options": "account_currency",
"read_only": 1
}
],
"hide_toolbar": 1,
"icon": "fa fa-check",
"idx": 1,
"issingle": 1,
"modified": "2020-04-06 16:12:06.628008",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Clearance",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"read": 1,
"role": "Accounts User",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"read_only": 1,
"sort_field": "modified",
"sort_order": "ASC"
}

View File

@ -11,7 +11,7 @@ form_grid_templates = {
"journal_entries": "templates/form_grid/bank_reconciliation_grid.html" "journal_entries": "templates/form_grid/bank_reconciliation_grid.html"
} }
class BankReconciliation(Document): class BankClearance(Document):
def get_payment_entries(self): def get_payment_entries(self):
if not (self.from_date and self.to_date): if not (self.from_date and self.to_date):
frappe.throw(_("From Date and To Date are Mandatory")) frappe.throw(_("From Date and To Date are Mandatory"))

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 TestBankClearance(unittest.TestCase):
pass

View File

@ -0,0 +1 @@
Detail of transaction for parent Bank Clearance.

View File

@ -326,7 +326,7 @@
"modified": "2019-01-07 16:52:07.174687", "modified": "2019-01-07 16:52:07.174687",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Reconciliation Detail", "name": "Bank Clearance Detail",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,

View File

@ -5,5 +5,5 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class BankReconciliationDetail(Document): class BankClearanceDetail(Document):
pass pass

View File

@ -1,484 +0,0 @@
{
"allow_copy": 1,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-01-10 16:34:05",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "bank_account.account",
"fetch_if_empty": 1,
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "account_currency",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account Currency",
"length": 0,
"no_copy": 0,
"options": "Currency",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "From Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "To Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Select the Bank Account to reconcile.",
"fetch_if_empty": 0,
"fieldname": "bank_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Bank Account",
"length": 0,
"no_copy": 0,
"options": "Bank Account",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "include_reconciled_entries",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Include Reconciled Entries",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "include_pos_transactions",
"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": "Include POS Transactions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "get_payment_entries",
"fieldtype": "Button",
"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": "Get Payment Entries",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"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,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 1,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_entries",
"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": "Payment Entries",
"length": 0,
"no_copy": 0,
"options": "Bank Reconciliation Detail",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "update_clearance_date",
"fieldtype": "Button",
"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": "Update Clearance Date",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_amount",
"fieldtype": "Currency",
"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": "Total Amount",
"length": 0,
"no_copy": 0,
"options": "account_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_toolbar": 1,
"icon": "fa fa-check",
"idx": 1,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2020-01-22 00:00:00.000000",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 1,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@ -1,22 +0,0 @@
QUnit.module('Account');
QUnit.test("test Bank Reconciliation", function(assert) {
assert.expect(0);
let done = assert.async();
frappe.run_serially([
() => frappe.set_route('Form', 'Bank Reconciliation'),
() => cur_frm.set_value('bank_account','Cash - FT'),
() => frappe.click_button('Get Payment Entries'),
() => {
for(var i=0;i<=cur_frm.doc.payment_entries.length-1;i++){
cur_frm.doc.payment_entries[i].clearance_date = frappe.datetime.add_days(frappe.datetime.now_date(), 2);
}
},
() => {cur_frm.refresh_fields('payment_entries');},
() => frappe.click_button('Update Clearance Date'),
() => frappe.timeout(0.5),
() => frappe.click_button('Close'),
() => done()
]);
});

View File

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

View File

@ -1 +0,0 @@
Detail of transaction for parent Bank Reconciliation.

View File

@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from frappe.utils import nowdate from frappe.utils import nowdate, now_datetime
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
@ -13,27 +13,28 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestBudget(unittest.TestCase): class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self): def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
self.assertTrue(frappe.db.get_value("GL Entry", self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name})) {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
budget.cancel() budget.cancel()
jv.cancel()
def test_monthly_budget_crossed_stop1(self): def test_monthly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28") "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -41,14 +42,14 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_exception_approver_role(self): def test_exception_approver_role(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-03-02") "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -112,16 +113,17 @@ class TestBudget(unittest.TestCase):
budget.load_from_db() budget.load_from_db()
budget.cancel() budget.cancel()
po.cancel()
def test_monthly_budget_crossed_stop2(self): def test_monthly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "project") set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-02-28") "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -129,86 +131,76 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_yearly_budget_crossed_stop1(self): def test_yearly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", posting_date="2013-03-28") "_Test Bank - _TC", 250000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
budget.cancel() budget.cancel()
def test_yearly_budget_crossed_stop2(self): def test_yearly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "project") set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-03-28") "_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
budget.cancel() budget.cancel()
def test_monthly_budget_on_cancellation1(self): def test_monthly_budget_on_cancellation1(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", for i in range(now_datetime().month):
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
self.assertTrue(frappe.db.get_value("GL Entry", self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv1.name})) {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel) self.assertRaises(BudgetError, jv.cancel)
budget.load_from_db() budget.load_from_db()
budget.cancel() budget.cancel()
def test_monthly_budget_on_cancellation2(self): def test_monthly_budget_on_cancellation2(self):
set_total_expense_zero("2013-02-28", "project") set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", for i in range(now_datetime().month):
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
self.assertTrue(frappe.db.get_value("GL Entry", self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv1.name})) {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project")
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel) self.assertRaises(BudgetError, jv.cancel)
budget.load_from_db() budget.load_from_db()
budget.cancel() budget.cancel()
def test_monthly_budget_against_group_cost_center(self): def test_monthly_budget_against_group_cost_center(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC") set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC") budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date="2013-02-28") "_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -231,7 +223,7 @@ class TestBudget(unittest.TestCase):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, cost_center, posting_date="2013-02-28") "_Test Bank - _TC", 40000, cost_center, posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -246,12 +238,14 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
else: else:
budget_against = budget_against_CC or "_Test Cost Center - _TC" budget_against = budget_against_CC or "_Test Cost Center - _TC"
fiscal_year = get_fiscal_year(nowdate())[0]
args = frappe._dict({ args = frappe._dict({
"account": "_Test Account Cost for Goods Sold - _TC", "account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date, "monthly_end_date": posting_date,
"company": "_Test Company", "company": "_Test Company",
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": fiscal_year,
"budget_against_field": budget_against_field, "budget_against_field": budget_against_field,
}) })
@ -263,10 +257,10 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
if existing_expense: if existing_expense:
if budget_against_field == "cost_center": if budget_against_field == "cost_center":
make_journal_entry("_Test Account Cost for Goods Sold - _TC", make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
elif budget_against_field == "project": elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC", make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28") "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate())
def make_budget(**args): def make_budget(**args):
args = frappe._dict(args) args = frappe._dict(args)
@ -274,10 +268,13 @@ def make_budget(**args):
budget_against=args.budget_against budget_against=args.budget_against
cost_center=args.cost_center cost_center=args.cost_center
fiscal_year = get_fiscal_year(nowdate())[0]
if budget_against == "Project": if budget_against == "Project":
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Project/_Test Fiscal Year 2013%")}) project_name = "{0}%".format("_Test Project/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", project_name)})
else: else:
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/_Test Fiscal Year 2013") cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)}) budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)})
for d in budget_list: for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d) frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
@ -290,8 +287,10 @@ def make_budget(**args):
else: else:
budget.cost_center =cost_center or "_Test Cost Center - _TC" budget.cost_center =cost_center or "_Test Cost Center - _TC"
monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution")
monthly_distribution.fiscal_year = fiscal_year
budget.fiscal_year = "_Test Fiscal Year 2013" budget.fiscal_year = fiscal_year
budget.monthly_distribution = "_Test Distribution" budget.monthly_distribution = "_Test Distribution"
budget.company = "_Test Company" budget.company = "_Test Company"
budget.applicable_on_booking_actual_expenses = 1 budget.applicable_on_booking_actual_expenses = 1
@ -300,7 +299,7 @@ def make_budget(**args):
budget.budget_against = budget_against budget.budget_against = budget_against
budget.append("accounts", { budget.append("accounts", {
"account": "_Test Account Cost for Goods Sold - _TC", "account": "_Test Account Cost for Goods Sold - _TC",
"budget_amount": 100000 "budget_amount": 200000
}) })
if args.applicable_on_material_request: if args.applicable_on_material_request:

View File

@ -18,7 +18,7 @@ frappe.ui.form.on('Cost Center', {
}, },
refresh: function(frm) { refresh: function(frm) {
if (!frm.is_new()) { if (!frm.is_new()) {
frm.add_custom_button(__('Update Cost Center Number'), function () { frm.add_custom_button(__('Update Cost Center Name / Number'), function () {
frm.trigger("update_cost_center_number"); frm.trigger("update_cost_center_number");
}); });
} }
@ -47,35 +47,45 @@ frappe.ui.form.on('Cost Center', {
}, },
update_cost_center_number: function(frm) { update_cost_center_number: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __('Update Cost Center Number'), title: __('Update Cost Center Name / Number'),
fields: [ fields: [
{ {
"label": 'Cost Center Number', "label": "Cost Center Name",
"fieldname": "cost_center_name",
"fieldtype": "Data",
"reqd": 1,
"default": frm.doc.cost_center_name
},
{
"label": "Cost Center Number",
"fieldname": "cost_center_number", "fieldname": "cost_center_number",
"fieldtype": "Data", "fieldtype": "Data",
"reqd": 1 "reqd": 1,
"default": frm.doc.cost_center_number
} }
], ],
primary_action: function() { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
if(data.cost_center_number === frm.doc.cost_center_number) { if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
d.hide(); d.hide();
return; return;
} }
frappe.dom.freeze();
frappe.call({ frappe.call({
method: "erpnext.accounts.utils.update_number_field", method: "erpnext.accounts.utils.update_cost_center",
args: { args: {
doctype_name: frm.doc.doctype, docname: frm.doc.name,
name: frm.doc.name, cost_center_name: data.cost_center_name,
field_name: d.fields[0].fieldname, cost_center_number: data.cost_center_number,
number_value: data.cost_center_number,
company: frm.doc.company company: frm.doc.company
}, },
callback: function(r) { callback: function(r) {
frappe.dom.unfreeze();
if(!r.exc) { if(!r.exc) {
if(r.message) { if(r.message) {
frappe.set_route("Form", "Cost Center", r.message); frappe.set_route("Form", "Cost Center", r.message);
} else { } else {
me.frm.set_value("cost_center_name", data.cost_center_name);
me.frm.set_value("cost_center_number", data.cost_center_number); me.frm.set_value("cost_center_number", data.cost_center_number);
} }
d.hide(); d.hide();

View File

@ -2,7 +2,6 @@
"actions": [], "actions": [],
"allow_copy": 1, "allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1,
"creation": "2013-01-23 19:57:17", "creation": "2013-01-23 19:57:17",
"description": "Track separate Income and Expense for product verticals or divisions.", "description": "Track separate Income and Expense for product verticals or divisions.",
"doctype": "DocType", "doctype": "DocType",
@ -126,7 +125,7 @@
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-03-18 17:59:04.321637", "modified": "2020-04-29 16:09:30.025214",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cost Center", "name": "Cost Center",

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "ACC-GLE-.YYYY.-.#####", "autoname": "ACC-GLE-.YYYY.-.#####",
"creation": "2013-01-10 16:34:06", "creation": "2013-01-10 16:34:06",
"doctype": "DocType", "doctype": "DocType",
@ -30,7 +31,8 @@
"company", "company",
"finance_book", "finance_book",
"to_rename", "to_rename",
"due_date" "due_date",
"is_cancelled"
], ],
"fields": [ "fields": [
{ {
@ -245,12 +247,18 @@
"fieldname": "due_date", "fieldname": "due_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Due Date" "label": "Due Date"
},
{
"default": "0",
"fieldname": "is_cancelled",
"fieldtype": "Check",
"label": "Is Cancelled"
} }
], ],
"icon": "fa fa-list", "icon": "fa fa-list",
"idx": 1, "idx": 1,
"in_create": 1, "in_create": 1,
"modified": "2020-03-28 16:22:33.766994", "modified": "2020-04-07 16:22:33.766994",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "GL Entry", "name": "GL Entry",

View File

@ -30,23 +30,20 @@ class GLEntry(Document):
self.pl_must_have_cost_center() self.pl_must_have_cost_center()
self.validate_cost_center() self.validate_cost_center()
if not self.flags.from_repost: self.check_pl_account()
self.check_pl_account() self.validate_party()
self.validate_party() self.validate_currency()
self.validate_currency()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
if not from_repost: self.validate_account_details(adv_adj)
self.validate_account_details(adv_adj) self.validate_dimensions_for_pl_and_bs()
self.validate_dimensions_for_pl_and_bs()
check_freezing_date(self.posting_date, adv_adj)
validate_frozen_account(self.account, adv_adj) validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj) validate_balance_type(self.account, adv_adj)
# Update outstanding amt on against voucher # Update outstanding amt on against voucher
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \ if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
and self.against_voucher and update_outstanding == 'Yes' and not from_repost: and self.against_voucher and update_outstanding == 'Yes':
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher) self.against_voucher)
@ -159,7 +156,6 @@ class GLEntry(Document):
if self.party_type and self.party: if self.party_type and self.party:
validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency) validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency)
def validate_and_set_fiscal_year(self): def validate_and_set_fiscal_year(self):
if not self.fiscal_year: if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0] self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
@ -176,19 +172,6 @@ def validate_balance_type(account, adv_adj=False):
(balance_must_be=="Credit" and flt(balance) > 0): (balance_must_be=="Credit" and flt(balance) > 0):
frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be))) frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be)))
def check_freezing_date(posting_date, adv_adj=False):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
"""
if not adv_adj:
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
and not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False): def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False):
if party_type and party: if party_type and party:
party_condition = " and party_type={0} and party={1}"\ party_condition = " and party_type={0} and party={1}"\

View File

@ -12,7 +12,6 @@ frappe.ui.form.on("Journal Entry", {
refresh: function(frm) { refresh: function(frm) {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
frm.cscript.voucher_type(frm.doc);
if(frm.doc.docstatus==1) { if(frm.doc.docstatus==1) {
frm.add_custom_button(__('Ledger'), function() { frm.add_custom_button(__('Ledger'), function() {
@ -120,9 +119,78 @@ frappe.ui.form.on("Journal Entry", {
} }
} }
}); });
},
voucher_type: function(frm){
if(!frm.doc.company) return null;
if((!(frm.doc.accounts || []).length) || ((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)) {
if(in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) {
return frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
args: {
"account_type": (frm.doc.voucher_type=="Bank Entry" ?
"Bank" : (frm.doc.voucher_type=="Cash Entry" ? "Cash" : null)),
"company": frm.doc.company
},
callback: function(r) {
if(r.message) {
// If default company bank account not set
if(!$.isEmptyObject(r.message)){
update_jv_details(frm.doc, [r.message]);
}
}
}
});
}
else if(frm.doc.voucher_type=="Opening Entry") {
return frappe.call({
type:"GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts",
args: {
"company": frm.doc.company
},
callback: function(r) {
frappe.model.clear_table(frm.doc, "accounts");
if(r.message) {
update_jv_details(frm.doc, r.message);
}
cur_frm.set_value("is_opening", "Yes");
}
});
}
}
},
from_template: function(frm){
if (frm.doc.from_template){
frappe.db.get_doc("Journal Entry Template", frm.doc.from_template)
.then((doc) => {
frappe.model.clear_table(frm.doc, "accounts");
frm.set_value({
"company": doc.company,
"voucher_type": doc.voucher_type,
"naming_series": doc.naming_series,
"is_opening": doc.is_opening,
"multi_currency": doc.multi_currency
})
update_jv_details(frm.doc, doc.accounts);
});
}
} }
}); });
var update_jv_details = function(doc, r) {
$.each(r, function(i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
row.account = d.account;
row.balance = d.balance;
});
refresh_field("accounts");
}
erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
onload: function() { onload: function() {
this.load_defaults(); this.load_defaults();
@ -375,56 +443,6 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
cur_frm.pformat.print_heading = __("Journal Entry"); cur_frm.pformat.print_heading = __("Journal Entry");
} }
cur_frm.cscript.voucher_type = function(doc, cdt, cdn) {
cur_frm.set_df_property("cheque_no", "reqd", doc.voucher_type=="Bank Entry");
cur_frm.set_df_property("cheque_date", "reqd", doc.voucher_type=="Bank Entry");
if(!doc.company) return;
var update_jv_details = function(doc, r) {
$.each(r, function(i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
row.account = d.account;
row.balance = d.balance;
});
refresh_field("accounts");
}
if((!(doc.accounts || []).length) || ((doc.accounts || []).length==1 && !doc.accounts[0].account)) {
if(in_list(["Bank Entry", "Cash Entry"], doc.voucher_type)) {
return frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
args: {
"account_type": (doc.voucher_type=="Bank Entry" ?
"Bank" : (doc.voucher_type=="Cash Entry" ? "Cash" : null)),
"company": doc.company
},
callback: function(r) {
if(r.message) {
update_jv_details(doc, [r.message]);
}
}
})
} else if(doc.voucher_type=="Opening Entry") {
return frappe.call({
type:"GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts",
args: {
"company": doc.company
},
callback: function(r) {
frappe.model.clear_table(doc, "accounts");
if(r.message) {
update_jv_details(doc, r.message);
}
cur_frm.set_value("is_opening", "Yes")
}
})
}
}
}
frappe.ui.form.on("Journal Entry Account", { frappe.ui.form.on("Journal Entry Account", {
party: function(frm, cdt, cdn) { party: function(frm, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn); var d = frappe.get_doc(cdt, cdn);

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-03-25 10:53:52", "creation": "2013-03-25 10:53:52",
@ -10,10 +11,11 @@
"title", "title",
"voucher_type", "voucher_type",
"naming_series", "naming_series",
"column_break1",
"posting_date",
"company",
"finance_book", "finance_book",
"column_break1",
"from_template",
"company",
"posting_date",
"2_add_edit_gl_entries", "2_add_edit_gl_entries",
"accounts", "accounts",
"section_break99", "section_break99",
@ -157,6 +159,7 @@
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Reference Number", "label": "Reference Number",
"mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "cheque_no", "oldfieldname": "cheque_no",
"oldfieldtype": "Data", "oldfieldtype": "Data",
@ -166,6 +169,7 @@
"fieldname": "cheque_date", "fieldname": "cheque_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Reference Date", "label": "Reference Date",
"mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "cheque_date", "oldfieldname": "cheque_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
@ -484,12 +488,22 @@
"options": "Journal Entry", "options": "Journal Entry",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "from_template",
"fieldtype": "Link",
"label": "From Template",
"no_copy": 1,
"options": "Journal Entry Template",
"print_hide": 1,
"report_hide": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 176, "idx": 176,
"is_submittable": 1, "is_submittable": 1,
"modified": "2020-01-16 13:05:30.634226", "links": [],
"modified": "2020-04-29 10:55:28.240916",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry", "name": "Journal Entry",

View File

@ -57,6 +57,7 @@ class JournalEntry(AccountsController):
from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip
unlink_ref_doc_from_payment_entries(self) unlink_ref_doc_from_payment_entries(self)
unlink_ref_doc_from_salary_slip(self.name) unlink_ref_doc_from_salary_slip(self.name)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.make_gl_entries(1) self.make_gl_entries(1)
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()
@ -559,20 +560,20 @@ class JournalEntry(AccountsController):
if self.write_off_based_on == 'Accounts Receivable': if self.write_off_based_on == 'Accounts Receivable':
jd1.party_type = "Customer" jd1.party_type = "Customer"
jd1.credit = flt(d.outstanding_amount, self.precision("credit", "accounts")) jd1.credit_in_account_currency = flt(d.outstanding_amount, self.precision("credit", "accounts"))
jd1.reference_type = "Sales Invoice" jd1.reference_type = "Sales Invoice"
jd1.reference_name = cstr(d.name) jd1.reference_name = cstr(d.name)
elif self.write_off_based_on == 'Accounts Payable': elif self.write_off_based_on == 'Accounts Payable':
jd1.party_type = "Supplier" jd1.party_type = "Supplier"
jd1.debit = flt(d.outstanding_amount, self.precision("debit", "accounts")) jd1.debit_in_account_currency = flt(d.outstanding_amount, self.precision("debit", "accounts"))
jd1.reference_type = "Purchase Invoice" jd1.reference_type = "Purchase Invoice"
jd1.reference_name = cstr(d.name) jd1.reference_name = cstr(d.name)
jd2 = self.append('accounts', {}) jd2 = self.append('accounts', {})
if self.write_off_based_on == 'Accounts Receivable': if self.write_off_based_on == 'Accounts Receivable':
jd2.debit = total jd2.debit_in_account_currency = total
elif self.write_off_based_on == 'Accounts Payable': elif self.write_off_based_on == 'Accounts Payable':
jd2.credit = total jd2.credit_in_account_currency = total
self.validate_total_debit_and_credit() self.validate_total_debit_and_credit()
@ -594,7 +595,7 @@ class JournalEntry(AccountsController):
for d in self.accounts: for d in self.accounts:
if d.reference_type=="Expense Claim" and d.reference_name: if d.reference_type=="Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name) doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc) update_reimbursed_amount(doc, jv=self.name)
def validate_expense_claim(self): def validate_expense_claim(self):

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-02-22 01:27:39", "creation": "2013-02-22 01:27:39",
"doctype": "DocType", "doctype": "DocType",
@ -271,7 +272,8 @@
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2020-01-13 12:41:33.968025", "links": [],
"modified": "2020-04-25 01:47:49.060128",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry Account", "name": "Journal Entry Account",
@ -280,4 +282,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1 "track_changes": 1
} }

View File

@ -0,0 +1,91 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Journal Entry Template", {
setup: function(frm) {
frappe.model.set_default_values(frm.doc);
frm.set_query("account" ,"accounts", function(){
var filters = {
company: frm.doc.company,
is_group: 0
};
if(!frm.doc.multi_currency) {
$.extend(filters, {
account_currency: frappe.get_doc(":Company", frm.doc.company).default_currency
});
}
return { filters: filters };
});
frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series",
callback: function(r){
if(r.message){
frm.set_df_property("naming_series", "options", r.message.split("\n"));
frm.set_value("naming_series", r.message.split("\n")[0]);
frm.refresh_field("naming_series");
}
}
});
},
voucher_type: function(frm) {
var add_accounts = function(doc, r) {
$.each(r, function(i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Template Account", "accounts");
row.account = d.account;
});
refresh_field("accounts");
};
if(!frm.doc.company) return;
frm.trigger("clear_child");
switch(frm.doc.voucher_type){
case "Opening Entry":
frm.set_value("is_opening", "Yes");
frappe.call({
type:"GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts",
args: {
"company": frm.doc.company
},
callback: function(r) {
if(r.message) {
add_accounts(frm.doc, r.message);
}
}
});
break;
case "Bank Entry":
case "Cash Entry":
frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
args: {
"account_type": (frm.doc.voucher_type=="Bank Entry" ?
"Bank" : (frm.doc.voucher_type=="Cash Entry" ? "Cash" : null)),
"company": frm.doc.company
},
callback: function(r) {
if(r.message) {
// If default company bank account not set
if(!$.isEmptyObject(r.message)){
add_accounts(frm.doc, [r.message]);
}
}
}
});
break;
default:
frm.trigger("clear_child");
}
},
clear_child: function(frm){
frappe.model.clear_table(frm.doc, "accounts");
frm.refresh_field("accounts");
}
});

View File

@ -0,0 +1,134 @@
{
"actions": [],
"autoname": "field:template_title",
"creation": "2020-04-09 01:32:51.332301",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"section_break_1",
"template_title",
"voucher_type",
"naming_series",
"column_break_3",
"company",
"is_opening",
"multi_currency",
"section_break_3",
"accounts"
],
"fields": [
{
"fieldname": "section_break_1",
"fieldtype": "Section Break"
},
{
"fieldname": "voucher_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Journal Entry Type",
"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",
"reqd": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"options": "Company",
"remember_last_selected_value": 1,
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"default": "No",
"fieldname": "is_opening",
"fieldtype": "Select",
"label": "Is Opening",
"options": "No\nYes"
},
{
"fieldname": "accounts",
"fieldtype": "Table",
"label": "Accounting Entries",
"options": "Journal Entry Template Account"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"no_copy": 1,
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
},
{
"fieldname": "template_title",
"fieldtype": "Data",
"label": "Template Title",
"reqd": 1,
"unique": 1
},
{
"fieldname": "section_break_3",
"fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "multi_currency",
"fieldtype": "Check",
"label": "Multi Currency"
}
],
"links": [],
"modified": "2020-05-01 18:32:01.420488",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Template",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Auditor",
"share": 1
}
],
"search_fields": "voucher_type, company",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "template_title",
"track_changes": 1
}

View File

@ -0,0 +1,14 @@
# -*- 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 JournalEntryTemplate(Document):
pass
@frappe.whitelist()
def get_naming_series():
return frappe.get_meta("Journal Entry").get_field("naming_series").options

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 TestJournalEntryTemplate(unittest.TestCase):
pass

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2020-04-09 01:48:42.783620",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account"
],
"fields": [
{
"fieldname": "account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Account",
"options": "Account",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-04-25 01:15:44.879839",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Template Account",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@ -60,6 +60,7 @@ class PaymentEntry(AccountsController):
self.set_remarks() self.set_remarks()
self.validate_duplicate_entry() self.validate_duplicate_entry()
self.validate_allocated_amount() self.validate_allocated_amount()
self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked() self.ensure_supplier_is_not_blocked()
self.set_status() self.set_status()
@ -75,6 +76,7 @@ class PaymentEntry(AccountsController):
self.set_status() self.set_status()
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.setup_party_account_field() self.setup_party_account_field()
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.update_outstanding_amounts() self.update_outstanding_amounts()
@ -226,6 +228,8 @@ class PaymentEntry(AccountsController):
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry") valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Employee": elif self.party_type == "Employee":
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance") valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
elif self.party_type == "Shareholder":
valid_reference_doctypes = ("Journal Entry")
for d in self.get("references"): for d in self.get("references"):
if not d.allocated_amount: if not d.allocated_amount:
@ -265,6 +269,25 @@ class PaymentEntry(AccountsController):
frappe.throw(_("{0} {1} must be submitted") frappe.throw(_("{0} {1} must be submitted")
.format(d.reference_doctype, d.reference_name)) .format(d.reference_doctype, d.reference_name))
def validate_paid_invoices(self):
no_oustanding_refs = {}
for d in self.get("references"):
if not d.allocated_amount:
continue
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"])
if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
for k, v in no_oustanding_refs.items():
frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
If this is undesirable please cancel the corresponding Payment Entry.")
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
title=_("Warning"), indicator="orange")
def validate_journal_entry(self): def validate_journal_entry(self):
for d in self.get("references"): for d in self.get("references"):
if d.allocated_amount and d.reference_doctype == "Journal Entry": if d.allocated_amount and d.reference_doctype == "Journal Entry":
@ -571,7 +594,7 @@ class PaymentEntry(AccountsController):
for d in self.get("references"): for d in self.get("references"):
if d.reference_doctype=="Expense Claim" and d.reference_name: if d.reference_doctype=="Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name) doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc) update_reimbursed_amount(doc, self.name)
def on_recurring(self, reference_doc, auto_repeat_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name self.reference_no = reference_doc.name

View File

@ -0,0 +1,12 @@
frappe.listview_settings['Payment Entry'] = {
onload: function(listview) {
listview.page.fields_dict.party_type.get_query = function() {
return {
"filters": {
"name": ["in", Object.keys(frappe.boot.party_account_types)],
}
};
};
}
};

View File

@ -35,8 +35,6 @@ class TestPaymentEntry(unittest.TestCase):
pe.cancel() pe.cancel()
self.assertFalse(self.get_gle(pe.name))
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid") so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
self.assertEqual(so_advance_paid, 0) self.assertEqual(so_advance_paid, 0)
@ -124,7 +122,6 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(outstanding_amount, 0) self.assertEqual(outstanding_amount, 0)
pe.cancel() pe.cancel()
self.assertFalse(self.get_gle(pe.name))
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 100) self.assertEqual(outstanding_amount, 100)
@ -381,7 +378,6 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(outstanding_amount, 0) self.assertEqual(outstanding_amount, 0)
pe3.cancel() pe3.cancel()
self.assertFalse(self.get_gle(pe3.name))
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, -100) self.assertEqual(outstanding_amount, -100)

View File

@ -5,7 +5,7 @@ frappe.ui.form.on('Period Closing Voucher', {
onload: function(frm) { onload: function(frm) {
if (!frm.doc.transaction_date) frm.doc.transaction_date = frappe.datetime.obj_to_str(new Date()); if (!frm.doc.transaction_date) frm.doc.transaction_date = frappe.datetime.obj_to_str(new Date());
}, },
setup: function(frm) { setup: function(frm) {
frm.set_query("closing_account_head", function() { frm.set_query("closing_account_head", function() {
return { return {
@ -18,9 +18,9 @@ frappe.ui.form.on('Period Closing Voucher', {
} }
}); });
}, },
refresh: function(frm) { refresh: 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,
@ -33,5 +33,5 @@ frappe.ui.form.on('Period Closing Voucher', {
}, "fa fa-table"); }, "fa fa-table");
} }
} }
}) })

View File

@ -7,6 +7,8 @@ from frappe.utils import flt
from frappe import _ from frappe import _
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
get_dimension_filters)
class PeriodClosingVoucher(AccountsController): class PeriodClosingVoucher(AccountsController):
def validate(self): def validate(self):
@ -17,8 +19,9 @@ class PeriodClosingVoucher(AccountsController):
self.make_gl_entries() self.make_gl_entries()
def on_cancel(self): def on_cancel(self):
frappe.db.sql("""delete from `tabGL Entry` self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
where voucher_type = 'Period Closing Voucher' and voucher_no=%s""", self.name) from erpnext.accounts.general_ledger import make_reverse_gl_entries
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
def validate_account_head(self): def validate_account_head(self):
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type") closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")
@ -49,7 +52,15 @@ class PeriodClosingVoucher(AccountsController):
def make_gl_entries(self): def make_gl_entries(self):
gl_entries = [] gl_entries = []
net_pl_balance = 0 net_pl_balance = 0
pl_accounts = self.get_pl_balances() dimension_fields = ['t1.cost_center']
accounting_dimensions = get_accounting_dimensions()
for dimension in accounting_dimensions:
dimension_fields.append('t1.{0}'.format(dimension))
dimension_filters, default_dimensions = get_dimension_filters()
pl_accounts = self.get_pl_balances(dimension_fields)
for acc in pl_accounts: for acc in pl_accounts:
if flt(acc.balance_in_company_currency): if flt(acc.balance_in_company_currency):
@ -65,34 +76,41 @@ class PeriodClosingVoucher(AccountsController):
if flt(acc.balance_in_account_currency) > 0 else 0, if flt(acc.balance_in_account_currency) > 0 else 0,
"credit": abs(flt(acc.balance_in_company_currency)) \ "credit": abs(flt(acc.balance_in_company_currency)) \
if flt(acc.balance_in_company_currency) > 0 else 0 if flt(acc.balance_in_company_currency) > 0 else 0
})) }, item=acc))
net_pl_balance += flt(acc.balance_in_company_currency) net_pl_balance += flt(acc.balance_in_company_currency)
if net_pl_balance: if net_pl_balance:
cost_center = frappe.db.get_value("Company", self.company, "cost_center") cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entries.append(self.get_gl_dict({ gl_entry = self.get_gl_dict({
"account": self.closing_account_head, "account": self.closing_account_head,
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0, "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0, "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0, "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
"cost_center": cost_center "cost_center": cost_center
})) })
for dimension in accounting_dimensions:
gl_entry.update({
dimension: default_dimensions.get(self.company, {}).get(dimension)
})
gl_entries.append(gl_entry)
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries) make_gl_entries(gl_entries)
def get_pl_balances(self): def get_pl_balances(self, dimension_fields):
"""Get balance for pl accounts""" """Get balance for pl accounts"""
return frappe.db.sql(""" return frappe.db.sql("""
select select
t1.account, t1.cost_center, t2.account_currency, t1.account, t2.account_currency, {dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency, sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2 from `tabGL Entry` t1, `tabAccount` t2
where t1.account = t2.name and t2.report_type = 'Profit and Loss' where t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = %s and t2.docstatus < 2 and t2.company = %s
and t1.posting_date between %s and %s and t1.posting_date between %s and %s
group by t1.account, t1.cost_center group by t1.account, {dimension_fields}
""", (self.company, self.get("year_start_date"), self.posting_date), as_dict=1) """.format(dimension_fields = ', '.join(dimension_fields)), (self.company, self.get("year_start_date"), self.posting_date), as_dict=1)

View File

@ -1,123 +1,39 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "creation": "2017-10-27 16:46:06.060930",
"allow_import": 0, "doctype": "DocType",
"allow_rename": 0, "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2017-10-27 16:46:06.060930", "field_order": [
"custom": 0, "default",
"docstatus": 0, "user"
"doctype": "DocType", ],
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_on_submit": 0, "fieldname": "default",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Default"
"fieldname": "default", },
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default",
"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": "user",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "User",
"columns": 0, "options": "User"
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-05-01 09:46:47.599173",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Accounts",
"in_create": 0, "name": "POS Profile User",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 0, "quick_entry": 1,
"max_attachments": 0, "sort_field": "modified",
"modified": "2017-11-23 17:13:16.005475", "sort_order": "DESC",
"modified_by": "Administrator", "track_changes": 1
"module": "Accounts",
"name": "POS Profile User",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
} }

View File

@ -0,0 +1,39 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Process Deferred Accounting', {
setup: function(frm) {
frm.set_query("document_type", function() {
return {
filters: {
'name': ['in', ['Sales Invoice', 'Purchase Invoice']]
}
};
});
},
validate: function() {
return new Promise((resolve) => {
return frappe.db.get_single_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')
.then(value => {
if(value) {
frappe.throw(__('Manual entry cannot be created! Disable automatic entry for deferred accounting in accounts settings and try again'));
}
resolve(value);
});
});
},
end_date: function(frm) {
if (frm.doc.end_date && frm.doc.end_date < frm.doc.start_date) {
frappe.throw(__("End date cannot be before start date"));
}
},
onload: function(frm) {
if (frm.doc.posting_date && frm.doc.docstatus === 0) {
frm.set_value('start_date', frappe.datetime.add_months(frm.doc.posting_date, -1));
frm.set_value('end_date', frm.doc.posting_date);
}
}
});

View File

@ -0,0 +1,128 @@
{
"actions": [],
"autoname": "ACC-PDA-.#####",
"creation": "2019-11-04 18:01:23.454775",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"type",
"account",
"column_break_3",
"posting_date",
"start_date",
"end_date",
"amended_from"
],
"fields": [
{
"fieldname": "type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"options": "\nIncome\nExpense",
"reqd": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Process Deferred Accounting",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Service Start Date",
"reqd": 1
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Service End Date",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Posting Date",
"reqd": 1
},
{
"fieldname": "account",
"fieldtype": "Link",
"label": "Account",
"options": "Account"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-02-06 18:18:09.852844",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Deferred Accounting",
"owner": "Administrator",
"permissions": [
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import erpnext
from frappe import _
from frappe.model.document import Document
from erpnext.accounts.general_ledger import make_reverse_gl_entries
from erpnext.accounts.deferred_revenue import convert_deferred_expense_to_expense, \
convert_deferred_revenue_to_income, build_conditions
class ProcessDeferredAccounting(Document):
def validate(self):
if self.end_date < self.start_date:
frappe.throw(_("End date cannot be before start date"))
def on_submit(self):
conditions = build_conditions(self.type, self.account, self.company)
if self.type == 'Income':
convert_deferred_revenue_to_income(self.name, self.start_date, self.end_date, conditions)
else:
convert_deferred_expense_to_expense(self.name, self.start_date, self.end_date, conditions)
def on_cancel(self):
self.ignore_linked_doctypes = ['GL Entry']
gl_entries = frappe.get_all('GL Entry', fields = ['*'],
filters={
'against_voucher_type': self.doctype,
'against_voucher': self.name
})
make_reverse_gl_entries(gl_entries=gl_entries)

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice, check_gl_entries
class TestProcessDeferredAccounting(unittest.TestCase):
def test_creation_of_ledger_entry_on_submit(self):
''' test creation of gl entries on submission of document '''
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_revenue = 1
item.deferred_revenue_account = deferred_account
item.no_of_months = 12
item.save()
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
si.items[0].deferred_revenue_account = deferred_account
si.save()
si.submit()
process_deferred_accounting = doc = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date="2019-01-01",
start_date="2019-01-01",
end_date="2019-01-31",
type="Income"
))
process_deferred_accounting.insert()
process_deferred_accounting.submit()
expected_gle = [
[deferred_account, 33.85, 0.0, "2019-01-31"],
["Sales - _TC", 0.0, 33.85, "2019-01-31"]
]
check_gl_entries(self, si.name, expected_gle, "2019-01-10")

View File

@ -382,11 +382,6 @@ function hide_fields(doc) {
cur_frm.refresh_fields(); cur_frm.refresh_fields();
} }
cur_frm.cscript.update_stock = function(doc, dt, dn) {
hide_fields(doc, dt, dn);
this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false)
}
cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { cur_frm.fields_dict.cash_bank_account.get_query = function(doc) {
return { return {
filters: [ filters: [
@ -528,5 +523,10 @@ frappe.ui.form.on("Purchase Invoice", {
erpnext.buying.get_default_bom(frm); erpnext.buying.get_default_bom(frm);
} }
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
},
update_stock: function(frm) {
hide_fields(frm.doc);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
} }
}) })

View File

@ -14,7 +14,7 @@ from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po
from erpnext.stock import get_warehouse_account_map from erpnext.stock import get_warehouse_account_map
from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, make_reverse_gl_entries
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
@ -382,7 +382,7 @@ class PurchaseInvoice(BuyingController):
self.update_project() self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): def make_gl_entries(self, gl_entries=None):
if not self.grand_total: if not self.grand_total:
return return
if not gl_entries: if not gl_entries:
@ -391,21 +391,17 @@ class PurchaseInvoice(BuyingController):
if gl_entries: if gl_entries:
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), if self.docstatus == 1:
update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No": if update_outstanding == "No":
update_outstanding_amt(self.credit_to, "Supplier", self.supplier, update_outstanding_amt(self.credit_to, "Supplier", self.supplier,
self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) and self.auto_accounting_for_stock:
from erpnext.controllers.stock_controller import update_gl_entries_after
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time,
warehouses, items, company = self.company)
elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock: elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock:
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
@ -934,6 +930,7 @@ class PurchaseInvoice(BuyingController):
frappe.db.set(self, 'status', 'Cancelled') frappe.db.set(self, 'status', 'Cancelled')
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
def update_project(self): def update_project(self):
project_list = [] project_list = []

View File

@ -138,13 +138,12 @@
"row_id": 7 "row_id": 7
} }
], ],
"posting_date": "2013-02-03",
"supplier": "_Test Supplier", "supplier": "_Test Supplier",
"supplier_name": "_Test Supplier" "supplier_name": "_Test Supplier"
}, },
{ {
"bill_no": "NA", "bill_no": "NA",
"buying_price_list": "_Test Price List", "buying_price_list": "_Test Price List",
@ -204,7 +203,6 @@
"tax_amount": 150.0 "tax_amount": 150.0
} }
], ],
"posting_date": "2013-02-03",
"supplier": "_Test Supplier", "supplier": "_Test Supplier",
"supplier_name": "_Test Supplier" "supplier_name": "_Test Supplier"
} }

View File

@ -345,7 +345,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
set_dynamic_labels: function() { set_dynamic_labels: function() {
this._super(); this._super();
this.hide_fields(this.frm.doc); this.frm.events.hide_fields(this.frm)
}, },
items_on_form_rendered: function() { items_on_form_rendered: function() {
@ -404,7 +404,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
if(r.message && r.message.print_format) { if(r.message && r.message.print_format) {
me.frm.pos_print_format = r.message.print_format; me.frm.pos_print_format = r.message.print_format;
} }
me.frm.script_manager.trigger("update_stock"); me.frm.trigger("update_stock");
if(me.frm.doc.taxes_and_charges) { if(me.frm.doc.taxes_and_charges) {
me.frm.script_manager.trigger("taxes_and_charges"); me.frm.script_manager.trigger("taxes_and_charges");
} }
@ -446,35 +446,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
// for backward compatibility: combine new and previous states // for backward compatibility: combine new and previous states
$.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm})); $.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm}));
// Hide Fields
// ------------
cur_frm.cscript.hide_fields = function(doc) {
var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances',
'advances', 'from_date', 'to_date'];
if(cint(doc.is_pos) == 1) {
hide_field(parent_fields);
} else {
for (var i in parent_fields) {
var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]];
if(!docfield.hidden) unhide_field(parent_fields[i]);
}
}
// India related fields
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
this.frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
cur_frm.refresh_fields();
}
cur_frm.cscript.update_stock = function(doc, dt, dn) {
cur_frm.cscript.hide_fields(doc, dt, dn);
this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false)
}
cur_frm.cscript['Make Delivery Note'] = function() { cur_frm.cscript['Make Delivery Note'] = function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_delivery_note", method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_delivery_note",
@ -719,6 +690,12 @@ frappe.ui.form.on('Sales Invoice', {
frm.redemption_conversion_factor = null; frm.redemption_conversion_factor = null;
}, },
update_stock: function(frm, dt, dn) {
frm.events.hide_fields(frm);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock);
frm.trigger('reset_posting_time');
},
redeem_loyalty_points: function(frm) { redeem_loyalty_points: function(frm) {
frm.events.get_loyalty_details(frm); frm.events.get_loyalty_details(frm);
}, },
@ -742,6 +719,29 @@ frappe.ui.form.on('Sales Invoice', {
} }
}, },
hide_fields: function(frm) {
let doc = frm.doc;
var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances',
'advances', 'from_date', 'to_date'];
if(cint(doc.is_pos) == 1) {
hide_field(parent_fields);
} else {
for (var i in parent_fields) {
var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]];
if(!docfield.hidden) unhide_field(parent_fields[i]);
}
}
// India related fields
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
frm.refresh_fields();
},
get_loyalty_details: function(frm) { get_loyalty_details: function(frm) {
if (frm.doc.customer && frm.doc.redeem_loyalty_points) { if (frm.doc.customer && frm.doc.redeem_loyalty_points) {
frappe.call({ frappe.call({

View File

@ -149,9 +149,9 @@
"edit_printing_settings", "edit_printing_settings",
"letter_head", "letter_head",
"group_same_items", "group_same_items",
"language",
"column_break_84",
"select_print_heading", "select_print_heading",
"column_break_84",
"language",
"more_information", "more_information",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"is_internal_customer", "is_internal_customer",
@ -1579,7 +1579,7 @@
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-17 12:38:41.435728", "modified": "2020-04-29 13:37:09.355300",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -7,7 +7,6 @@ import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option
@ -282,6 +281,8 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains: if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_cancel") manage_invoice_submit_cancel(self, "on_cancel")
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
def update_status_updater_args(self): def update_status_updater_args(self):
if cint(self.update_stock): if cint(self.update_stock):
self.status_updater.append({ self.status_updater.append({
@ -717,7 +718,9 @@ class SalesInvoice(SellingController):
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1: if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): def make_gl_entries(self, gl_entries=None):
from erpnext.accounts.general_ledger import make_reverse_gl_entries
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if not gl_entries: if not gl_entries:
gl_entries = self.get_gl_entries() gl_entries = self.get_gl_entries()
@ -729,23 +732,19 @@ class SalesInvoice(SellingController):
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
cint(self.redeem_loyalty_points)) else "Yes" cint(self.redeem_loyalty_points)) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), if self.docstatus == 1:
update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No": if update_outstanding == "No":
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
update_outstanding_amt(self.debit_to, "Customer", self.customer, update_outstanding_amt(self.debit_to, "Customer", self.customer,
self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) \
and cint(auto_accounting_for_stock):
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time,
warehouses, items, company = self.company)
elif self.docstatus == 2 and cint(self.update_stock) \ elif self.docstatus == 2 and cint(self.update_stock) \
and cint(auto_accounting_for_stock): and cint(auto_accounting_for_stock):
from erpnext.accounts.general_ledger import delete_gl_entries make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import merge_similar_entries from erpnext.accounts.general_ledger import merge_similar_entries

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest, copy, time import unittest, copy, time
from frappe.utils import nowdate, flt, getdate, cint, add_days from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months
from frappe.model.dynamic_links import get_dynamic_link_map from frappe.model.dynamic_links import get_dynamic_link_map
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
@ -364,7 +364,7 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertTrue(gle)
def test_tax_calculation_with_multiple_items(self): def test_tax_calculation_with_multiple_items(self):
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True) si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
@ -678,14 +678,15 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertTrue(gle)
def test_pos_gl_entry_with_perpetual_inventory(self): def test_pos_gl_entry_with_perpetual_inventory(self):
make_pos_profile() make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1 pos.is_pos = 1
pos.update_stock = 1 pos.update_stock = 1
@ -766,9 +767,13 @@ class TestSalesInvoice(unittest.TestCase):
def test_pos_change_amount(self): def test_pos_change_amount(self):
make_pos_profile() make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1 pos.is_pos = 1
pos.update_stock = 1 pos.update_stock = 1
@ -787,8 +792,15 @@ class TestSalesInvoice(unittest.TestCase):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
pos_profile = make_pos_profile() pos_profile = make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",
warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1 pos.is_pos = 1
pos.update_stock = 1 pos.update_stock = 1
@ -891,11 +903,9 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertTrue(gle)
frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Profile`")
si.delete()
def test_pos_si_without_payment(self): def test_pos_si_without_payment(self):
set_perpetual_inventory() set_perpetual_inventory()
@ -1012,9 +1022,6 @@ class TestSalesInvoice(unittest.TestCase):
si.cancel() si.cancel()
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_name=%s""", si.name))
def test_serialized(self): def test_serialized(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -1230,7 +1237,7 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select name from `tabGL Entry` gle = frappe.db.sql("""select name from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertTrue(gle)
def test_invalid_currency(self): def test_invalid_currency(self):
# Customer currency = USD # Customer currency = USD
@ -1714,37 +1721,76 @@ class TestSalesInvoice(unittest.TestCase):
si.submit() si.submit()
from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-01-31")
pda1 = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date=nowdate(),
start_date="2019-01-01",
end_date="2019-03-31",
type="Income",
company="_Test Company"
))
pda1.insert()
pda1.submit()
expected_gle = [ expected_gle = [
[deferred_account, 33.85, 0.0, "2019-01-31"], [deferred_account, 33.85, 0.0, "2019-01-31"],
["Sales - _TC", 0.0, 33.85, "2019-01-31"] ["Sales - _TC", 0.0, 33.85, "2019-01-31"],
]
self.check_gl_entries(si.name, expected_gle, "2019-01-10")
convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-03-31")
expected_gle = [
[deferred_account, 43.08, 0.0, "2019-02-28"], [deferred_account, 43.08, 0.0, "2019-02-28"],
["Sales - _TC", 0.0, 43.08, "2019-02-28"], ["Sales - _TC", 0.0, 43.08, "2019-02-28"],
[deferred_account, 23.07, 0.0, "2019-03-15"], [deferred_account, 23.07, 0.0, "2019-03-15"],
["Sales - _TC", 0.0, 23.07, "2019-03-15"] ["Sales - _TC", 0.0, 23.07, "2019-03-15"]
] ]
self.check_gl_entries(si.name, expected_gle, "2019-01-31") check_gl_entries(self, si.name, expected_gle, "2019-01-30")
def check_gl_entries(self, voucher_no, expected_gle, posting_date): def test_deferred_error_email(self):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date deferred_account = create_account(account_name="Deferred Revenue",
from `tabGL Entry` parent_account="Current Liabilities - _TC", company="_Test Company")
where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s
order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
for i, gle in enumerate(gl_entries): item = create_item("_Test Item for Deferred Accounting")
self.assertEqual(expected_gle[i][0], gle.account) item.enable_deferred_revenue = 1
self.assertEqual(expected_gle[i][1], gle.debit) item.deferred_revenue_account = deferred_account
self.assertEqual(expected_gle[i][2], gle.credit) item.no_of_months = 12
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) item.save()
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
si.items[0].deferred_revenue_account = deferred_account
si.save()
si.submit()
from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
acc_settings.acc_frozen_upto = '2019-01-31'
acc_settings.save()
pda = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date=nowdate(),
start_date="2019-01-01",
end_date="2019-03-31",
type="Income",
company="_Test Company"
))
pda.insert()
pda.submit()
email = frappe.db.sql(""" select name from `tabEmail Queue`
where message like %(txt)s """, {
'txt': "%%%s%%" % "Error while processing deferred accounting for {0}".format(pda.name)
})
self.assertTrue(email)
acc_settings.load_from_db()
acc_settings.acc_frozen_upto = None
acc_settings.save()
def test_inter_company_transaction(self): def test_inter_company_transaction(self):
@ -1905,6 +1951,18 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s
order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
doc.assertEqual(expected_gle[i][1], gle.debit)
doc.assertEqual(expected_gle[i][2], gle.credit)
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
def test_item_tax_validity(self): def test_item_tax_validity(self):
item = frappe.get_doc("Item", "_Test Item 2") item = frappe.get_doc("Item", "_Test Item 2")

View File

@ -168,18 +168,20 @@ class ShareTransfer(Document):
return 'Outside' return 'Outside'
def folio_no_validation(self): def folio_no_validation(self):
shareholders = ['from_shareholder', 'to_shareholder'] shareholder_fields = ['from_shareholder', 'to_shareholder']
shareholders = [shareholder for shareholder in shareholders if self.get(shareholder) is not ''] for shareholder_field in shareholder_fields:
for shareholder in shareholders: shareholder_name = self.get(shareholder_field)
doc = self.get_shareholder_doc(self.get(shareholder)) if not shareholder_name:
continue
doc = self.get_shareholder_doc(shareholder_name)
if doc.company != self.company: if doc.company != self.company:
frappe.throw(_('The shareholder does not belong to this company')) frappe.throw(_('The shareholder does not belong to this company'))
if not doc.folio_no: if not doc.folio_no:
doc.folio_no = self.from_folio_no \ doc.folio_no = self.from_folio_no \
if (shareholder == 'from_shareholder') else self.to_folio_no if (shareholder_field == 'from_shareholder') else self.to_folio_no
doc.save() doc.save()
else: else:
if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no): if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder_field == 'from_shareholder') else self.to_folio_no):
frappe.throw(_('The folio numbers are not matching')) frappe.throw(_('The folio numbers are not matching'))
def autoname_folio(self, shareholder, is_company=False): def autoname_folio(self, shareholder, is_company=False):

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe.utils import flt, cstr, cint, comma_and from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now
from frappe import _ from frappe import _
from erpnext.accounts.utils import get_stock_and_account_balance from erpnext.accounts.utils import get_stock_and_account_balance
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
@ -15,17 +15,17 @@ class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass class StockAccountInvalidTransaction(frappe.ValidationError): pass
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False): def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
if gl_map: if gl_map:
if not cancel: if not cancel:
validate_accounting_period(gl_map) validate_accounting_period(gl_map)
gl_map = process_gl_map(gl_map, merge_entries) gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1: if gl_map and len(gl_map) > 1:
save_entries(gl_map, adv_adj, update_outstanding, from_repost) save_entries(gl_map, adv_adj, update_outstanding)
else: else:
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction.")) frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
else: else:
delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
def validate_accounting_period(gl_map): def validate_accounting_period(gl_map):
accounting_periods = frappe.db.sql(""" SELECT accounting_periods = frappe.db.sql(""" SELECT
@ -119,33 +119,36 @@ def check_if_in_list(gle, gl_map, dimensions=None):
if same_head: if same_head:
return e return e
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): def save_entries(gl_map, adv_adj, update_outstanding):
if not from_repost: validate_cwip_accounts(gl_map)
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map) round_off_debit_credit(gl_map)
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
for entry in gl_map: for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding, from_repost) make_entry(entry, adv_adj, update_outstanding)
# check against budget # check against budget
if not from_repost: validate_expense_against_budget(entry)
validate_expense_against_budget(entry)
if not from_repost: validate_account_for_perpetual_inventory(gl_map)
validate_account_for_perpetual_inventory(gl_map)
def make_entry(args, adv_adj, update_outstanding, from_repost=False): def make_entry(args, adv_adj, update_outstanding):
gle = frappe.new_doc("GL Entry") gle = frappe.new_doc("GL Entry")
gle.update(args) gle.update(args)
gle.flags.ignore_permissions = 1 gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost
gle.validate() gle.validate()
gle.db_insert() gle.db_insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) gle.run_method("on_update_with_args", adv_adj, update_outstanding)
gle.flags.ignore_validate = True gle.flags.ignore_validate = True
gle.submit() gle.submit()
# check against budget
validate_expense_against_budget(args)
def validate_account_for_perpetual_inventory(gl_map): def validate_account_for_perpetual_inventory(gl_map):
if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)): if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
account_list = [gl_entries.account for gl_entries in gl_map] account_list = [gl_entries.account for gl_entries in gl_map]
@ -169,33 +172,33 @@ def validate_account_for_perpetual_inventory(gl_map):
.format(account), StockAccountInvalidTransaction) .format(account), StockAccountInvalidTransaction)
# This has been comment for a temporary, will add this code again on release of immutable ledger # This has been comment for a temporary, will add this code again on release of immutable ledger
# elif account_bal != stock_bal: elif account_bal != stock_bal:
# precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
# currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
# diff = flt(stock_bal - account_bal, precision) diff = flt(stock_bal - account_bal, precision)
# error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
# stock_bal, account_bal, frappe.bold(account)) stock_bal, account_bal, frappe.bold(account))
# error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
# stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
# db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
# db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
# journal_entry_args = { journal_entry_args = {
# 'accounts':[ 'accounts':[
# {'account': account, db_or_cr_warehouse_account : abs(diff)}, {'account': account, db_or_cr_warehouse_account : abs(diff)},
# {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
# } }
# frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution), frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
# raise_exception=StockValueAndAccountBalanceOutOfSync, raise_exception=StockValueAndAccountBalanceOutOfSync,
# title=_('Values Out Of Sync'), title=_('Values Out Of Sync'),
# primary_action={ primary_action={
# 'label': _('Make Journal Entry'), 'label': _('Make Journal Entry'),
# 'client_action': 'erpnext.route_to_adjustment_jv', 'client_action': 'erpnext.route_to_adjustment_jv',
# 'args': journal_entry_args 'args': journal_entry_args
# }) })
def validate_cwip_accounts(gl_map): def validate_cwip_accounts(gl_map):
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
@ -282,31 +285,64 @@ def get_round_off_account_and_cost_center(company):
return round_off_account, round_off_cost_center return round_off_account, round_off_cost_center
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
adv_adj=False, update_outstanding="Yes"): adv_adj=False, update_outstanding="Yes"):
"""
from erpnext.accounts.doctype.gl_entry.gl_entry import validate_balance_type, \ Get original gl entries of the voucher
check_freezing_date, update_outstanding_amt, validate_frozen_account and make reverse gl entries by swapping debit and credit
"""
if not gl_entries: if not gl_entries:
gl_entries = frappe.db.sql(""" gl_entries = frappe.get_all("GL Entry",
select account, posting_date, party_type, party, cost_center, fiscal_year,voucher_type, fields = ["*"],
voucher_no, against_voucher_type, against_voucher, cost_center, company filters = {
from `tabGL Entry` "voucher_type": voucher_type,
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), as_dict=True) "voucher_no": voucher_no
})
if gl_entries: if gl_entries:
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
check_freezing_date(gl_entries[0]["posting_date"], adv_adj) check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", for entry in gl_entries:
(voucher_type or gl_entries[0]["voucher_type"], voucher_no or gl_entries[0]["voucher_no"])) entry['name'] = None
debit = entry.get('debit', 0)
credit = entry.get('credit', 0)
for entry in gl_entries: debit_in_account_currency = entry.get('debit_in_account_currency', 0)
validate_frozen_account(entry["account"], adv_adj) credit_in_account_currency = entry.get('credit_in_account_currency', 0)
validate_balance_type(entry["account"], adv_adj)
if not adv_adj:
validate_expense_against_budget(entry)
if entry.get("against_voucher") and update_outstanding == 'Yes' and not adv_adj: entry['debit'] = credit
update_outstanding_amt(entry["account"], entry.get("party_type"), entry.get("party"), entry.get("against_voucher_type"), entry['credit'] = debit
entry.get("against_voucher"), on_cancel=True) entry['debit_in_account_currency'] = credit_in_account_currency
entry['credit_in_account_currency'] = debit_in_account_currency
entry['remarks'] = "On cancellation of " + entry['voucher_no']
entry['is_cancelled'] = 1
entry['posting_date'] = today()
if entry['debit'] or entry['credit']:
make_entry(entry, adv_adj, "Yes")
def check_freezing_date(posting_date, adv_adj=False):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
"""
if not adv_adj:
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
and not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
def set_as_cancel(voucher_type, voucher_no):
"""
Set is_cancelled=1 in all original gl entries for the voucher
"""
frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1,
modified=%s, modified_by=%s
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no))

View File

@ -162,7 +162,7 @@ def get_default_price_list(party):
def set_price_list(party_details, party, party_type, given_price_list, pos=None): def set_price_list(party_details, party, party_type, given_price_list, pos=None):
# price list # price list
price_list = get_permitted_documents('Price List') price_list = get_permitted_documents('Price List')
# if there is only one permitted document based on user permissions, set it # if there is only one permitted document based on user permissions, set it
if price_list and len(price_list) == 1: if price_list and len(price_list) == 1:
price_list = price_list[0] price_list = price_list[0]
@ -465,23 +465,25 @@ def get_timeline_data(doctype, name):
from frappe.desk.form.load import get_communication_data from frappe.desk.form.load import get_communication_data
out = {} out = {}
fields = 'date(creation), count(name)' fields = 'creation, count(*)'
after = add_years(None, -1).strftime('%Y-%m-%d') after = add_years(None, -1).strftime('%Y-%m-%d')
group_by='group by date(creation)' group_by='group by Date(creation)'
data = get_communication_data(doctype, name, after=after, group_by='group by date(creation)', data = get_communication_data(doctype, name, after=after, group_by='group by creation',
fields='date(C.creation) as creation, count(C.name)',as_dict=False) fields='C.creation as creation, count(C.name)',as_dict=False)
# fetch and append data from Activity Log # fetch and append data from Activity Log
data += frappe.db.sql("""select {fields} data += frappe.db.sql("""select {fields}
from `tabActivity Log` from `tabActivity Log`
where (reference_doctype="{doctype}" and reference_name="{name}") where (reference_doctype=%(doctype)s and reference_name=%(name)s)
or (timeline_doctype in ("{doctype}") and timeline_name="{name}") or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s)
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}") or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s)
and status!='Success' and creation > {after} and status!='Success' and creation > {after}
{group_by} order by creation desc {group_by} order by creation desc
""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields, """.format(fields=fields, group_by=group_by, after=after), {
group_by=group_by, after=after), as_dict=False) "doctype": doctype,
"name": name
}, as_dict=False)
timeline_items = dict(data) timeline_items = dict(data)
@ -600,10 +602,12 @@ def get_party_shipping_address(doctype, name):
else: else:
return '' return ''
def get_partywise_advanced_payment_amount(party_type, posting_date = None): def get_partywise_advanced_payment_amount(party_type, posting_date = None, company=None):
cond = "1=1" cond = "1=1"
if posting_date: if posting_date:
cond = "posting_date <= '{0}'".format(posting_date) cond = "posting_date <= '{0}'".format(posting_date)
if company:
cond += "and company = '{0}'".format(company)
data = frappe.db.sql(""" SELECT party, sum({0}) as amount data = frappe.db.sql(""" SELECT party, sum({0}) as amount
FROM `tabGL Entry` FROM `tabGL Entry`

View File

@ -33,7 +33,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.get_party_total(args) self.get_party_total(args)
party_advance_amount = get_partywise_advanced_payment_amount(self.party_type, party_advance_amount = get_partywise_advanced_payment_amount(self.party_type,
self.filters.report_date) or {} self.filters.report_date, self.filters.company) or {}
for party, party_dict in iteritems(self.party_total): for party, party_dict in iteritems(self.party_total):
if party_dict.outstanding == 0: if party_dict.outstanding == 0:

View File

@ -154,8 +154,12 @@ frappe.query_reports["General Ledger"] = {
{ {
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"), "label": __("Include Default Book Entries"),
"fieldtype": "Check", "fieldtype": "Check"
"default": 1 },
{
"fieldname": "show_cancelled_entries",
"label": __("Show Cancelled Entries"),
"fieldtype": "Check"
} }
] ]
} }

View File

@ -188,6 +188,9 @@ def get_conditions(filters):
else: else:
conditions.append("finance_book in (%(finance_book)s)") conditions.append("finance_book in (%(finance_book)s)")
if not filters.get("show_cancelled_entries"):
conditions.append("is_cancelled = 0")
from frappe.desk.reportview import build_match_conditions from frappe.desk.reportview import build_match_conditions
match_conditions = build_match_conditions("GL Entry") match_conditions = build_match_conditions("GL Entry")
@ -293,6 +296,9 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
if data[key].against_voucher:
data[key].against_voucher += ', ' + gle.against_voucher
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date) from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
for gle in gl_entries: for gle in gl_entries:
if (gle.posting_date < from_date or if (gle.posting_date < from_date or

View File

@ -35,6 +35,12 @@ def execute(filters=None):
}) })
return columns, data return columns, data
# to avoid error eg: gross_income[0] : list index out of range
if not gross_income:
gross_income = [{}]
if not gross_expense:
gross_expense = [{}]
data.append({ data.append({
"account_name": "'" + _("Included in Gross Profit") + "'", "account_name": "'" + _("Included in Gross Profit") + "'",
"account": "'" + _("Included in Gross Profit") + "'" "account": "'" + _("Included in Gross Profit") + "'"

View File

@ -817,48 +817,37 @@ def create_payment_gateway_account(gateway):
pass pass
@frappe.whitelist() @frappe.whitelist()
def update_number_field(doctype_name, name, field_name, number_value, company): def update_cost_center(docname, cost_center_name, cost_center_number, company):
''' '''
doctype_name = Name of the DocType
name = Docname being referred
field_name = Name of the field thats holding the 'number' attribute
number_value = Numeric value entered in field_name
Stores the number entered in the dialog to the DocType's field.
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.
''' '''
doc_title = frappe.db.get_value(doctype_name, name, frappe.scrub(doctype_name)+"_name") validate_field_number("Cost Center", docname, cost_center_number, company, "cost_center_number")
validate_field_number(doctype_name, name, number_value, company, field_name) if cost_center_number:
frappe.db.set_value("Cost Center", docname, "cost_center_number", cost_center_number.strip())
else:
frappe.db.set_value("Cost Center", docname, "cost_center_number", "")
frappe.db.set_value(doctype_name, name, field_name, number_value) frappe.db.set_value("Cost Center", docname, "cost_center_name", cost_center_name.strip())
if doc_title[0].isdigit(): new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company)
separator = " - " if " - " in doc_title else " " if docname != new_name:
doc_title = doc_title.split(separator, 1)[1] frappe.rename_doc("Cost Center", docname, new_name, force=1)
frappe.db.set_value(doctype_name, name, frappe.scrub(doctype_name)+"_name", doc_title)
new_name = get_autoname_with_number(number_value, doc_title, name, company)
if name != new_name:
frappe.rename_doc(doctype_name, name, new_name)
return new_name return new_name
def validate_field_number(doctype_name, name, number_value, company, field_name): def validate_field_number(doctype_name, docname, number_value, company, field_name):
''' Validate if the number entered isn't already assigned to some other document. ''' ''' Validate if the number entered isn't already assigned to some other document. '''
if number_value: if number_value:
filters = {field_name: number_value, "name": ["!=", docname]}
if company: if company:
doctype_with_same_number = frappe.db.get_value(doctype_name, filters["company"] = company
{field_name: number_value, "company": company, "name": ["!=", name]})
else: doctype_with_same_number = frappe.db.get_value(doctype_name, filters)
doctype_with_same_number = frappe.db.get_value(doctype_name,
{field_name: number_value, "name": ["!=", name]})
if doctype_with_same_number: if doctype_with_same_number:
frappe.throw(_("{0} Number {1} already used in account {2}") frappe.throw(_("{0} Number {1} is already used in {2} {3}")
.format(doctype_name, number_value, doctype_with_same_number)) .format(doctype_name, number_value, doctype_name.lower(), doctype_with_same_number))
def get_autoname_with_number(number_value, doc_title, name, company): def get_autoname_with_number(number_value, doc_title, name, company):
''' append title with prefix as number and suffix as company's abbreviation separated by '-' ''' ''' append title with prefix as number and suffix as company's abbreviation separated by '-' '''
@ -897,4 +886,60 @@ def get_stock_accounts(company):
return frappe.get_all("Account", filters = { return frappe.get_all("Account", filters = {
"account_type": "Stock", "account_type": "Stock",
"company": company "company": company
}) })
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
for voucher_type, voucher_no in future_stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
future_stock_vouchers = []
values = []
condition = ""
if for_items:
condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items)))
values += for_items
if for_warehouses:
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
values += for_warehouses
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries = {}
if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries

View File

@ -11,7 +11,7 @@ from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \ from erpnext.assets.doctype.asset.depreciation \
import get_disposal_account_and_cost_center, get_depreciation_accounts import get_disposal_account_and_cost_center, get_depreciation_accounts
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@ -22,6 +22,7 @@ class Asset(AccountsController):
self.validate_item() self.validate_item()
self.set_missing_values() self.set_missing_values()
self.prepare_depreciation_data() self.prepare_depreciation_data()
self.validate_gross_and_purchase_amount()
if self.get("schedules"): if self.get("schedules"):
self.validate_expected_value_after_useful_life() self.validate_expected_value_after_useful_life()
@ -31,7 +32,7 @@ class Asset(AccountsController):
self.validate_in_use_date() self.validate_in_use_date()
self.set_status() self.set_status()
self.make_asset_movement() self.make_asset_movement()
if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category): if not self.booked_fixed_asset and self.validate_make_gl_entry():
self.make_gl_entries() self.make_gl_entries()
def before_cancel(self): def before_cancel(self):
@ -41,7 +42,8 @@ class Asset(AccountsController):
self.validate_cancellation() self.validate_cancellation()
self.delete_depreciation_entries() self.delete_depreciation_entries()
self.set_status() self.set_status()
delete_gl_entries(voucher_type='Asset', voucher_no=self.name) self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
make_reverse_gl_entries(voucher_type='Asset', voucher_no=self.name)
self.db_set('booked_fixed_asset', 0) self.db_set('booked_fixed_asset', 0)
def validate_asset_and_reference(self): def validate_asset_and_reference(self):
@ -123,6 +125,12 @@ class Asset(AccountsController):
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
frappe.throw(_("Available-for-use Date should be after purchase date")) frappe.throw(_("Available-for-use Date should be after purchase date"))
def validate_gross_and_purchase_amount(self):
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\
Please do not book expense of multiple assets against one single Asset.")
.format(frappe.bold("equal"), "<br>"), title=_("Invalid Gross Purchase Amount"))
def cancel_auto_gen_movement(self): def cancel_auto_gen_movement(self):
movements = frappe.db.sql( movements = frappe.db.sql(
@ -447,18 +455,55 @@ class Asset(AccountsController):
for d in self.get('finance_books'): for d in self.get('finance_books'):
if d.finance_book == self.default_finance_book: if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1 return cint(d.idx) - 1
def validate_make_gl_entry(self):
purchase_document = self.get_purchase_document()
asset_bought_with_invoice = purchase_document == self.purchase_invoice
fixed_asset_account, cwip_account = self.get_asset_accounts()
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
# check if expense already has been booked in case of cwip was enabled after purchasing asset
expense_booked = False
cwip_booked = False
if asset_bought_with_invoice:
expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
(purchase_document, fixed_asset_account), as_dict=1)
else:
cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
(purchase_document, cwip_account), as_dict=1)
if cwip_enabled and (expense_booked or not cwip_booked):
# if expense has already booked from invoice or cwip is booked from receipt
return False
elif not cwip_enabled and (not expense_booked or cwip_booked):
# if cwip is disabled but expense hasn't been booked yet
return True
elif cwip_enabled:
# default condition
return True
def get_purchase_document(self):
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
return purchase_document
def get_asset_accounts(self):
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
asset_category = self.asset_category, company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account",
self.name, self.asset_category, self.company)
return fixed_asset_account, cwip_account
def make_gl_entries(self): def make_gl_entries(self):
gl_entries = [] gl_entries = []
if ((self.purchase_receipt \ purchase_document = self.get_purchase_document()
or (self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock'))) fixed_asset_account, cwip_account = self.get_asset_accounts()
and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
asset_category = self.asset_category, company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account", if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
self.name, self.asset_category, self.company)
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": cwip_account, "account": cwip_account,

View File

@ -66,9 +66,6 @@ class TestAsset(unittest.TestCase):
pr.cancel() pr.cancel()
self.assertEqual(asset.docstatus, 2) self.assertEqual(asset.docstatus, 2)
self.assertFalse(frappe.db.get_value("GL Entry",
{"voucher_type": "Purchase Invoice", "voucher_no": pi.name}))
def test_is_fixed_asset_set(self): def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset = 1) asset = create_asset(is_existing_asset = 1)
doc = frappe.new_doc('Purchase Invoice') doc = frappe.new_doc('Purchase Invoice')
@ -82,7 +79,6 @@ class TestAsset(unittest.TestCase):
doc.set_missing_values() doc.set_missing_values()
self.assertEquals(doc.items[0].is_fixed_asset, 1) self.assertEquals(doc.items[0].is_fixed_asset, 1)
def test_schedule_for_straight_line_method(self): def test_schedule_for_straight_line_method(self):
pr = make_purchase_receipt(item_code="Macbook Pro", pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=100000.0, location="Test Location") qty=1, rate=100000.0, location="Test Location")
@ -564,6 +560,81 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle) self.assertEqual(gle, expected_gle)
def test_gle_with_cwip_toggling(self):
# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=5000, do_not_submit=True, location="Test Location")
pr.set('taxes', [{
'category': 'Total',
'add_deduct_tax': 'Add',
'charge_type': 'On Net Total',
'account_head': '_Test Account Service Tax - _TC',
'description': '_Test Account Service Tax',
'cost_center': 'Main - _TC',
'rate': 5.0
}, {
'category': 'Valuation and Total',
'add_deduct_tax': 'Add',
'charge_type': 'On Net Total',
'account_head': '_Test Account Shipping Charges - _TC',
'description': '_Test Account Shipping Charges',
'cost_center': 'Main - _TC',
'rate': 5.0
}])
pr.submit()
expected_gle = (
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
("CWIP Account - _TC", 5250.0, 0.0)
)
pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no = %s
order by account""", pr.name)
self.assertEqual(pr_gle, expected_gle)
pi = make_invoice(pr.name)
pi.submit()
expected_gle = (
("_Test Account Service Tax - _TC", 250.0, 0.0),
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
("Creditors - _TC", 0.0, 5500.0),
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
)
pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no = %s
order by account""", pi.name)
self.assertEqual(pi_gle, expected_gle)
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
month_end_date = get_last_day(nowdate())
asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
asset_doc.append("finance_books", {
"expected_value_after_useful_life": 200,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": month_end_date
})
# disable cwip and try submitting
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
asset_doc.submit()
# asset should have gl entries even if cwip is disabled
expected_gle = (
("_Test Fixed Asset - _TC", 5250.0, 0.0),
("CWIP Account - _TC", 0.0, 5250.0)
)
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Asset' and voucher_no = %s
order by account""", asset_doc.name)
self.assertEqual(gle, expected_gle)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
def test_expense_head(self): def test_expense_head(self):
pr = make_purchase_receipt(item_code="Macbook Pro", pr = make_purchase_receipt(item_code="Macbook Pro",
qty=2, rate=200000.0, location="Test Location") qty=2, rate=200000.0, location="Test Location")
@ -599,6 +670,7 @@ def create_asset(**args):
"purchase_date": "2015-01-01", "purchase_date": "2015-01-01",
"calculate_depreciation": 0, "calculate_depreciation": 0,
"gross_purchase_amount": 100000, "gross_purchase_amount": 100000,
"purchase_receipt_amount": 100000,
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"available_for_use_date": "2020-06-06", "available_for_use_date": "2020-06-06",

View File

@ -11,7 +11,8 @@ from frappe.model.document import Document
class AssetCategory(Document): class AssetCategory(Document):
def validate(self): def validate(self):
self.validate_finance_books() self.validate_finance_books()
self.validate_accounts() self.validate_account_types()
self.validate_account_currency()
def validate_finance_books(self): def validate_finance_books(self):
for d in self.finance_books: for d in self.finance_books:
@ -19,7 +20,26 @@ class AssetCategory(Document):
if cint(d.get(frappe.scrub(field)))<1: if cint(d.get(frappe.scrub(field)))<1:
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
def validate_accounts(self): def validate_account_currency(self):
account_types = [
'fixed_asset_account', 'accumulated_depreciation_account', 'depreciation_expense_account', 'capital_work_in_progress_account'
]
invalid_accounts = []
for d in self.accounts:
company_currency = frappe.get_value('Company', d.get('company_name'), 'default_currency')
for type_of_account in account_types:
if d.get(type_of_account):
account_currency = frappe.get_value("Account", d.get(type_of_account), "account_currency")
if account_currency != company_currency:
invalid_accounts.append(frappe._dict({ 'type': type_of_account, 'idx': d.idx, 'account': d.get(type_of_account) }))
for d in invalid_accounts:
frappe.throw(_("Row #{}: Currency of {} - {} doesn't matches company currency.")
.format(d.idx, frappe.bold(frappe.unscrub(d.type)), frappe.bold(d.account)),
title=_("Invalid Account"))
def validate_account_types(self):
account_type_map = { account_type_map = {
'fixed_asset_account': { 'account_type': 'Fixed Asset' }, 'fixed_asset_account': { 'account_type': 'Fixed Asset' },
'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' }, 'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' },

View File

@ -110,6 +110,7 @@ class AssetMovement(Document):
ORDER BY ORDER BY
asm.transaction_date asc asm.transaction_date asc
""", (d.asset, self.company, 'Receipt'), as_dict=1) """, (d.asset, self.company, 'Receipt'), as_dict=1)
if auto_gen_movement_entry and auto_gen_movement_entry[0].get('name') == self.name: if auto_gen_movement_entry and auto_gen_movement_entry[0].get('name') == self.name:
frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \ frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \
auto generated for Asset {1}').format(self.name, d.asset)) auto generated for Asset {1}').format(self.name, d.asset))

View File

@ -183,8 +183,8 @@ def get_data():
}, },
{ {
"type": "doctype", "type": "doctype",
"label": _("Update Bank Transaction Dates"), "label": _("Update Bank Clearance Dates"),
"name": "Bank Reconciliation", "name": "Bank Clearance",
"description": _("Update bank payment dates with journals.") "description": _("Update bank payment dates with journals.")
}, },
{ {
@ -245,6 +245,10 @@ def get_data():
"name": "Supplier Ledger Summary", "name": "Supplier Ledger Summary",
"doctype": "Sales Invoice", "doctype": "Sales Invoice",
"is_query_report": True, "is_query_report": True,
},
{
"type": "doctype",
"name": "Process Deferred Accounting"
} }
] ]
}, },

View File

@ -170,6 +170,10 @@ def get_data():
"type": "doctype", "type": "doctype",
"name": "Payroll Period", "name": "Payroll Period",
}, },
{
"type": "doctype",
"name": "Income Tax Slab",
},
{ {
"type": "doctype", "type": "doctype",
"name": "Salary Component", "name": "Salary Component",
@ -209,6 +213,10 @@ def get_data():
"name": "Employee Tax Exemption Proof Submission", "name": "Employee Tax Exemption Proof Submission",
"dependencies": ["Employee"] "dependencies": ["Employee"]
}, },
{
"type": "doctype",
"name": "Employee Other Income",
},
{ {
"type": "doctype", "type": "doctype",
"name": "Employee Benefit Application", "name": "Employee Benefit Application",

View File

@ -664,23 +664,26 @@ class AccountsController(TransactionBase):
def set_total_advance_paid(self): def set_total_advance_paid(self):
if self.doctype == "Sales Order": if self.doctype == "Sales Order":
dr_or_cr = "credit_in_account_currency" dr_or_cr = "credit_in_account_currency"
rev_dr_or_cr = "debit_in_account_currency"
party = self.customer party = self.customer
else: else:
dr_or_cr = "debit_in_account_currency" dr_or_cr = "debit_in_account_currency"
rev_dr_or_cr = "credit_in_account_currency"
party = self.supplier party = self.supplier
advance = frappe.db.sql(""" advance = frappe.db.sql("""
select select
account_currency, sum({dr_or_cr}) as amount account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
from from
`tabGL Entry` `tabGL Entry`
where where
against_voucher_type = %s and against_voucher = %s and party=%s against_voucher_type = %s and against_voucher = %s and party=%s
and docstatus = 1 and docstatus = 1
""".format(dr_or_cr=dr_or_cr), (self.doctype, self.name, party), as_dict=1) """.format(dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr), (self.doctype, self.name, party), as_dict=1) #nosec
if advance: if advance:
advance = advance[0] advance = advance[0]
advance_paid = flt(advance.amount, self.precision("advance_paid")) advance_paid = flt(advance.amount, self.precision("advance_paid"))
formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"), formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"),
currency=advance.account_currency) currency=advance.account_currency)

View File

@ -101,7 +101,7 @@ class BuyingController(StockController):
for d in tax_for_valuation: for d in tax_for_valuation:
d.category = 'Total' d.category = 'Total'
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items')) msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
def validate_asset_return(self): def validate_asset_return(self):
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return: if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
return return
@ -691,10 +691,10 @@ class BuyingController(StockController):
for qty in range(cint(d.qty)): for qty in range(cint(d.qty)):
asset = self.make_asset(d) asset = self.make_asset(d)
created_assets.append(asset) created_assets.append(asset)
if len(created_assets) > 5: if len(created_assets) > 5:
# dont show asset form links if more than 5 assets are created # dont show asset form links if more than 5 assets are created
messages.append(_('{} Asset{} created for {}').format(len(created_assets), is_plural, frappe.bold(d.item_code))) messages.append(_('{} Assets created for {}').format(len(created_assets), frappe.bold(d.item_code)))
else: else:
assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets)) assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets))
assets_link = frappe.bold(','.join(assets_link)) assets_link = frappe.bold(','.join(assets_link))

View File

@ -74,7 +74,7 @@ def validate_returned_items(doc):
for d in doc.get("items"): for d in doc.get("items"):
if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0): if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0):
if d.item_code not in valid_items: if d.item_code not in valid_items:
frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}") frappe.throw(_("Row # {0}: Returned Item {1} does not exist in {2} {3}")
.format(d.idx, d.item_code, doc.doctype, doc.return_against)) .format(d.idx, d.item_code, doc.doctype, doc.return_against))
else: else:
ref = valid_items.get(d.item_code, frappe._dict()) ref = valid_items.get(d.item_code, frappe._dict())
@ -266,6 +266,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_order_item = source_doc.purchase_order_item target_doc.purchase_order_item = source_doc.purchase_order_item
target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.rejected_warehouse = source_doc.rejected_warehouse
target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice": elif doctype == "Purchase Invoice":
target_doc.received_qty = -1* source_doc.received_qty target_doc.received_qty = -1* source_doc.received_qty
target_doc.rejected_qty = -1* source_doc.rejected_qty target_doc.rejected_qty = -1* source_doc.rejected_qty
@ -282,6 +284,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.so_detail = source_doc.so_detail target_doc.so_detail = source_doc.so_detail
target_doc.si_detail = source_doc.si_detail target_doc.si_detail = source_doc.si_detail
target_doc.expense_account = source_doc.expense_account target_doc.expense_account = source_doc.expense_account
target_doc.dn_detail = source_doc.name
if default_warehouse_for_sales_return: if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return target_doc.warehouse = default_warehouse_for_sales_return
elif doctype == "Sales Invoice": elif doctype == "Sales Invoice":

View File

@ -165,9 +165,9 @@ class SellingController(StockController):
d.stock_qty = flt(d.qty) * flt(d.conversion_factor) d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
def validate_selling_price(self): def validate_selling_price(self):
def throw_message(item_name, rate, ref_rate_field): def throw_message(idx, item_name, rate, ref_rate_field):
frappe.throw(_("""Selling rate for item {0} is lower than its {1}. Selling rate should be atleast {2}""") frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""")
.format(item_name, ref_rate_field, rate)) .format(idx, item_name, ref_rate_field, rate))
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
return return
@ -181,8 +181,8 @@ class SellingController(StockController):
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"]) last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1) last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom) and not self.get('is_internal_customer'): if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom):
throw_message(it.item_name, last_purchase_rate_in_sales_uom, "last purchase rate") throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate")
last_valuation_rate = frappe.db.sql(""" last_valuation_rate = frappe.db.sql("""
SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s
@ -193,7 +193,7 @@ class SellingController(StockController):
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1) last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1)
if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \ if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \
and not self.get('is_internal_customer'): and not self.get('is_internal_customer'):
throw_message(it.name, last_valuation_rate_in_sales_uom, "valuation rate") throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate")
def get_item_list(self): def get_item_list(self):

View File

@ -7,7 +7,7 @@ from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
from frappe import _ from frappe import _
import frappe.defaults import frappe.defaults
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock.stock_ledger import get_valuation_rate
from erpnext.stock import get_warehouse_account_map from erpnext.stock import get_warehouse_account_map
@ -23,9 +23,9 @@ class StockController(AccountsController):
self.validate_serialized_batch() self.validate_serialized_batch()
self.validate_customer_provided_item() self.validate_customer_provided_item()
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): def make_gl_entries(self, gl_entries=None):
if self.docstatus == 2: if self.docstatus == 2:
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if cint(erpnext.is_perpetual_inventory_enabled(self.company)): if cint(erpnext.is_perpetual_inventory_enabled(self.company)):
warehouse_account = get_warehouse_account_map(self.company) warehouse_account = get_warehouse_account_map(self.company)
@ -33,16 +33,12 @@ class StockController(AccountsController):
if self.docstatus==1: if self.docstatus==1:
if not gl_entries: if not gl_entries:
gl_entries = self.get_gl_entries(warehouse_account) gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries, from_repost=from_repost) make_gl_entries(gl_entries)
if (repost_future_gle or self.flags.repost_future_gle):
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
warehouse_account, company=self.company)
elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1: elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
gl_entries = [] gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries) gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost) make_gl_entries(gl_entries)
def validate_serialized_batch(self): def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -274,21 +270,21 @@ class StockController(AccountsController):
"batch_no": cstr(d.get("batch_no")).strip(), "batch_no": cstr(d.get("batch_no")).strip(),
"serial_no": d.get("serial_no"), "serial_no": d.get("serial_no"),
"project": d.get("project") or self.get('project'), "project": d.get("project") or self.get('project'),
"is_cancelled": self.docstatus==2 and "Yes" or "No" "is_cancelled": 1 if self.docstatus==2 else 0
}) })
sl_dict.update(args) sl_dict.update(args)
return sl_dict return sl_dict
def make_sl_entries(self, sl_entries, is_amended=None, allow_negative_stock=False, def make_sl_entries(self, sl_entries, allow_negative_stock=False,
via_landed_cost_voucher=False): via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries from erpnext.stock.stock_ledger import make_sl_entries
make_sl_entries(sl_entries, is_amended, allow_negative_stock, via_landed_cost_voucher) make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
def make_gl_entries_on_cancel(self, repost_future_gle=True): def make_gl_entries_on_cancel(self):
if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s
and voucher_no=%s""", (self.doctype, self.name)): and voucher_no=%s""", (self.doctype, self.name)):
self.make_gl_entries(repost_future_gle=repost_future_gle) self.make_gl_entries()
def get_serialized_items(self): def get_serialized_items(self):
serialized_items = [] serialized_items = []
@ -391,29 +387,6 @@ class StockController(AccountsController):
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1 d.allow_zero_valuation_rate = 1
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
for voucher_type, voucher_no in future_stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
def compare_existing_and_expected_gle(existing_gle, expected_gle): def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = True matched = True
for entry in expected_gle: for entry in expected_gle:
@ -430,36 +403,3 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = False matched = False
break break
return matched return matched
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
future_stock_vouchers = []
values = []
condition = ""
if for_items:
condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items)))
values += for_items
if for_warehouses:
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
values += for_warehouses
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries = {}
if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries

24
erpnext/crm/utils.py Normal file
View File

@ -0,0 +1,24 @@
import frappe
def update_lead_phone_numbers(contact, method):
if contact.phone_nos:
contact_lead = contact.get_link_for("Lead")
if contact_lead:
phone = mobile_no = contact.phone_nos[0].phone
if len(contact.phone_nos) > 1:
# get the default phone number
primary_phones = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_phone]
if primary_phones:
phone = primary_phones[0]
# get the default mobile number
primary_mobile_nos = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_mobile_no]
if primary_mobile_nos:
mobile_no = primary_mobile_nos[0]
lead = frappe.get_doc("Lead", contact_lead)
lead.phone = phone
lead.mobile_no = mobile_no
lead.save()

View File

@ -10,7 +10,7 @@ from frappe.utils import money_in_words
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from frappe.utils.csvutils import getlink from frappe.utils.csvutils import getlink
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import delete_gl_entries from erpnext.accounts.general_ledger import make_reverse_gl_entries
class Fees(AccountsController): class Fees(AccountsController):
@ -81,7 +81,8 @@ class Fees(AccountsController):
frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name))) frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name)))
def on_cancel(self): def on_cancel(self):
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
# frappe.db.set(self, 'status', 'Cancelled') # frappe.db.set(self, 'status', 'Cancelled')

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Video", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Video
() => frappe.tests.make('Video', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

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

View File

@ -1,8 +0,0 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Video', {
refresh: function(frm) {
}
});

View File

@ -1,112 +0,0 @@
{
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
"creation": "2018-10-17 05:47:13.087395",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"provider",
"url",
"column_break_4",
"publish_date",
"duration",
"section_break_7",
"description"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1,
"unique": 1
},
{
"fieldname": "description",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Description",
"reqd": 1
},
{
"fieldname": "duration",
"fieldtype": "Data",
"label": "Duration"
},
{
"fieldname": "url",
"fieldtype": "Data",
"in_list_view": 1,
"label": "URL",
"reqd": 1
},
{
"fieldname": "publish_date",
"fieldtype": "Date",
"label": "Publish Date"
},
{
"fieldname": "provider",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Provider",
"options": "YouTube\nVimeo",
"reqd": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break"
}
],
"modified": "2019-06-12 12:36:48.753092",
"modified_by": "Administrator",
"module": "Education",
"name": "Video",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Instructor",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS User",
"share": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -47,7 +47,12 @@
} }
], ],
"category": "Domains", "category": "Domains",
"charts": [], "charts": [
{
"chart_name": "Patient Appointments",
"label": "Patient Appointments"
}
],
"charts_label": "", "charts_label": "",
"creation": "2020-03-02 17:23:17.919682", "creation": "2020-03-02 17:23:17.919682",
"developer_mode_only": 0, "developer_mode_only": 0,
@ -58,7 +63,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Healthcare", "label": "Healthcare",
"modified": "2020-04-20 11:42:43.889576", "modified": "2020-04-25 22:31:36.576444",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Healthcare", "name": "Healthcare",

View File

@ -80,6 +80,7 @@ frappe.ui.form.on('Clinical Procedure', {
frappe.call({ frappe.call({
method: 'complete_procedure', method: 'complete_procedure',
doc: frm.doc, doc: frm.doc,
freeze: true,
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
frappe.show_alert({ frappe.show_alert({
@ -87,8 +88,8 @@ frappe.ui.form.on('Clinical Procedure', {
['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']), ['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
indicator: 'green' indicator: 'green'
}); });
frm.reload_doc();
} }
frm.reload_doc();
} }
}); });
} }
@ -111,9 +112,10 @@ frappe.ui.form.on('Clinical Procedure', {
frappe.call({ frappe.call({
doc: frm.doc, doc: frm.doc,
method: 'make_material_receipt', method: 'make_material_receipt',
freeze: true,
callback: function(r) { callback: function(r) {
if (!r.exc) { if (!r.exc) {
cur_frm.reload_doc(); frm.reload_doc();
let doclist = frappe.model.sync(r.message); let doclist = frappe.model.sync(r.message);
frappe.set_route('Form', doclist[0].doctype, doclist[0].name); frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
} }
@ -122,7 +124,7 @@ frappe.ui.form.on('Clinical Procedure', {
} }
); );
} else { } else {
cur_frm.reload_doc(); frm.reload_doc();
} }
} }
} }

View File

@ -87,7 +87,8 @@ class ClinicalProcedure(Document):
else: else:
frappe.throw(_('Please set Customer in Patient {0}').format(frappe.bold(self.patient)), title=_('Customer Not Found')) frappe.throw(_('Please set Customer in Patient {0}').format(frappe.bold(self.patient)), title=_('Customer Not Found'))
frappe.db.set_value('Clinical Procedure', self.name, 'status', 'Completed') self.db_set('status', 'Completed')
if self.consume_stock and self.items: if self.consume_stock and self.items:
return stock_entry return stock_entry
@ -245,9 +246,9 @@ def make_procedure(source_name, target_doc=None):
def insert_clinical_procedure_to_medical_record(doc): def insert_clinical_procedure_to_medical_record(doc):
subject = cstr(doc.procedure_template) subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
if doc.practitioner: if doc.practitioner:
subject += ' ' + doc.practitioner subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
if subject and doc.notes: if subject and doc.notes:
subject += '<br/>' + doc.notes subject += '<br/>' + doc.notes

View File

@ -24,6 +24,8 @@ erpnext.ExerciseEditor = Class.extend({
this.exercise_cards = $('<div class="exercise-cards"></div>').appendTo(this.wrapper); this.exercise_cards = $('<div class="exercise-cards"></div>').appendTo(this.wrapper);
this.row = $('<div class="exercise-row"></div>').appendTo(this.wrapper);
let me = this; let me = this;
this.exercise_toolbar.find(".btn-add") this.exercise_toolbar.find(".btn-add")
@ -32,7 +34,7 @@ erpnext.ExerciseEditor = Class.extend({
me.show_add_card_dialog(frm); me.show_add_card_dialog(frm);
}); });
if (frm.doc.steps_table.length > 0) { if (frm.doc.steps_table && frm.doc.steps_table.length > 0) {
this.make_cards(frm); this.make_cards(frm);
this.make_buttons(frm); this.make_buttons(frm);
} }
@ -41,7 +43,6 @@ erpnext.ExerciseEditor = Class.extend({
make_cards: function(frm) { make_cards: function(frm) {
var me = this; var me = this;
$(me.exercise_cards).empty(); $(me.exercise_cards).empty();
this.row = $('<div class="exercise-row"></div>').appendTo(me.exercise_cards);
$.each(frm.doc.steps_table, function(i, step) { $.each(frm.doc.steps_table, function(i, step) {
$(repl(` $(repl(`
@ -78,6 +79,7 @@ erpnext.ExerciseEditor = Class.extend({
frm.doc.steps_table.pop(id); frm.doc.steps_table.pop(id);
frm.refresh_field('steps_table'); frm.refresh_field('steps_table');
$('#col-'+id).remove(); $('#col-'+id).remove();
frm.dirty();
}, 300); }, 300);
}); });
}, },
@ -106,7 +108,10 @@ erpnext.ExerciseEditor = Class.extend({
], ],
primary_action: function() { primary_action: function() {
let data = d.get_values(); let data = d.get_values();
let i = frm.doc.steps_table.length; let i = 0;
if (frm.doc.steps_table) {
i = frm.doc.steps_table.length;
}
$(repl(` $(repl(`
<div class="exercise-col col-sm-4" id="%(col_id)s"> <div class="exercise-col col-sm-4" id="%(col_id)s">
<div class="card h-100 exercise-card" id="%(card_id)s"> <div class="card h-100 exercise-card" id="%(card_id)s">
@ -165,9 +170,10 @@ erpnext.ExerciseEditor = Class.extend({
frm.doc.steps_table[id].image = data.image; frm.doc.steps_table[id].image = data.image;
frm.doc.steps_table[id].description = data.step_description; frm.doc.steps_table[id].description = data.step_description;
refresh_field('steps_table'); refresh_field('steps_table');
frm.dirty();
new_dialog.hide(); new_dialog.hide();
}, },
primary_action_label: __("Save"), primary_action_label: __("Edit"),
}); });
new_dialog.set_values({ new_dialog.set_values({

View File

@ -288,23 +288,23 @@ def insert_lab_test_to_medical_record(doc):
table_row = False table_row = False
subject = cstr(doc.lab_test_name) subject = cstr(doc.lab_test_name)
if doc.practitioner: if doc.practitioner:
subject += " "+ doc.practitioner subject += frappe.bold(_("Healthcare Practitioner: "))+ doc.practitioner + "<br>"
if doc.normal_test_items: if doc.normal_test_items:
item = doc.normal_test_items[0] item = doc.normal_test_items[0]
comment = "" comment = ""
if item.lab_test_comment: if item.lab_test_comment:
comment = str(item.lab_test_comment) comment = str(item.lab_test_comment)
table_row = item.lab_test_name table_row = frappe.bold(_("Lab Test Conducted: ")) + item.lab_test_name
if item.lab_test_event: if item.lab_test_event:
table_row += " " + item.lab_test_event table_row += frappe.bold(_("Lab Test Event: ")) + item.lab_test_event
if item.result_value: if item.result_value:
table_row += " " + item.result_value table_row += " " + frappe.bold(_("Lab Test Result: ")) + item.result_value
if item.normal_range: if item.normal_range:
table_row += " normal_range("+item.normal_range+")" table_row += " " + _("Normal Range:") + item.normal_range
table_row += " "+comment table_row += " " + comment
elif doc.special_test_items: elif doc.special_test_items:
item = doc.special_test_items[0] item = doc.special_test_items[0]
@ -316,12 +316,12 @@ def insert_lab_test_to_medical_record(doc):
item = doc.sensitivity_test_items[0] item = doc.sensitivity_test_items[0]
if item.antibiotic and item.antibiotic_sensitivity: if item.antibiotic and item.antibiotic_sensitivity:
table_row = item.antibiotic +" "+ item.antibiotic_sensitivity table_row = item.antibiotic + " " + item.antibiotic_sensitivity
if table_row: if table_row:
subject += "<br/>"+table_row subject += "<br>" + table_row
if doc.lab_test_comment: if doc.lab_test_comment:
subject += "<br/>"+ cstr(doc.lab_test_comment) subject += "<br>" + cstr(doc.lab_test_comment)
medical_record = frappe.new_doc("Patient Medical Record") medical_record = frappe.new_doc("Patient Medical Record")
medical_record.patient = doc.patient medical_record.patient = doc.patient

View File

@ -325,7 +325,11 @@ def update_status(appointment_id, status):
def send_confirmation_msg(doc): def send_confirmation_msg(doc):
if frappe.db.get_single_value('Healthcare Settings', 'send_appointment_confirmation'): if frappe.db.get_single_value('Healthcare Settings', 'send_appointment_confirmation'):
message = frappe.db.get_single_value('Healthcare Settings', 'appointment_confirmation_msg') message = frappe.db.get_single_value('Healthcare Settings', 'appointment_confirmation_msg')
send_message(doc, message) try:
send_message(doc, message)
except Exception:
frappe.log_error(frappe.get_traceback(), _('Appointment Confirmation Message Not Sent'))
frappe.msgprint(_('Appointment Confirmation Message Not Sent'), indicator='orange')
@frappe.whitelist() @frappe.whitelist()

View File

@ -18,6 +18,9 @@ class PatientEncounter(Document):
def after_insert(self): def after_insert(self):
insert_encounter_to_medical_record(self) insert_encounter_to_medical_record(self)
def on_submit(self):
update_encounter_medical_record(self)
def on_cancel(self): def on_cancel(self):
if self.appointment: if self.appointment:
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
@ -66,22 +69,26 @@ def delete_medical_record(encounter):
frappe.db.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name) frappe.db.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name)
def set_subject_field(encounter): def set_subject_field(encounter):
subject = encounter.practitioner + '\n' subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
if encounter.symptoms: if encounter.symptoms:
subject += _('Symptoms: ') + cstr(encounter.symptoms) + '\n' subject += frappe.bold(_('Symptoms: ')) + '<br>'
for entry in encounter.symptoms:
subject += cstr(entry.complaint) + '<br>'
else: else:
subject += _('No Symptoms') + '\n' subject += frappe.bold(_('No Symptoms')) + '<br>'
if encounter.diagnosis: if encounter.diagnosis:
subject += _('Diagnosis: ') + cstr(encounter.diagnosis) + '\n' subject += frappe.bold(_('Diagnosis: ')) + '<br>'
for entry in encounter.diagnosis:
subject += cstr(entry.diagnosis) + '<br>'
else: else:
subject += _('No Diagnosis') + '\n' subject += frappe.bold(_('No Diagnosis')) + '<br>'
if encounter.drug_prescription: if encounter.drug_prescription:
subject += '\n' + _('Drug(s) Prescribed.') subject += '<br>' + _('Drug(s) Prescribed.')
if encounter.lab_test_prescription: if encounter.lab_test_prescription:
subject += '\n' + _('Test(s) Prescribed.') subject += '<br>' + _('Test(s) Prescribed.')
if encounter.procedure_prescription: if encounter.procedure_prescription:
subject += '\n' + _('Procedure(s) Prescribed.') subject += '<br>' + _('Procedure(s) Prescribed.')
return subject return subject

View File

@ -57,7 +57,7 @@
}, },
{ {
"fieldname": "subject", "fieldname": "subject",
"fieldtype": "Small Text", "fieldtype": "Text Editor",
"ignore_xss_filter": 1, "ignore_xss_filter": 1,
"label": "Subject" "label": "Subject"
}, },
@ -125,7 +125,7 @@
], ],
"in_create": 1, "in_create": 1,
"links": [], "links": [],
"modified": "2020-03-23 19:26:59.308383", "modified": "2020-04-29 12:26:57.679402",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Patient Medical Record", "name": "Patient Medical Record",

View File

@ -21,8 +21,14 @@ class TherapyPlan(Document):
self.status = 'Completed' self.status = 'Completed'
def set_totals(self): def set_totals(self):
total_sessions = sum([int(d.no_of_sessions) for d in self.get('therapy_plan_details')]) total_sessions = 0
total_sessions_completed = sum([int(d.sessions_completed) for d in self.get('therapy_plan_details')]) total_sessions_completed = 0
for entry in self.therapy_plan_details:
if entry.no_of_sessions:
total_sessions += entry.no_of_sessions
if entry.sessions_completed:
total_sessions_completed += entry.sessions_completed
self.db_set('total_sessions', total_sessions) self.db_set('total_sessions', total_sessions)
self.db_set('total_sessions_completed', total_sessions_completed) self.db_set('total_sessions_completed', total_sessions_completed)

View File

@ -13,23 +13,92 @@ frappe.ui.form.on('Therapy Session', {
refresh: function(frm) { refresh: function(frm) {
if (!frm.doc.__islocal) { if (!frm.doc.__islocal) {
let target = 0; frm.dashboard.add_indicator(__('Counts Targeted: {0}', [frm.doc.total_counts_targeted]), 'blue');
let completed = 0; frm.dashboard.add_indicator(__('Counts Completed: {0}', [frm.doc.total_counts_completed]),
$.each(frm.doc.exercises, function(_i, e) { (frm.doc.total_counts_completed < frm.doc.total_counts_targeted) ? 'orange' : 'green');
target += e.counts_target;
completed += e.counts_completed;
});
frm.dashboard.add_indicator(__('Counts Targetted: {0}', [target]), 'blue');
frm.dashboard.add_indicator(__('Counts Completed: {0}', [completed]), (completed < target) ? 'orange' : 'green');
} }
if (frm.doc.docstatus === 1) { if (frm.doc.docstatus === 1) {
frm.add_custom_button(__('Patient Assessment'),function() { frm.add_custom_button(__('Patient Assessment'), function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment', method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment',
frm: frm, frm: frm,
}) })
}, 'Create'); }, 'Create');
frm.add_custom_button(__('Sales Invoice'), function() {
frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session',
frm: frm,
})
}, 'Create');
}
},
patient: function(frm) {
if (frm.doc.patient) {
frappe.call({
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
args: {
patient: frm.doc.patient
},
callback: function (data) {
let age = '';
if (data.message.dob) {
age = calculate_age(data.message.dob);
} else if (data.message.age) {
age = data.message.age;
if (data.message.age_as_on) {
age = __('{0} as on {1}', [age, data.message.age_as_on]);
}
}
frm.set_value('patient_age', age);
frm.set_value('gender', data.message.sex);
frm.set_value('patient_name', data.message.patient_name);
}
});
} else {
frm.set_value('patient_age', '');
frm.set_value('gender', '');
frm.set_value('patient_name', '');
}
},
appointment: function(frm) {
if (frm.doc.appointment) {
frappe.call({
'method': 'frappe.client.get',
args: {
doctype: 'Patient Appointment',
name: frm.doc.appointment
},
callback: function(data) {
let values = {
'patient':data.message.patient,
'therapy_type': data.message.therapy_type,
'therapy_plan': data.message.therapy_plan,
'practitioner': data.message.practitioner,
'department': data.message.department,
'start_date': data.message.appointment_date,
'start_time': data.message.appointment_time,
'service_unit': data.message.service_unit,
'company': data.message.company
};
frm.set_value(values);
}
});
} else {
let values = {
'patient': '',
'therapy_type': '',
'therapy_plan': '',
'practitioner': '',
'department': '',
'start_date': '',
'start_time': '',
'service_unit': '',
};
frm.set_value(values);
} }
}, },
@ -44,6 +113,8 @@ frappe.ui.form.on('Therapy Session', {
callback: function(data) { callback: function(data) {
frm.set_value('duration', data.message.default_duration); frm.set_value('duration', data.message.default_duration);
frm.set_value('rate', data.message.rate); frm.set_value('rate', data.message.rate);
frm.set_value('service_unit', data.message.healthcare_service_unit);
frm.set_value('department', data.message.medical_department);
frm.doc.exercises = []; frm.doc.exercises = [];
$.each(data.message.exercises, function(_i, e) { $.each(data.message.exercises, function(_i, e) {
let exercise = frm.add_child('exercises'); let exercise = frm.add_child('exercises');

View File

@ -9,9 +9,11 @@
"naming_series", "naming_series",
"appointment", "appointment",
"patient", "patient",
"patient_name",
"patient_age", "patient_age",
"gender", "gender",
"column_break_5", "column_break_5",
"company",
"therapy_plan", "therapy_plan",
"therapy_type", "therapy_type",
"practitioner", "practitioner",
@ -20,7 +22,6 @@
"duration", "duration",
"rate", "rate",
"location", "location",
"company",
"column_break_12", "column_break_12",
"service_unit", "service_unit",
"start_date", "start_date",
@ -28,6 +29,10 @@
"invoiced", "invoiced",
"exercises_section", "exercises_section",
"exercises", "exercises",
"section_break_23",
"total_counts_targeted",
"column_break_25",
"total_counts_completed",
"amended_from" "amended_from"
], ],
"fields": [ "fields": [
@ -159,7 +164,8 @@
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
"options": "Company" "options": "Company",
"reqd": 1
}, },
{ {
"default": "0", "default": "0",
@ -173,11 +179,38 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Patient Age", "label": "Patient Age",
"read_only": 1 "read_only": 1
},
{
"fieldname": "total_counts_targeted",
"fieldtype": "Int",
"label": "Total Counts Targeted",
"read_only": 1
},
{
"fieldname": "total_counts_completed",
"fieldtype": "Int",
"label": "Total Counts Completed",
"read_only": 1
},
{
"fieldname": "section_break_23",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_25",
"fieldtype": "Column Break"
},
{
"fetch_from": "patient.patient_name",
"fieldname": "patient_name",
"fieldtype": "Data",
"label": "Patient Name",
"read_only": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-21 13:16:46.378798", "modified": "2020-04-29 16:49:16.286006",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Therapy Session", "name": "Therapy Session",

View File

@ -6,10 +6,17 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe import _
from frappe.utils import cstr, getdate
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
class TherapySession(Document): class TherapySession(Document):
def validate(self):
self.set_total_counts()
def on_submit(self): def on_submit(self):
self.update_sessions_count_in_therapy_plan() self.update_sessions_count_in_therapy_plan()
insert_session_medical_record(self)
def on_cancel(self): def on_cancel(self):
self.update_sessions_count_in_therapy_plan(on_cancel=True) self.update_sessions_count_in_therapy_plan(on_cancel=True)
@ -24,6 +31,18 @@ class TherapySession(Document):
entry.sessions_completed += 1 entry.sessions_completed += 1
therapy_plan.save() therapy_plan.save()
def set_total_counts(self):
target_total = 0
counts_completed = 0
for entry in self.exercises:
if entry.counts_target:
target_total += entry.counts_target
if entry.counts_completed:
counts_completed += entry.counts_completed
self.db_set('total_counts_targeted', target_total)
self.db_set('total_counts_completed', counts_completed)
@frappe.whitelist() @frappe.whitelist()
def create_therapy_session(source_name, target_doc=None): def create_therapy_session(source_name, target_doc=None):
@ -52,4 +71,62 @@ def create_therapy_session(source_name, target_doc=None):
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doc return doc
@frappe.whitelist()
def invoice_therapy_session(source_name, target_doc=None):
def set_missing_values(source, target):
target.customer = frappe.db.get_value('Patient', source.patient, 'customer')
target.due_date = getdate()
target.debit_to = get_receivable_account(source.company)
item = target.append('items', {})
item = get_therapy_item(source, item)
target.set_missing_values(for_validate=True)
doc = get_mapped_doc('Therapy Session', source_name, {
'Therapy Session': {
'doctype': 'Sales Invoice',
'field_map': [
['patient', 'patient'],
['referring_practitioner', 'practitioner'],
['company', 'company'],
['due_date', 'start_date']
]
}
}, target_doc, set_missing_values)
return doc
def get_therapy_item(therapy, item):
item.item_code = frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item')
item.description = _('Therapy Session Charges: {0}').format(therapy.practitioner)
item.income_account = get_income_account(therapy.practitioner, therapy.company)
item.cost_center = frappe.get_cached_value('Company', therapy.company, 'cost_center')
item.rate = therapy.rate
item.amount = therapy.rate
item.qty = 1
item.reference_dt = 'Therapy Session'
item.reference_dn = therapy.name
return item
def insert_session_medical_record(doc):
subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
if doc.therapy_plan:
subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.start_date
medical_record.reference_doctype = 'Therapy Session'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)

View File

@ -35,17 +35,17 @@ def delete_vital_signs_from_medical_record(doc):
def set_subject_field(doc): def set_subject_field(doc):
subject = '' subject = ''
if(doc.temperature): if doc.temperature:
subject += _('Temperature: ') + '\n'+ cstr(doc.temperature) + '. ' subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
if(doc.pulse): if doc.pulse:
subject += _('Pulse: ') + '\n' + cstr(doc.pulse) + '. ' subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
if(doc.respiratory_rate): if doc.respiratory_rate:
subject += _('Respiratory Rate: ') + '\n' + cstr(doc.respiratory_rate) + '. ' subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
if(doc.bp): if doc.bp:
subject += _('BP: ') + '\n' + cstr(doc.bp) + '. ' subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
if(doc.bmi): if doc.bmi:
subject += _('BMI: ') + '\n' + cstr(doc.bmi) + '. ' subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
if(doc.nutrition_note): if doc.nutrition_note:
subject += _('Note: ') + '\n' + cstr(doc.nutrition_note) + '. ' subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
return subject return subject

View File

@ -250,7 +250,8 @@ doc_events = {
}, },
"Contact": { "Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue", "on_trash": "erpnext.support.doctype.issue.issue.update_issue",
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information" "after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information",
"validate": "erpnext.crm.utils.update_lead_phone_numbers"
}, },
"Lead": { "Lead": {
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information" "after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
@ -537,4 +538,4 @@ global_search_doctypes = {
{'doctype': 'Hotel Room Package', 'index': 3}, {'doctype': 'Hotel Room Package', 'index': 3},
{'doctype': 'Hotel Room Type', 'index': 4} {'doctype': 'Hotel Room Type', 'index': 4}
] ]
} }

View File

@ -23,7 +23,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Payroll", "label": "Payroll",
"links": "[\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Structure\",\n \"Employee\"\n ],\n \"label\": \"Salary Structure Assignment\",\n \"name\": \"Salary Structure Assignment\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Entry\",\n \"name\": \"Payroll Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Slip\",\n \"name\": \"Salary Slip\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Component\",\n \"name\": \"Salary Component\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"is_query_report\": true,\n \"label\": \"Salary Register\",\n \"name\": \"Salary Register\",\n \"type\": \"report\"\n }\n]" "links": "[\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Structure\",\n \"Employee\"\n ],\n \"label\": \"Salary Structure Assignment\",\n \"name\": \"Salary Structure Assignment\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Entry\",\n \"name\": \"Payroll Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Slip\",\n \"name\": \"Salary Slip\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Component\",\n \"name\": \"Salary Component\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"is_query_report\": true,\n \"label\": \"Salary Register\",\n \"name\": \"Salary Register\",\n \"type\": \"report\"\n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -73,7 +73,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Employee Tax and Benefits", "label": "Employee Tax and Benefits",
"links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\",\n \"Payroll Period\"\n ],\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n }\n]"
} }
], ],
"category": "Modules", "category": "Modules",
@ -88,7 +88,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "HR", "label": "HR",
"modified": "2020-04-01 11:28:50.860012", "modified": "2020-04-29 20:29:22.114309",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR", "name": "HR",

View File

@ -13,5 +13,5 @@ frappe.ui.form.on('Additional Salary', {
} }
}; };
}); });
} },
}); });

View File

@ -13,10 +13,14 @@
"salary_component", "salary_component",
"overwrite_salary_structure_amount", "overwrite_salary_structure_amount",
"deduct_full_tax_on_selected_payroll_date", "deduct_full_tax_on_selected_payroll_date",
"ref_doctype",
"ref_docname",
"column_break_5", "column_break_5",
"company", "company",
"is_recurring",
"from_date",
"to_date",
"payroll_date", "payroll_date",
"salary_slip",
"type", "type",
"department", "department",
"amount", "amount",
@ -74,12 +78,13 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval:(doc.is_recurring==0)",
"description": "Date on which this component is applied", "description": "Date on which this component is applied",
"fieldname": "payroll_date", "fieldname": "payroll_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"label": "Payroll Date", "label": "Payroll Date",
"reqd": 1, "mandatory_depends_on": "eval:(doc.is_recurring==0)",
"search_index": 1 "search_index": 1
}, },
{ {
@ -105,13 +110,6 @@
"options": "Company", "options": "Company",
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "salary_slip",
"fieldtype": "Link",
"label": "Salary Slip",
"options": "Salary Slip",
"read_only": 1
},
{ {
"fetch_from": "salary_component.type", "fetch_from": "salary_component.type",
"fieldname": "type", "fieldname": "type",
@ -127,11 +125,45 @@
"options": "Additional Salary", "options": "Additional Salary",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"default": "0",
"fieldname": "is_recurring",
"fieldtype": "Check",
"label": "Is Recurring"
},
{
"depends_on": "eval:(doc.is_recurring==1)",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"mandatory_depends_on": "eval:(doc.is_recurring==1)"
},
{
"depends_on": "eval:(doc.is_recurring==1)",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"mandatory_depends_on": "eval:(doc.is_recurring==1)"
},
{
"fieldname": "ref_doctype",
"fieldtype": "Link",
"label": "Reference Document Type",
"options": "DocType",
"read_only": 1
},
{
"fieldname": "ref_docname",
"fieldtype": "Dynamic Link",
"label": "Reference Document",
"options": "ref_doctype",
"read_only": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2019-12-12 19:07:23.635901", "modified": "2020-04-04 18:06:29.170878",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Additional Salary", "name": "Additional Salary",

View File

@ -9,6 +9,11 @@ from frappe import _
from frappe.utils import getdate, date_diff from frappe.utils import getdate, date_diff
class AdditionalSalary(Document): class AdditionalSalary(Document):
def on_submit(self):
if self.ref_doctype == "Employee Advance" and self.ref_docname:
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
def before_insert(self): def before_insert(self):
if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component, if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}): "amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}):
@ -21,10 +26,19 @@ class AdditionalSalary(Document):
frappe.throw(_("Amount should not be less than zero.")) frappe.throw(_("Amount should not be less than zero."))
def validate_dates(self): def validate_dates(self):
date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee, date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee,
["date_of_joining", "relieving_date"]) ["date_of_joining", "relieving_date"])
if date_of_joining and getdate(self.payroll_date) < getdate(date_of_joining):
frappe.throw(_("Payroll date can not be less than employee's joining date")) if getdate(self.from_date) > getdate(self.to_date):
frappe.throw(_("From Date can not be greater than To Date."))
if date_of_joining:
if getdate(self.payroll_date) < getdate(date_of_joining):
frappe.throw(_("Payroll date can not be less than employee's joining date."))
elif getdate(self.from_date) < getdate(date_of_joining):
frappe.throw(_("From date can not be less than employee's joining date."))
elif getdate(self.to_date) > getdate(relieving_date):
frappe.throw(_("To date can not be greater than employee's relieving date."))
def get_amount(self, sal_start_date, sal_end_date): def get_amount(self, sal_start_date, sal_end_date):
start_date = getdate(sal_start_date) start_date = getdate(sal_start_date)
@ -40,15 +54,18 @@ class AdditionalSalary(Document):
@frappe.whitelist() @frappe.whitelist()
def get_additional_salary_component(employee, start_date, end_date, component_type): def get_additional_salary_component(employee, start_date, end_date, component_type):
additional_components = frappe.db.sql(""" additional_salaries = frappe.db.sql("""
select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
from `tabAdditional Salary` from `tabAdditional Salary`
where employee=%(employee)s where employee=%(employee)s
and docstatus = 1 and docstatus = 1
and payroll_date between %(from_date)s and %(to_date)s and (
and type = %(component_type)s payroll_date between %(from_date)s and %(to_date)s
group by salary_component, overwrite_salary_structure_amount or
order by salary_component, overwrite_salary_structure_amount from_date <= %(to_date)s and to_date >= %(to_date)s
)
and type = %(component_type)s
order by salary_component, overwrite_salary_structure_amount DESC
""", { """, {
'employee': employee, 'employee': employee,
'from_date': start_date, 'from_date': start_date,
@ -56,21 +73,38 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty
'component_type': "Earning" if component_type == "earnings" else "Deduction" 'component_type': "Earning" if component_type == "earnings" else "Deduction"
}, as_dict=1) }, as_dict=1)
additional_components_list = [] existing_salary_components= []
salary_components_details = {}
additional_salary_details = []
overwrites_components = [ele.salary_component for ele in additional_salaries if ele.overwrite_salary_structure_amount == 1]
component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type'] component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type']
for d in additional_components: for d in additional_salaries:
struct_row = frappe._dict({'salary_component': d.salary_component})
component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields)
if component:
struct_row.update(component[0])
struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date if d.salary_component not in existing_salary_components:
struct_row['is_additional_component'] = 1 component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields)
struct_row = frappe._dict({'salary_component': d.salary_component})
if component:
struct_row.update(component[0])
additional_components_list.append(frappe._dict({ struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date
'amount': d.amount, struct_row['is_additional_component'] = 1
'type': component[0].type,
'struct_row': struct_row, salary_components_details[d.salary_component] = struct_row
'overwrite': d.overwrite_salary_structure_amount,
}))
return additional_components_list if overwrites_components.count(d.salary_component) > 1:
frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component: {0} between {1} and {2}.".format(d.salary_component, start_date, end_date)), title=_("Error"))
else:
additional_salary_details.append({
'name': d.name,
'component': d.salary_component,
'amount': d.amount,
'type': d.type,
'overwrite': d.overwrite_salary_structure_amount,
})
existing_salary_components.append(d.salary_component)
return salary_components_details, additional_salary_details

View File

@ -3,6 +3,44 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe, erpnext
from frappe.utils import nowdate, add_days
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.salary_component.test_salary_component import create_salary_component
from erpnext.hr.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, setup_test
class TestAdditionalSalary(unittest.TestCase): class TestAdditionalSalary(unittest.TestCase):
pass
def setUp(self):
setup_test()
def test_recurring_additional_salary(self):
emp_id = make_employee("test_additional@salary.com")
frappe.db.set_value("Employee", emp_id, "relieving_date", add_days(nowdate(), 1800))
add_sal = get_additional_salary(emp_id)
ss = make_employee_salary_slip("test_additional@salary.com", "Monthly")
for earning in ss.earnings:
if earning.salary_component == "Recurring Salary Component":
amount = earning.amount
salary_component = earning.salary_component
self.assertEqual(amount, add_sal.amount)
self.assertEqual(salary_component, add_sal.salary_component)
def get_additional_salary(emp_id):
create_salary_component("Recurring Salary Component")
add_sal = frappe.new_doc("Additional Salary")
add_sal.employee = emp_id
add_sal.salary_component = "Recurring Salary Component"
add_sal.is_recurring = 1
add_sal.from_date = add_days(nowdate(), -50)
add_sal.to_date = add_days(nowdate(), 180)
add_sal.amount = 5000
add_sal.save()
add_sal.submit()
return add_sal

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