mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 19:19:17 +00:00
Merge branch 'develop' into loan_exposure_report_fix
This commit is contained in:
@@ -109,7 +109,7 @@ def get_region(company=None):
|
|||||||
'''
|
'''
|
||||||
if company or frappe.flags.company:
|
if company or frappe.flags.company:
|
||||||
return frappe.get_cached_value('Company',
|
return frappe.get_cached_value('Company',
|
||||||
company or frappe.flags.company, 'country')
|
company or frappe.flags.company, 'country')
|
||||||
elif frappe.flags.country:
|
elif frappe.flags.country:
|
||||||
return frappe.flags.country
|
return frappe.flags.country
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -179,10 +179,18 @@ class POSInvoice(SalesInvoice):
|
|||||||
if d.get("serial_no"):
|
if d.get("serial_no"):
|
||||||
serial_nos = get_serial_nos(d.serial_no)
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
for sr in serial_nos:
|
for sr in serial_nos:
|
||||||
serial_no_exists = frappe.db.exists("POS Invoice Item", {
|
serial_no_exists = frappe.db.sql("""
|
||||||
"parent": self.return_against,
|
SELECT name
|
||||||
"serial_no": ["like", d.get("serial_no")]
|
FROM `tabPOS Invoice Item`
|
||||||
})
|
WHERE
|
||||||
|
parent = %s
|
||||||
|
and (serial_no = %s
|
||||||
|
or serial_no like %s
|
||||||
|
or serial_no like %s
|
||||||
|
or serial_no like %s
|
||||||
|
)
|
||||||
|
""", (self.return_against, sr, sr+'\n%', '%\n'+sr, '%\n'+sr+'\n%'))
|
||||||
|
|
||||||
if not serial_no_exists:
|
if not serial_no_exists:
|
||||||
bold_return_against = frappe.bold(self.return_against)
|
bold_return_against = frappe.bold(self.return_against)
|
||||||
bold_serial_no = frappe.bold(sr)
|
bold_serial_no = frappe.bold(sr)
|
||||||
@@ -190,7 +198,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
_("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}")
|
_("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}")
|
||||||
.format(d.idx, bold_serial_no, bold_return_against)
|
.format(d.idx, bold_serial_no, bold_return_against)
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_non_stock_items(self):
|
def validate_non_stock_items(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
||||||
@@ -292,7 +300,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
if not self.get('payments') and not for_validate:
|
if not self.get('payments') and not for_validate:
|
||||||
update_multi_mode_option(self, profile)
|
update_multi_mode_option(self, profile)
|
||||||
|
|
||||||
if self.is_return and not for_validate:
|
if self.is_return and not for_validate:
|
||||||
add_return_modes(self, profile)
|
add_return_modes(self, profile)
|
||||||
|
|
||||||
|
|||||||
@@ -198,6 +198,65 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(pos_return.get('payments')[0].amount, -500)
|
self.assertEqual(pos_return.get('payments')[0].amount, -500)
|
||||||
self.assertEqual(pos_return.get('payments')[1].amount, -500)
|
self.assertEqual(pos_return.get('payments')[1].amount, -500)
|
||||||
|
|
||||||
|
def test_pos_return_for_serialized_item(self):
|
||||||
|
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
|
||||||
|
|
||||||
|
se = make_serialized_item(company='_Test Company',
|
||||||
|
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||||
|
|
||||||
|
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||||
|
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||||
|
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||||
|
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||||
|
|
||||||
|
pos.get("items")[0].serial_no = serial_nos[0]
|
||||||
|
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000, 'default': 1})
|
||||||
|
|
||||||
|
pos.insert()
|
||||||
|
pos.submit()
|
||||||
|
|
||||||
|
pos_return = make_sales_return(pos.name)
|
||||||
|
|
||||||
|
pos_return.insert()
|
||||||
|
pos_return.submit()
|
||||||
|
self.assertEqual(pos_return.get('items')[0].serial_no, serial_nos[0])
|
||||||
|
|
||||||
|
def test_partial_pos_returns(self):
|
||||||
|
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
|
||||||
|
|
||||||
|
se = make_serialized_item(company='_Test Company',
|
||||||
|
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||||
|
|
||||||
|
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||||
|
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||||
|
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||||
|
item=se.get("items")[0].item_code, qty=2, rate=1000, do_not_save=1)
|
||||||
|
|
||||||
|
pos.get("items")[0].serial_no = serial_nos[0] + "\n" + serial_nos[1]
|
||||||
|
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000, 'default': 1})
|
||||||
|
|
||||||
|
pos.insert()
|
||||||
|
pos.submit()
|
||||||
|
|
||||||
|
pos_return1 = make_sales_return(pos.name)
|
||||||
|
|
||||||
|
# partial return 1
|
||||||
|
pos_return1.get('items')[0].qty = -1
|
||||||
|
pos_return1.get('items')[0].serial_no = serial_nos[0]
|
||||||
|
pos_return1.insert()
|
||||||
|
pos_return1.submit()
|
||||||
|
|
||||||
|
# partial return 2
|
||||||
|
pos_return2 = make_sales_return(pos.name)
|
||||||
|
self.assertEqual(pos_return2.get('items')[0].qty, -1)
|
||||||
|
self.assertEqual(pos_return2.get('items')[0].serial_no, serial_nos[1])
|
||||||
|
|
||||||
def test_pos_change_amount(self):
|
def test_pos_change_amount(self):
|
||||||
pos = create_pos_invoice(company= "_Test Company", debit_to="Debtors - _TC",
|
pos = create_pos_invoice(company= "_Test Company", debit_to="Debtors - _TC",
|
||||||
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
|
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
"edit_references",
|
"edit_references",
|
||||||
"sales_order",
|
"sales_order",
|
||||||
"so_detail",
|
"so_detail",
|
||||||
|
"pos_invoice_item",
|
||||||
"column_break_74",
|
"column_break_74",
|
||||||
"delivery_note",
|
"delivery_note",
|
||||||
"dn_detail",
|
"dn_detail",
|
||||||
@@ -790,11 +791,20 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Project",
|
"label": "Project",
|
||||||
"options": "Project"
|
"options": "Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pos_invoice_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
|
"label": "POS Invoice Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-22 13:40:34.418346",
|
"modified": "2021-01-04 17:34:49.924531",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice Item",
|
"name": "POS Invoice Item",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
for d in self.pos_invoices:
|
for d in self.pos_invoices:
|
||||||
status, docstatus, is_return, return_against = frappe.db.get_value(
|
status, docstatus, is_return, return_against = frappe.db.get_value(
|
||||||
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
|
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
|
||||||
|
|
||||||
bold_pos_invoice = frappe.bold(d.pos_invoice)
|
bold_pos_invoice = frappe.bold(d.pos_invoice)
|
||||||
bold_status = frappe.bold(status)
|
bold_status = frappe.bold(status)
|
||||||
if docstatus != 1:
|
if docstatus != 1:
|
||||||
@@ -58,7 +58,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
sales_invoice, credit_note = "", ""
|
sales_invoice, credit_note = "", ""
|
||||||
if sales:
|
if sales:
|
||||||
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
||||||
|
|
||||||
if returns:
|
if returns:
|
||||||
credit_note = self.process_merging_into_credit_note(returns)
|
credit_note = self.process_merging_into_credit_note(returns)
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
def process_merging_into_sales_invoice(self, data):
|
def process_merging_into_sales_invoice(self, data):
|
||||||
sales_invoice = self.get_new_sales_invoice()
|
sales_invoice = self.get_new_sales_invoice()
|
||||||
|
|
||||||
sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
|
sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
|
||||||
|
|
||||||
sales_invoice.is_consolidated = 1
|
sales_invoice.is_consolidated = 1
|
||||||
@@ -98,19 +98,19 @@ class POSInvoiceMergeLog(Document):
|
|||||||
self.consolidated_credit_note = credit_note.name
|
self.consolidated_credit_note = credit_note.name
|
||||||
|
|
||||||
return credit_note.name
|
return credit_note.name
|
||||||
|
|
||||||
def merge_pos_invoice_into(self, invoice, data):
|
def merge_pos_invoice_into(self, invoice, data):
|
||||||
items, payments, taxes = [], [], []
|
items, payments, taxes = [], [], []
|
||||||
loyalty_amount_sum, loyalty_points_sum = 0, 0
|
loyalty_amount_sum, loyalty_points_sum = 0, 0
|
||||||
for doc in data:
|
for doc in data:
|
||||||
map_doc(doc, invoice, table_map={ "doctype": invoice.doctype })
|
map_doc(doc, invoice, table_map={ "doctype": invoice.doctype })
|
||||||
|
|
||||||
if doc.redeem_loyalty_points:
|
if doc.redeem_loyalty_points:
|
||||||
invoice.loyalty_redemption_account = doc.loyalty_redemption_account
|
invoice.loyalty_redemption_account = doc.loyalty_redemption_account
|
||||||
invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center
|
invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center
|
||||||
loyalty_points_sum += doc.loyalty_points
|
loyalty_points_sum += doc.loyalty_points
|
||||||
loyalty_amount_sum += doc.loyalty_amount
|
loyalty_amount_sum += doc.loyalty_amount
|
||||||
|
|
||||||
for item in doc.get('items'):
|
for item in doc.get('items'):
|
||||||
found = False
|
found = False
|
||||||
for i in items:
|
for i in items:
|
||||||
@@ -118,12 +118,13 @@ class POSInvoiceMergeLog(Document):
|
|||||||
i.uom == item.uom and i.net_rate == item.net_rate):
|
i.uom == item.uom and i.net_rate == item.net_rate):
|
||||||
found = True
|
found = True
|
||||||
i.qty = i.qty + item.qty
|
i.qty = i.qty + item.qty
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
item.rate = item.net_rate
|
item.rate = item.net_rate
|
||||||
item.price_list_rate = 0
|
item.price_list_rate = 0
|
||||||
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
||||||
items.append(si_item)
|
items.append(si_item)
|
||||||
|
|
||||||
for tax in doc.get('taxes'):
|
for tax in doc.get('taxes'):
|
||||||
found = False
|
found = False
|
||||||
for t in taxes:
|
for t in taxes:
|
||||||
@@ -162,7 +163,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
invoice.ignore_pricing_rule = 1
|
invoice.ignore_pricing_rule = 1
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
def get_new_sales_invoice(self):
|
def get_new_sales_invoice(self):
|
||||||
sales_invoice = frappe.new_doc('Sales Invoice')
|
sales_invoice = frappe.new_doc('Sales Invoice')
|
||||||
sales_invoice.customer = self.customer
|
sales_invoice.customer = self.customer
|
||||||
@@ -194,7 +195,7 @@ def get_all_unconsolidated_invoices():
|
|||||||
}
|
}
|
||||||
pos_invoices = frappe.db.get_all('POS Invoice', filters=filters,
|
pos_invoices = frappe.db.get_all('POS Invoice', filters=filters,
|
||||||
fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer'])
|
fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer'])
|
||||||
|
|
||||||
return pos_invoices
|
return pos_invoices
|
||||||
|
|
||||||
def get_invoice_customer_map(pos_invoices):
|
def get_invoice_customer_map(pos_invoices):
|
||||||
@@ -204,7 +205,7 @@ def get_invoice_customer_map(pos_invoices):
|
|||||||
customer = invoice.get('customer')
|
customer = invoice.get('customer')
|
||||||
pos_invoice_customer_map.setdefault(customer, [])
|
pos_invoice_customer_map.setdefault(customer, [])
|
||||||
pos_invoice_customer_map[customer].append(invoice)
|
pos_invoice_customer_map[customer].append(invoice)
|
||||||
|
|
||||||
return pos_invoice_customer_map
|
return pos_invoice_customer_map
|
||||||
|
|
||||||
def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
|
def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
|
|||||||
|
|
||||||
set_gl_entries_by_account(start_date,
|
set_gl_entries_by_account(start_date,
|
||||||
end_date, root.lft, root.rgt, filters,
|
end_date, root.lft, root.rgt, filters,
|
||||||
gl_entries_by_account, accounts_by_name, ignore_closing_entries=False)
|
gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False)
|
||||||
|
|
||||||
calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters)
|
calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters)
|
||||||
accumulate_values_into_parents(accounts, accounts_by_name, companies)
|
accumulate_values_into_parents(accounts, accounts_by_name, companies)
|
||||||
@@ -339,7 +339,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account,
|
def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account,
|
||||||
accounts_by_name, ignore_closing_entries=False):
|
accounts_by_name, accounts, ignore_closing_entries=False):
|
||||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||||
|
|
||||||
company_lft, company_rgt = frappe.get_cached_value('Company',
|
company_lft, company_rgt = frappe.get_cached_value('Company',
|
||||||
@@ -382,15 +382,31 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
|
|||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
key = entry.account_number or entry.account_name
|
key = entry.account_number or entry.account_name
|
||||||
validate_entries(key, entry, accounts_by_name)
|
validate_entries(key, entry, accounts_by_name, accounts)
|
||||||
gl_entries_by_account.setdefault(key, []).append(entry)
|
gl_entries_by_account.setdefault(key, []).append(entry)
|
||||||
|
|
||||||
return gl_entries_by_account
|
return gl_entries_by_account
|
||||||
|
|
||||||
def validate_entries(key, entry, accounts_by_name):
|
def get_account_details(account):
|
||||||
|
return frappe.get_cached_value('Account', account, ['name', 'report_type', 'root_type', 'company',
|
||||||
|
'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1)
|
||||||
|
|
||||||
|
def validate_entries(key, entry, accounts_by_name, accounts):
|
||||||
if key not in accounts_by_name:
|
if key not in accounts_by_name:
|
||||||
field = "Account number" if entry.account_number else "Account name"
|
args = get_account_details(entry.account)
|
||||||
frappe.throw(_("{0} {1} is not present in the parent company").format(field, key))
|
|
||||||
|
if args.parent_account:
|
||||||
|
parent_args = get_account_details(args.parent_account)
|
||||||
|
|
||||||
|
args.update({
|
||||||
|
'lft': parent_args.lft + 1,
|
||||||
|
'rgt': parent_args.rgt - 1,
|
||||||
|
'root_type': parent_args.root_type,
|
||||||
|
'report_type': parent_args.report_type
|
||||||
|
})
|
||||||
|
|
||||||
|
accounts_by_name.setdefault(key, args)
|
||||||
|
accounts.append(args)
|
||||||
|
|
||||||
def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||||
additional_conditions = []
|
additional_conditions = []
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
|
|||||||
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
|
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
|
||||||
if company:
|
if company:
|
||||||
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
|
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
|
||||||
|
|
||||||
if verbose==1: frappe.msgprint(error_msg)
|
if verbose==1: frappe.msgprint(error_msg)
|
||||||
raise FiscalYearError(error_msg)
|
raise FiscalYearError(error_msg)
|
||||||
|
|
||||||
@@ -888,17 +888,22 @@ def get_coa(doctype, parent, is_root, chart=None):
|
|||||||
|
|
||||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
||||||
warehouse_account=None, company=None):
|
warehouse_account=None, company=None):
|
||||||
|
stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items, company)
|
||||||
|
repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company, warehouse_account)
|
||||||
|
|
||||||
|
|
||||||
|
def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, warehouse_account=None):
|
||||||
def _delete_gl_entries(voucher_type, voucher_no):
|
def _delete_gl_entries(voucher_type, voucher_no):
|
||||||
frappe.db.sql("""delete from `tabGL Entry`
|
frappe.db.sql("""delete from `tabGL Entry`
|
||||||
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
|
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
|
||||||
|
|
||||||
|
|
||||||
if not warehouse_account:
|
if not warehouse_account:
|
||||||
warehouse_account = get_warehouse_account_map(company)
|
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(stock_vouchers, posting_date)
|
||||||
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
|
|
||||||
|
|
||||||
for voucher_type, voucher_no in future_stock_vouchers:
|
for voucher_type, voucher_no in stock_vouchers:
|
||||||
existing_gle = gle.get((voucher_type, voucher_no), [])
|
existing_gle = gle.get((voucher_type, voucher_no), [])
|
||||||
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
|
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
|
||||||
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
|
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
|
||||||
@@ -909,7 +914,7 @@ def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for
|
|||||||
else:
|
else:
|
||||||
_delete_gl_entries(voucher_type, voucher_no)
|
_delete_gl_entries(voucher_type, voucher_no)
|
||||||
|
|
||||||
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
|
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None):
|
||||||
future_stock_vouchers = []
|
future_stock_vouchers = []
|
||||||
|
|
||||||
values = []
|
values = []
|
||||||
@@ -922,6 +927,10 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
|
|||||||
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
|
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
|
||||||
values += for_warehouses
|
values += for_warehouses
|
||||||
|
|
||||||
|
if company:
|
||||||
|
condition += " and company = %s"
|
||||||
|
values.append(company)
|
||||||
|
|
||||||
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
|
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
|
||||||
from `tabStock Ledger Entry` sle
|
from `tabStock Ledger Entry` sle
|
||||||
where
|
where
|
||||||
@@ -982,7 +991,7 @@ def check_if_stock_and_account_balance_synced(posting_date, company, voucher_typ
|
|||||||
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format(
|
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format(
|
||||||
stock_bal, account_bal, frappe.bold(account), posting_date)
|
stock_bal, account_bal, frappe.bold(account), posting_date)
|
||||||
error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\
|
error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\
|
||||||
.format(frappe.bold(diff), frappe.bold(posting_date))
|
.format(frappe.bold(diff), frappe.bold(posting_date))
|
||||||
|
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
|
msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
|
||||||
|
|||||||
@@ -127,6 +127,10 @@ class RequestforQuotation(BuyingController):
|
|||||||
'link_doctype': 'Supplier',
|
'link_doctype': 'Supplier',
|
||||||
'link_name': rfq_supplier.supplier
|
'link_name': rfq_supplier.supplier
|
||||||
})
|
})
|
||||||
|
contact.append('email_ids', {
|
||||||
|
'email_id': user.name,
|
||||||
|
'is_primary': 1
|
||||||
|
})
|
||||||
|
|
||||||
if not contact.email_id and not contact.user:
|
if not contact.email_id and not contact.user:
|
||||||
contact.email_id = user.name
|
contact.email_id = user.name
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
"supplier_group",
|
"supplier_group",
|
||||||
"supplier_type",
|
"supplier_type",
|
||||||
"pan",
|
"pan",
|
||||||
"language",
|
|
||||||
"allow_purchase_invoice_creation_without_purchase_order",
|
"allow_purchase_invoice_creation_without_purchase_order",
|
||||||
"allow_purchase_invoice_creation_without_purchase_receipt",
|
"allow_purchase_invoice_creation_without_purchase_receipt",
|
||||||
"disabled",
|
"disabled",
|
||||||
@@ -57,6 +56,7 @@
|
|||||||
"website",
|
"website",
|
||||||
"supplier_details",
|
"supplier_details",
|
||||||
"column_break_30",
|
"column_break_30",
|
||||||
|
"language",
|
||||||
"is_frozen"
|
"is_frozen"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -384,7 +384,7 @@
|
|||||||
"idx": 370,
|
"idx": 370,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-17 23:18:20",
|
"modified": "2021-01-06 19:51:40.939087",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier",
|
"name": "Supplier",
|
||||||
|
|||||||
@@ -1309,45 +1309,28 @@ def add_taxes_from_tax_template(child_item, parent_doc):
|
|||||||
})
|
})
|
||||||
tax_row.db_insert()
|
tax_row.db_insert()
|
||||||
|
|
||||||
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
|
def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item):
|
||||||
"""
|
"""
|
||||||
Returns a Sales Order Item child item containing the default values
|
Returns a Sales/Purchase Order Item child item containing the default values
|
||||||
"""
|
"""
|
||||||
p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
|
p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
|
||||||
child_item = frappe.new_doc('Sales Order Item', p_doc, child_docname)
|
child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
|
||||||
item = frappe.get_doc("Item", trans_item.get('item_code'))
|
item = frappe.get_doc("Item", trans_item.get('item_code'))
|
||||||
child_item.item_code = item.item_code
|
for field in ("item_code", "item_name", "description", "item_group"):
|
||||||
child_item.item_name = item.item_name
|
child_item.update({field: item.get(field)})
|
||||||
child_item.description = item.description
|
date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date"
|
||||||
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
|
child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)})
|
||||||
child_item.uom = trans_item.get("uom") or item.stock_uom
|
child_item.uom = trans_item.get("uom") or item.stock_uom
|
||||||
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
|
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
|
||||||
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
|
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
|
||||||
set_child_tax_template_and_map(item, child_item, p_doc)
|
if child_doctype == "Purchase Order Item":
|
||||||
add_taxes_from_tax_template(child_item, p_doc)
|
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
||||||
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
||||||
if not child_item.warehouse:
|
if child_doctype == "Sales Order Item":
|
||||||
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
||||||
.format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
|
if not child_item.warehouse:
|
||||||
return child_item
|
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
||||||
|
.format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
|
||||||
|
|
||||||
def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
|
|
||||||
"""
|
|
||||||
Returns a Purchase Order Item child item containing the default values
|
|
||||||
"""
|
|
||||||
p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
|
|
||||||
child_item = frappe.new_doc('Purchase Order Item', p_doc, child_docname)
|
|
||||||
item = frappe.get_doc("Item", trans_item.get('item_code'))
|
|
||||||
child_item.item_code = item.item_code
|
|
||||||
child_item.item_name = item.item_name
|
|
||||||
child_item.description = item.description
|
|
||||||
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
|
|
||||||
child_item.uom = trans_item.get("uom") or item.stock_uom
|
|
||||||
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
|
|
||||||
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
|
|
||||||
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
|
||||||
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
|
||||||
set_child_tax_template_and_map(item, child_item, p_doc)
|
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||||
add_taxes_from_tax_template(child_item, p_doc)
|
add_taxes_from_tax_template(child_item, p_doc)
|
||||||
return child_item
|
return child_item
|
||||||
@@ -1411,8 +1394,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_new_child_item(item_row):
|
def get_new_child_item(item_row):
|
||||||
new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults
|
child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
|
||||||
return new_child_function(parent_doctype, parent_doctype_name, child_docname, item_row)
|
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
|
||||||
|
|
||||||
def validate_quantity(child_item, d):
|
def validate_quantity(child_item, d):
|
||||||
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
|
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
|
||||||
|
|||||||
@@ -204,8 +204,6 @@ def get_already_returned_items(doc):
|
|||||||
return items
|
return items
|
||||||
|
|
||||||
def get_returned_qty_map_for_row(row_name, doctype):
|
def get_returned_qty_map_for_row(row_name, doctype):
|
||||||
if doctype == "POS Invoice": return {}
|
|
||||||
|
|
||||||
child_doctype = doctype + " Item"
|
child_doctype = doctype + " Item"
|
||||||
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
|
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
|
||||||
|
|
||||||
@@ -354,7 +352,12 @@ 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.dn_detail = source_doc.dn_detail
|
target_doc.dn_detail = source_doc.dn_detail
|
||||||
target_doc.expense_account = source_doc.expense_account
|
target_doc.expense_account = source_doc.expense_account
|
||||||
target_doc.sales_invoice_item = source_doc.name
|
|
||||||
|
if doctype == "Sales Invoice":
|
||||||
|
target_doc.sales_invoice_item = source_doc.name
|
||||||
|
else:
|
||||||
|
target_doc.pos_invoice_item = source_doc.name
|
||||||
|
|
||||||
target_doc.price_list_rate = 0
|
target_doc.price_list_rate = 0
|
||||||
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
|
||||||
|
|||||||
@@ -488,13 +488,12 @@ class StockController(AccountsController):
|
|||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"company": self.company
|
"company": self.company
|
||||||
})
|
})
|
||||||
|
|
||||||
if check_if_future_sle_exists(args):
|
if check_if_future_sle_exists(args):
|
||||||
create_repost_item_valuation_entry(args)
|
create_repost_item_valuation_entry(args)
|
||||||
elif not is_reposting_pending():
|
elif not is_reposting_pending():
|
||||||
check_if_stock_and_account_balance_synced(self.posting_date,
|
check_if_stock_and_account_balance_synced(self.posting_date,
|
||||||
self.company, self.doctype, self.name)
|
self.company, self.doctype, self.name)
|
||||||
|
|
||||||
def is_reposting_pending():
|
def is_reposting_pending():
|
||||||
return frappe.db.exists("Repost Item Valuation",
|
return frappe.db.exists("Repost Item Valuation",
|
||||||
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_ra
|
|||||||
class calculate_taxes_and_totals(object):
|
class calculate_taxes_and_totals(object):
|
||||||
def __init__(self, doc):
|
def __init__(self, doc):
|
||||||
self.doc = doc
|
self.doc = doc
|
||||||
|
frappe.flags.round_off_applicable_accounts = []
|
||||||
|
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
|
||||||
self.calculate()
|
self.calculate()
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self):
|
||||||
@@ -332,10 +334,18 @@ class calculate_taxes_and_totals(object):
|
|||||||
elif tax.charge_type == "On Item Quantity":
|
elif tax.charge_type == "On Item Quantity":
|
||||||
current_tax_amount = tax_rate * item.qty
|
current_tax_amount = tax_rate * item.qty
|
||||||
|
|
||||||
|
current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
|
||||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||||
|
|
||||||
return current_tax_amount
|
return current_tax_amount
|
||||||
|
|
||||||
|
def get_final_current_tax_amount(self, tax, current_tax_amount):
|
||||||
|
# Some countries need individual tax components to be rounded
|
||||||
|
# Handeled via regional doctypess
|
||||||
|
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||||
|
current_tax_amount = round(current_tax_amount, 0)
|
||||||
|
return current_tax_amount
|
||||||
|
|
||||||
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
|
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
|
||||||
# store tax breakup for each item
|
# store tax breakup for each item
|
||||||
key = item.item_code or item.item_name
|
key = item.item_code or item.item_name
|
||||||
@@ -693,6 +703,15 @@ def get_itemised_tax_breakup_html(doc):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_round_off_applicable_accounts(company, account_list):
|
||||||
|
account_list = get_regional_round_off_accounts(company, account_list)
|
||||||
|
|
||||||
|
return account_list
|
||||||
|
|
||||||
|
@erpnext.allow_regional
|
||||||
|
def get_regional_round_off_accounts(company, account_list):
|
||||||
|
pass
|
||||||
|
|
||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def update_itemised_tax_data(doc):
|
def update_itemised_tax_data(doc):
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
"phone",
|
"phone",
|
||||||
"mobile_no",
|
"mobile_no",
|
||||||
"fax",
|
"fax",
|
||||||
|
"website",
|
||||||
"more_info",
|
"more_info",
|
||||||
"type",
|
"type",
|
||||||
"market_segment",
|
"market_segment",
|
||||||
@@ -56,8 +57,8 @@
|
|||||||
"request_type",
|
"request_type",
|
||||||
"column_break3",
|
"column_break3",
|
||||||
"company",
|
"company",
|
||||||
"website",
|
|
||||||
"territory",
|
"territory",
|
||||||
|
"language",
|
||||||
"unsubscribed",
|
"unsubscribed",
|
||||||
"blog_subscriber",
|
"blog_subscriber",
|
||||||
"title"
|
"title"
|
||||||
@@ -447,13 +448,19 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Address Type",
|
"label": "Address Type",
|
||||||
"options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nCurrent\nPermanent\nOther"
|
"options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nCurrent\nPermanent\nOther"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "language",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Print Language",
|
||||||
|
"options": "Language"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 5,
|
"idx": 5,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-13 15:24:00.094811",
|
"modified": "2021-01-06 19:39:58.748978",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Lead",
|
"name": "Lead",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"campaign",
|
"campaign",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"transaction_date",
|
"transaction_date",
|
||||||
|
"language",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
"lost_reasons"
|
"lost_reasons"
|
||||||
],
|
],
|
||||||
@@ -419,12 +420,18 @@
|
|||||||
"fieldtype": "Duration",
|
"fieldtype": "Duration",
|
||||||
"label": "First Response Time",
|
"label": "First Response Time",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "language",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Print Language",
|
||||||
|
"options": "Language"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-info-sign",
|
"icon": "fa fa-info-sign",
|
||||||
"idx": 195,
|
"idx": 195,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-12 17:34:35.066961",
|
"modified": "2021-01-06 19:42:46.190051",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Opportunity",
|
"name": "Opportunity",
|
||||||
|
|||||||
@@ -2,4 +2,82 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Appointment Type', {
|
frappe.ui.form.on('Appointment Type', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
frm.set_query('price_list', function() {
|
||||||
|
return {
|
||||||
|
filters: {'selling': 1}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('medical_department', 'items', function(doc) {
|
||||||
|
let item_list = doc.items.map(({medical_department}) => medical_department);
|
||||||
|
return {
|
||||||
|
filters: [
|
||||||
|
['Medical Department', 'name', 'not in', item_list]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('op_consulting_charge_item', 'items', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_stock_item: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('inpatient_visit_charge_item', 'items', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_stock_item: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Appointment Type Service Item', {
|
||||||
|
op_consulting_charge_item: function(frm, cdt, cdn) {
|
||||||
|
let d = locals[cdt][cdn];
|
||||||
|
if (frm.doc.price_list && d.op_consulting_charge_item) {
|
||||||
|
frappe.call({
|
||||||
|
'method': 'frappe.client.get_value',
|
||||||
|
args: {
|
||||||
|
'doctype': 'Item Price',
|
||||||
|
'filters': {
|
||||||
|
'item_code': d.op_consulting_charge_item,
|
||||||
|
'price_list': frm.doc.price_list
|
||||||
|
},
|
||||||
|
'fieldname': ['price_list_rate']
|
||||||
|
},
|
||||||
|
callback: function(data) {
|
||||||
|
if (data.message.price_list_rate) {
|
||||||
|
frappe.model.set_value(cdt, cdn, 'op_consulting_charge', data.message.price_list_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
inpatient_visit_charge_item: function(frm, cdt, cdn) {
|
||||||
|
let d = locals[cdt][cdn];
|
||||||
|
if (frm.doc.price_list && d.inpatient_visit_charge_item) {
|
||||||
|
frappe.call({
|
||||||
|
'method': 'frappe.client.get_value',
|
||||||
|
args: {
|
||||||
|
'doctype': 'Item Price',
|
||||||
|
'filters': {
|
||||||
|
'item_code': d.inpatient_visit_charge_item,
|
||||||
|
'price_list': frm.doc.price_list
|
||||||
|
},
|
||||||
|
'fieldname': ['price_list_rate']
|
||||||
|
},
|
||||||
|
callback: function (data) {
|
||||||
|
if (data.message.price_list_rate) {
|
||||||
|
frappe.model.set_value(cdt, cdn, 'inpatient_visit_charge', data.message.price_list_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -12,7 +12,10 @@
|
|||||||
"appointment_type",
|
"appointment_type",
|
||||||
"ip",
|
"ip",
|
||||||
"default_duration",
|
"default_duration",
|
||||||
"color"
|
"color",
|
||||||
|
"billing_section",
|
||||||
|
"price_list",
|
||||||
|
"items"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -52,10 +55,27 @@
|
|||||||
"label": "Color",
|
"label": "Color",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"report_hide": 1
|
"report_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Billing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "price_list",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Price List",
|
||||||
|
"options": "Price List"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "items",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Appointment Type Service Items",
|
||||||
|
"options": "Appointment Type Service Item"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-03 21:06:05.833050",
|
"modified": "2021-01-22 09:41:05.010524",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Appointment Type",
|
"name": "Appointment Type",
|
||||||
|
|||||||
@@ -4,6 +4,53 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
import frappe
|
||||||
|
|
||||||
class AppointmentType(Document):
|
class AppointmentType(Document):
|
||||||
pass
|
def validate(self):
|
||||||
|
if self.items and self.price_list:
|
||||||
|
for item in self.items:
|
||||||
|
existing_op_item_price = frappe.db.exists('Item Price', {
|
||||||
|
'item_code': item.op_consulting_charge_item,
|
||||||
|
'price_list': self.price_list
|
||||||
|
})
|
||||||
|
|
||||||
|
if not existing_op_item_price and item.op_consulting_charge_item and item.op_consulting_charge:
|
||||||
|
make_item_price(self.price_list, item.op_consulting_charge_item, item.op_consulting_charge)
|
||||||
|
|
||||||
|
existing_ip_item_price = frappe.db.exists('Item Price', {
|
||||||
|
'item_code': item.inpatient_visit_charge_item,
|
||||||
|
'price_list': self.price_list
|
||||||
|
})
|
||||||
|
|
||||||
|
if not existing_ip_item_price and item.inpatient_visit_charge_item and item.inpatient_visit_charge:
|
||||||
|
make_item_price(self.price_list, item.inpatient_visit_charge_item, item.inpatient_visit_charge)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_service_item_based_on_department(appointment_type, department):
|
||||||
|
item_list = frappe.db.get_value('Appointment Type Service Item',
|
||||||
|
filters = {'medical_department': department, 'parent': appointment_type},
|
||||||
|
fieldname = ['op_consulting_charge_item',
|
||||||
|
'inpatient_visit_charge_item', 'op_consulting_charge', 'inpatient_visit_charge'],
|
||||||
|
as_dict = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# if department wise items are not set up
|
||||||
|
# use the generic items
|
||||||
|
if not item_list:
|
||||||
|
item_list = frappe.db.get_value('Appointment Type Service Item',
|
||||||
|
filters = {'parent': appointment_type},
|
||||||
|
fieldname = ['op_consulting_charge_item',
|
||||||
|
'inpatient_visit_charge_item', 'op_consulting_charge', 'inpatient_visit_charge'],
|
||||||
|
as_dict = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
return item_list
|
||||||
|
|
||||||
|
def make_item_price(price_list, item, item_price):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Item Price',
|
||||||
|
'price_list': price_list,
|
||||||
|
'item_code': item,
|
||||||
|
'price_list_rate': item_price
|
||||||
|
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-01-22 09:34:53.373105",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"medical_department",
|
||||||
|
"op_consulting_charge_item",
|
||||||
|
"op_consulting_charge",
|
||||||
|
"column_break_4",
|
||||||
|
"inpatient_visit_charge_item",
|
||||||
|
"inpatient_visit_charge"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "medical_department",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Medical Department",
|
||||||
|
"options": "Medical Department"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "op_consulting_charge_item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Out Patient Consulting Charge Item",
|
||||||
|
"options": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "op_consulting_charge",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Out Patient Consulting Charge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "inpatient_visit_charge_item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Inpatient Visit Charge Item",
|
||||||
|
"options": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "inpatient_visit_charge",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Inpatient Visit Charge Item"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-01-22 09:35:26.503443",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Appointment Type Service Item",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, 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 AppointmentTypeServiceItem(Document):
|
||||||
|
pass
|
||||||
@@ -121,6 +121,7 @@ class ClinicalProcedure(Document):
|
|||||||
|
|
||||||
stock_entry.stock_entry_type = 'Material Receipt'
|
stock_entry.stock_entry_type = 'Material Receipt'
|
||||||
stock_entry.to_warehouse = self.warehouse
|
stock_entry.to_warehouse = self.warehouse
|
||||||
|
stock_entry.company = self.company
|
||||||
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', self.company)
|
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', self.company)
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if item.qty > item.actual_qty:
|
if item.qty > item.actual_qty:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2017, ESS LLP and Contributors
|
# Copyright (c) 2017, ESS LLP and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
@@ -60,6 +60,7 @@ def create_procedure(procedure_template, patient, practitioner):
|
|||||||
procedure.practitioner = practitioner
|
procedure.practitioner = practitioner
|
||||||
procedure.consume_stock = procedure_template.allow_stock_consumption
|
procedure.consume_stock = procedure_template.allow_stock_consumption
|
||||||
procedure.items = procedure_template.items
|
procedure.items = procedure_template.items
|
||||||
procedure.warehouse = frappe.db.get_single_value('Stock Settings', 'default_warehouse')
|
procedure.company = "_Test Company"
|
||||||
|
procedure.warehouse = "_Test Warehouse - _TC"
|
||||||
procedure.submit()
|
procedure.submit()
|
||||||
return procedure
|
return procedure
|
||||||
@@ -159,6 +159,7 @@
|
|||||||
"fieldname": "op_consulting_charge",
|
"fieldname": "op_consulting_charge",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Out Patient Consulting Charge",
|
"label": "Out Patient Consulting Charge",
|
||||||
|
"mandatory_depends_on": "op_consulting_charge_item",
|
||||||
"options": "Currency"
|
"options": "Currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -174,7 +175,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "inpatient_visit_charge",
|
"fieldname": "inpatient_visit_charge",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Inpatient Visit Charge"
|
"label": "Inpatient Visit Charge",
|
||||||
|
"mandatory_depends_on": "inpatient_visit_charge_item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: !doc.__islocal",
|
"depends_on": "eval: !doc.__islocal",
|
||||||
@@ -280,7 +282,7 @@
|
|||||||
],
|
],
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-06 13:44:24.759623",
|
"modified": "2021-01-22 10:14:43.187675",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Healthcare Practitioner",
|
"name": "Healthcare Practitioner",
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ frappe.ui.form.on('Patient Appointment', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('practitioner', function() {
|
frm.set_query('practitioner', function() {
|
||||||
return {
|
if (frm.doc.department) {
|
||||||
filters: {
|
return {
|
||||||
'department': frm.doc.department
|
filters: {
|
||||||
}
|
'department': frm.doc.department
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('service_unit', function() {
|
frm.set_query('service_unit', function() {
|
||||||
@@ -140,6 +142,20 @@ frappe.ui.form.on('Patient Appointment', {
|
|||||||
patient: function(frm) {
|
patient: function(frm) {
|
||||||
if (frm.doc.patient) {
|
if (frm.doc.patient) {
|
||||||
frm.trigger('toggle_payment_fields');
|
frm.trigger('toggle_payment_fields');
|
||||||
|
frappe.call({
|
||||||
|
method: 'frappe.client.get',
|
||||||
|
args: {
|
||||||
|
doctype: 'Patient',
|
||||||
|
name: frm.doc.patient
|
||||||
|
},
|
||||||
|
callback: function (data) {
|
||||||
|
let age = null;
|
||||||
|
if (data.message.dob) {
|
||||||
|
age = calculate_age(data.message.dob);
|
||||||
|
}
|
||||||
|
frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', age);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
frm.set_value('patient_name', '');
|
frm.set_value('patient_name', '');
|
||||||
frm.set_value('patient_sex', '');
|
frm.set_value('patient_sex', '');
|
||||||
@@ -148,6 +164,37 @@ frappe.ui.form.on('Patient Appointment', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
practitioner: function(frm) {
|
||||||
|
if (frm.doc.practitioner ) {
|
||||||
|
frm.events.set_payment_details(frm);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
appointment_type: function(frm) {
|
||||||
|
if (frm.doc.appointment_type) {
|
||||||
|
frm.events.set_payment_details(frm);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
set_payment_details: function(frm) {
|
||||||
|
frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing').then(val => {
|
||||||
|
if (val) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.healthcare.utils.get_service_item_and_practitioner_charge',
|
||||||
|
args: {
|
||||||
|
doc: frm.doc
|
||||||
|
},
|
||||||
|
callback: function(data) {
|
||||||
|
if (data.message) {
|
||||||
|
frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.practitioner_charge);
|
||||||
|
frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.service_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
therapy_plan: function(frm) {
|
therapy_plan: function(frm) {
|
||||||
frm.trigger('set_therapy_type_filter');
|
frm.trigger('set_therapy_type_filter');
|
||||||
},
|
},
|
||||||
@@ -190,14 +237,18 @@ frappe.ui.form.on('Patient Appointment', {
|
|||||||
// show payment fields as non-mandatory
|
// show payment fields as non-mandatory
|
||||||
frm.toggle_display('mode_of_payment', 0);
|
frm.toggle_display('mode_of_payment', 0);
|
||||||
frm.toggle_display('paid_amount', 0);
|
frm.toggle_display('paid_amount', 0);
|
||||||
|
frm.toggle_display('billing_item', 0);
|
||||||
frm.toggle_reqd('mode_of_payment', 0);
|
frm.toggle_reqd('mode_of_payment', 0);
|
||||||
frm.toggle_reqd('paid_amount', 0);
|
frm.toggle_reqd('paid_amount', 0);
|
||||||
|
frm.toggle_reqd('billing_item', 0);
|
||||||
} else {
|
} else {
|
||||||
// if automated appointment invoicing is disabled, hide fields
|
// if automated appointment invoicing is disabled, hide fields
|
||||||
frm.toggle_display('mode_of_payment', data.message ? 1 : 0);
|
frm.toggle_display('mode_of_payment', data.message ? 1 : 0);
|
||||||
frm.toggle_display('paid_amount', data.message ? 1 : 0);
|
frm.toggle_display('paid_amount', data.message ? 1 : 0);
|
||||||
|
frm.toggle_display('billing_item', data.message ? 1 : 0);
|
||||||
frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0);
|
frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0);
|
||||||
frm.toggle_reqd('paid_amount', data.message ? 1 :0);
|
frm.toggle_reqd('paid_amount', data.message ? 1 :0);
|
||||||
|
frm.toggle_reqd('billing_item', data.message ? 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -540,57 +591,6 @@ let update_status = function(frm, status){
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
frappe.ui.form.on('Patient Appointment', 'practitioner', function(frm) {
|
|
||||||
if (frm.doc.practitioner) {
|
|
||||||
frappe.call({
|
|
||||||
method: 'frappe.client.get',
|
|
||||||
args: {
|
|
||||||
doctype: 'Healthcare Practitioner',
|
|
||||||
name: frm.doc.practitioner
|
|
||||||
},
|
|
||||||
callback: function (data) {
|
|
||||||
frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department);
|
|
||||||
frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.op_consulting_charge);
|
|
||||||
frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frappe.ui.form.on('Patient Appointment', 'patient', function(frm) {
|
|
||||||
if (frm.doc.patient) {
|
|
||||||
frappe.call({
|
|
||||||
method: 'frappe.client.get',
|
|
||||||
args: {
|
|
||||||
doctype: 'Patient',
|
|
||||||
name: frm.doc.patient
|
|
||||||
},
|
|
||||||
callback: function (data) {
|
|
||||||
let age = null;
|
|
||||||
if (data.message.dob) {
|
|
||||||
age = calculate_age(data.message.dob);
|
|
||||||
}
|
|
||||||
frappe.model.set_value(frm.doctype,frm.docname, 'patient_age', age);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frappe.ui.form.on('Patient Appointment', 'appointment_type', function(frm) {
|
|
||||||
if (frm.doc.appointment_type) {
|
|
||||||
frappe.call({
|
|
||||||
method: 'frappe.client.get',
|
|
||||||
args: {
|
|
||||||
doctype: 'Appointment Type',
|
|
||||||
name: frm.doc.appointment_type
|
|
||||||
},
|
|
||||||
callback: function(data) {
|
|
||||||
frappe.model.set_value(frm.doctype,frm.docname, 'duration',data.message.default_duration);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let calculate_age = function(birth) {
|
let calculate_age = function(birth) {
|
||||||
let ageMS = Date.parse(Date()) - Date.parse(birth);
|
let ageMS = Date.parse(Date()) - Date.parse(birth);
|
||||||
let age = new Date();
|
let age = new Date();
|
||||||
|
|||||||
@@ -19,19 +19,19 @@
|
|||||||
"inpatient_record",
|
"inpatient_record",
|
||||||
"column_break_1",
|
"column_break_1",
|
||||||
"company",
|
"company",
|
||||||
|
"practitioner",
|
||||||
|
"practitioner_name",
|
||||||
|
"department",
|
||||||
"service_unit",
|
"service_unit",
|
||||||
|
"section_break_12",
|
||||||
|
"appointment_type",
|
||||||
|
"duration",
|
||||||
"procedure_template",
|
"procedure_template",
|
||||||
"get_procedure_from_encounter",
|
"get_procedure_from_encounter",
|
||||||
"procedure_prescription",
|
"procedure_prescription",
|
||||||
"therapy_plan",
|
"therapy_plan",
|
||||||
"therapy_type",
|
"therapy_type",
|
||||||
"get_prescribed_therapies",
|
"get_prescribed_therapies",
|
||||||
"practitioner",
|
|
||||||
"practitioner_name",
|
|
||||||
"department",
|
|
||||||
"section_break_12",
|
|
||||||
"appointment_type",
|
|
||||||
"duration",
|
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"appointment_date",
|
"appointment_date",
|
||||||
"appointment_time",
|
"appointment_time",
|
||||||
@@ -79,6 +79,7 @@
|
|||||||
"set_only_once": 1
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "appointment_type.default_duration",
|
||||||
"fieldname": "duration",
|
"fieldname": "duration",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
@@ -144,7 +145,6 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Healthcare Practitioner",
|
"label": "Healthcare Practitioner",
|
||||||
"options": "Healthcare Practitioner",
|
"options": "Healthcare Practitioner",
|
||||||
"read_only": 1,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1,
|
"search_index": 1,
|
||||||
"set_only_once": 1
|
"set_only_once": 1
|
||||||
@@ -158,7 +158,6 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Department",
|
"label": "Department",
|
||||||
"options": "Medical Department",
|
"options": "Medical Department",
|
||||||
"read_only": 1,
|
|
||||||
"search_index": 1,
|
"search_index": 1,
|
||||||
"set_only_once": 1
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
@@ -227,12 +226,14 @@
|
|||||||
"fieldname": "mode_of_payment",
|
"fieldname": "mode_of_payment",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Mode of Payment",
|
"label": "Mode of Payment",
|
||||||
"options": "Mode of Payment"
|
"options": "Mode of Payment",
|
||||||
|
"read_only_depends_on": "invoiced"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "paid_amount",
|
"fieldname": "paid_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Paid Amount"
|
"label": "Paid Amount",
|
||||||
|
"read_only_depends_on": "invoiced"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_2",
|
"fieldname": "column_break_2",
|
||||||
@@ -302,7 +303,8 @@
|
|||||||
"fieldname": "therapy_plan",
|
"fieldname": "therapy_plan",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Therapy Plan",
|
"label": "Therapy Plan",
|
||||||
"options": "Therapy Plan"
|
"options": "Therapy Plan",
|
||||||
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "ref_sales_invoice",
|
"fieldname": "ref_sales_invoice",
|
||||||
@@ -347,7 +349,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-16 13:16:58.578503",
|
"modified": "2021-02-08 13:13:15.116833",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Patient Appointment",
|
"name": "Patient Appointment",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class PatientAppointment(Document):
|
|||||||
|
|
||||||
def after_insert(self):
|
def after_insert(self):
|
||||||
self.update_prescription_details()
|
self.update_prescription_details()
|
||||||
|
self.set_payment_details()
|
||||||
invoice_appointment(self)
|
invoice_appointment(self)
|
||||||
self.update_fee_validity()
|
self.update_fee_validity()
|
||||||
send_confirmation_msg(self)
|
send_confirmation_msg(self)
|
||||||
@@ -85,6 +86,13 @@ class PatientAppointment(Document):
|
|||||||
def set_appointment_datetime(self):
|
def set_appointment_datetime(self):
|
||||||
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")
|
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")
|
||||||
|
|
||||||
|
def set_payment_details(self):
|
||||||
|
if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'):
|
||||||
|
details = get_service_item_and_practitioner_charge(self)
|
||||||
|
self.db_set('billing_item', details.get('service_item'))
|
||||||
|
if not self.paid_amount:
|
||||||
|
self.db_set('paid_amount', details.get('practitioner_charge'))
|
||||||
|
|
||||||
def validate_customer_created(self):
|
def validate_customer_created(self):
|
||||||
if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'):
|
if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'):
|
||||||
if not frappe.db.get_value('Patient', self.patient, 'customer'):
|
if not frappe.db.get_value('Patient', self.patient, 'customer'):
|
||||||
@@ -148,31 +156,37 @@ def invoice_appointment(appointment_doc):
|
|||||||
fee_validity = None
|
fee_validity = None
|
||||||
|
|
||||||
if automate_invoicing and not appointment_invoiced and not fee_validity:
|
if automate_invoicing and not appointment_invoiced and not fee_validity:
|
||||||
sales_invoice = frappe.new_doc('Sales Invoice')
|
create_sales_invoice(appointment_doc)
|
||||||
sales_invoice.patient = appointment_doc.patient
|
|
||||||
sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer')
|
|
||||||
sales_invoice.appointment = appointment_doc.name
|
|
||||||
sales_invoice.due_date = getdate()
|
|
||||||
sales_invoice.company = appointment_doc.company
|
|
||||||
sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
|
|
||||||
|
|
||||||
item = sales_invoice.append('items', {})
|
|
||||||
item = get_appointment_item(appointment_doc, item)
|
|
||||||
|
|
||||||
# Add payments if payment details are supplied else proceed to create invoice as Unpaid
|
def create_sales_invoice(appointment_doc):
|
||||||
if appointment_doc.mode_of_payment and appointment_doc.paid_amount:
|
sales_invoice = frappe.new_doc('Sales Invoice')
|
||||||
sales_invoice.is_pos = 1
|
sales_invoice.patient = appointment_doc.patient
|
||||||
payment = sales_invoice.append('payments', {})
|
sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer')
|
||||||
payment.mode_of_payment = appointment_doc.mode_of_payment
|
sales_invoice.appointment = appointment_doc.name
|
||||||
payment.amount = appointment_doc.paid_amount
|
sales_invoice.due_date = getdate()
|
||||||
|
sales_invoice.company = appointment_doc.company
|
||||||
|
sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
|
||||||
|
|
||||||
sales_invoice.set_missing_values(for_validate=True)
|
item = sales_invoice.append('items', {})
|
||||||
sales_invoice.flags.ignore_mandatory = True
|
item = get_appointment_item(appointment_doc, item)
|
||||||
sales_invoice.save(ignore_permissions=True)
|
|
||||||
sales_invoice.submit()
|
# Add payments if payment details are supplied else proceed to create invoice as Unpaid
|
||||||
frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True)
|
if appointment_doc.mode_of_payment and appointment_doc.paid_amount:
|
||||||
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1)
|
sales_invoice.is_pos = 1
|
||||||
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name)
|
payment = sales_invoice.append('payments', {})
|
||||||
|
payment.mode_of_payment = appointment_doc.mode_of_payment
|
||||||
|
payment.amount = appointment_doc.paid_amount
|
||||||
|
|
||||||
|
sales_invoice.set_missing_values(for_validate=True)
|
||||||
|
sales_invoice.flags.ignore_mandatory = True
|
||||||
|
sales_invoice.save(ignore_permissions=True)
|
||||||
|
sales_invoice.submit()
|
||||||
|
frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True)
|
||||||
|
frappe.db.set_value('Patient Appointment', appointment_doc.name, {
|
||||||
|
'invoiced': 1,
|
||||||
|
'ref_sales_invoice': sales_invoice.name
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def check_is_new_patient(patient, name=None):
|
def check_is_new_patient(patient, name=None):
|
||||||
@@ -187,13 +201,14 @@ def check_is_new_patient(patient, name=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_appointment_item(appointment_doc, item):
|
def get_appointment_item(appointment_doc, item):
|
||||||
service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment_doc)
|
details = get_service_item_and_practitioner_charge(appointment_doc)
|
||||||
item.item_code = service_item
|
charge = appointment_doc.paid_amount or details.get('practitioner_charge')
|
||||||
|
item.item_code = details.get('service_item')
|
||||||
item.description = _('Consulting Charges: {0}').format(appointment_doc.practitioner)
|
item.description = _('Consulting Charges: {0}').format(appointment_doc.practitioner)
|
||||||
item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company)
|
item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company)
|
||||||
item.cost_center = frappe.get_cached_value('Company', appointment_doc.company, 'cost_center')
|
item.cost_center = frappe.get_cached_value('Company', appointment_doc.company, 'cost_center')
|
||||||
item.rate = practitioner_charge
|
item.rate = charge
|
||||||
item.amount = practitioner_charge
|
item.amount = charge
|
||||||
item.qty = 1
|
item.qty = 1
|
||||||
item.reference_dt = 'Patient Appointment'
|
item.reference_dt = 'Patient Appointment'
|
||||||
item.reference_dn = appointment_doc.name
|
item.reference_dn = appointment_doc.name
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ class TestPatientAppointment(unittest.TestCase):
|
|||||||
patient, medical_department, practitioner = create_healthcare_docs()
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
|
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
|
||||||
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1)
|
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1)
|
||||||
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1)
|
appointment.reload()
|
||||||
|
self.assertEqual(appointment.invoiced, 1)
|
||||||
encounter = make_encounter(appointment.name)
|
encounter = make_encounter(appointment.name)
|
||||||
self.assertTrue(encounter)
|
self.assertTrue(encounter)
|
||||||
self.assertEqual(encounter.company, appointment.company)
|
self.assertEqual(encounter.company, appointment.company)
|
||||||
@@ -41,7 +42,7 @@ class TestPatientAppointment(unittest.TestCase):
|
|||||||
# invoiced flag mapped from appointment
|
# invoiced flag mapped from appointment
|
||||||
self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'))
|
self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'))
|
||||||
|
|
||||||
def test_invoicing(self):
|
def test_auto_invoicing(self):
|
||||||
patient, medical_department, practitioner = create_healthcare_docs()
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
|
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
|
||||||
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
|
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
|
||||||
@@ -57,6 +58,50 @@ class TestPatientAppointment(unittest.TestCase):
|
|||||||
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient)
|
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient)
|
||||||
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
|
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
|
||||||
|
|
||||||
|
def test_auto_invoicing_based_on_department(self):
|
||||||
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
|
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
|
||||||
|
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
|
||||||
|
appointment_type = create_appointment_type()
|
||||||
|
|
||||||
|
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2),
|
||||||
|
invoice=1, appointment_type=appointment_type.name, department='_Test Medical Department')
|
||||||
|
appointment.reload()
|
||||||
|
|
||||||
|
self.assertEqual(appointment.invoiced, 1)
|
||||||
|
self.assertEqual(appointment.billing_item, 'HLC-SI-001')
|
||||||
|
self.assertEqual(appointment.paid_amount, 200)
|
||||||
|
|
||||||
|
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
|
||||||
|
self.assertTrue(sales_invoice_name)
|
||||||
|
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
|
||||||
|
|
||||||
|
def test_auto_invoicing_according_to_appointment_type_charge(self):
|
||||||
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
|
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
|
||||||
|
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
|
||||||
|
|
||||||
|
item = create_healthcare_service_items()
|
||||||
|
items = [{
|
||||||
|
'op_consulting_charge_item': item,
|
||||||
|
'op_consulting_charge': 300
|
||||||
|
}]
|
||||||
|
appointment_type = create_appointment_type(args={
|
||||||
|
'name': 'Generic Appointment Type charge',
|
||||||
|
'items': items
|
||||||
|
})
|
||||||
|
|
||||||
|
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2),
|
||||||
|
invoice=1, appointment_type=appointment_type.name)
|
||||||
|
appointment.reload()
|
||||||
|
|
||||||
|
self.assertEqual(appointment.invoiced, 1)
|
||||||
|
self.assertEqual(appointment.billing_item, item)
|
||||||
|
self.assertEqual(appointment.paid_amount, 300)
|
||||||
|
|
||||||
|
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
|
||||||
|
self.assertTrue(sales_invoice_name)
|
||||||
|
|
||||||
def test_appointment_cancel(self):
|
def test_appointment_cancel(self):
|
||||||
patient, medical_department, practitioner = create_healthcare_docs()
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
|
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
|
||||||
@@ -178,14 +223,15 @@ def create_encounter(appointment):
|
|||||||
encounter.submit()
|
encounter.submit()
|
||||||
return encounter
|
return encounter
|
||||||
|
|
||||||
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1):
|
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0,
|
||||||
|
service_unit=None, appointment_type=None, save=1, department=None):
|
||||||
item = create_healthcare_service_items()
|
item = create_healthcare_service_items()
|
||||||
frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
|
frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
|
||||||
frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
|
frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
|
||||||
appointment = frappe.new_doc('Patient Appointment')
|
appointment = frappe.new_doc('Patient Appointment')
|
||||||
appointment.patient = patient
|
appointment.patient = patient
|
||||||
appointment.practitioner = practitioner
|
appointment.practitioner = practitioner
|
||||||
appointment.department = '_Test Medical Department'
|
appointment.department = department or '_Test Medical Department'
|
||||||
appointment.appointment_date = appointment_date
|
appointment.appointment_date = appointment_date
|
||||||
appointment.company = '_Test Company'
|
appointment.company = '_Test Company'
|
||||||
appointment.duration = 15
|
appointment.duration = 15
|
||||||
@@ -193,7 +239,8 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
|
|||||||
appointment.service_unit = service_unit
|
appointment.service_unit = service_unit
|
||||||
if invoice:
|
if invoice:
|
||||||
appointment.mode_of_payment = 'Cash'
|
appointment.mode_of_payment = 'Cash'
|
||||||
appointment.paid_amount = 500
|
if appointment_type:
|
||||||
|
appointment.appointment_type = appointment_type
|
||||||
if procedure_template:
|
if procedure_template:
|
||||||
appointment.procedure_template = create_clinical_procedure_template().get('name')
|
appointment.procedure_template = create_clinical_procedure_template().get('name')
|
||||||
if save:
|
if save:
|
||||||
@@ -223,4 +270,29 @@ def create_clinical_procedure_template():
|
|||||||
template.description = 'Knee Surgery and Rehab'
|
template.description = 'Knee Surgery and Rehab'
|
||||||
template.rate = 50000
|
template.rate = 50000
|
||||||
template.save()
|
template.save()
|
||||||
return template
|
return template
|
||||||
|
|
||||||
|
def create_appointment_type(args=None):
|
||||||
|
if not args:
|
||||||
|
args = frappe.local.form_dict
|
||||||
|
|
||||||
|
name = args.get('name') or 'Test Appointment Type wise Charge'
|
||||||
|
|
||||||
|
if frappe.db.exists('Appointment Type', name):
|
||||||
|
return frappe.get_doc('Appointment Type', name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
item = create_healthcare_service_items()
|
||||||
|
items = [{
|
||||||
|
'medical_department': '_Test Medical Department',
|
||||||
|
'op_consulting_charge_item': item,
|
||||||
|
'op_consulting_charge': 200
|
||||||
|
}]
|
||||||
|
return frappe.get_doc({
|
||||||
|
'doctype': 'Appointment Type',
|
||||||
|
'appointment_type': args.get('name') or 'Test Appointment Type wise Charge',
|
||||||
|
'default_duration': args.get('default_duration') or 20,
|
||||||
|
'color': args.get('color') or '#7575ff',
|
||||||
|
'price_list': args.get('price_list') or frappe.db.get_value("Price List", {"selling": 1}),
|
||||||
|
'items': args.get('items') or items
|
||||||
|
}).insert()
|
||||||
@@ -5,9 +5,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import math
|
import math
|
||||||
import frappe
|
import frappe
|
||||||
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils.formatters import format_value
|
from frappe.utils.formatters import format_value
|
||||||
from frappe.utils import time_diff_in_hours, rounded
|
from frappe.utils import time_diff_in_hours, rounded
|
||||||
|
from six import string_types
|
||||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
|
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
|
||||||
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
|
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
|
||||||
from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
|
from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
|
||||||
@@ -64,7 +66,9 @@ def get_appointments_to_invoice(patient, company):
|
|||||||
income_account = None
|
income_account = None
|
||||||
service_item = None
|
service_item = None
|
||||||
if appointment.practitioner:
|
if appointment.practitioner:
|
||||||
service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment)
|
details = get_service_item_and_practitioner_charge(appointment)
|
||||||
|
service_item = details.get('service_item')
|
||||||
|
practitioner_charge = details.get('practitioner_charge')
|
||||||
income_account = get_income_account(appointment.practitioner, appointment.company)
|
income_account = get_income_account(appointment.practitioner, appointment.company)
|
||||||
appointments_to_invoice.append({
|
appointments_to_invoice.append({
|
||||||
'reference_type': 'Patient Appointment',
|
'reference_type': 'Patient Appointment',
|
||||||
@@ -97,7 +101,9 @@ def get_encounters_to_invoice(patient, company):
|
|||||||
frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'):
|
frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
service_item, practitioner_charge = get_service_item_and_practitioner_charge(encounter)
|
details = get_service_item_and_practitioner_charge(encounter)
|
||||||
|
service_item = details.get('service_item')
|
||||||
|
practitioner_charge = details.get('practitioner_charge')
|
||||||
income_account = get_income_account(encounter.practitioner, encounter.company)
|
income_account = get_income_account(encounter.practitioner, encounter.company)
|
||||||
|
|
||||||
encounters_to_invoice.append({
|
encounters_to_invoice.append({
|
||||||
@@ -173,7 +179,7 @@ def get_clinical_procedures_to_invoice(patient, company):
|
|||||||
if procedure.invoice_separately_as_consumables and procedure.consume_stock \
|
if procedure.invoice_separately_as_consumables and procedure.consume_stock \
|
||||||
and procedure.status == 'Completed' and not procedure.consumption_invoiced:
|
and procedure.status == 'Completed' and not procedure.consumption_invoiced:
|
||||||
|
|
||||||
service_item = get_healthcare_service_item('clinical_procedure_consumable_item')
|
service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item')
|
||||||
if not service_item:
|
if not service_item:
|
||||||
msg = _('Please Configure Clinical Procedure Consumable Item in ')
|
msg = _('Please Configure Clinical Procedure Consumable Item in ')
|
||||||
msg += '''<b><a href='/app/Form/Healthcare Settings'>Healthcare Settings</a></b>'''
|
msg += '''<b><a href='/app/Form/Healthcare Settings'>Healthcare Settings</a></b>'''
|
||||||
@@ -304,24 +310,50 @@ def get_therapy_sessions_to_invoice(patient, company):
|
|||||||
|
|
||||||
return therapy_sessions_to_invoice
|
return therapy_sessions_to_invoice
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_service_item_and_practitioner_charge(doc):
|
def get_service_item_and_practitioner_charge(doc):
|
||||||
|
if isinstance(doc, string_types):
|
||||||
|
doc = json.loads(doc)
|
||||||
|
doc = frappe.get_doc(doc)
|
||||||
|
|
||||||
|
service_item = None
|
||||||
|
practitioner_charge = None
|
||||||
|
department = doc.medical_department if doc.doctype == 'Patient Encounter' else doc.department
|
||||||
|
|
||||||
is_inpatient = doc.inpatient_record
|
is_inpatient = doc.inpatient_record
|
||||||
if is_inpatient:
|
|
||||||
service_item = get_practitioner_service_item(doc.practitioner, 'inpatient_visit_charge_item')
|
if doc.get('appointment_type'):
|
||||||
|
service_item, practitioner_charge = get_appointment_type_service_item(doc.appointment_type, department, is_inpatient)
|
||||||
|
|
||||||
|
if not service_item and not practitioner_charge:
|
||||||
|
service_item, practitioner_charge = get_practitioner_service_item(doc.practitioner, is_inpatient)
|
||||||
if not service_item:
|
if not service_item:
|
||||||
service_item = get_healthcare_service_item('inpatient_visit_charge_item')
|
service_item = get_healthcare_service_item(is_inpatient)
|
||||||
else:
|
|
||||||
service_item = get_practitioner_service_item(doc.practitioner, 'op_consulting_charge_item')
|
|
||||||
if not service_item:
|
|
||||||
service_item = get_healthcare_service_item('op_consulting_charge_item')
|
|
||||||
if not service_item:
|
if not service_item:
|
||||||
throw_config_service_item(is_inpatient)
|
throw_config_service_item(is_inpatient)
|
||||||
|
|
||||||
practitioner_charge = get_practitioner_charge(doc.practitioner, is_inpatient)
|
|
||||||
if not practitioner_charge:
|
if not practitioner_charge:
|
||||||
throw_config_practitioner_charge(is_inpatient, doc.practitioner)
|
throw_config_practitioner_charge(is_inpatient, doc.practitioner)
|
||||||
|
|
||||||
|
return {'service_item': service_item, 'practitioner_charge': practitioner_charge}
|
||||||
|
|
||||||
|
|
||||||
|
def get_appointment_type_service_item(appointment_type, department, is_inpatient):
|
||||||
|
from erpnext.healthcare.doctype.appointment_type.appointment_type import get_service_item_based_on_department
|
||||||
|
|
||||||
|
item_list = get_service_item_based_on_department(appointment_type, department)
|
||||||
|
service_item = None
|
||||||
|
practitioner_charge = None
|
||||||
|
|
||||||
|
if item_list:
|
||||||
|
if is_inpatient:
|
||||||
|
service_item = item_list.get('inpatient_visit_charge_item')
|
||||||
|
practitioner_charge = item_list.get('inpatient_visit_charge')
|
||||||
|
else:
|
||||||
|
service_item = item_list.get('op_consulting_charge_item')
|
||||||
|
practitioner_charge = item_list.get('op_consulting_charge')
|
||||||
|
|
||||||
return service_item, practitioner_charge
|
return service_item, practitioner_charge
|
||||||
|
|
||||||
|
|
||||||
@@ -345,12 +377,27 @@ def throw_config_practitioner_charge(is_inpatient, practitioner):
|
|||||||
frappe.throw(msg, title=_('Missing Configuration'))
|
frappe.throw(msg, title=_('Missing Configuration'))
|
||||||
|
|
||||||
|
|
||||||
def get_practitioner_service_item(practitioner, service_item_field):
|
def get_practitioner_service_item(practitioner, is_inpatient):
|
||||||
return frappe.db.get_value('Healthcare Practitioner', practitioner, service_item_field)
|
service_item = None
|
||||||
|
practitioner_charge = None
|
||||||
|
|
||||||
|
if is_inpatient:
|
||||||
|
service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['inpatient_visit_charge_item', 'inpatient_visit_charge'])
|
||||||
|
else:
|
||||||
|
service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['op_consulting_charge_item', 'op_consulting_charge'])
|
||||||
|
|
||||||
|
return service_item, practitioner_charge
|
||||||
|
|
||||||
|
|
||||||
def get_healthcare_service_item(service_item_field):
|
def get_healthcare_service_item(is_inpatient):
|
||||||
return frappe.db.get_single_value('Healthcare Settings', service_item_field)
|
service_item = None
|
||||||
|
|
||||||
|
if is_inpatient:
|
||||||
|
service_item = frappe.db.get_single_value('Healthcare Settings', 'inpatient_visit_charge_item')
|
||||||
|
else:
|
||||||
|
service_item = frappe.db.get_single_value('Healthcare Settings', 'op_consulting_charge_item')
|
||||||
|
|
||||||
|
return service_item
|
||||||
|
|
||||||
|
|
||||||
def get_practitioner_charge(practitioner, is_inpatient):
|
def get_practitioner_charge(practitioner, is_inpatient):
|
||||||
@@ -381,7 +428,8 @@ def set_invoiced(item, method, ref_invoice=None):
|
|||||||
invoiced = True
|
invoiced = True
|
||||||
|
|
||||||
if item.reference_dt == 'Clinical Procedure':
|
if item.reference_dt == 'Clinical Procedure':
|
||||||
if get_healthcare_service_item('clinical_procedure_consumable_item') == item.item_code:
|
service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item')
|
||||||
|
if service_item == item.item_code:
|
||||||
frappe.db.set_value(item.reference_dt, item.reference_dn, 'consumption_invoiced', invoiced)
|
frappe.db.set_value(item.reference_dt, item.reference_dn, 'consumption_invoiced', invoiced)
|
||||||
else:
|
else:
|
||||||
frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced)
|
frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced)
|
||||||
@@ -403,7 +451,8 @@ def set_invoiced(item, method, ref_invoice=None):
|
|||||||
|
|
||||||
|
|
||||||
def validate_invoiced_on_submit(item):
|
def validate_invoiced_on_submit(item):
|
||||||
if item.reference_dt == 'Clinical Procedure' and get_healthcare_service_item('clinical_procedure_consumable_item') == item.item_code:
|
if item.reference_dt == 'Clinical Procedure' and \
|
||||||
|
frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') == item.item_code:
|
||||||
is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'consumption_invoiced')
|
is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'consumption_invoiced')
|
||||||
else:
|
else:
|
||||||
is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'invoiced')
|
is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'invoiced')
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ regional_overrides = {
|
|||||||
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header',
|
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header',
|
||||||
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data',
|
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data',
|
||||||
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
|
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
|
||||||
|
'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
|
||||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||||
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
||||||
|
|||||||
@@ -267,6 +267,17 @@ class JobCard(Document):
|
|||||||
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
|
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
|
||||||
filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id})
|
filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id})
|
||||||
|
|
||||||
|
def set_transferred_qty_in_job_card(self, ste_doc):
|
||||||
|
for row in ste_doc.items:
|
||||||
|
if not row.job_card_item: continue
|
||||||
|
|
||||||
|
qty = frappe.db.sql(""" SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se
|
||||||
|
WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and
|
||||||
|
se.purpose = 'Material Transfer for Manufacture'
|
||||||
|
""", (row.job_card_item))[0][0]
|
||||||
|
|
||||||
|
frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty))
|
||||||
|
|
||||||
def set_transferred_qty(self, update_status=False):
|
def set_transferred_qty(self, update_status=False):
|
||||||
if not self.items:
|
if not self.items:
|
||||||
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
|
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
|
||||||
@@ -279,7 +290,8 @@ class JobCard(Document):
|
|||||||
self.transferred_qty = frappe.db.get_value('Stock Entry', {
|
self.transferred_qty = frappe.db.get_value('Stock Entry', {
|
||||||
'job_card': self.name,
|
'job_card': self.name,
|
||||||
'work_order': self.work_order,
|
'work_order': self.work_order,
|
||||||
'docstatus': 1
|
'docstatus': 1,
|
||||||
|
'purpose': 'Material Transfer for Manufacture'
|
||||||
}, 'sum(fg_completed_qty)') or 0
|
}, 'sum(fg_completed_qty)') or 0
|
||||||
|
|
||||||
self.db_set("transferred_qty", self.transferred_qty)
|
self.db_set("transferred_qty", self.transferred_qty)
|
||||||
@@ -420,6 +432,7 @@ def make_stock_entry(source_name, target_doc=None):
|
|||||||
target.purpose = "Material Transfer for Manufacture"
|
target.purpose = "Material Transfer for Manufacture"
|
||||||
target.from_bom = 1
|
target.from_bom = 1
|
||||||
target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0)
|
target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0)
|
||||||
|
target.set_transfer_qty()
|
||||||
target.calculate_rate_and_amount()
|
target.calculate_rate_and_amount()
|
||||||
target.set_missing_values()
|
target.set_missing_values()
|
||||||
target.set_stock_entry_type()
|
target.set_stock_entry_type()
|
||||||
@@ -437,9 +450,10 @@ def make_stock_entry(source_name, target_doc=None):
|
|||||||
"field_map": {
|
"field_map": {
|
||||||
"source_warehouse": "s_warehouse",
|
"source_warehouse": "s_warehouse",
|
||||||
"required_qty": "qty",
|
"required_qty": "qty",
|
||||||
"uom": "stock_uom"
|
"name": "job_card_item"
|
||||||
},
|
},
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
|
"condition": lambda doc: doc.required_qty > 0
|
||||||
}
|
}
|
||||||
}, target_doc, set_missing_values)
|
}, target_doc, set_missing_values)
|
||||||
|
|
||||||
|
|||||||
@@ -1,363 +1,120 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"creation": "2018-07-09 17:20:44.737289",
|
||||||
"allow_import": 0,
|
"doctype": "DocType",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"beta": 0,
|
"engine": "InnoDB",
|
||||||
"creation": "2018-07-09 17:20:44.737289",
|
"field_order": [
|
||||||
"custom": 0,
|
"item_code",
|
||||||
"docstatus": 0,
|
"source_warehouse",
|
||||||
"doctype": "DocType",
|
"uom",
|
||||||
"document_type": "",
|
"item_group",
|
||||||
"editable_grid": 1,
|
"column_break_3",
|
||||||
"engine": "InnoDB",
|
"stock_uom",
|
||||||
|
"item_name",
|
||||||
|
"description",
|
||||||
|
"qty_section",
|
||||||
|
"required_qty",
|
||||||
|
"column_break_9",
|
||||||
|
"transferred_qty",
|
||||||
|
"allow_alternative_item"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "item_code",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Item Code",
|
||||||
"collapsible": 0,
|
"options": "Item",
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"fieldname": "item_code",
|
},
|
||||||
"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": "Item Code",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Item",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "source_warehouse",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"ignore_user_permissions": 1,
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Source Warehouse",
|
||||||
"columns": 0,
|
"options": "Warehouse"
|
||||||
"fieldname": "source_warehouse",
|
},
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 1,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Source Warehouse",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Warehouse",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "uom",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "UOM",
|
||||||
"bold": 0,
|
"options": "UOM"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "uom",
|
|
||||||
"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": "UOM",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "UOM",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_3",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "item_name",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"label": "Item Name",
|
||||||
"bold": 0,
|
"read_only": 1
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "item_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Item Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "description",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Text",
|
||||||
"allow_on_submit": 0,
|
"label": "Description",
|
||||||
"bold": 0,
|
"read_only": 1
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "description",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"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": "Description",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "qty_section",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break",
|
||||||
"allow_on_submit": 0,
|
"label": "Qty"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "qty_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Qty",
|
|
||||||
"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,
|
"fieldname": "required_qty",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Required Qty",
|
||||||
"collapsible": 0,
|
"read_only": 1
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "required_qty",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"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": "Required Qty",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_9",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_9",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "allow_alternative_item",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Check",
|
||||||
"bold": 0,
|
"label": "Allow Alternative Item"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
{
|
||||||
"fieldname": "allow_alternative_item",
|
"fetch_from": "item_code.item_group",
|
||||||
"fieldtype": "Check",
|
"fieldname": "item_group",
|
||||||
"hidden": 0,
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 0,
|
"label": "Item Group",
|
||||||
"ignore_xss_filter": 0,
|
"options": "Item Group",
|
||||||
"in_filter": 0,
|
"read_only": 1
|
||||||
"in_global_search": 0,
|
},
|
||||||
"in_list_view": 0,
|
{
|
||||||
"in_standard_filter": 0,
|
"fetch_from": "item_code.stock_uom",
|
||||||
"label": "Allow Alternative Item",
|
"fieldname": "stock_uom",
|
||||||
"length": 0,
|
"fieldtype": "Link",
|
||||||
"no_copy": 0,
|
"label": "Stock UOM",
|
||||||
"permlevel": 0,
|
"options": "UOM"
|
||||||
"precision": "",
|
},
|
||||||
"print_hide": 0,
|
{
|
||||||
"print_hide_if_no_value": 0,
|
"fieldname": "transferred_qty",
|
||||||
"read_only": 0,
|
"fieldtype": "Float",
|
||||||
"remember_last_selected_value": 0,
|
"label": "Transferred Qty",
|
||||||
"report_hide": 0,
|
"no_copy": 1,
|
||||||
"reqd": 0,
|
"print_hide": 1,
|
||||||
"search_index": 0,
|
"read_only": 1
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
"istable": 1,
|
||||||
"hide_toolbar": 0,
|
"links": [],
|
||||||
"idx": 0,
|
"modified": "2021-02-11 13:50:13.804108",
|
||||||
"image_view": 0,
|
"modified_by": "Administrator",
|
||||||
"in_create": 0,
|
"module": "Manufacturing",
|
||||||
"is_submittable": 0,
|
"name": "Job Card Item",
|
||||||
"issingle": 0,
|
"owner": "Administrator",
|
||||||
"istable": 1,
|
"permissions": [],
|
||||||
"max_attachments": 0,
|
"quick_entry": 1,
|
||||||
"modified": "2018-08-28 15:23:48.099459",
|
"sort_field": "modified",
|
||||||
"modified_by": "Administrator",
|
"sort_order": "DESC",
|
||||||
"module": "Manufacturing",
|
"track_changes": 1
|
||||||
"name": "Job Card Item",
|
|
||||||
"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,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -94,11 +94,11 @@ class TestWorkOrder(unittest.TestCase):
|
|||||||
wo_order = make_wo_order_test_record(item="_Test FG Item", qty=2,
|
wo_order = make_wo_order_test_record(item="_Test FG Item", qty=2,
|
||||||
source_warehouse=warehouse, skip_transfer=1)
|
source_warehouse=warehouse, skip_transfer=1)
|
||||||
|
|
||||||
bin1_on_submit = get_bin(item, warehouse)
|
reserved_qty_on_submission = cint(get_bin(item, warehouse).reserved_qty_for_production)
|
||||||
|
|
||||||
# reserved qty for production is updated
|
# reserved qty for production is updated
|
||||||
self.assertEqual(cint(bin1_at_start.reserved_qty_for_production) + 2,
|
self.assertEqual(cint(bin1_at_start.reserved_qty_for_production) + 2, reserved_qty_on_submission)
|
||||||
cint(bin1_on_submit.reserved_qty_for_production))
|
|
||||||
|
|
||||||
test_stock_entry.make_stock_entry(item_code="_Test Item",
|
test_stock_entry.make_stock_entry(item_code="_Test Item",
|
||||||
target=warehouse, qty=100, basic_rate=100)
|
target=warehouse, qty=100, basic_rate=100)
|
||||||
@@ -109,9 +109,9 @@ class TestWorkOrder(unittest.TestCase):
|
|||||||
s.submit()
|
s.submit()
|
||||||
|
|
||||||
bin1_at_completion = get_bin(item, warehouse)
|
bin1_at_completion = get_bin(item, warehouse)
|
||||||
|
|
||||||
self.assertEqual(cint(bin1_at_completion.reserved_qty_for_production),
|
self.assertEqual(cint(bin1_at_completion.reserved_qty_for_production),
|
||||||
cint(bin1_on_submit.reserved_qty_for_production) - 1)
|
reserved_qty_on_submission - 1)
|
||||||
|
|
||||||
def test_production_item(self):
|
def test_production_item(self):
|
||||||
wo_order = make_wo_order_test_record(item="_Test FG Item", qty=1, do_not_save=True)
|
wo_order = make_wo_order_test_record(item="_Test FG Item", qty=1, do_not_save=True)
|
||||||
|
|||||||
@@ -752,3 +752,5 @@ erpnext.patches.v13_0.set_company_in_leave_ledger_entry
|
|||||||
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
|
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
|
||||||
erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
|
erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
|
||||||
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
|
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
|
||||||
|
erpnext.patches.v12_0.add_state_code_for_ladakh
|
||||||
|
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
|
||||||
|
|||||||
16
erpnext/patches/v12_0/add_state_code_for_ladakh.py
Normal file
16
erpnext/patches/v12_0/add_state_code_for_ladakh.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import frappe
|
||||||
|
from erpnext.regional.india import states
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
|
||||||
|
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||||
|
if not company:
|
||||||
|
return
|
||||||
|
|
||||||
|
custom_fields = ['Address-gst_state', 'Tax Category-gst_state']
|
||||||
|
|
||||||
|
# Update options in gst_state custom fields
|
||||||
|
for field in custom_fields:
|
||||||
|
gst_state_field = frappe.get_doc('Custom Field', field)
|
||||||
|
gst_state_field.options = '\n'.join(states)
|
||||||
|
gst_state_field.save()
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from erpnext.stock.stock_ledger import update_entries_after
|
||||||
|
from erpnext.accounts.utils import update_gl_entries_after
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time
|
||||||
|
from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0
|
||||||
|
order by timestamp(posting_date, posting_time) asc, creation asc''', as_dict=1)
|
||||||
|
|
||||||
|
for index, d in enumerate(data):
|
||||||
|
update_entries_after({
|
||||||
|
"item_code": d.item_code,
|
||||||
|
"warehouse": d.warehouse,
|
||||||
|
"posting_date": d.posting_date,
|
||||||
|
"posting_time": d.posting_time,
|
||||||
|
"voucher_type": d.voucher_type,
|
||||||
|
"voucher_no": d.voucher_no,
|
||||||
|
"sle_id": d.name
|
||||||
|
}, allow_negative_stock=True)
|
||||||
|
|
||||||
|
frappe.db.auto_commit_on_many_writes = 1
|
||||||
|
|
||||||
|
for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
|
||||||
|
update_gl_entries_after('2020-12-25', '01:58:55', company=row.name)
|
||||||
|
|
||||||
|
frappe.db.auto_commit_on_many_writes = 0
|
||||||
@@ -1103,10 +1103,10 @@ class SalarySlip(TransactionBase):
|
|||||||
self.calculate_total_for_salary_slip_based_on_timesheet()
|
self.calculate_total_for_salary_slip_based_on_timesheet()
|
||||||
else:
|
else:
|
||||||
self.total_deduction = 0.0
|
self.total_deduction = 0.0
|
||||||
if self.earnings:
|
if hasattr(self, "earnings"):
|
||||||
for earning in self.earnings:
|
for earning in self.earnings:
|
||||||
self.gross_pay += flt(earning.amount, earning.precision("amount"))
|
self.gross_pay += flt(earning.amount, earning.precision("amount"))
|
||||||
if self.deductions:
|
if hasattr(self, "deductions"):
|
||||||
for deduction in self.deductions:
|
for deduction in self.deductions:
|
||||||
self.total_deduction += flt(deduction.amount, deduction.precision("amount"))
|
self.total_deduction += flt(deduction.amount, deduction.precision("amount"))
|
||||||
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
|
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
|
||||||
|
|||||||
BIN
erpnext/public/images/erpnext-logo.png
Normal file
BIN
erpnext/public/images/erpnext-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -2,7 +2,9 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
erpnext.taxes_and_totals = erpnext.payments.extend({
|
erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||||
setup: function() {},
|
setup: function() {
|
||||||
|
this.fetch_round_off_accounts();
|
||||||
|
},
|
||||||
|
|
||||||
apply_pricing_rule_on_item: function(item) {
|
apply_pricing_rule_on_item: function(item) {
|
||||||
let effective_item_rate = item.price_list_rate;
|
let effective_item_rate = item.price_list_rate;
|
||||||
@@ -152,6 +154,22 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetch_round_off_accounts: function() {
|
||||||
|
let me = this;
|
||||||
|
frappe.flags.round_off_applicable_accounts = [];
|
||||||
|
|
||||||
|
return frappe.call({
|
||||||
|
"method": "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts",
|
||||||
|
"args": {
|
||||||
|
"company": me.frm.doc.company,
|
||||||
|
"account_list": frappe.flags.round_off_applicable_accounts
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
frappe.flags.round_off_applicable_accounts.push(...r.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
determine_exclusive_rate: function() {
|
determine_exclusive_rate: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
@@ -372,11 +390,21 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
|||||||
} else if (tax.charge_type == "On Item Quantity") {
|
} else if (tax.charge_type == "On Item Quantity") {
|
||||||
current_tax_amount = tax_rate * item.qty;
|
current_tax_amount = tax_rate * item.qty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current_tax_amount = this.get_final_tax_amount(tax, current_tax_amount);
|
||||||
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
|
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
|
||||||
|
|
||||||
return current_tax_amount;
|
return current_tax_amount;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_final_tax_amount: function(tax, current_tax_amount) {
|
||||||
|
if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
|
||||||
|
current_tax_amount = Math.round(current_tax_amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return current_tax_amount;
|
||||||
|
},
|
||||||
|
|
||||||
set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) {
|
set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) {
|
||||||
// store tax breakup for each item
|
// store tax breakup for each item
|
||||||
let tax_detail = tax.item_wise_tax_detail;
|
let tax_detail = tax.item_wise_tax_detail;
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
() => me.update_batch_serial_no_items(),
|
() => me.update_batch_serial_no_items(),
|
||||||
() => {
|
() => {
|
||||||
refresh_field("items");
|
refresh_field("items");
|
||||||
|
refresh_field("packed_items");
|
||||||
if (me.callback) {
|
if (me.callback) {
|
||||||
return me.callback(me.item);
|
return me.callback(me.item);
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
if (this.item.serial_no) {
|
if (this.item.serial_no) {
|
||||||
this.dialog.fields_dict.serial_no.set_value(this.item.serial_no);
|
this.dialog.fields_dict.serial_no.set_value(this.item.serial_no);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.has_batch && !this.has_serial_no && d.batch_no) {
|
if (this.has_batch && !this.has_serial_no && d.batch_no) {
|
||||||
this.frm.doc.items.forEach(data => {
|
this.frm.doc.items.forEach(data => {
|
||||||
if(data.item_code == d.item_code) {
|
if(data.item_code == d.item_code) {
|
||||||
@@ -231,7 +232,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
this.map_row_values(row, batch, 'batch_no',
|
this.map_row_values(row, batch, 'batch_no',
|
||||||
'selected_qty', this.values.warehouse);
|
'selected_qty', this.values.warehouse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
update_serial_no_item() {
|
update_serial_no_item() {
|
||||||
@@ -250,7 +251,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
filters: { 'name': ["in", selected_serial_nos]},
|
filters: { 'name': ["in", selected_serial_nos]},
|
||||||
fields: ["batch_no", "name"]
|
fields: ["batch_no", "name"]
|
||||||
}).then((data) => {
|
}).then((data) => {
|
||||||
// data = [{batch_no: 'batch-1', name: "SR-001"},
|
// data = [{batch_no: 'batch-1', name: "SR-001"},
|
||||||
// {batch_no: 'batch-2', name: "SR-003"}, {batch_no: 'batch-2', name: "SR-004"}]
|
// {batch_no: 'batch-2', name: "SR-003"}, {batch_no: 'batch-2', name: "SR-004"}]
|
||||||
const batch_serial_map = data.reduce((acc, d) => {
|
const batch_serial_map = data.reduce((acc, d) => {
|
||||||
if (!acc[d['batch_no']]) acc[d['batch_no']] = [];
|
if (!acc[d['batch_no']]) acc[d['batch_no']] = [];
|
||||||
@@ -298,6 +299,8 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
} else {
|
} else {
|
||||||
row.warehouse = values.warehouse || warehouse;
|
row.warehouse = values.warehouse || warehouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.frm.dirty();
|
||||||
},
|
},
|
||||||
|
|
||||||
update_total_qty: function() {
|
update_total_qty: function() {
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ body[data-route*="marketplace"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hub-image-loading, .hub-image-broken {
|
.hub-image-loading, .hub-image-broken {
|
||||||
.img-background();
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bg-light-gray);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -1,222 +1,86 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"creation": "2017-06-27 15:09:01.318003",
|
||||||
"allow_import": 0,
|
"doctype": "DocType",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"beta": 0,
|
"engine": "InnoDB",
|
||||||
"creation": "2017-06-27 15:09:01.318003",
|
"field_order": [
|
||||||
"custom": 0,
|
"gst_summary",
|
||||||
"docstatus": 0,
|
"column_break_2",
|
||||||
"doctype": "DocType",
|
"round_off_gst_values",
|
||||||
"document_type": "",
|
"gstin_email_sent_on",
|
||||||
"editable_grid": 1,
|
"section_break_4",
|
||||||
"engine": "InnoDB",
|
"gst_accounts",
|
||||||
|
"b2c_limit"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "gst_summary",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "HTML",
|
||||||
"bold": 0,
|
"label": "GST Summary",
|
||||||
"collapsible": 0,
|
"show_days": 1,
|
||||||
"columns": 0,
|
"show_seconds": 1
|
||||||
"fieldname": "gst_summary",
|
},
|
||||||
"fieldtype": "HTML",
|
|
||||||
"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": "GST Summary",
|
|
||||||
"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": "column_break_2",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Column Break",
|
||||||
"bold": 0,
|
"show_days": 1,
|
||||||
"collapsible": 0,
|
"show_seconds": 1
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "column_break_2",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "gstin_email_sent_on",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Date",
|
||||||
"bold": 0,
|
"label": "GSTIN Email Sent On",
|
||||||
"collapsible": 0,
|
"read_only": 1,
|
||||||
"columns": 0,
|
"show_days": 1,
|
||||||
"fieldname": "gstin_email_sent_on",
|
"show_seconds": 1
|
||||||
"fieldtype": "Date",
|
},
|
||||||
"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": "GSTIN Email Sent On",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "section_break_4",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Section Break",
|
||||||
"bold": 0,
|
"show_days": 1,
|
||||||
"collapsible": 0,
|
"show_seconds": 1
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "section_break_4",
|
|
||||||
"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,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "gst_accounts",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Table",
|
||||||
"bold": 0,
|
"label": "GST Accounts",
|
||||||
"collapsible": 0,
|
"options": "GST Account",
|
||||||
"columns": 0,
|
"show_days": 1,
|
||||||
"fieldname": "gst_accounts",
|
"show_seconds": 1
|
||||||
"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": "GST Accounts",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "GST Account",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "250000",
|
||||||
"allow_on_submit": 0,
|
"description": "Set Invoice Value for B2C. B2CL and B2CS calculated based on this invoice value.",
|
||||||
"bold": 0,
|
"fieldname": "b2c_limit",
|
||||||
"collapsible": 0,
|
"fieldtype": "Data",
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"default": "250000",
|
"label": "B2C Limit",
|
||||||
"description": "Set Invoice Value for B2C. B2CL and B2CS calculated based on this invoice value.",
|
"reqd": 1,
|
||||||
"fieldname": "b2c_limit",
|
"show_days": 1,
|
||||||
"fieldtype": "Data",
|
"show_seconds": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
{
|
||||||
"ignore_xss_filter": 0,
|
"default": "0",
|
||||||
"in_filter": 0,
|
"description": "Enabling this option will round off individual GST components in all the Invoices",
|
||||||
"in_global_search": 0,
|
"fieldname": "round_off_gst_values",
|
||||||
"in_list_view": 1,
|
"fieldtype": "Check",
|
||||||
"in_standard_filter": 0,
|
"label": "Round Off GST Values",
|
||||||
"label": "B2C Limit",
|
"show_days": 1,
|
||||||
"length": 0,
|
"show_seconds": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
"issingle": 1,
|
||||||
"hide_toolbar": 0,
|
"links": [],
|
||||||
"idx": 0,
|
"modified": "2021-01-28 17:19:47.969260",
|
||||||
"image_view": 0,
|
"modified_by": "Administrator",
|
||||||
"in_create": 0,
|
"module": "Regional",
|
||||||
"is_submittable": 0,
|
"name": "GST Settings",
|
||||||
"issingle": 1,
|
"owner": "Administrator",
|
||||||
"istable": 0,
|
"permissions": [],
|
||||||
"max_attachments": 0,
|
"quick_entry": 1,
|
||||||
"modified": "2018-02-14 08:14:15.375181",
|
"sort_field": "modified",
|
||||||
"modified_by": "Administrator",
|
"sort_order": "DESC",
|
||||||
"module": "Regional",
|
"track_changes": 1
|
||||||
"name": "GST Settings",
|
}
|
||||||
"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
|
|
||||||
}
|
|
||||||
@@ -14,8 +14,20 @@ import json
|
|||||||
test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"]
|
test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"]
|
||||||
|
|
||||||
class TestGSTR3BReport(unittest.TestCase):
|
class TestGSTR3BReport(unittest.TestCase):
|
||||||
def test_gstr_3b_report(self):
|
def setUp(self):
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'")
|
||||||
|
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'")
|
||||||
|
frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'")
|
||||||
|
|
||||||
|
make_company()
|
||||||
|
make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000})
|
||||||
|
set_account_heads()
|
||||||
|
make_customers()
|
||||||
|
make_suppliers()
|
||||||
|
|
||||||
|
def test_gstr_3b_report(self):
|
||||||
month_number_mapping = {
|
month_number_mapping = {
|
||||||
1: "January",
|
1: "January",
|
||||||
2: "February",
|
2: "February",
|
||||||
@@ -31,17 +43,6 @@ class TestGSTR3BReport(unittest.TestCase):
|
|||||||
12: "December"
|
12: "December"
|
||||||
}
|
}
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
|
|
||||||
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'")
|
|
||||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'")
|
|
||||||
frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'")
|
|
||||||
|
|
||||||
make_company()
|
|
||||||
make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000})
|
|
||||||
set_account_heads()
|
|
||||||
make_customers()
|
|
||||||
make_suppliers()
|
|
||||||
make_sales_invoice()
|
make_sales_invoice()
|
||||||
create_purchase_invoices()
|
create_purchase_invoices()
|
||||||
|
|
||||||
@@ -67,6 +68,42 @@ class TestGSTR3BReport(unittest.TestCase):
|
|||||||
self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50)
|
self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50)
|
||||||
self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50)
|
self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50)
|
||||||
|
|
||||||
|
def test_gst_rounding(self):
|
||||||
|
gst_settings = frappe.get_doc('GST Settings')
|
||||||
|
gst_settings.round_off_gst_values = 1
|
||||||
|
gst_settings.save()
|
||||||
|
|
||||||
|
current_country = frappe.flags.country
|
||||||
|
frappe.flags.country = 'India'
|
||||||
|
|
||||||
|
si = create_sales_invoice(company="_Test Company GST",
|
||||||
|
customer = '_Test GST Customer',
|
||||||
|
currency = 'INR',
|
||||||
|
warehouse = 'Finished Goods - _GST',
|
||||||
|
debit_to = 'Debtors - _GST',
|
||||||
|
income_account = 'Sales - _GST',
|
||||||
|
expense_account = 'Cost of Goods Sold - _GST',
|
||||||
|
cost_center = 'Main - _GST',
|
||||||
|
rate=216,
|
||||||
|
do_not_save=1
|
||||||
|
)
|
||||||
|
|
||||||
|
si.append("taxes", {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "IGST - _GST",
|
||||||
|
"cost_center": "Main - _GST",
|
||||||
|
"description": "IGST @ 18.0",
|
||||||
|
"rate": 18
|
||||||
|
})
|
||||||
|
|
||||||
|
si.save()
|
||||||
|
# Check for 39 instead of 38.88
|
||||||
|
self.assertEqual(si.taxes[0].base_tax_amount_after_discount_amount, 39)
|
||||||
|
|
||||||
|
frappe.flags.country = current_country
|
||||||
|
gst_settings.round_off_gst_values = 1
|
||||||
|
gst_settings.save()
|
||||||
|
|
||||||
def make_sales_invoice():
|
def make_sales_invoice():
|
||||||
si = create_sales_invoice(company="_Test Company GST",
|
si = create_sales_invoice(company="_Test Company GST",
|
||||||
customer = '_Test GST Customer',
|
customer = '_Test GST Customer',
|
||||||
@@ -145,7 +182,6 @@ def make_sales_invoice():
|
|||||||
si3.submit()
|
si3.submit()
|
||||||
|
|
||||||
def create_purchase_invoices():
|
def create_purchase_invoices():
|
||||||
|
|
||||||
pi = make_purchase_invoice(
|
pi = make_purchase_invoice(
|
||||||
company="_Test Company GST",
|
company="_Test Company GST",
|
||||||
supplier = '_Test Registered Supplier',
|
supplier = '_Test Registered Supplier',
|
||||||
@@ -193,7 +229,6 @@ def create_purchase_invoices():
|
|||||||
pi1.submit()
|
pi1.submit()
|
||||||
|
|
||||||
def make_suppliers():
|
def make_suppliers():
|
||||||
|
|
||||||
if not frappe.db.exists("Supplier", "_Test Registered Supplier"):
|
if not frappe.db.exists("Supplier", "_Test Registered Supplier"):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"supplier_group": "_Test Supplier Group",
|
"supplier_group": "_Test Supplier Group",
|
||||||
@@ -257,7 +292,6 @@ def make_suppliers():
|
|||||||
address.save()
|
address.save()
|
||||||
|
|
||||||
def make_customers():
|
def make_customers():
|
||||||
|
|
||||||
if not frappe.db.exists("Customer", "_Test GST Customer"):
|
if not frappe.db.exists("Customer", "_Test GST Customer"):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"customer_group": "_Test Customer Group",
|
"customer_group": "_Test Customer Group",
|
||||||
@@ -354,9 +388,9 @@ def make_customers():
|
|||||||
address.save()
|
address.save()
|
||||||
|
|
||||||
def make_company():
|
def make_company():
|
||||||
|
|
||||||
if frappe.db.exists("Company", "_Test Company GST"):
|
if frappe.db.exists("Company", "_Test Company GST"):
|
||||||
return
|
return
|
||||||
|
|
||||||
company = frappe.new_doc("Company")
|
company = frappe.new_doc("Company")
|
||||||
company.company_name = "_Test Company GST"
|
company.company_name = "_Test Company GST"
|
||||||
company.abbr = "_GST"
|
company.abbr = "_GST"
|
||||||
@@ -388,7 +422,6 @@ def make_company():
|
|||||||
address.save()
|
address.save()
|
||||||
|
|
||||||
def set_account_heads():
|
def set_account_heads():
|
||||||
|
|
||||||
gst_settings = frappe.get_doc("GST Settings")
|
gst_settings = frappe.get_doc("GST Settings")
|
||||||
|
|
||||||
gst_account = frappe.get_all(
|
gst_account = frappe.get_all(
|
||||||
|
|||||||
@@ -5,12 +5,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate, get_link_to_form
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
class LowerDeductionCertificate(Document):
|
class LowerDeductionCertificate(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_dates()
|
||||||
|
self.validate_supplier_against_section_code()
|
||||||
|
|
||||||
|
def validate_dates(self):
|
||||||
if getdate(self.valid_upto) < getdate(self.valid_from):
|
if getdate(self.valid_upto) < getdate(self.valid_from):
|
||||||
frappe.throw(_("Valid Upto date cannot be before Valid From date"))
|
frappe.throw(_("Valid Upto date cannot be before Valid From date"))
|
||||||
|
|
||||||
@@ -24,3 +28,20 @@ class LowerDeductionCertificate(Document):
|
|||||||
<= fiscal_year.year_end_date):
|
<= fiscal_year.year_end_date):
|
||||||
frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year)))
|
frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year)))
|
||||||
|
|
||||||
|
def validate_supplier_against_section_code(self):
|
||||||
|
duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'section_code': self.section_code}, ['name', 'valid_from', 'valid_upto'], as_dict=True)
|
||||||
|
if duplicate_certificate and self.are_dates_overlapping(duplicate_certificate):
|
||||||
|
certificate_link = get_link_to_form('Lower Deduction Certificate', duplicate_certificate.name)
|
||||||
|
frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against Section Code {2} for this time period.")
|
||||||
|
.format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.section_code)))
|
||||||
|
|
||||||
|
def are_dates_overlapping(self,duplicate_certificate):
|
||||||
|
valid_from = duplicate_certificate.valid_from
|
||||||
|
valid_upto = duplicate_certificate.valid_upto
|
||||||
|
if valid_from <= getdate(self.valid_from) <= valid_upto:
|
||||||
|
return True
|
||||||
|
elif valid_from <= getdate(self.valid_upto) <= valid_upto:
|
||||||
|
return True
|
||||||
|
elif getdate(self.valid_from) <= valid_from and valid_upto <= getdate(self.valid_upto):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@@ -20,6 +20,7 @@ states = [
|
|||||||
'Jharkhand',
|
'Jharkhand',
|
||||||
'Karnataka',
|
'Karnataka',
|
||||||
'Kerala',
|
'Kerala',
|
||||||
|
'Ladakh',
|
||||||
'Lakshadweep Islands',
|
'Lakshadweep Islands',
|
||||||
'Madhya Pradesh',
|
'Madhya Pradesh',
|
||||||
'Maharashtra',
|
'Maharashtra',
|
||||||
@@ -59,6 +60,7 @@ state_numbers = {
|
|||||||
"Jharkhand": "20",
|
"Jharkhand": "20",
|
||||||
"Karnataka": "29",
|
"Karnataka": "29",
|
||||||
"Kerala": "32",
|
"Kerala": "32",
|
||||||
|
"Ladakh": "38",
|
||||||
"Lakshadweep Islands": "31",
|
"Lakshadweep Islands": "31",
|
||||||
"Madhya Pradesh": "23",
|
"Madhya Pradesh": "23",
|
||||||
"Maharashtra": "27",
|
"Maharashtra": "27",
|
||||||
@@ -80,4 +82,4 @@ state_numbers = {
|
|||||||
"West Bengal": "19",
|
"West Bengal": "19",
|
||||||
}
|
}
|
||||||
|
|
||||||
number_state_mapping = {v: k for k, v in iteritems(state_numbers)}
|
number_state_mapping = {v: k for k, v in iteritems(state_numbers)}
|
||||||
|
|||||||
@@ -168,5 +168,10 @@
|
|||||||
"state_number": "37",
|
"state_number": "37",
|
||||||
"state_code": "AD",
|
"state_code": "AD",
|
||||||
"state_name": "Andhra Pradesh (New)"
|
"state_name": "Andhra Pradesh (New)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state_number": "38",
|
||||||
|
"state_code": "LA",
|
||||||
|
"state_name": "Ladakh"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -772,3 +772,24 @@ def make_regional_gl_entries(gl_entries, doc):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_regional_round_off_accounts(company, account_list):
|
||||||
|
country = frappe.get_cached_value('Company', company, 'country')
|
||||||
|
|
||||||
|
if country != 'India':
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(account_list, string_types):
|
||||||
|
account_list = json.loads(account_list)
|
||||||
|
|
||||||
|
if not frappe.db.get_single_value('GST Settings', 'round_off_gst_values'):
|
||||||
|
return
|
||||||
|
|
||||||
|
gst_accounts = get_gst_accounts(company)
|
||||||
|
gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
|
||||||
|
+ gst_accounts.get('igst_account')
|
||||||
|
|
||||||
|
account_list.extend(gst_account_list)
|
||||||
|
|
||||||
|
return account_list
|
||||||
@@ -236,6 +236,7 @@ class Gstr1Report(object):
|
|||||||
self.cgst_sgst_invoices = []
|
self.cgst_sgst_invoices = []
|
||||||
|
|
||||||
unidentified_gst_accounts = []
|
unidentified_gst_accounts = []
|
||||||
|
unidentified_gst_accounts_invoice = []
|
||||||
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
|
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
|
||||||
if account in self.gst_accounts.cess_account:
|
if account in self.gst_accounts.cess_account:
|
||||||
self.invoice_cess.setdefault(parent, tax_amount)
|
self.invoice_cess.setdefault(parent, tax_amount)
|
||||||
@@ -251,6 +252,7 @@ class Gstr1Report(object):
|
|||||||
if not (cgst_or_sgst or account in self.gst_accounts.igst_account):
|
if not (cgst_or_sgst or account in self.gst_accounts.igst_account):
|
||||||
if "gst" in account.lower() and account not in unidentified_gst_accounts:
|
if "gst" in account.lower() and account not in unidentified_gst_accounts:
|
||||||
unidentified_gst_accounts.append(account)
|
unidentified_gst_accounts.append(account)
|
||||||
|
unidentified_gst_accounts_invoice.append(parent)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for item_code, tax_amounts in item_wise_tax_detail.items():
|
for item_code, tax_amounts in item_wise_tax_detail.items():
|
||||||
@@ -273,7 +275,7 @@ class Gstr1Report(object):
|
|||||||
|
|
||||||
# Build itemised tax for export invoices where tax table is blank
|
# Build itemised tax for export invoices where tax table is blank
|
||||||
for invoice, items in iteritems(self.invoice_items):
|
for invoice, items in iteritems(self.invoice_items):
|
||||||
if invoice not in self.items_based_on_tax_rate \
|
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
|
||||||
and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
|
and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
|
||||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,8 @@
|
|||||||
"companies",
|
"companies",
|
||||||
"currency_and_price_list",
|
"currency_and_price_list",
|
||||||
"default_currency",
|
"default_currency",
|
||||||
"default_price_list",
|
|
||||||
"column_break_14",
|
"column_break_14",
|
||||||
"language",
|
"default_price_list",
|
||||||
"address_contacts",
|
"address_contacts",
|
||||||
"address_html",
|
"address_html",
|
||||||
"website",
|
"website",
|
||||||
@@ -59,6 +58,7 @@
|
|||||||
"column_break_45",
|
"column_break_45",
|
||||||
"market_segment",
|
"market_segment",
|
||||||
"industry",
|
"industry",
|
||||||
|
"language",
|
||||||
"is_frozen",
|
"is_frozen",
|
||||||
"column_break_38",
|
"column_break_38",
|
||||||
"loyalty_program",
|
"loyalty_program",
|
||||||
@@ -485,7 +485,7 @@
|
|||||||
"idx": 363,
|
"idx": 363,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-17 11:03:42.706907",
|
"modified": "2021-01-06 19:35:25.418017",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Customer",
|
"name": "Customer",
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
make_delivery_note: function() {
|
make_delivery_note: function() {
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
|
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
|
||||||
frm: me.frm
|
frm: this.frm
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ class SalesOrder(SellingController):
|
|||||||
update_coupon_code_count(self.coupon_code,'used')
|
update_coupon_code_count(self.coupon_code,'used')
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
||||||
super(SalesOrder, self).on_cancel()
|
super(SalesOrder, self).on_cancel()
|
||||||
|
|
||||||
# Cannot cancel closed SO
|
# Cannot cancel closed SO
|
||||||
|
|||||||
@@ -17,6 +17,18 @@ from erpnext.selling.doctype.product_bundle.test_product_bundle import make_prod
|
|||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
|
||||||
class TestSalesOrder(unittest.TestCase):
|
class TestSalesOrder(unittest.TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.unlink_setting = int(frappe.db.get_value("Accounts Settings", "Accounts Settings",
|
||||||
|
"unlink_advance_payment_on_cancelation_of_order"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls) -> None:
|
||||||
|
# reset config to previous state
|
||||||
|
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
||||||
|
"unlink_advance_payment_on_cancelation_of_order", cls.unlink_setting)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
@@ -1049,6 +1061,38 @@ class TestSalesOrder(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(frappe.LinkExistsError, so_doc.cancel)
|
self.assertRaises(frappe.LinkExistsError, so_doc.cancel)
|
||||||
|
|
||||||
|
def test_cancel_sales_order_after_cancel_payment_entry(self):
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
|
# make a sales order
|
||||||
|
so = make_sales_order()
|
||||||
|
|
||||||
|
# disable unlinking of payment entry
|
||||||
|
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
||||||
|
"unlink_advance_payment_on_cancelation_of_order", 0)
|
||||||
|
|
||||||
|
# create a payment entry against sales order
|
||||||
|
pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC")
|
||||||
|
pe.reference_no = "1"
|
||||||
|
pe.reference_date = nowdate()
|
||||||
|
pe.paid_from_account_currency = so.currency
|
||||||
|
pe.paid_to_account_currency = so.currency
|
||||||
|
pe.source_exchange_rate = 1
|
||||||
|
pe.target_exchange_rate = 1
|
||||||
|
pe.paid_amount = so.grand_total
|
||||||
|
pe.save(ignore_permissions=True)
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
# Cancel payment entry
|
||||||
|
po_doc = frappe.get_doc("Payment Entry", pe.name)
|
||||||
|
po_doc.cancel()
|
||||||
|
|
||||||
|
# Cancel sales order
|
||||||
|
try:
|
||||||
|
so_doc = frappe.get_doc('Sales Order', so.name)
|
||||||
|
so_doc.cancel()
|
||||||
|
except Exception:
|
||||||
|
self.fail("Can not cancel sales order with linked cancelled payment entry")
|
||||||
|
|
||||||
def test_request_for_raw_materials(self):
|
def test_request_for_raw_materials(self):
|
||||||
item = make_item("_Test Finished Item", {"is_stock_item": 1,
|
item = make_item("_Test Finished Item", {"is_stock_item": 1,
|
||||||
"maintain_stock": 1,
|
"maintain_stock": 1,
|
||||||
@@ -1207,4 +1251,4 @@ def make_sales_order_workflow():
|
|||||||
))
|
))
|
||||||
workflow.insert(ignore_permissions=True)
|
workflow.insert(ignore_permissions=True)
|
||||||
|
|
||||||
return workflow
|
return workflow
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ def delete_company_transactions(company_name):
|
|||||||
"Party Account", "Employee", "Sales Taxes and Charges Template",
|
"Party Account", "Employee", "Sales Taxes and Charges Template",
|
||||||
"Purchase Taxes and Charges Template", "POS Profile", "BOM",
|
"Purchase Taxes and Charges Template", "POS Profile", "BOM",
|
||||||
"Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
|
"Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
|
||||||
"Item Default", "Customer", "Supplier"):
|
"Item Default", "Customer", "Supplier", "GST Account"):
|
||||||
delete_for_doctype(doctype, company_name)
|
delete_for_doctype(doctype, company_name)
|
||||||
|
|
||||||
# reset company values
|
# reset company values
|
||||||
|
|||||||
@@ -462,6 +462,9 @@ def get_party(user=None):
|
|||||||
return customer
|
return customer
|
||||||
|
|
||||||
def get_debtors_account(cart_settings):
|
def get_debtors_account(cart_settings):
|
||||||
|
if not cart_settings.payment_gateway_account:
|
||||||
|
frappe.throw(_("Payment Gateway Account not set"), _("Mandatory"))
|
||||||
|
|
||||||
payment_gateway_account_currency = \
|
payment_gateway_account_currency = \
|
||||||
frappe.get_doc("Payment Gateway Account", cart_settings.payment_gateway_account).currency
|
frappe.get_doc("Payment Gateway Account", cart_settings.payment_gateway_account).currency
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,10 @@
|
|||||||
"quotation_series",
|
"quotation_series",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"enable_checkout",
|
"enable_checkout",
|
||||||
"payment_success_url",
|
|
||||||
"column_break_11",
|
|
||||||
"save_quotations_as_draft",
|
"save_quotations_as_draft",
|
||||||
"payment_gateway_account"
|
"column_break_11",
|
||||||
|
"payment_gateway_account",
|
||||||
|
"payment_success_url"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -143,10 +143,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Orders",
|
"default": "Orders",
|
||||||
|
"depends_on": "enable_checkout",
|
||||||
"description": "After payment completion redirect user to selected page.",
|
"description": "After payment completion redirect user to selected page.",
|
||||||
"fieldname": "payment_success_url",
|
"fieldname": "payment_success_url",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Payment Success Url",
|
"label": "Payment Success Url",
|
||||||
|
"mandatory_depends_on": "enable_checkout",
|
||||||
"options": "\nOrders\nInvoices\nMy Account"
|
"options": "\nOrders\nInvoices\nMy Account"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -154,9 +156,11 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "enable_checkout",
|
||||||
"fieldname": "payment_gateway_account",
|
"fieldname": "payment_gateway_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Payment Gateway Account",
|
"label": "Payment Gateway Account",
|
||||||
|
"mandatory_depends_on": "enable_checkout",
|
||||||
"options": "Payment Gateway Account"
|
"options": "Payment Gateway Account"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -186,7 +190,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-01 18:18:54.606535",
|
"modified": "2021-02-11 18:48:30.433058",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Shopping Cart",
|
"module": "Shopping Cart",
|
||||||
"name": "Shopping Cart Settings",
|
"name": "Shopping Cart Settings",
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ def get_warehouse_account(warehouse, warehouse_account=None):
|
|||||||
if not account and warehouse.company:
|
if not account and warehouse.company:
|
||||||
account = get_company_default_inventory_account(warehouse.company)
|
account = get_company_default_inventory_account(warehouse.company)
|
||||||
|
|
||||||
if not account and warehouse.company:
|
if not account and warehouse.company and not warehouse.is_group:
|
||||||
frappe.throw(_("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}")
|
frappe.throw(_("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}")
|
||||||
.format(warehouse.name, warehouse.company))
|
.format(warehouse.name, warehouse.company))
|
||||||
return account
|
return account
|
||||||
|
|
||||||
def get_company_default_inventory_account(company):
|
def get_company_default_inventory_account(company):
|
||||||
return frappe.get_cached_value('Company', company, 'default_inventory_account')
|
return frappe.get_cached_value('Company', company, 'default_inventory_account')
|
||||||
|
|||||||
@@ -298,9 +298,9 @@ class TestBatch(unittest.TestCase):
|
|||||||
self.assertEqual(details.get('price_list_rate'), 400)
|
self.assertEqual(details.get('price_list_rate'), 400)
|
||||||
|
|
||||||
def create_batch(item_code, rate, create_item_price_for_batch):
|
def create_batch(item_code, rate, create_item_price_for_batch):
|
||||||
pi = make_purchase_invoice(company="_Test Company with perpetual inventory",
|
pi = make_purchase_invoice(company="_Test Company",
|
||||||
warehouse= "Stores - TCP1", cost_center = "Main - TCP1", update_stock=1,
|
warehouse= "Stores - _TC", cost_center = "Main - _TC", update_stock=1,
|
||||||
expense_account ="_Test Account Cost for Goods Sold - TCP1", item_code=item_code)
|
expense_account ="_Test Account Cost for Goods Sold - _TC", item_code=item_code)
|
||||||
|
|
||||||
batch = frappe.db.get_value('Batch', {'item': item_code, 'reference_name': pi.name})
|
batch = frappe.db.get_value('Batch', {'item': item_code, 'reference_name': pi.name})
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ class Bin(Document):
|
|||||||
def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False):
|
def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||||
'''Called from erpnext.stock.utils.update_bin'''
|
'''Called from erpnext.stock.utils.update_bin'''
|
||||||
self.update_qty(args)
|
self.update_qty(args)
|
||||||
|
|
||||||
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
|
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
|
||||||
from erpnext.stock.stock_ledger import update_entries_after, validate_negative_qty_in_future_sle
|
from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle
|
||||||
|
|
||||||
if not args.get("posting_date"):
|
if not args.get("posting_date"):
|
||||||
args["posting_date"] = nowdate()
|
args["posting_date"] = nowdate()
|
||||||
@@ -34,11 +35,13 @@ class Bin(Document):
|
|||||||
"posting_time": args.get("posting_time"),
|
"posting_time": args.get("posting_time"),
|
||||||
"voucher_type": args.get("voucher_type"),
|
"voucher_type": args.get("voucher_type"),
|
||||||
"voucher_no": args.get("voucher_no"),
|
"voucher_no": args.get("voucher_no"),
|
||||||
"sle_id": args.name
|
"sle_id": args.name,
|
||||||
|
"creation": args.creation
|
||||||
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
|
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
|
||||||
|
|
||||||
# Validate negative qty in future transactions
|
# update qty in future ale and Validate negative qty
|
||||||
validate_negative_qty_in_future_sle(args)
|
update_qty_in_future_sle(args, allow_negative_stock)
|
||||||
|
|
||||||
|
|
||||||
def update_qty(self, args):
|
def update_qty(self, args):
|
||||||
# update the stock values (for current quantities)
|
# update the stock values (for current quantities)
|
||||||
@@ -51,7 +54,7 @@ class Bin(Document):
|
|||||||
self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty"))
|
self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty"))
|
||||||
self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty"))
|
self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty"))
|
||||||
self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty"))
|
self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty"))
|
||||||
|
|
||||||
self.set_projected_qty()
|
self.set_projected_qty()
|
||||||
self.db_update()
|
self.db_update()
|
||||||
|
|
||||||
|
|||||||
@@ -489,7 +489,10 @@ class TestDeliveryNote(unittest.TestCase):
|
|||||||
def test_closed_delivery_note(self):
|
def test_closed_delivery_note(self):
|
||||||
from erpnext.stock.doctype.delivery_note.delivery_note import update_delivery_note_status
|
from erpnext.stock.doctype.delivery_note.delivery_note import update_delivery_note_status
|
||||||
|
|
||||||
dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1", do_not_submit=True)
|
make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100)
|
||||||
|
|
||||||
|
dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
|
||||||
|
cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1", do_not_submit=True)
|
||||||
|
|
||||||
dn.submit()
|
dn.submit()
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ class TestLandedCostVoucher(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def test_landed_cost_voucher_for_odd_numbers (self):
|
def test_landed_cost_voucher_for_odd_numbers (self):
|
||||||
|
|
||||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", do_not_save=True)
|
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", do_not_save=True)
|
||||||
pr.items[0].cost_center = "Main - TCP1"
|
pr.items[0].cost_center = "Main - TCP1"
|
||||||
for x in range(2):
|
for x in range(2):
|
||||||
@@ -208,6 +207,10 @@ class TestLandedCostVoucher(unittest.TestCase):
|
|||||||
self.assertEqual(pr.items[1].landed_cost_voucher_amount, 100)
|
self.assertEqual(pr.items[1].landed_cost_voucher_amount, 100)
|
||||||
|
|
||||||
def test_multi_currency_lcv(self):
|
def test_multi_currency_lcv(self):
|
||||||
|
from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records
|
||||||
|
|
||||||
|
save_new_records(test_records)
|
||||||
|
|
||||||
## Create USD Shipping charges_account
|
## Create USD Shipping charges_account
|
||||||
usd_shipping = create_account(account_name="Shipping Charges USD",
|
usd_shipping = create_account(account_name="Shipping Charges USD",
|
||||||
parent_account="Duties and Taxes - TCP1", company="_Test Company with perpetual inventory",
|
parent_account="Duties and Taxes - TCP1", company="_Test Company with perpetual inventory",
|
||||||
|
|||||||
@@ -94,10 +94,15 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
frappe.get_doc('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice').delete()
|
frappe.get_doc('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice').delete()
|
||||||
|
|
||||||
def test_purchase_receipt_no_gl_entry(self):
|
def test_purchase_receipt_no_gl_entry(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
|
company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
|
||||||
|
|
||||||
existing_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item",
|
existing_bin_qty, existing_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item",
|
||||||
"warehouse": "_Test Warehouse - _TC"}, "stock_value")
|
"warehouse": "_Test Warehouse - _TC"}, ["actual_qty", "stock_value"])
|
||||||
|
|
||||||
|
if existing_bin_qty < 0:
|
||||||
|
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=abs(existing_bin_qty))
|
||||||
|
|
||||||
pr = make_purchase_receipt()
|
pr = make_purchase_receipt()
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ class RepostItemValuation(Document):
|
|||||||
|
|
||||||
def repost(doc):
|
def repost(doc):
|
||||||
try:
|
try:
|
||||||
|
if not frappe.db.exists("Repost Item Valuation", doc.name):
|
||||||
|
return
|
||||||
|
|
||||||
doc.set_status('In Progress')
|
doc.set_status('In Progress')
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
@@ -64,7 +67,7 @@ def repost(doc):
|
|||||||
message += "<br>" + "Traceback: <br>" + traceback
|
message += "<br>" + "Traceback: <br>" + traceback
|
||||||
frappe.db.set_value(doc.doctype, doc.name, 'error_log', message)
|
frappe.db.set_value(doc.doctype, doc.name, 'error_log', message)
|
||||||
|
|
||||||
notify_error_to_stock_managers(doc)
|
notify_error_to_stock_managers(doc, message)
|
||||||
doc.set_status('Failed')
|
doc.set_status('Failed')
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ def create_shipment_company(company_name, abbr):
|
|||||||
company.abbr = abbr
|
company.abbr = abbr
|
||||||
company.default_currency = 'EUR'
|
company.default_currency = 'EUR'
|
||||||
company.country = 'Germany'
|
company.country = 'Germany'
|
||||||
|
company.enable_perpetual_inventory = 0
|
||||||
company.insert()
|
company.insert()
|
||||||
return company
|
return company
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class StockEntry(StockController):
|
|||||||
if self.purpose not in valid_purposes:
|
if self.purpose not in valid_purposes:
|
||||||
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
|
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
|
||||||
|
|
||||||
if self.job_card and self.purpose != 'Material Transfer for Manufacture':
|
if self.job_card and self.purpose not in ['Material Transfer for Manufacture', 'Repack']:
|
||||||
frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
|
frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
|
||||||
.format(self.job_card))
|
.format(self.job_card))
|
||||||
|
|
||||||
@@ -823,6 +823,7 @@ class StockEntry(StockController):
|
|||||||
if self.job_card:
|
if self.job_card:
|
||||||
job_doc = frappe.get_doc('Job Card', self.job_card)
|
job_doc = frappe.get_doc('Job Card', self.job_card)
|
||||||
job_doc.set_transferred_qty(update_status=True)
|
job_doc.set_transferred_qty(update_status=True)
|
||||||
|
job_doc.set_transferred_qty_in_job_card(self)
|
||||||
|
|
||||||
if self.work_order:
|
if self.work_order:
|
||||||
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
||||||
|
|||||||
@@ -69,7 +69,8 @@
|
|||||||
"putaway_rule",
|
"putaway_rule",
|
||||||
"column_break_51",
|
"column_break_51",
|
||||||
"reference_purchase_receipt",
|
"reference_purchase_receipt",
|
||||||
"quality_inspection"
|
"quality_inspection",
|
||||||
|
"job_card_item"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -532,13 +533,22 @@
|
|||||||
"fieldname": "is_finished_item",
|
"fieldname": "is_finished_item",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Finished Item"
|
"label": "Is Finished Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "job_card_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Job Card Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-30 15:00:44.489442",
|
"modified": "2021-02-11 13:47:50.158754",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Entry Detail",
|
"name": "Stock Entry Detail",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class StockLedgerEntry(Document):
|
|||||||
self.block_transactions_against_group_warehouse()
|
self.block_transactions_against_group_warehouse()
|
||||||
self.validate_with_last_transaction_posting_time()
|
self.validate_with_last_transaction_posting_time()
|
||||||
|
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.check_stock_frozen_date()
|
self.check_stock_frozen_date()
|
||||||
self.actual_amt_check()
|
self.actual_amt_check()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
|
|||||||
|
|
||||||
cancel = sl_entries[0].get("is_cancelled")
|
cancel = sl_entries[0].get("is_cancelled")
|
||||||
if cancel:
|
if cancel:
|
||||||
|
validate_cancellation(sl_entries)
|
||||||
set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
|
set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
|
||||||
|
|
||||||
for sle in sl_entries:
|
for sle in sl_entries:
|
||||||
@@ -45,6 +46,20 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
|
|||||||
args = sle_doc.as_dict()
|
args = sle_doc.as_dict()
|
||||||
update_bin(args, allow_negative_stock, via_landed_cost_voucher)
|
update_bin(args, allow_negative_stock, via_landed_cost_voucher)
|
||||||
|
|
||||||
|
def validate_cancellation(args):
|
||||||
|
if args[0].get("is_cancelled"):
|
||||||
|
repost_entry = frappe.db.get_value("Repost Item Valuation", {
|
||||||
|
'voucher_type': args[0].voucher_type,
|
||||||
|
'voucher_no': args[0].voucher_no,
|
||||||
|
'docstatus': 1
|
||||||
|
}, ['name', 'status'], as_dict=1)
|
||||||
|
|
||||||
|
if repost_entry:
|
||||||
|
if repost_entry.status == 'In Progress':
|
||||||
|
frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet."))
|
||||||
|
if repost_entry.status == 'Queued':
|
||||||
|
frappe.delete_doc("Repost Item Valuation", repost_entry.name)
|
||||||
|
|
||||||
|
|
||||||
def set_as_cancel(voucher_type, voucher_no):
|
def set_as_cancel(voucher_type, voucher_no):
|
||||||
frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1,
|
frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1,
|
||||||
@@ -74,7 +89,8 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat
|
|||||||
"item_code": args[i].item_code,
|
"item_code": args[i].item_code,
|
||||||
"warehouse": args[i].warehouse,
|
"warehouse": args[i].warehouse,
|
||||||
"posting_date": args[i].posting_date,
|
"posting_date": args[i].posting_date,
|
||||||
"posting_time": args[i].posting_time
|
"posting_time": args[i].posting_time,
|
||||||
|
"creation": args[i].get("creation")
|
||||||
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
|
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
|
||||||
|
|
||||||
for item_wh, new_sle in iteritems(obj.new_items):
|
for item_wh, new_sle in iteritems(obj.new_items):
|
||||||
@@ -86,7 +102,7 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat
|
|||||||
def get_args_for_voucher(voucher_type, voucher_no):
|
def get_args_for_voucher(voucher_type, voucher_no):
|
||||||
return frappe.db.get_all("Stock Ledger Entry",
|
return frappe.db.get_all("Stock Ledger Entry",
|
||||||
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
|
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
|
||||||
fields=["item_code", "warehouse", "posting_date", "posting_time"],
|
fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"],
|
||||||
order_by="creation asc",
|
order_by="creation asc",
|
||||||
group_by="item_code, warehouse"
|
group_by="item_code, warehouse"
|
||||||
)
|
)
|
||||||
@@ -117,7 +133,7 @@ class update_entries_after(object):
|
|||||||
self.item_code = args.get("item_code")
|
self.item_code = args.get("item_code")
|
||||||
if self.args.sle_id:
|
if self.args.sle_id:
|
||||||
self.args['name'] = self.args.sle_id
|
self.args['name'] = self.args.sle_id
|
||||||
|
|
||||||
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
|
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
|
||||||
self.get_precision()
|
self.get_precision()
|
||||||
self.valuation_method = get_valuation_method(self.item_code)
|
self.valuation_method = get_valuation_method(self.item_code)
|
||||||
@@ -155,7 +171,7 @@ class update_entries_after(object):
|
|||||||
"""
|
"""
|
||||||
self.data.setdefault(args.warehouse, frappe._dict())
|
self.data.setdefault(args.warehouse, frappe._dict())
|
||||||
warehouse_dict = self.data[args.warehouse]
|
warehouse_dict = self.data[args.warehouse]
|
||||||
previous_sle = self.get_sle_before_datetime(args)
|
previous_sle = self.get_previous_sle_of_current_voucher(args)
|
||||||
warehouse_dict.previous_sle = previous_sle
|
warehouse_dict.previous_sle = previous_sle
|
||||||
|
|
||||||
for key in ("qty_after_transaction", "valuation_rate", "stock_value"):
|
for key in ("qty_after_transaction", "valuation_rate", "stock_value"):
|
||||||
@@ -167,9 +183,35 @@ class update_entries_after(object):
|
|||||||
"stock_value_difference": 0.0
|
"stock_value_difference": 0.0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def get_previous_sle_of_current_voucher(self, args):
|
||||||
|
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
||||||
|
|
||||||
|
args['time_format'] = '%H:%i:%s'
|
||||||
|
if not args.get("posting_date"):
|
||||||
|
args["posting_date"] = "1900-01-01"
|
||||||
|
if not args.get("posting_time"):
|
||||||
|
args["posting_time"] = "00:00"
|
||||||
|
|
||||||
|
sle = frappe.db.sql("""
|
||||||
|
select *, timestamp(posting_date, posting_time) as "timestamp"
|
||||||
|
from `tabStock Ledger Entry`
|
||||||
|
where item_code = %(item_code)s
|
||||||
|
and warehouse = %(warehouse)s
|
||||||
|
and is_cancelled = 0
|
||||||
|
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
|
||||||
|
order by timestamp(posting_date, posting_time) desc, creation desc
|
||||||
|
limit 1""", args, as_dict=1)
|
||||||
|
|
||||||
|
return sle[0] if sle else frappe._dict()
|
||||||
|
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
|
from erpnext.controllers.stock_controller import check_if_future_sle_exists
|
||||||
|
|
||||||
if self.args.get("sle_id"):
|
if self.args.get("sle_id"):
|
||||||
self.process_sle_against_current_voucher()
|
self.process_sle_against_current_timestamp()
|
||||||
|
if not check_if_future_sle_exists(self.args):
|
||||||
|
self.update_bin()
|
||||||
else:
|
else:
|
||||||
entries_to_fix = self.get_future_entries_to_fix()
|
entries_to_fix = self.get_future_entries_to_fix()
|
||||||
|
|
||||||
@@ -182,18 +224,20 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
if sle.dependant_sle_voucher_detail_no:
|
if sle.dependant_sle_voucher_detail_no:
|
||||||
entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle)
|
entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle)
|
||||||
|
|
||||||
|
self.update_bin()
|
||||||
|
|
||||||
if self.exceptions:
|
if self.exceptions:
|
||||||
self.raise_exceptions()
|
self.raise_exceptions()
|
||||||
|
|
||||||
self.update_bin()
|
def process_sle_against_current_timestamp(self):
|
||||||
|
|
||||||
def process_sle_against_current_voucher(self):
|
|
||||||
sl_entries = self.get_sle_against_current_voucher()
|
sl_entries = self.get_sle_against_current_voucher()
|
||||||
for sle in sl_entries:
|
for sle in sl_entries:
|
||||||
self.process_sle(sle)
|
self.process_sle(sle)
|
||||||
|
|
||||||
def get_sle_against_current_voucher(self):
|
def get_sle_against_current_voucher(self):
|
||||||
|
self.args['time_format'] = '%H:%i:%s'
|
||||||
|
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select
|
select
|
||||||
*, timestamp(posting_date, posting_time) as "timestamp"
|
*, timestamp(posting_date, posting_time) as "timestamp"
|
||||||
@@ -202,7 +246,8 @@ class update_entries_after(object):
|
|||||||
where
|
where
|
||||||
item_code = %(item_code)s
|
item_code = %(item_code)s
|
||||||
and warehouse = %(warehouse)s
|
and warehouse = %(warehouse)s
|
||||||
and timestamp(posting_date, time_format(posting_time, '%H:%i:%s')) = timestamp(%(posting_date)s, time_format(%(posting_time)s, '%H:%i:%s'))
|
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
|
||||||
|
|
||||||
order by
|
order by
|
||||||
creation ASC
|
creation ASC
|
||||||
for update
|
for update
|
||||||
@@ -229,7 +274,6 @@ class update_entries_after(object):
|
|||||||
return entries_to_fix
|
return entries_to_fix
|
||||||
elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
|
elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
|
||||||
return entries_to_fix
|
return entries_to_fix
|
||||||
|
|
||||||
self.initialize_previous_data(dependant_sle)
|
self.initialize_previous_data(dependant_sle)
|
||||||
|
|
||||||
args = self.data[dependant_sle.warehouse].previous_sle \
|
args = self.data[dependant_sle.warehouse].previous_sle \
|
||||||
@@ -636,7 +680,6 @@ class update_entries_after(object):
|
|||||||
# update bin for each warehouse
|
# update bin for each warehouse
|
||||||
for warehouse, data in iteritems(self.data):
|
for warehouse, data in iteritems(self.data):
|
||||||
bin_doc = get_bin(self.item_code, warehouse)
|
bin_doc = get_bin(self.item_code, warehouse)
|
||||||
|
|
||||||
bin_doc.update({
|
bin_doc.update({
|
||||||
"valuation_rate": data.valuation_rate,
|
"valuation_rate": data.valuation_rate,
|
||||||
"actual_qty": data.qty_after_transaction,
|
"actual_qty": data.qty_after_transaction,
|
||||||
@@ -762,6 +805,25 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
|
|||||||
|
|
||||||
return valuation_rate
|
return valuation_rate
|
||||||
|
|
||||||
|
def update_qty_in_future_sle(args, allow_negative_stock=None):
|
||||||
|
frappe.db.sql("""
|
||||||
|
update `tabStock Ledger Entry`
|
||||||
|
set qty_after_transaction = qty_after_transaction + {qty}
|
||||||
|
where
|
||||||
|
item_code = %(item_code)s
|
||||||
|
and warehouse = %(warehouse)s
|
||||||
|
and voucher_no != %(voucher_no)s
|
||||||
|
and is_cancelled = 0
|
||||||
|
and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)
|
||||||
|
or (
|
||||||
|
timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s)
|
||||||
|
and creation > %(creation)s
|
||||||
|
)
|
||||||
|
)
|
||||||
|
""".format(qty=args.actual_qty), args)
|
||||||
|
|
||||||
|
validate_negative_qty_in_future_sle(args, allow_negative_stock)
|
||||||
|
|
||||||
def validate_negative_qty_in_future_sle(args, allow_negative_stock=None):
|
def validate_negative_qty_in_future_sle(args, allow_negative_stock=None):
|
||||||
allow_negative_stock = allow_negative_stock \
|
allow_negative_stock = allow_negative_stock \
|
||||||
or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
||||||
@@ -790,7 +852,7 @@ def get_future_sle_with_negative_qty(args):
|
|||||||
and voucher_no != %(voucher_no)s
|
and voucher_no != %(voucher_no)s
|
||||||
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
|
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
|
||||||
and is_cancelled = 0
|
and is_cancelled = 0
|
||||||
and qty_after_transaction + {0} < 0
|
and qty_after_transaction < 0
|
||||||
order by timestamp(posting_date, posting_time) asc
|
order by timestamp(posting_date, posting_time) asc
|
||||||
limit 1
|
limit 1
|
||||||
""".format(args.actual_qty), args, as_dict=1)
|
""", args, as_dict=1)
|
||||||
Reference in New Issue
Block a user