Merge branch 'develop' into debit-credit-opening-invoice-tool
This commit is contained in:
commit
9a75a60784
@ -101,7 +101,7 @@ class Account(NestedSet):
|
|||||||
return
|
return
|
||||||
if not frappe.db.get_value("Account",
|
if not frappe.db.get_value("Account",
|
||||||
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
|
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
|
||||||
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
|
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
|
||||||
elif self.parent_account:
|
elif self.parent_account:
|
||||||
descendants = get_descendants_of('Company', self.company)
|
descendants = get_descendants_of('Company', self.company)
|
||||||
if not descendants: return
|
if not descendants: return
|
||||||
@ -164,9 +164,19 @@ class Account(NestedSet):
|
|||||||
|
|
||||||
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
|
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
|
||||||
for company in descendants:
|
for company in descendants:
|
||||||
|
company_bold = frappe.bold(company)
|
||||||
|
parent_acc_name_bold = frappe.bold(parent_acc_name)
|
||||||
if not parent_acc_name_map.get(company):
|
if not parent_acc_name_map.get(company):
|
||||||
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
|
frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
|
||||||
.format(company, parent_acc_name))
|
.format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
|
||||||
|
|
||||||
|
# validate if parent of child company account to be added is a group
|
||||||
|
if (frappe.db.get_value("Account", self.parent_account, "is_group")
|
||||||
|
and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
|
||||||
|
msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
|
||||||
|
msg += "<br><br>"
|
||||||
|
msg += _("Please convert the parent account in corresponding child company to a group account.")
|
||||||
|
frappe.throw(msg, title=_("Invalid Parent Account"))
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
"account_name": self.account_name,
|
"account_name": self.account_name,
|
||||||
@ -309,8 +319,9 @@ def update_account_number(name, account_name, account_number=None, from_descenda
|
|||||||
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
||||||
|
|
||||||
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
|
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
|
||||||
message += "<br>" + _("Renaming it is only allowed via parent company {0}, \
|
message += "<br>"
|
||||||
to avoid mismatch.").format(frappe.bold(ancestor)) + "<br><br>"
|
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(frappe.bold(ancestor))
|
||||||
|
message += "<br><br>"
|
||||||
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
|
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
|
||||||
|
|
||||||
frappe.throw(message, title=_("Rename Not Allowed"))
|
frappe.throw(message, title=_("Rename Not Allowed"))
|
||||||
|
@ -111,6 +111,17 @@ class TestAccount(unittest.TestCase):
|
|||||||
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
|
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
|
||||||
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
|
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
|
||||||
|
|
||||||
|
def test_add_account_to_a_group(self):
|
||||||
|
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 1)
|
||||||
|
|
||||||
|
acc = frappe.new_doc("Account")
|
||||||
|
acc.account_name = "Test Group Account"
|
||||||
|
acc.parent_account = "Office Rent - _TC3"
|
||||||
|
acc.company = "_Test Company 3"
|
||||||
|
self.assertRaises(frappe.ValidationError, acc.insert)
|
||||||
|
|
||||||
|
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 0)
|
||||||
|
|
||||||
def test_account_rename_sync(self):
|
def test_account_rename_sync(self):
|
||||||
frappe.local.flags.pop("ignore_root_company_validation", None)
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
|
|
||||||
@ -160,6 +171,7 @@ class TestAccount(unittest.TestCase):
|
|||||||
for doc in to_delete:
|
for doc in to_delete:
|
||||||
frappe.delete_doc("Account", doc)
|
frappe.delete_doc("Account", doc)
|
||||||
|
|
||||||
|
|
||||||
def _make_test_records(verbose):
|
def _make_test_records(verbose):
|
||||||
from frappe.test_runner import make_test_objects
|
from frappe.test_runner import make_test_objects
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ frappe.ui.form.on('Accounting Dimension', {
|
|||||||
frm.set_query('document_type', () => {
|
frm.set_query('document_type', () => {
|
||||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||||
invalid_doctypes.push('Accounting Dimension', 'Project',
|
invalid_doctypes.push('Accounting Dimension', 'Project',
|
||||||
'Cost Center', 'Accounting Dimension Detail');
|
'Cost Center', 'Accounting Dimension Detail', 'Company');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
@ -19,7 +19,7 @@ class AccountingDimension(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
||||||
'Cost Center', 'Accounting Dimension Detail') :
|
'Cost Center', 'Accounting Dimension Detail', 'Company') :
|
||||||
|
|
||||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
@ -51,6 +51,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
args: {
|
args: {
|
||||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||||
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||||
|
pos_profile: frm.doc.pos_profile,
|
||||||
user: frm.doc.user
|
user: frm.doc.user
|
||||||
},
|
},
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
|
@ -14,19 +14,51 @@ from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import
|
|||||||
|
|
||||||
class POSClosingEntry(Document):
|
class POSClosingEntry(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
user = frappe.get_all('POS Closing Entry',
|
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||||
filters = { 'user': self.user, 'docstatus': 1 },
|
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||||
|
|
||||||
|
self.validate_pos_closing()
|
||||||
|
self.validate_pos_invoices()
|
||||||
|
|
||||||
|
def validate_pos_closing(self):
|
||||||
|
user = frappe.get_all("POS Closing Entry",
|
||||||
|
filters = { "user": self.user, "docstatus": 1, "pos_profile": self.pos_profile },
|
||||||
or_filters = {
|
or_filters = {
|
||||||
'period_start_date': ('between', [self.period_start_date, self.period_end_date]),
|
"period_start_date": ("between", [self.period_start_date, self.period_end_date]),
|
||||||
'period_end_date': ('between', [self.period_start_date, self.period_end_date])
|
"period_end_date": ("between", [self.period_start_date, self.period_end_date])
|
||||||
})
|
})
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
frappe.throw(_("POS Closing Entry {} against {} between selected period"
|
bold_already_exists = frappe.bold(_("already exists"))
|
||||||
.format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period"))
|
bold_user = frappe.bold(self.user)
|
||||||
|
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||||
|
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||||
|
|
||||||
|
def validate_pos_invoices(self):
|
||||||
|
invalid_rows = []
|
||||||
|
for d in self.pos_transactions:
|
||||||
|
invalid_row = {'idx': d.idx}
|
||||||
|
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||||
|
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
|
||||||
|
if pos_invoice.consolidated_invoice:
|
||||||
|
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
|
||||||
|
invalid_rows.append(invalid_row)
|
||||||
|
continue
|
||||||
|
if pos_invoice.pos_profile != self.pos_profile:
|
||||||
|
invalid_row.setdefault('msg', []).append(_("POS Profile doesn't matches {}").format(frappe.bold(self.pos_profile)))
|
||||||
|
if pos_invoice.docstatus != 1:
|
||||||
|
invalid_row.setdefault('msg', []).append(_('POS Invoice is not {}').format(frappe.bold("submitted")))
|
||||||
|
if pos_invoice.owner != self.user:
|
||||||
|
invalid_row.setdefault('msg', []).append(_("POS Invoice isn't created by user {}").format(frappe.bold(self.owner)))
|
||||||
|
|
||||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
if invalid_row.get('msg'):
|
||||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
invalid_rows.append(invalid_row)
|
||||||
|
|
||||||
|
if not invalid_rows:
|
||||||
|
return
|
||||||
|
|
||||||
|
error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
|
||||||
|
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
merge_pos_invoices(self.pos_transactions)
|
merge_pos_invoices(self.pos_transactions)
|
||||||
@ -47,16 +79,15 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
return [c['user'] for c in cashiers_list]
|
return [c['user'] for c in cashiers_list]
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pos_invoices(start, end, user):
|
def get_pos_invoices(start, end, pos_profile, user):
|
||||||
data = frappe.db.sql("""
|
data = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
name, timestamp(posting_date, posting_time) as "timestamp"
|
name, timestamp(posting_date, posting_time) as "timestamp"
|
||||||
from
|
from
|
||||||
`tabPOS Invoice`
|
`tabPOS Invoice`
|
||||||
where
|
where
|
||||||
owner = %s and docstatus = 1 and
|
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
|
||||||
(consolidated_invoice is NULL or consolidated_invoice = '')
|
""", (user, pos_profile), as_dict=1)
|
||||||
""", (user), as_dict=1)
|
|
||||||
|
|
||||||
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
|
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
|
||||||
# need to get taxes and payments so can't avoid get_doc
|
# need to get taxes and payments so can't avoid get_doc
|
||||||
@ -76,7 +107,8 @@ def make_closing_entry_from_opening(opening_entry):
|
|||||||
closing_entry.net_total = 0
|
closing_entry.net_total = 0
|
||||||
closing_entry.total_quantity = 0
|
closing_entry.total_quantity = 0
|
||||||
|
|
||||||
invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date, closing_entry.user)
|
invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date,
|
||||||
|
closing_entry.pos_profile, closing_entry.user)
|
||||||
|
|
||||||
pos_transactions = []
|
pos_transactions = []
|
||||||
taxes = []
|
taxes = []
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"mode_of_payment",
|
"mode_of_payment",
|
||||||
"opening_amount",
|
"opening_amount",
|
||||||
"closing_amount",
|
|
||||||
"expected_amount",
|
"expected_amount",
|
||||||
|
"closing_amount",
|
||||||
"difference"
|
"difference"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -26,8 +26,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Expected Amount",
|
"label": "Expected Amount",
|
||||||
"options": "company:company_currency",
|
"options": "company:company_currency",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "difference",
|
"fieldname": "difference",
|
||||||
@ -55,9 +54,10 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:03:34.533607",
|
"modified": "2020-10-23 16:45:43.662034",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Closing Entry Detail",
|
"name": "POS Closing Entry Detail",
|
||||||
|
@ -9,80 +9,63 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
|||||||
this._super(doc);
|
this._super(doc);
|
||||||
},
|
},
|
||||||
|
|
||||||
onload() {
|
onload(doc) {
|
||||||
this._super();
|
this._super();
|
||||||
if(this.frm.doc.__islocal && this.frm.doc.is_pos) {
|
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||||
//Load pos profile data on the invoice if the default value of Is POS is 1
|
this.frm.script_manager.trigger("is_pos");
|
||||||
|
this.frm.refresh_fields();
|
||||||
me.frm.script_manager.trigger("is_pos");
|
|
||||||
me.frm.refresh_fields();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh(doc) {
|
refresh(doc) {
|
||||||
this._super();
|
this._super();
|
||||||
if (doc.docstatus == 1 && !doc.is_return) {
|
if (doc.docstatus == 1 && !doc.is_return) {
|
||||||
if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) {
|
this.frm.add_custom_button(__('Return'), this.make_sales_return, __('Create'));
|
||||||
cur_frm.add_custom_button(__('Return'),
|
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
this.make_sales_return, __('Create'));
|
|
||||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.frm.doc.is_return) {
|
if (doc.is_return && doc.__islocal) {
|
||||||
this.frm.return_print_format = "Sales Invoice Return";
|
this.frm.return_print_format = "Sales Invoice Return";
|
||||||
cur_frm.set_value('consolidated_invoice', '');
|
this.frm.set_value('consolidated_invoice', '');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
is_pos: function(frm){
|
is_pos: function() {
|
||||||
this.set_pos_data();
|
this.set_pos_data();
|
||||||
},
|
},
|
||||||
|
|
||||||
set_pos_data: function() {
|
set_pos_data: async function() {
|
||||||
if(this.frm.doc.is_pos) {
|
if(this.frm.doc.is_pos) {
|
||||||
this.frm.set_value("allocate_advances_automatically", 0);
|
this.frm.set_value("allocate_advances_automatically", 0);
|
||||||
if(!this.frm.doc.company) {
|
if(!this.frm.doc.company) {
|
||||||
this.frm.set_value("is_pos", 0);
|
this.frm.set_value("is_pos", 0);
|
||||||
frappe.msgprint(__("Please specify Company to proceed"));
|
frappe.msgprint(__("Please specify Company to proceed"));
|
||||||
} else {
|
} else {
|
||||||
var me = this;
|
const r = await this.frm.call({
|
||||||
return this.frm.call({
|
doc: this.frm.doc,
|
||||||
doc: me.frm.doc,
|
|
||||||
method: "set_missing_values",
|
method: "set_missing_values",
|
||||||
callback: function(r) {
|
freeze: true
|
||||||
if(!r.exc) {
|
|
||||||
if(r.message) {
|
|
||||||
me.frm.pos_print_format = r.message.print_format || "";
|
|
||||||
me.frm.meta.default_print_format = r.message.print_format || "";
|
|
||||||
me.frm.allow_edit_rate = r.message.allow_edit_rate;
|
|
||||||
me.frm.allow_edit_discount = r.message.allow_edit_discount;
|
|
||||||
me.frm.doc.campaign = r.message.campaign;
|
|
||||||
me.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
|
||||||
}
|
|
||||||
me.frm.script_manager.trigger("update_stock");
|
|
||||||
me.calculate_taxes_and_totals();
|
|
||||||
if(me.frm.doc.taxes_and_charges) {
|
|
||||||
me.frm.script_manager.trigger("taxes_and_charges");
|
|
||||||
}
|
|
||||||
frappe.model.set_default_values(me.frm.doc);
|
|
||||||
me.set_dynamic_labels();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
if(!r.exc) {
|
||||||
|
if(r.message) {
|
||||||
|
this.frm.pos_print_format = r.message.print_format || "";
|
||||||
|
this.frm.meta.default_print_format = r.message.print_format || "";
|
||||||
|
this.frm.doc.campaign = r.message.campaign;
|
||||||
|
this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
||||||
|
}
|
||||||
|
this.frm.script_manager.trigger("update_stock");
|
||||||
|
this.calculate_taxes_and_totals();
|
||||||
|
this.frm.doc.taxes_and_charges && this.frm.script_manager.trigger("taxes_and_charges");
|
||||||
|
frappe.model.set_default_values(this.frm.doc);
|
||||||
|
this.set_dynamic_labels();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else this.frm.trigger("refresh");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
customer() {
|
customer() {
|
||||||
if (!this.frm.doc.customer) return
|
if (!this.frm.doc.customer) return
|
||||||
|
const pos_profile = this.frm.doc.pos_profile;
|
||||||
if (this.frm.doc.is_pos){
|
|
||||||
var pos_profile = this.frm.doc.pos_profile;
|
|
||||||
}
|
|
||||||
var me = this;
|
|
||||||
if(this.frm.updating_party_details) return;
|
if(this.frm.updating_party_details) return;
|
||||||
erpnext.utils.get_party_details(this.frm,
|
erpnext.utils.get_party_details(this.frm,
|
||||||
"erpnext.accounts.party.get_party_details", {
|
"erpnext.accounts.party.get_party_details", {
|
||||||
@ -92,8 +75,8 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
|||||||
account: this.frm.doc.debit_to,
|
account: this.frm.doc.debit_to,
|
||||||
price_list: this.frm.doc.selling_price_list,
|
price_list: this.frm.doc.selling_price_list,
|
||||||
pos_profile: pos_profile
|
pos_profile: pos_profile
|
||||||
}, function() {
|
}, () => {
|
||||||
me.apply_pricing_rule();
|
this.apply_pricing_rule();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -10,12 +10,10 @@ from erpnext.controllers.selling_controller import SellingController
|
|||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
|
||||||
get_loyalty_program_details_with_points, validate_loyalty_points
|
|
||||||
|
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option
|
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
|
|
||||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||||
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option, get_mode_of_payment_info
|
||||||
|
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
@ -30,8 +28,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
# run on validate method of selling controller
|
# run on validate method of selling controller
|
||||||
super(SalesInvoice, self).validate()
|
super(SalesInvoice, self).validate()
|
||||||
self.validate_auto_set_posting_time()
|
self.validate_auto_set_posting_time()
|
||||||
self.validate_pos_paid_amount()
|
self.validate_mode_of_payment()
|
||||||
self.validate_pos_return()
|
|
||||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||||
self.validate_uom_is_integer("uom", "qty")
|
self.validate_uom_is_integer("uom", "qty")
|
||||||
self.validate_debit_to_acc()
|
self.validate_debit_to_acc()
|
||||||
@ -41,11 +38,11 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.validate_item_cost_centers()
|
self.validate_item_cost_centers()
|
||||||
self.validate_serialised_or_batched_item()
|
self.validate_serialised_or_batched_item()
|
||||||
self.validate_stock_availablility()
|
self.validate_stock_availablility()
|
||||||
self.validate_return_items()
|
self.validate_return_items_qty()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.set_account_for_mode_of_payment()
|
self.set_account_for_mode_of_payment()
|
||||||
self.validate_pos()
|
self.validate_pos()
|
||||||
self.verify_payment_amount()
|
self.validate_payment_amount()
|
||||||
self.validate_loyalty_transaction()
|
self.validate_loyalty_transaction()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
@ -84,70 +81,98 @@ class POSInvoice(SalesInvoice):
|
|||||||
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||||
|
|
||||||
def validate_stock_availablility(self):
|
def validate_stock_availablility(self):
|
||||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
if self.is_return:
|
||||||
|
return
|
||||||
|
|
||||||
|
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||||
|
error_msg = []
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
|
msg = ""
|
||||||
if d.serial_no:
|
if d.serial_no:
|
||||||
filters = {
|
filters = { "item_code": d.item_code, "warehouse": d.warehouse }
|
||||||
"item_code": d.item_code,
|
|
||||||
"warehouse": d.warehouse,
|
|
||||||
"delivery_document_no": "",
|
|
||||||
"sales_invoice": ""
|
|
||||||
}
|
|
||||||
if d.batch_no:
|
if d.batch_no:
|
||||||
filters["batch_no"] = d.batch_no
|
filters["batch_no"] = d.batch_no
|
||||||
reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
|
||||||
serial_nos = d.serial_no.split("\n")
|
|
||||||
serial_nos = ' '.join(serial_nos).split() # remove whitespaces
|
|
||||||
invalid_serial_nos = []
|
|
||||||
for s in serial_nos:
|
|
||||||
if s in reserved_serial_nos:
|
|
||||||
invalid_serial_nos.append(s)
|
|
||||||
|
|
||||||
if len(invalid_serial_nos):
|
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||||
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. Please select valid serial no.").format(
|
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||||
d.idx, multiple_nos, frappe.bold(', '.join(invalid_serial_nos))), title=_("Not Available"))
|
|
||||||
|
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
|
||||||
|
if len(invalid_serial_nos) == 1:
|
||||||
|
msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||||
|
.format(d.idx, bold_invalid_serial_nos))
|
||||||
|
elif invalid_serial_nos:
|
||||||
|
msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||||
|
.format(d.idx, bold_invalid_serial_nos))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if allow_negative_stock:
|
if allow_negative_stock:
|
||||||
return
|
return
|
||||||
|
|
||||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||||
if not (flt(available_stock) > 0):
|
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.').format(
|
if flt(available_stock) <= 0:
|
||||||
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse)), title=_("Not Available"))
|
msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
|
||||||
elif flt(available_stock) < flt(d.qty):
|
elif flt(available_stock) < flt(d.qty):
|
||||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.').format(
|
msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||||
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)), title=_("Not Available"))
|
.format(d.idx, item_code, warehouse, qty))
|
||||||
|
if msg:
|
||||||
|
error_msg.append(msg)
|
||||||
|
|
||||||
|
if error_msg:
|
||||||
|
frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
|
||||||
|
|
||||||
def validate_serialised_or_batched_item(self):
|
def validate_serialised_or_batched_item(self):
|
||||||
|
error_msg = []
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
serialized = d.get("has_serial_no")
|
serialized = d.get("has_serial_no")
|
||||||
batched = d.get("has_batch_no")
|
batched = d.get("has_batch_no")
|
||||||
no_serial_selected = not d.get("serial_no")
|
no_serial_selected = not d.get("serial_no")
|
||||||
no_batch_selected = not d.get("batch_no")
|
no_batch_selected = not d.get("batch_no")
|
||||||
|
|
||||||
|
msg = ""
|
||||||
|
item_code = frappe.bold(d.item_code)
|
||||||
if serialized and batched and (no_batch_selected or no_serial_selected):
|
if serialized and batched and (no_batch_selected or no_serial_selected):
|
||||||
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.').format(
|
msg = (_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.')
|
||||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
if serialized and no_serial_selected:
|
if serialized and no_serial_selected:
|
||||||
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.').format(
|
msg = (_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.')
|
||||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
if batched and no_batch_selected:
|
if batched and no_batch_selected:
|
||||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.').format(
|
msg = (_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.')
|
||||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
|
if msg:
|
||||||
|
error_msg.append(msg)
|
||||||
|
|
||||||
def validate_return_items(self):
|
if error_msg:
|
||||||
|
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
||||||
|
|
||||||
|
def validate_return_items_qty(self):
|
||||||
if not self.get("is_return"): return
|
if not self.get("is_return"): return
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.get("qty") > 0:
|
if d.get("qty") > 0:
|
||||||
frappe.throw(_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
|
frappe.throw(
|
||||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
|
||||||
|
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")
|
||||||
|
)
|
||||||
|
if d.get("serial_no"):
|
||||||
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
|
for sr in serial_nos:
|
||||||
|
serial_no_exists = frappe.db.exists("POS Invoice Item", {
|
||||||
|
"parent": self.return_against,
|
||||||
|
"serial_no": ["like", d.get("serial_no")]
|
||||||
|
})
|
||||||
|
if not serial_no_exists:
|
||||||
|
bold_return_against = frappe.bold(self.return_against)
|
||||||
|
bold_serial_no = frappe.bold(sr)
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}")
|
||||||
|
.format(d.idx, bold_serial_no, bold_return_against)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_pos_paid_amount(self):
|
def validate_mode_of_payment(self):
|
||||||
if len(self.payments) == 0 and self.is_pos:
|
if len(self.payments) == 0:
|
||||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||||
|
|
||||||
def validate_change_account(self):
|
def validate_change_account(self):
|
||||||
@ -165,20 +190,18 @@ class POSInvoice(SalesInvoice):
|
|||||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||||
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||||
|
|
||||||
def verify_payment_amount(self):
|
def validate_payment_amount(self):
|
||||||
|
total_amount_in_payments = 0
|
||||||
for entry in self.payments:
|
for entry in self.payments:
|
||||||
|
total_amount_in_payments += entry.amount
|
||||||
if not self.is_return and entry.amount < 0:
|
if not self.is_return and entry.amount < 0:
|
||||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
||||||
if self.is_return and entry.amount > 0:
|
if self.is_return and entry.amount > 0:
|
||||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
||||||
|
|
||||||
def validate_pos_return(self):
|
if self.is_return:
|
||||||
if self.is_pos and self.is_return:
|
|
||||||
total_amount_in_payments = 0
|
|
||||||
for payment in self.payments:
|
|
||||||
total_amount_in_payments += payment.amount
|
|
||||||
invoice_total = self.rounded_total or self.grand_total
|
invoice_total = self.rounded_total or self.grand_total
|
||||||
if total_amount_in_payments < invoice_total:
|
if total_amount_in_payments and total_amount_in_payments < invoice_total:
|
||||||
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
|
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
|
||||||
|
|
||||||
def validate_loyalty_transaction(self):
|
def validate_loyalty_transaction(self):
|
||||||
@ -233,55 +256,45 @@ class POSInvoice(SalesInvoice):
|
|||||||
pos_profile = get_pos_profile(self.company) or {}
|
pos_profile = get_pos_profile(self.company) or {}
|
||||||
self.pos_profile = pos_profile.get('name')
|
self.pos_profile = pos_profile.get('name')
|
||||||
|
|
||||||
pos = {}
|
profile = {}
|
||||||
if self.pos_profile:
|
if self.pos_profile:
|
||||||
pos = frappe.get_doc('POS Profile', self.pos_profile)
|
profile = frappe.get_doc('POS Profile', self.pos_profile)
|
||||||
|
|
||||||
if not self.get('payments') and not for_validate:
|
if not self.get('payments') and not for_validate:
|
||||||
update_multi_mode_option(self, pos)
|
update_multi_mode_option(self, profile)
|
||||||
|
|
||||||
if not self.account_for_change_amount:
|
if self.is_return and not for_validate:
|
||||||
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
add_return_modes(self, profile)
|
||||||
|
|
||||||
if pos:
|
|
||||||
if not for_validate:
|
|
||||||
self.tax_category = pos.get("tax_category")
|
|
||||||
|
|
||||||
|
if profile:
|
||||||
if not for_validate and not self.customer:
|
if not for_validate and not self.customer:
|
||||||
self.customer = pos.customer
|
self.customer = profile.customer
|
||||||
|
|
||||||
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
self.ignore_pricing_rule = profile.ignore_pricing_rule
|
||||||
if pos.get('account_for_change_amount'):
|
self.account_for_change_amount = profile.get('account_for_change_amount') or self.account_for_change_amount
|
||||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
self.set_warehouse = profile.get('warehouse') or self.set_warehouse
|
||||||
if pos.get('warehouse'):
|
|
||||||
self.set_warehouse = pos.get('warehouse')
|
|
||||||
|
|
||||||
for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
|
for fieldname in ('currency', 'letter_head', 'tc_name',
|
||||||
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
||||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
'write_off_cost_center', 'apply_discount_on', 'cost_center', 'tax_category',
|
||||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
'ignore_pricing_rule', 'company_address', 'update_stock'):
|
||||||
self.set(fieldname, pos.get(fieldname))
|
if not for_validate:
|
||||||
|
self.set(fieldname, profile.get(fieldname))
|
||||||
if pos.get("company_address"):
|
|
||||||
self.company_address = pos.get("company_address")
|
|
||||||
|
|
||||||
if self.customer:
|
if self.customer:
|
||||||
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
||||||
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
||||||
selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
|
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
|
||||||
else:
|
else:
|
||||||
selling_price_list = pos.get('selling_price_list')
|
selling_price_list = profile.get('selling_price_list')
|
||||||
|
|
||||||
if selling_price_list:
|
if selling_price_list:
|
||||||
self.set('selling_price_list', selling_price_list)
|
self.set('selling_price_list', selling_price_list)
|
||||||
|
|
||||||
if not for_validate:
|
|
||||||
self.update_stock = cint(pos.get("update_stock"))
|
|
||||||
|
|
||||||
# set pos values in items
|
# set pos values in items
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get('item_code'):
|
if item.get('item_code'):
|
||||||
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
|
profile_details = get_pos_profile_item_details(profile.get("company"), frappe._dict(item.as_dict()), profile)
|
||||||
for fname, val in iteritems(profile_details):
|
for fname, val in iteritems(profile_details):
|
||||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||||
item.set(fname, val)
|
item.set(fname, val)
|
||||||
@ -294,10 +307,13 @@ class POSInvoice(SalesInvoice):
|
|||||||
if self.taxes_and_charges and not len(self.get("taxes")):
|
if self.taxes_and_charges and not len(self.get("taxes")):
|
||||||
self.set_taxes()
|
self.set_taxes()
|
||||||
|
|
||||||
return pos
|
if not self.account_for_change_amount:
|
||||||
|
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
||||||
|
|
||||||
|
return profile
|
||||||
|
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
pos = self.set_pos_fields(for_validate)
|
profile = self.set_pos_fields(for_validate)
|
||||||
|
|
||||||
if not self.debit_to:
|
if not self.debit_to:
|
||||||
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
||||||
@ -307,17 +323,15 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
super(SalesInvoice, self).set_missing_values(for_validate)
|
super(SalesInvoice, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
print_format = pos.get("print_format") if pos else None
|
print_format = profile.get("print_format") if profile else None
|
||||||
if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
|
if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
|
||||||
print_format = 'POS Invoice'
|
print_format = 'POS Invoice'
|
||||||
|
|
||||||
if pos:
|
if profile:
|
||||||
return {
|
return {
|
||||||
"print_format": print_format,
|
"print_format": print_format,
|
||||||
"allow_edit_rate": pos.get("allow_user_to_edit_rate"),
|
"campaign": profile.get("campaign"),
|
||||||
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
|
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
||||||
"campaign": pos.get("campaign"),
|
|
||||||
"allow_print_before_pay": pos.get("allow_print_before_pay")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_account_for_mode_of_payment(self):
|
def set_account_for_mode_of_payment(self):
|
||||||
@ -373,11 +387,9 @@ def get_stock_availability(item_code, warehouse):
|
|||||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||||
|
|
||||||
if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty:
|
if sle_qty and pos_sales_qty:
|
||||||
return sle_qty - pos_sales_qty
|
return sle_qty - pos_sales_qty
|
||||||
else:
|
else:
|
||||||
# when sle_qty is 0
|
|
||||||
# when sle_qty > 0 and pos_sales_qty is 0
|
|
||||||
return sle_qty
|
return sle_qty
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@ -410,4 +422,19 @@ def make_merge_log(invoices):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if merge_log.get('pos_invoices'):
|
if merge_log.get('pos_invoices'):
|
||||||
return merge_log.as_dict()
|
return merge_log.as_dict()
|
||||||
|
|
||||||
|
def add_return_modes(doc, pos_profile):
|
||||||
|
def append_payment(payment_mode):
|
||||||
|
payment = doc.append('payments', {})
|
||||||
|
payment.default = payment_mode.default
|
||||||
|
payment.mode_of_payment = payment_mode.parent
|
||||||
|
payment.account = payment_mode.default_account
|
||||||
|
payment.type = payment_mode.type
|
||||||
|
|
||||||
|
for pos_payment_method in pos_profile.get('payments'):
|
||||||
|
pos_payment_method = pos_payment_method.as_dict()
|
||||||
|
mode_of_payment = pos_payment_method.mode_of_payment
|
||||||
|
if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]:
|
||||||
|
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
||||||
|
append_payment(payment_mode[0])
|
@ -26,18 +26,25 @@ 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_status = frappe.bold(status)
|
||||||
if docstatus != 1:
|
if docstatus != 1:
|
||||||
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
|
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice))
|
||||||
if status == "Consolidated":
|
if status == "Consolidated":
|
||||||
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
|
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, bold_pos_invoice, bold_status))
|
||||||
if is_return and return_against not in [d.pos_invoice for d in self.pos_invoices] and status != "Consolidated":
|
if is_return and return_against and return_against not in [d.pos_invoice for d in self.pos_invoices]:
|
||||||
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
bold_return_against = frappe.bold(return_against)
|
||||||
frappe.throw(
|
return_against_status = frappe.db.get_value('POS Invoice', return_against, "status")
|
||||||
_("Row #{}: Return Invoice {} cannot be made against unconsolidated invoice. \
|
if return_against_status != "Consolidated":
|
||||||
You can add original invoice {} manually to proceed.")
|
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||||
.format(d.idx, frappe.bold(d.pos_invoice), frappe.bold(return_against))
|
bold_unconsolidated = frappe.bold("not Consolidated")
|
||||||
)
|
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ")
|
||||||
|
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
|
||||||
|
msg += _("Original invoice should be consolidated before or along with the return invoice.")
|
||||||
|
msg += "<br><br>"
|
||||||
|
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
|
||||||
|
frappe.throw(msg)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
|
@ -17,18 +17,25 @@ class POSOpeningEntry(StatusUpdater):
|
|||||||
|
|
||||||
def validate_pos_profile_and_cashier(self):
|
def validate_pos_profile_and_cashier(self):
|
||||||
if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
|
if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
|
||||||
frappe.throw(_("POS Profile {} does not belongs to company {}".format(self.pos_profile, self.company)))
|
frappe.throw(_("POS Profile {} does not belongs to company {}").format(self.pos_profile, self.company))
|
||||||
|
|
||||||
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
||||||
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
|
frappe.throw(_("User {} is disabled. Please select valid user/cashier").format(self.user))
|
||||||
|
|
||||||
def validate_payment_method_account(self):
|
def validate_payment_method_account(self):
|
||||||
|
invalid_modes = []
|
||||||
for d in self.balance_details:
|
for d in self.balance_details:
|
||||||
account = frappe.db.get_value("Mode of Payment Account",
|
account = frappe.db.get_value("Mode of Payment Account",
|
||||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
||||||
if not account:
|
if not account:
|
||||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
@ -6,6 +6,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"default",
|
"default",
|
||||||
|
"allow_in_returns",
|
||||||
"mode_of_payment"
|
"mode_of_payment"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -24,11 +25,19 @@
|
|||||||
"label": "Mode of Payment",
|
"label": "Mode of Payment",
|
||||||
"options": "Mode of Payment",
|
"options": "Mode of Payment",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_in_returns",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Allow In Returns"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:08:41.704844",
|
"modified": "2020-10-20 12:58:46.114456",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Payment Method",
|
"name": "POS Payment Method",
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"column_break_9",
|
"column_break_9",
|
||||||
"update_stock",
|
"update_stock",
|
||||||
"ignore_pricing_rule",
|
"ignore_pricing_rule",
|
||||||
|
"hide_unavailable_items",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"campaign",
|
"campaign",
|
||||||
"company_address",
|
"company_address",
|
||||||
@ -290,28 +291,36 @@
|
|||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Warehouse",
|
"label": "Warehouse",
|
||||||
"mandatory_depends_on": "update_stock",
|
|
||||||
"oldfieldname": "warehouse",
|
"oldfieldname": "warehouse",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse",
|
||||||
},
|
"reqd": 1
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "update_stock",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Update Stock"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "ignore_pricing_rule",
|
"fieldname": "ignore_pricing_rule",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Pricing Rule"
|
"label": "Ignore Pricing Rule"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "update_stock",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Update Stock",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "hide_unavailable_items",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Hide Unavailable Items"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-01 17:29:27.759088",
|
"modified": "2020-10-29 13:18:38.795925",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Profile",
|
"name": "POS Profile",
|
||||||
|
@ -56,19 +56,29 @@ class POSProfile(Document):
|
|||||||
if not self.payments:
|
if not self.payments:
|
||||||
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
||||||
|
|
||||||
default_mode_of_payment = [d.default for d in self.payments if d.default]
|
default_mode = [d.default for d in self.payments if d.default]
|
||||||
if not default_mode_of_payment:
|
if not default_mode:
|
||||||
frappe.throw(_("Please select a default mode of payment"))
|
frappe.throw(_("Please select a default mode of payment"))
|
||||||
|
|
||||||
if len(default_mode_of_payment) > 1:
|
if len(default_mode) > 1:
|
||||||
frappe.throw(_("You can only select one mode of payment as default"))
|
frappe.throw(_("You can only select one mode of payment as default"))
|
||||||
|
|
||||||
|
invalid_modes = []
|
||||||
for d in self.payments:
|
for d in self.payments:
|
||||||
account = frappe.db.get_value("Mode of Payment Account",
|
account = frappe.db.get_value(
|
||||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
"Mode of Payment Account",
|
||||||
|
{"parent": d.mode_of_payment, "company": self.company},
|
||||||
|
"default_account"
|
||||||
|
)
|
||||||
if not account:
|
if not account:
|
||||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
|
@ -9,8 +9,7 @@ frappe.ui.form.on('POS Settings', {
|
|||||||
get_invoice_fields: function(frm) {
|
get_invoice_fields: function(frm) {
|
||||||
frappe.model.with_doctype("POS Invoice", () => {
|
frappe.model.with_doctype("POS Invoice", () => {
|
||||||
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || ['Button'].includes(d.fieldtype)) {
|
||||||
['Table', 'Button'].includes(d.fieldtype)) {
|
|
||||||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -504,10 +504,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
|
"depends_on": "eval:in_list(['Discount Percentage'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
|
||||||
"fieldname": "apply_discount_on_rate",
|
"fieldname": "apply_discount_on_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Apply Discount on Rate"
|
"label": "Apply Discount on Discounted Rate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@ -563,7 +563,7 @@
|
|||||||
"icon": "fa fa-gift",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-26 12:24:44.740734",
|
"modified": "2020-10-28 16:53:14.416172",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
@ -60,6 +60,15 @@ class PricingRule(Document):
|
|||||||
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
|
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
|
||||||
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
|
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
|
||||||
|
|
||||||
|
if self.apply_discount_on_rate:
|
||||||
|
if not self.priority:
|
||||||
|
throw(_("As the field {0} is enabled, the field {1} is mandatory.")
|
||||||
|
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
|
||||||
|
|
||||||
|
if self.priority and cint(self.priority) == 1:
|
||||||
|
throw(_("As the field {0} is enabled, the value of the field {1} should be more than 1.")
|
||||||
|
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
|
||||||
|
|
||||||
def validate_applicable_for_selling_or_buying(self):
|
def validate_applicable_for_selling_or_buying(self):
|
||||||
if not self.selling and not self.buying:
|
if not self.selling and not self.buying:
|
||||||
throw(_("Atleast one of the Selling or Buying must be selected"))
|
throw(_("Atleast one of the Selling or Buying must be selected"))
|
||||||
@ -226,12 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
|||||||
|
|
||||||
item_details = frappe._dict({
|
item_details = frappe._dict({
|
||||||
"doctype": args.doctype,
|
"doctype": args.doctype,
|
||||||
|
"has_margin": False,
|
||||||
"name": args.name,
|
"name": args.name,
|
||||||
"parent": args.parent,
|
"parent": args.parent,
|
||||||
"parenttype": args.parenttype,
|
"parenttype": args.parenttype,
|
||||||
"child_docname": args.get('child_docname'),
|
"child_docname": args.get('child_docname')
|
||||||
"discount_percentage_on_rate": [],
|
|
||||||
"discount_amount_on_rate": []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if args.ignore_pricing_rule or not args.item_code:
|
if args.ignore_pricing_rule or not args.item_code:
|
||||||
@ -279,6 +287,10 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
|||||||
else:
|
else:
|
||||||
get_product_discount_rule(pricing_rule, item_details, args, doc)
|
get_product_discount_rule(pricing_rule, item_details, args, doc)
|
||||||
|
|
||||||
|
if not item_details.get("has_margin"):
|
||||||
|
item_details.margin_type = None
|
||||||
|
item_details.margin_rate_or_amount = 0.0
|
||||||
|
|
||||||
item_details.has_pricing_rule = 1
|
item_details.has_pricing_rule = 1
|
||||||
|
|
||||||
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
|
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
|
||||||
@ -330,13 +342,11 @@ def get_pricing_rule_details(args, pricing_rule):
|
|||||||
def apply_price_discount_rule(pricing_rule, item_details, args):
|
def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||||
item_details.pricing_rule_for = pricing_rule.rate_or_discount
|
item_details.pricing_rule_for = pricing_rule.rate_or_discount
|
||||||
|
|
||||||
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)
|
if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
|
||||||
or (pricing_rule.margin_type == 'Percentage')):
|
or (pricing_rule.margin_type == 'Percentage')):
|
||||||
item_details.margin_type = pricing_rule.margin_type
|
item_details.margin_type = pricing_rule.margin_type
|
||||||
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||||
else:
|
item_details.has_margin = True
|
||||||
item_details.margin_type = None
|
|
||||||
item_details.margin_rate_or_amount = 0.0
|
|
||||||
|
|
||||||
if pricing_rule.rate_or_discount == 'Rate':
|
if pricing_rule.rate_or_discount == 'Rate':
|
||||||
pricing_rule_rate = 0.0
|
pricing_rule_rate = 0.0
|
||||||
@ -351,9 +361,9 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
|||||||
if pricing_rule.rate_or_discount != apply_on: continue
|
if pricing_rule.rate_or_discount != apply_on: continue
|
||||||
|
|
||||||
field = frappe.scrub(apply_on)
|
field = frappe.scrub(apply_on)
|
||||||
if pricing_rule.apply_discount_on_rate:
|
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
|
||||||
discount_field = "{0}_on_rate".format(field)
|
# Apply discount on discounted rate
|
||||||
item_details[discount_field].append(pricing_rule.get(field, 0))
|
item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100))
|
||||||
else:
|
else:
|
||||||
if field not in item_details:
|
if field not in item_details:
|
||||||
item_details.setdefault(field, 0)
|
item_details.setdefault(field, 0)
|
||||||
@ -361,14 +371,6 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
|||||||
item_details[field] += (pricing_rule.get(field, 0)
|
item_details[field] += (pricing_rule.get(field, 0)
|
||||||
if pricing_rule else args.get(field, 0))
|
if pricing_rule else args.get(field, 0))
|
||||||
|
|
||||||
def set_discount_amount(rate, item_details):
|
|
||||||
for field in ['discount_percentage_on_rate', 'discount_amount_on_rate']:
|
|
||||||
for d in item_details.get(field):
|
|
||||||
dis_amount = (rate * d / 100
|
|
||||||
if field == 'discount_percentage_on_rate' else d)
|
|
||||||
rate -= dis_amount
|
|
||||||
item_details.rate = rate
|
|
||||||
|
|
||||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
||||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
||||||
get_pricing_rule_items)
|
get_pricing_rule_items)
|
||||||
|
@ -385,7 +385,7 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
so.load_from_db()
|
so.load_from_db()
|
||||||
self.assertEqual(so.items[1].is_free_item, 1)
|
self.assertEqual(so.items[1].is_free_item, 1)
|
||||||
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
||||||
|
|
||||||
def test_cumulative_pricing_rule(self):
|
def test_cumulative_pricing_rule(self):
|
||||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
|
frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
|
||||||
test_record = {
|
test_record = {
|
||||||
@ -429,34 +429,61 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
details = get_item_details(args)
|
details = get_item_details(args)
|
||||||
|
|
||||||
self.assertTrue(details)
|
self.assertTrue(details)
|
||||||
|
|
||||||
def test_pricing_rule_for_condition(self):
|
def test_pricing_rule_for_condition(self):
|
||||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
|
||||||
make_pricing_rule(selling=1, margin_type="Percentage", \
|
make_pricing_rule(selling=1, margin_type="Percentage", \
|
||||||
condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
|
condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
|
||||||
|
|
||||||
# Incorrect Customer and Correct is_return value
|
# Incorrect Customer and Correct is_return value
|
||||||
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
|
||||||
si.items[0].price_list_rate = 1000
|
si.items[0].price_list_rate = 1000
|
||||||
si.submit()
|
si.submit()
|
||||||
item = si.items[0]
|
item = si.items[0]
|
||||||
self.assertEquals(item.rate, 100)
|
self.assertEquals(item.rate, 100)
|
||||||
|
|
||||||
# Correct Customer and Incorrect is_return value
|
# Correct Customer and Incorrect is_return value
|
||||||
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
|
||||||
si.items[0].price_list_rate = 1000
|
si.items[0].price_list_rate = 1000
|
||||||
si.submit()
|
si.submit()
|
||||||
item = si.items[0]
|
item = si.items[0]
|
||||||
self.assertEquals(item.rate, 100)
|
self.assertEquals(item.rate, 100)
|
||||||
|
|
||||||
# Correct Customer and correct is_return value
|
# Correct Customer and correct is_return value
|
||||||
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
|
||||||
si.items[0].price_list_rate = 1000
|
si.items[0].price_list_rate = 1000
|
||||||
si.submit()
|
si.submit()
|
||||||
item = si.items[0]
|
item = si.items[0]
|
||||||
self.assertEquals(item.rate, 900)
|
self.assertEquals(item.rate, 900)
|
||||||
|
|
||||||
|
def test_multiple_pricing_rules(self):
|
||||||
|
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
|
||||||
|
title="_Test Pricing Rule 1")
|
||||||
|
make_pricing_rule(discount_percentage=10, selling=1, title="_Test Pricing Rule 2", priority=2,
|
||||||
|
apply_multiple_pricing_rules=1)
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
|
||||||
|
self.assertEqual(si.items[0].discount_percentage, 30)
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
|
||||||
|
def test_multiple_pricing_rules_with_apply_discount_on_discounted_rate(self):
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
|
||||||
|
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
|
||||||
|
title="_Test Pricing Rule 1")
|
||||||
|
make_pricing_rule(discount_percentage=10, selling=1, priority=2,
|
||||||
|
apply_discount_on_rate=1, title="_Test Pricing Rule 2", apply_multiple_pricing_rules=1)
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
|
||||||
|
self.assertEqual(si.items[0].discount_percentage, 28)
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
|
||||||
def make_pricing_rule(**args):
|
def make_pricing_rule(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
@ -468,6 +495,7 @@ def make_pricing_rule(**args):
|
|||||||
"applicable_for": args.applicable_for,
|
"applicable_for": args.applicable_for,
|
||||||
"selling": args.selling or 0,
|
"selling": args.selling or 0,
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
|
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
|
||||||
"buying": args.buying or 0,
|
"buying": args.buying or 0,
|
||||||
"min_qty": args.min_qty or 0.0,
|
"min_qty": args.min_qty or 0.0,
|
||||||
"max_qty": args.max_qty or 0.0,
|
"max_qty": args.max_qty or 0.0,
|
||||||
@ -476,9 +504,13 @@ def make_pricing_rule(**args):
|
|||||||
"rate": args.rate or 0.0,
|
"rate": args.rate or 0.0,
|
||||||
"margin_type": args.margin_type,
|
"margin_type": args.margin_type,
|
||||||
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
||||||
"condition": args.condition or ''
|
"condition": args.condition or '',
|
||||||
|
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if args.get("priority"):
|
||||||
|
doc.priority = args.get("priority")
|
||||||
|
|
||||||
apply_on = doc.apply_on.replace(' ', '_').lower()
|
apply_on = doc.apply_on.replace(' ', '_').lower()
|
||||||
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
||||||
doc.append(child_table.get(doc.apply_on), {
|
doc.append(child_table.get(doc.apply_on), {
|
||||||
|
@ -14,9 +14,8 @@ import frappe
|
|||||||
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
|
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
|
||||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
from frappe import _, throw
|
from frappe import _, bold
|
||||||
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
|
from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
|
||||||
|
|
||||||
|
|
||||||
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
||||||
|
|
||||||
@ -42,6 +41,7 @@ def get_pricing_rules(args, doc=None):
|
|||||||
if not pricing_rules: return []
|
if not pricing_rules: return []
|
||||||
|
|
||||||
if apply_multiple_pricing_rules(pricing_rules):
|
if apply_multiple_pricing_rules(pricing_rules):
|
||||||
|
pricing_rules = sorted_by_priority(pricing_rules)
|
||||||
for pricing_rule in pricing_rules:
|
for pricing_rule in pricing_rules:
|
||||||
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
||||||
if pricing_rule:
|
if pricing_rule:
|
||||||
@ -53,6 +53,20 @@ def get_pricing_rules(args, doc=None):
|
|||||||
|
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
def sorted_by_priority(pricing_rules):
|
||||||
|
# If more than one pricing rules, then sort by priority
|
||||||
|
pricing_rules_list = []
|
||||||
|
pricing_rule_dict = {}
|
||||||
|
for pricing_rule in pricing_rules:
|
||||||
|
if not pricing_rule.get("priority"): continue
|
||||||
|
|
||||||
|
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
|
||||||
|
|
||||||
|
for key in sorted(pricing_rule_dict):
|
||||||
|
pricing_rules_list.append(pricing_rule_dict.get(key))
|
||||||
|
|
||||||
|
return pricing_rules_list or pricing_rules
|
||||||
|
|
||||||
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
|
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
|
||||||
filtered_pricing_rules = []
|
filtered_pricing_rules = []
|
||||||
if doc:
|
if doc:
|
||||||
@ -284,12 +298,13 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr
|
|||||||
fieldname = field
|
fieldname = field
|
||||||
|
|
||||||
if fieldname:
|
if fieldname:
|
||||||
msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b>
|
msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.")
|
||||||
will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
|
.format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)))
|
||||||
|
|
||||||
if fieldname in ['min_amt', 'max_amt']:
|
if fieldname in ['min_amt', 'max_amt']:
|
||||||
msg = _("""If you {0} {1} worth item <b>{2}</b>, the scheme <b>{3}</b> will be applied on the item.
|
msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.")
|
||||||
""").format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
|
.format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")),
|
||||||
|
bold(item_code), bold(args.rule_description)))
|
||||||
|
|
||||||
frappe.msgprint(msg)
|
frappe.msgprint(msg)
|
||||||
|
|
||||||
|
@ -156,14 +156,16 @@ class PurchaseInvoice(BuyingController):
|
|||||||
["account_type", "report_type", "account_currency"], as_dict=True)
|
["account_type", "report_type", "account_currency"], as_dict=True)
|
||||||
|
|
||||||
if account.report_type != "Balance Sheet":
|
if account.report_type != "Balance Sheet":
|
||||||
frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
|
frappe.throw(
|
||||||
You can change the parent account to a Balance Sheet account or select a different account.")
|
_("Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account.")
|
||||||
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
|
.format(frappe.bold("Credit To")), title=_("Invalid Account")
|
||||||
|
)
|
||||||
|
|
||||||
if self.supplier and account.account_type != "Payable":
|
if self.supplier and account.account_type != "Payable":
|
||||||
frappe.throw(_("Please ensure {} account is a Payable account. \
|
frappe.throw(
|
||||||
Change the account type to Payable or select a different account.")
|
_("Please ensure {} account is a Payable account. Change the account type to Payable or select a different account.")
|
||||||
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
|
.format(frappe.bold("Credit To")), title=_("Invalid Account")
|
||||||
|
)
|
||||||
|
|
||||||
self.party_account_currency = account.account_currency
|
self.party_account_currency = account.account_currency
|
||||||
|
|
||||||
@ -249,10 +251,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if self.update_stock and (not item.from_warehouse):
|
if self.update_stock and (not item.from_warehouse):
|
||||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because account {2}
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
|
||||||
is not linked to warehouse {3} or it is not the default inventory account'''.format(
|
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||||
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]),
|
msg += _("or it is not the default inventory account")
|
||||||
frappe.bold(item.expense_account), frappe.bold(item.warehouse))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||||
else:
|
else:
|
||||||
@ -264,19 +266,19 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if negative_expense_booked_in_pr:
|
if negative_expense_booked_in_pr:
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||||
expense is booked against this account in Purchase Receipt {2}'''.format(
|
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
|
||||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
else:
|
else:
|
||||||
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
||||||
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} as no Purchase
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||||
Receipt is created against Item {2}. This is done to handle accounting for cases
|
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
|
||||||
when Purchase Receipt is created after Purchase Invoice'''.format(
|
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
||||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
|
|
||||||
@ -304,10 +306,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if not d.purchase_order:
|
if not d.purchase_order:
|
||||||
throw(_("""Purchase Order Required for item {0}
|
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
||||||
To submit the invoice without purchase order please set
|
msg += "<br><br>"
|
||||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Order Required')),
|
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
|
||||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
|
throw(msg, title=_("Mandatory Purchase Order"))
|
||||||
|
|
||||||
def pr_required(self):
|
def pr_required(self):
|
||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
@ -318,10 +321,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if not d.purchase_receipt and d.item_code in stock_items:
|
if not d.purchase_receipt and d.item_code in stock_items:
|
||||||
throw(_("""Purchase Receipt Required for item {0}
|
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
||||||
To submit the invoice without purchase receipt please set
|
msg += "<br><br>"
|
||||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Receipt Required')),
|
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
|
||||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
|
throw(msg, title=_("Mandatory Purchase Receipt"))
|
||||||
|
|
||||||
def validate_write_off_account(self):
|
def validate_write_off_account(self):
|
||||||
if self.write_off_amount and not self.write_off_account:
|
if self.write_off_amount and not self.write_off_account:
|
||||||
|
@ -484,14 +484,14 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
||||||
|
|
||||||
if account.report_type != "Balance Sheet":
|
if account.report_type != "Balance Sheet":
|
||||||
frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
|
msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
|
||||||
You can change the parent account to a Balance Sheet account or select a different account.")
|
msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
|
||||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
frappe.throw(msg, title=_("Invalid Account"))
|
||||||
|
|
||||||
if self.customer and account.account_type != "Receivable":
|
if self.customer and account.account_type != "Receivable":
|
||||||
frappe.throw(_("Please ensure {} account is a Receivable account. \
|
msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
|
||||||
Change the account type to Receivable or select a different account.")
|
msg += _("Change the account type to Receivable or select a different account.")
|
||||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
frappe.throw(msg, title=_("Invalid Account"))
|
||||||
|
|
||||||
self.party_account_currency = account.account_currency
|
self.party_account_currency = account.account_currency
|
||||||
|
|
||||||
@ -1146,8 +1146,10 @@ class SalesInvoice(SellingController):
|
|||||||
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
|
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
|
||||||
if against_lp_entry:
|
if against_lp_entry:
|
||||||
invoice_list = ", ".join([d.invoice for d in against_lp_entry])
|
invoice_list = ", ".join([d.invoice for d in against_lp_entry])
|
||||||
frappe.throw(_('''{} can't be cancelled since the Loyalty Points earned has been redeemed.
|
frappe.throw(
|
||||||
First cancel the {} No {}''').format(self.doctype, self.doctype, invoice_list))
|
_('''{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {}''')
|
||||||
|
.format(self.doctype, self.doctype, invoice_list)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
|
frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
|
||||||
# Set loyalty program
|
# Set loyalty program
|
||||||
@ -1618,17 +1620,25 @@ def update_multi_mode_option(doc, pos_profile):
|
|||||||
payment.type = payment_mode.type
|
payment.type = payment_mode.type
|
||||||
|
|
||||||
doc.set('payments', [])
|
doc.set('payments', [])
|
||||||
|
invalid_modes = []
|
||||||
for pos_payment_method in pos_profile.get('payments'):
|
for pos_payment_method in pos_profile.get('payments'):
|
||||||
pos_payment_method = pos_payment_method.as_dict()
|
pos_payment_method = pos_payment_method.as_dict()
|
||||||
|
|
||||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||||
if not payment_mode:
|
if not payment_mode:
|
||||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment))
|
||||||
.format(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)), title=_("Missing Account"))
|
continue
|
||||||
|
|
||||||
payment_mode[0].default = pos_payment_method.default
|
payment_mode[0].default = pos_payment_method.default
|
||||||
append_payment(payment_mode[0])
|
append_payment(payment_mode[0])
|
||||||
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def get_all_mode_of_payments(doc):
|
def get_all_mode_of_payments(doc):
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select mpa.default_account, mpa.parent, mp.type as type
|
select mpa.default_account, mpa.parent, mp.type as type
|
||||||
|
@ -237,7 +237,7 @@ class TestSubscription(unittest.TestCase):
|
|||||||
subscription.party_type = 'Customer'
|
subscription.party_type = 'Customer'
|
||||||
subscription.party = '_Test Customer'
|
subscription.party = '_Test Customer'
|
||||||
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
|
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
|
||||||
subscription.start_date = '2018-01-01'
|
subscription.start_date = add_days(nowdate(), -1000)
|
||||||
subscription.insert()
|
subscription.insert()
|
||||||
subscription.process() # generate first invoice
|
subscription.process() # generate first invoice
|
||||||
|
|
||||||
|
@ -307,7 +307,7 @@ def get_accounts(company, root_type):
|
|||||||
where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
|
where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
def filter_accounts(accounts, depth=10):
|
def filter_accounts(accounts, depth=20):
|
||||||
parent_children_map = {}
|
parent_children_map = {}
|
||||||
accounts_by_name = {}
|
accounts_by_name = {}
|
||||||
for d in accounts:
|
for d in accounts:
|
||||||
|
@ -63,6 +63,7 @@ def get_pos_entries(filters, group_by_field):
|
|||||||
FROM
|
FROM
|
||||||
`tabPOS Invoice` p {from_sales_invoice_payment}
|
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||||
WHERE
|
WHERE
|
||||||
|
p.docstatus = 1 and
|
||||||
{group_by_mop_condition}
|
{group_by_mop_condition}
|
||||||
{conditions}
|
{conditions}
|
||||||
ORDER BY
|
ORDER BY
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
"filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}",
|
"filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}",
|
||||||
"group_by_type": "Count",
|
"group_by_type": "Count",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 0,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-23 13:53:33.211371",
|
"modified": "2020-10-28 23:15:58.432189",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Value Analytics",
|
"name": "Asset Value Analytics",
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
||||||
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}",
|
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 0,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-23 13:39:32.429240",
|
"modified": "2020-10-28 23:16:16.939070",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Category-wise Asset Value",
|
"name": "Category-wise Asset Value",
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
||||||
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}",
|
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 0,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-23 13:42:44.912551",
|
"modified": "2020-10-28 23:16:07.883312",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Location-wise Asset Value",
|
"name": "Location-wise Asset Value",
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Depreciation Posting Date",
|
"label": "Depreciation Posting Date",
|
||||||
|
"mandatory_depends_on": "eval:parent.doctype == 'Asset'",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -86,7 +87,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-16 12:11:30.631788",
|
"modified": "2020-10-30 15:22:29.119868",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Finance Book",
|
"name": "Asset Finance Book",
|
||||||
|
@ -19,6 +19,8 @@ from erpnext.controllers.accounts_controller import update_child_qty_rate
|
|||||||
from erpnext.controllers.status_updater import OverAllowanceError
|
from erpnext.controllers.status_updater import OverAllowanceError
|
||||||
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
|
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.batch.test_batch import make_new_batch
|
||||||
|
from erpnext.controllers.buying_controller import get_backflushed_subcontracted_raw_materials
|
||||||
|
|
||||||
class TestPurchaseOrder(unittest.TestCase):
|
class TestPurchaseOrder(unittest.TestCase):
|
||||||
def test_make_purchase_receipt(self):
|
def test_make_purchase_receipt(self):
|
||||||
@ -686,7 +688,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
|
|
||||||
def test_exploded_items_in_subcontracted(self):
|
def test_exploded_items_in_subcontracted(self):
|
||||||
item_code = "_Test Subcontracted FG Item 1"
|
item_code = "_Test Subcontracted FG Item 1"
|
||||||
make_subcontracted_item(item_code)
|
make_subcontracted_item(item_code=item_code)
|
||||||
|
|
||||||
po = create_purchase_order(item_code=item_code, qty=1,
|
po = create_purchase_order(item_code=item_code, qty=1,
|
||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1)
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1)
|
||||||
@ -708,7 +710,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
|
|
||||||
def test_backflush_based_on_stock_entry(self):
|
def test_backflush_based_on_stock_entry(self):
|
||||||
item_code = "_Test Subcontracted FG Item 1"
|
item_code = "_Test Subcontracted FG Item 1"
|
||||||
make_subcontracted_item(item_code)
|
make_subcontracted_item(item_code=item_code)
|
||||||
make_item('Sub Contracted Raw Material 1', {
|
make_item('Sub Contracted Raw Material 1', {
|
||||||
'is_stock_item': 1,
|
'is_stock_item': 1,
|
||||||
'is_sub_contracted_item': 1
|
'is_sub_contracted_item': 1
|
||||||
@ -767,6 +769,133 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
|
|
||||||
update_backflush_based_on("BOM")
|
update_backflush_based_on("BOM")
|
||||||
|
|
||||||
|
def test_backflushed_based_on_for_multiple_batches(self):
|
||||||
|
item_code = "_Test Subcontracted FG Item 2"
|
||||||
|
make_item('Sub Contracted Raw Material 2', {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
'is_sub_contracted_item': 1
|
||||||
|
})
|
||||||
|
|
||||||
|
make_subcontracted_item(item_code=item_code, has_batch_no=1, create_new_batch=1,
|
||||||
|
raw_materials=["Sub Contracted Raw Material 2"])
|
||||||
|
|
||||||
|
update_backflush_based_on("Material Transferred for Subcontract")
|
||||||
|
|
||||||
|
order_qty = 500
|
||||||
|
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
||||||
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||||
|
|
||||||
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
|
item_code = "Sub Contracted Raw Material 2", qty=552, basic_rate=100)
|
||||||
|
|
||||||
|
rm_items = [
|
||||||
|
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 2","item_name":"_Test Item",
|
||||||
|
"qty":552,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}]
|
||||||
|
|
||||||
|
rm_item_string = json.dumps(rm_items)
|
||||||
|
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
for batch in ["ABCD1", "ABCD2", "ABCD3", "ABCD4"]:
|
||||||
|
make_new_batch(batch_id=batch, item_code=item_code)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(po.name)
|
||||||
|
|
||||||
|
# partial receipt
|
||||||
|
pr.get('items')[0].qty = 30
|
||||||
|
pr.get('items')[0].batch_no = "ABCD1"
|
||||||
|
|
||||||
|
purchase_order = po.name
|
||||||
|
purchase_order_item = po.items[0].name
|
||||||
|
|
||||||
|
for batch_no, qty in {"ABCD2": 60, "ABCD3": 70, "ABCD4":40}.items():
|
||||||
|
pr.append("items", {
|
||||||
|
"item_code": pr.get('items')[0].item_code,
|
||||||
|
"item_name": pr.get('items')[0].item_name,
|
||||||
|
"uom": pr.get('items')[0].uom,
|
||||||
|
"stock_uom": pr.get('items')[0].stock_uom,
|
||||||
|
"warehouse": pr.get('items')[0].warehouse,
|
||||||
|
"conversion_factor": pr.get('items')[0].conversion_factor,
|
||||||
|
"cost_center": pr.get('items')[0].cost_center,
|
||||||
|
"rate": pr.get('items')[0].rate,
|
||||||
|
"qty": qty,
|
||||||
|
"batch_no": batch_no,
|
||||||
|
"purchase_order": purchase_order,
|
||||||
|
"purchase_order_item": purchase_order_item
|
||||||
|
})
|
||||||
|
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
pr1 = make_purchase_receipt(po.name)
|
||||||
|
pr1.get('items')[0].qty = 300
|
||||||
|
pr1.get('items')[0].batch_no = "ABCD1"
|
||||||
|
pr1.save()
|
||||||
|
|
||||||
|
pr_key = ("Sub Contracted Raw Material 2", po.name)
|
||||||
|
consumed_qty = get_backflushed_subcontracted_raw_materials([po.name]).get(pr_key)
|
||||||
|
|
||||||
|
self.assertTrue(pr1.supplied_items[0].consumed_qty > 0)
|
||||||
|
self.assertTrue(pr1.supplied_items[0].consumed_qty, flt(552.0) - flt(consumed_qty))
|
||||||
|
|
||||||
|
update_backflush_based_on("BOM")
|
||||||
|
|
||||||
|
def test_supplied_qty_against_subcontracted_po(self):
|
||||||
|
item_code = "_Test Subcontracted FG Item 5"
|
||||||
|
make_item('Sub Contracted Raw Material 4', {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
'is_sub_contracted_item': 1
|
||||||
|
})
|
||||||
|
|
||||||
|
make_subcontracted_item(item_code=item_code, raw_materials=["Sub Contracted Raw Material 4"])
|
||||||
|
|
||||||
|
update_backflush_based_on("Material Transferred for Subcontract")
|
||||||
|
|
||||||
|
order_qty = 250
|
||||||
|
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
||||||
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", do_not_save=True)
|
||||||
|
|
||||||
|
# Add same subcontracted items multiple times
|
||||||
|
po.append("items", {
|
||||||
|
"item_code": item_code,
|
||||||
|
"qty": order_qty,
|
||||||
|
"schedule_date": add_days(nowdate(), 1),
|
||||||
|
"warehouse": "_Test Warehouse - _TC"
|
||||||
|
})
|
||||||
|
|
||||||
|
po.set_missing_values()
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
# Material receipt entry for the raw materials which will be send to supplier
|
||||||
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
|
item_code = "Sub Contracted Raw Material 4", qty=500, basic_rate=100)
|
||||||
|
|
||||||
|
rm_items = [
|
||||||
|
{
|
||||||
|
"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
|
||||||
|
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
|
||||||
|
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Raw Materials transfer entry from stores to supplier's warehouse
|
||||||
|
rm_item_string = json.dumps(rm_items)
|
||||||
|
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
# Test po_detail field has value or not
|
||||||
|
for item_row in se.items:
|
||||||
|
self.assertEqual(item_row.po_detail, po.supplied_items[item_row.idx - 1].name)
|
||||||
|
|
||||||
|
po_doc = frappe.get_doc("Purchase Order", po.name)
|
||||||
|
for row in po_doc.supplied_items:
|
||||||
|
# Valid that whether transferred quantity is matching with supplied qty or not in the purchase order
|
||||||
|
self.assertEqual(row.supplied_qty, 250.0)
|
||||||
|
|
||||||
|
update_backflush_based_on("BOM")
|
||||||
|
|
||||||
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
||||||
@ -839,27 +968,33 @@ def make_pr_against_po(po, received_qty=0):
|
|||||||
pr.submit()
|
pr.submit()
|
||||||
return pr
|
return pr
|
||||||
|
|
||||||
def make_subcontracted_item(item_code):
|
def make_subcontracted_item(**args):
|
||||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
if not frappe.db.exists('Item', item_code):
|
args = frappe._dict(args)
|
||||||
make_item(item_code, {
|
|
||||||
|
if not frappe.db.exists('Item', args.item_code):
|
||||||
|
make_item(args.item_code, {
|
||||||
'is_stock_item': 1,
|
'is_stock_item': 1,
|
||||||
'is_sub_contracted_item': 1
|
'is_sub_contracted_item': 1,
|
||||||
|
'has_batch_no': args.get("has_batch_no") or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
if not frappe.db.exists('Item', "Test Extra Item 1"):
|
if not args.raw_materials:
|
||||||
make_item("Test Extra Item 1", {
|
if not frappe.db.exists('Item', "Test Extra Item 1"):
|
||||||
'is_stock_item': 1,
|
make_item("Test Extra Item 1", {
|
||||||
})
|
'is_stock_item': 1,
|
||||||
|
})
|
||||||
|
|
||||||
if not frappe.db.exists('Item', "Test Extra Item 2"):
|
if not frappe.db.exists('Item', "Test Extra Item 2"):
|
||||||
make_item("Test Extra Item 2", {
|
make_item("Test Extra Item 2", {
|
||||||
'is_stock_item': 1,
|
'is_stock_item': 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
if not frappe.db.get_value('BOM', {'item': item_code}, 'name'):
|
args.raw_materials = ['_Test FG Item', 'Test Extra Item 1']
|
||||||
make_bom(item = item_code, raw_materials = ['_Test FG Item', 'Test Extra Item 1'])
|
|
||||||
|
if not frappe.db.get_value('BOM', {'item': args.item_code}, 'name'):
|
||||||
|
make_bom(item = args.item_code, raw_materials = args.get("raw_materials"))
|
||||||
|
|
||||||
def update_backflush_based_on(based_on):
|
def update_backflush_based_on(based_on):
|
||||||
doc = frappe.get_doc('Buying Settings')
|
doc = frappe.get_doc('Buying Settings')
|
||||||
|
@ -263,6 +263,7 @@ class AccountsController(TransactionBase):
|
|||||||
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
|
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
|
||||||
parent_dict.update({"customer": parent_dict.get("party_name")})
|
parent_dict.update({"customer": parent_dict.get("party_name")})
|
||||||
|
|
||||||
|
self.pricing_rules = []
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get("item_code"):
|
if item.get("item_code"):
|
||||||
args = parent_dict.copy()
|
args = parent_dict.copy()
|
||||||
@ -301,6 +302,7 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
if ret.get("pricing_rules"):
|
if ret.get("pricing_rules"):
|
||||||
self.apply_pricing_rule_on_items(item, ret)
|
self.apply_pricing_rule_on_items(item, ret)
|
||||||
|
self.set_pricing_rule_details(item, ret)
|
||||||
|
|
||||||
if self.doctype == "Purchase Invoice":
|
if self.doctype == "Purchase Invoice":
|
||||||
self.set_expense_account(for_validate)
|
self.set_expense_account(for_validate)
|
||||||
@ -322,6 +324,9 @@ class AccountsController(TransactionBase):
|
|||||||
if item.get('discount_amount'):
|
if item.get('discount_amount'):
|
||||||
item.rate = item.price_list_rate - item.discount_amount
|
item.rate = item.price_list_rate - item.discount_amount
|
||||||
|
|
||||||
|
if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
|
||||||
|
item.rate = pricing_rule_args.get("rate")
|
||||||
|
|
||||||
elif pricing_rule_args.get('free_item_data'):
|
elif pricing_rule_args.get('free_item_data'):
|
||||||
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
|
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
|
||||||
|
|
||||||
@ -335,6 +340,18 @@ class AccountsController(TransactionBase):
|
|||||||
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
|
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
|
||||||
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
|
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
|
||||||
|
|
||||||
|
def set_pricing_rule_details(self, item_row, args):
|
||||||
|
pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
|
||||||
|
if not pricing_rules: return
|
||||||
|
|
||||||
|
for pricing_rule in pricing_rules:
|
||||||
|
self.append("pricing_rules", {
|
||||||
|
"pricing_rule": pricing_rule,
|
||||||
|
"item_code": item_row.item_code,
|
||||||
|
"child_docname": item_row.name,
|
||||||
|
"rule_applied": True
|
||||||
|
})
|
||||||
|
|
||||||
def set_taxes(self):
|
def set_taxes(self):
|
||||||
if not self.meta.get_field("taxes"):
|
if not self.meta.get_field("taxes"):
|
||||||
return
|
return
|
||||||
@ -961,8 +978,10 @@ def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, c
|
|||||||
company_currency = frappe.get_cached_value('Company', company, "default_currency")
|
company_currency = frappe.get_cached_value('Company', company, "default_currency")
|
||||||
|
|
||||||
if not conversion_rate:
|
if not conversion_rate:
|
||||||
throw(_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.").format(
|
throw(
|
||||||
conversion_rate_label, currency, company_currency))
|
_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.")
|
||||||
|
.format(conversion_rate_label, currency, company_currency)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_taxes_and_charges(tax):
|
def validate_taxes_and_charges(tax):
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.utils import flt,cint, cstr, getdate
|
from frappe.utils import flt,cint, cstr, getdate
|
||||||
|
from six import iteritems
|
||||||
from erpnext.accounts.party import get_party_details
|
from erpnext.accounts.party import get_party_details
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
|
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
|
||||||
@ -112,8 +112,8 @@ class BuyingController(StockController):
|
|||||||
"docstatus": 1
|
"docstatus": 1
|
||||||
})]
|
})]
|
||||||
if self.is_return and len(not_cancelled_asset):
|
if self.is_return and len(not_cancelled_asset):
|
||||||
frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.".format(self.return_against)),
|
frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.")
|
||||||
title=_("Not Allowed"))
|
.format(self.return_against), title=_("Not Allowed"))
|
||||||
|
|
||||||
def get_asset_items(self):
|
def get_asset_items(self):
|
||||||
if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
|
if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
|
||||||
@ -298,10 +298,10 @@ class BuyingController(StockController):
|
|||||||
title=_("Limit Crossed"))
|
title=_("Limit Crossed"))
|
||||||
|
|
||||||
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
||||||
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
# backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
||||||
|
|
||||||
for raw_material in transferred_raw_materials + non_stock_items:
|
for raw_material in transferred_raw_materials + non_stock_items:
|
||||||
rm_item_key = '{}{}'.format(raw_material.rm_item_code, item.purchase_order)
|
rm_item_key = (raw_material.rm_item_code, item.item_code, item.purchase_order)
|
||||||
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
||||||
|
|
||||||
consumed_qty = raw_material_data.get('qty', 0)
|
consumed_qty = raw_material_data.get('qty', 0)
|
||||||
@ -330,8 +330,10 @@ class BuyingController(StockController):
|
|||||||
set_serial_nos(raw_material, consumed_serial_nos, qty)
|
set_serial_nos(raw_material, consumed_serial_nos, qty)
|
||||||
|
|
||||||
if raw_material.batch_nos:
|
if raw_material.batch_nos:
|
||||||
|
backflushed_batch_qty_map = raw_material_data.get('consumed_batch', {})
|
||||||
|
|
||||||
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
||||||
qty, transferred_batch_qty_map, backflushed_batch_qty_map)
|
qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
|
||||||
for batch_data in batches_qty:
|
for batch_data in batches_qty:
|
||||||
qty = batch_data['qty']
|
qty = batch_data['qty']
|
||||||
raw_material.batch_no = batch_data['batch']
|
raw_material.batch_no = batch_data['batch']
|
||||||
@ -343,6 +345,10 @@ class BuyingController(StockController):
|
|||||||
rm = self.append('supplied_items', {})
|
rm = self.append('supplied_items', {})
|
||||||
rm.update(raw_material_data)
|
rm.update(raw_material_data)
|
||||||
|
|
||||||
|
if not rm.main_item_code:
|
||||||
|
rm.main_item_code = fg_item_doc.item_code
|
||||||
|
|
||||||
|
rm.reference_name = fg_item_doc.name
|
||||||
rm.required_qty = qty
|
rm.required_qty = qty
|
||||||
rm.consumed_qty = qty
|
rm.consumed_qty = qty
|
||||||
|
|
||||||
@ -792,8 +798,8 @@ class BuyingController(StockController):
|
|||||||
asset.set(field, None)
|
asset.set(field, None)
|
||||||
asset.supplier = None
|
asset.supplier = None
|
||||||
if asset.docstatus == 1 and delete_asset:
|
if asset.docstatus == 1 and delete_asset:
|
||||||
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\
|
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue.')
|
||||||
Please cancel the it to continue.').format(frappe.utils.get_link_to_form('Asset', asset.name)))
|
.format(frappe.utils.get_link_to_form('Asset', asset.name)))
|
||||||
|
|
||||||
asset.flags.ignore_validate_update_after_submit = True
|
asset.flags.ignore_validate_update_after_submit = True
|
||||||
asset.flags.ignore_mandatory = True
|
asset.flags.ignore_mandatory = True
|
||||||
@ -873,7 +879,7 @@ def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
|
|||||||
AND se.purpose='Send to Subcontractor'
|
AND se.purpose='Send to Subcontractor'
|
||||||
AND se.purchase_order = %s
|
AND se.purchase_order = %s
|
||||||
AND IFNULL(sed.t_warehouse, '') != ''
|
AND IFNULL(sed.t_warehouse, '') != ''
|
||||||
AND sed.subcontracted_item = %s
|
AND IFNULL(sed.subcontracted_item, '') in ('', %s)
|
||||||
GROUP BY sed.item_code, sed.subcontracted_item
|
GROUP BY sed.item_code, sed.subcontracted_item
|
||||||
"""
|
"""
|
||||||
raw_materials = frappe.db.multisql({
|
raw_materials = frappe.db.multisql({
|
||||||
@ -890,39 +896,49 @@ def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
|
|||||||
return raw_materials
|
return raw_materials
|
||||||
|
|
||||||
def get_backflushed_subcontracted_raw_materials(purchase_orders):
|
def get_backflushed_subcontracted_raw_materials(purchase_orders):
|
||||||
common_query = """
|
purchase_receipts = frappe.get_all("Purchase Receipt Item",
|
||||||
SELECT
|
fields = ["purchase_order", "item_code", "name", "parent"],
|
||||||
CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
|
filters={"docstatus": 1, "purchase_order": ("in", list(purchase_orders))})
|
||||||
SUM(prsi.consumed_qty) AS qty,
|
|
||||||
{serial_no_concat_syntax} AS serial_nos,
|
|
||||||
{batch_no_concat_syntax} AS batch_nos
|
|
||||||
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
|
|
||||||
WHERE
|
|
||||||
pr.name = pri.parent
|
|
||||||
AND pr.name = prsi.parent
|
|
||||||
AND pri.purchase_order IN %s
|
|
||||||
AND pri.item_code = prsi.main_item_code
|
|
||||||
AND pr.docstatus = 1
|
|
||||||
GROUP BY prsi.rm_item_code, pri.purchase_order
|
|
||||||
"""
|
|
||||||
|
|
||||||
backflushed_raw_materials = frappe.db.multisql({
|
distinct_purchase_receipts = {}
|
||||||
'mariadb': common_query.format(
|
for pr in purchase_receipts:
|
||||||
serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
|
key = (pr.purchase_order, pr.item_code, pr.parent)
|
||||||
batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
|
distinct_purchase_receipts.setdefault(key, []).append(pr.name)
|
||||||
),
|
|
||||||
'postgres': common_query.format(
|
|
||||||
serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
|
|
||||||
batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
|
|
||||||
)
|
|
||||||
}, (purchase_orders, ), as_dict=1)
|
|
||||||
|
|
||||||
backflushed_raw_materials_map = frappe._dict()
|
backflushed_raw_materials_map = frappe._dict()
|
||||||
for item in backflushed_raw_materials:
|
for args, references in iteritems(distinct_purchase_receipts):
|
||||||
backflushed_raw_materials_map.setdefault(item.item_key, item)
|
purchase_receipt_supplied_items = get_supplied_items(args[1], args[2], references)
|
||||||
|
|
||||||
|
for data in purchase_receipt_supplied_items:
|
||||||
|
pr_key = (data.rm_item_code, data.main_item_code, args[0])
|
||||||
|
if pr_key not in backflushed_raw_materials_map:
|
||||||
|
backflushed_raw_materials_map.setdefault(pr_key, frappe._dict({
|
||||||
|
"qty": 0.0,
|
||||||
|
"serial_no": [],
|
||||||
|
"batch_no": [],
|
||||||
|
"consumed_batch": {}
|
||||||
|
}))
|
||||||
|
|
||||||
|
row = backflushed_raw_materials_map.get(pr_key)
|
||||||
|
row.qty += data.consumed_qty
|
||||||
|
|
||||||
|
for field in ["serial_no", "batch_no"]:
|
||||||
|
if data.get(field):
|
||||||
|
row[field].append(data.get(field))
|
||||||
|
|
||||||
|
if data.get("batch_no"):
|
||||||
|
if data.get("batch_no") in row.consumed_batch:
|
||||||
|
row.consumed_batch[data.get("batch_no")] += data.consumed_qty
|
||||||
|
else:
|
||||||
|
row.consumed_batch[data.get("batch_no")] = data.consumed_qty
|
||||||
|
|
||||||
return backflushed_raw_materials_map
|
return backflushed_raw_materials_map
|
||||||
|
|
||||||
|
def get_supplied_items(item_code, purchase_receipt, references):
|
||||||
|
return frappe.get_all("Purchase Receipt Item Supplied",
|
||||||
|
fields=["rm_item_code", "main_item_code", "consumed_qty", "serial_no", "batch_no"],
|
||||||
|
filters={"main_item_code": item_code, "parent": purchase_receipt, "reference_name": ("in", references)})
|
||||||
|
|
||||||
def get_asset_item_details(asset_items):
|
def get_asset_item_details(asset_items):
|
||||||
asset_items_data = {}
|
asset_items_data = {}
|
||||||
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
|
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
|
||||||
@ -1004,14 +1020,15 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
|
|||||||
SELECT
|
SELECT
|
||||||
sed.batch_no,
|
sed.batch_no,
|
||||||
SUM(sed.qty) AS qty,
|
SUM(sed.qty) AS qty,
|
||||||
sed.item_code
|
sed.item_code,
|
||||||
|
sed.subcontracted_item
|
||||||
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||||
WHERE
|
WHERE
|
||||||
se.name = sed.parent
|
se.name = sed.parent
|
||||||
AND se.docstatus=1
|
AND se.docstatus=1
|
||||||
AND se.purpose='Send to Subcontractor'
|
AND se.purpose='Send to Subcontractor'
|
||||||
AND se.purchase_order = %s
|
AND se.purchase_order = %s
|
||||||
AND sed.subcontracted_item = %s
|
AND ifnull(sed.subcontracted_item, '') in ('', %s)
|
||||||
AND sed.batch_no IS NOT NULL
|
AND sed.batch_no IS NOT NULL
|
||||||
GROUP BY
|
GROUP BY
|
||||||
sed.batch_no,
|
sed.batch_no,
|
||||||
@ -1019,8 +1036,10 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
|
|||||||
""", (purchase_order, fg_item), as_dict=1)
|
""", (purchase_order, fg_item), as_dict=1)
|
||||||
|
|
||||||
for batch_data in transferred_batches:
|
for batch_data in transferred_batches:
|
||||||
transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
|
key = ((batch_data.item_code, fg_item)
|
||||||
transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
|
if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
|
||||||
|
transferred_batch_qty_map.setdefault(key, {})
|
||||||
|
transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
|
||||||
|
|
||||||
return transferred_batch_qty_map
|
return transferred_batch_qty_map
|
||||||
|
|
||||||
@ -1057,10 +1076,11 @@ def get_backflushed_batch_qty_map(purchase_order, fg_item):
|
|||||||
|
|
||||||
return backflushed_batch_qty_map
|
return backflushed_batch_qty_map
|
||||||
|
|
||||||
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
|
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batches, po):
|
||||||
# Returns available batches to be backflushed based on requirements
|
# Returns available batches to be backflushed based on requirements
|
||||||
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
||||||
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
|
if not transferred_batches:
|
||||||
|
transferred_batches = transferred_batch_qty_map.get((item_code, po), {})
|
||||||
|
|
||||||
available_batches = []
|
available_batches = []
|
||||||
|
|
||||||
|
@ -608,16 +608,19 @@ class calculate_taxes_and_totals(object):
|
|||||||
base_rate_with_margin = 0.0
|
base_rate_with_margin = 0.0
|
||||||
if item.price_list_rate:
|
if item.price_list_rate:
|
||||||
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
||||||
|
has_margin = False
|
||||||
for d in get_applied_pricing_rules(item.pricing_rules):
|
for d in get_applied_pricing_rules(item.pricing_rules):
|
||||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||||
|
|
||||||
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
|
if (pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == self.doc.currency)\
|
||||||
or (pricing_rule.margin_type == 'Percentage'):
|
or (pricing_rule.margin_type == 'Percentage'):
|
||||||
item.margin_type = pricing_rule.margin_type
|
item.margin_type = pricing_rule.margin_type
|
||||||
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||||
else:
|
has_margin = True
|
||||||
item.margin_type = None
|
|
||||||
item.margin_rate_or_amount = 0.0
|
if not has_margin:
|
||||||
|
item.margin_type = None
|
||||||
|
item.margin_rate_or_amount = 0.0
|
||||||
|
|
||||||
if item.margin_type and item.margin_rate_or_amount:
|
if item.margin_type and item.margin_rate_or_amount:
|
||||||
margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
|
margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
|
||||||
|
@ -49,6 +49,22 @@ data = {
|
|||||||
'fieldname': 'reference_dn', 'label': 'Reference Name', 'fieldtype': 'Dynamic Link', 'options': 'reference_dt',
|
'fieldname': 'reference_dn', 'label': 'Reference Name', 'fieldtype': 'Dynamic Link', 'options': 'reference_dt',
|
||||||
'insert_after': 'reference_dt'
|
'insert_after': 'reference_dt'
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
'Stock Entry': [
|
||||||
|
{
|
||||||
|
'fieldname': 'inpatient_medication_entry', 'label': 'Inpatient Medication Entry', 'fieldtype': 'Link', 'options': 'Inpatient Medication Entry',
|
||||||
|
'insert_after': 'credit_note', 'read_only': True
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'Stock Entry Detail': [
|
||||||
|
{
|
||||||
|
'fieldname': 'patient', 'label': 'Patient', 'fieldtype': 'Link', 'options': 'Patient',
|
||||||
|
'insert_after': 'po_detail', 'read_only': True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'inpatient_medication_entry_child', 'label': 'Inpatient Medication Entry Child', 'fieldtype': 'Data',
|
||||||
|
'insert_after': 'patient', 'read_only': True
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'on_setup': 'erpnext.healthcare.setup.setup_healthcare'
|
'on_setup': 'erpnext.healthcare.setup.setup_healthcare'
|
||||||
|
@ -7,7 +7,7 @@ import frappe
|
|||||||
import json
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import flt, cstr
|
from frappe.utils import flt, cstr, getdate
|
||||||
from frappe.email.doctype.email_group.email_group import add_subscribers
|
from frappe.email.doctype.email_group.email_group import add_subscribers
|
||||||
|
|
||||||
def get_course(program):
|
def get_course(program):
|
||||||
@ -67,6 +67,13 @@ def mark_attendance(students_present, students_absent, course_schedule=None, stu
|
|||||||
:param date: Date.
|
:param date: Date.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if student_group:
|
||||||
|
academic_year = frappe.db.get_value('Student Group', student_group, 'academic_year')
|
||||||
|
if academic_year:
|
||||||
|
year_start_date, year_end_date = frappe.db.get_value('Academic Year', academic_year, ['year_start_date', 'year_end_date'])
|
||||||
|
if getdate(date) < getdate(year_start_date) or getdate(date) > getdate(year_end_date):
|
||||||
|
frappe.throw(_('Attendance cannot be marked outside of Academic Year {0}').format(academic_year))
|
||||||
|
|
||||||
present = json.loads(students_present)
|
present = json.loads(students_present)
|
||||||
absent = json.loads(students_absent)
|
absent = json.loads(students_absent)
|
||||||
|
|
||||||
|
@ -30,6 +30,23 @@ frappe.ui.form.on('Assessment Plan', {
|
|||||||
frappe.set_route('Form', 'Assessment Result Tool');
|
frappe.set_route('Form', 'Assessment Result Tool');
|
||||||
}, __('Tools'));
|
}, __('Tools'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.set_query('course', function() {
|
||||||
|
return {
|
||||||
|
query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses',
|
||||||
|
filters: {
|
||||||
|
'program': frm.doc.program
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('academic_term', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'academic_year': frm.doc.academic_year
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
course: function(frm) {
|
course: function(frm) {
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
"assessment_group",
|
"assessment_group",
|
||||||
"grading_scale",
|
"grading_scale",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"course",
|
|
||||||
"program",
|
"program",
|
||||||
|
"course",
|
||||||
"academic_year",
|
"academic_year",
|
||||||
"academic_term",
|
"academic_term",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
@ -198,7 +198,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-09 14:56:26.746988",
|
"modified": "2020-10-23 15:55:35.076251",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Education",
|
"module": "Education",
|
||||||
"name": "Assessment Plan",
|
"name": "Assessment Plan",
|
||||||
|
@ -7,6 +7,23 @@ frappe.ui.form.on('Assessment Result', {
|
|||||||
frm.trigger('setup_chart');
|
frm.trigger('setup_chart');
|
||||||
}
|
}
|
||||||
frm.set_df_property('details', 'read_only', 1);
|
frm.set_df_property('details', 'read_only', 1);
|
||||||
|
|
||||||
|
frm.set_query('course', function() {
|
||||||
|
return {
|
||||||
|
query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses',
|
||||||
|
filters: {
|
||||||
|
'program': frm.doc.program
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('academic_term', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'academic_year': frm.doc.academic_year
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
|
@ -41,5 +41,24 @@ frappe.ui.form.on("Instructor", {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("academic_term", "instructor_log", function(_doc, cdt, cdn) {
|
||||||
|
let d = locals[cdt][cdn];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"academic_year": d.academic_year
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("course", "instructor_log", function(_doc, cdt, cdn) {
|
||||||
|
let d = locals[cdt][cdn];
|
||||||
|
return {
|
||||||
|
query: "erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses",
|
||||||
|
filters: {
|
||||||
|
"program": d.program
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -1,336 +1,88 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
"creation": "2017-12-27 08:55:52.680284",
|
||||||
"allow_guest_to_view": 0,
|
"doctype": "DocType",
|
||||||
"allow_import": 0,
|
"editable_grid": 1,
|
||||||
"allow_rename": 0,
|
"engine": "InnoDB",
|
||||||
"beta": 0,
|
"field_order": [
|
||||||
"creation": "2017-12-27 08:55:52.680284",
|
"academic_year",
|
||||||
"custom": 0,
|
"academic_term",
|
||||||
"docstatus": 0,
|
"department",
|
||||||
"doctype": "DocType",
|
"column_break_3",
|
||||||
"document_type": "",
|
"program",
|
||||||
"editable_grid": 1,
|
"course",
|
||||||
"engine": "InnoDB",
|
"student_group",
|
||||||
|
"section_break_8",
|
||||||
|
"other_details"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "academic_year",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Academic Year",
|
||||||
"collapsible": 0,
|
"options": "Academic Year",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"fieldname": "academic_year",
|
},
|
||||||
"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": "Academic Year",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Academic Year",
|
|
||||||
"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": "academic_term",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Academic Term",
|
||||||
"collapsible": 0,
|
"options": "Academic Term"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "academic_term",
|
|
||||||
"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": "Academic Term",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Academic Term",
|
|
||||||
"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": "department",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Department",
|
||||||
"bold": 0,
|
"options": "Department"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "department",
|
|
||||||
"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": "Department",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Department",
|
|
||||||
"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": "program",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Program",
|
||||||
"collapsible": 0,
|
"options": "Program",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"fieldname": "program",
|
},
|
||||||
"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": "Program",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Program",
|
|
||||||
"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": "course",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Course",
|
||||||
"collapsible": 0,
|
"options": "Course"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "course",
|
|
||||||
"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": "Course",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Course",
|
|
||||||
"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": "student_group",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Student Group",
|
||||||
"bold": 0,
|
"options": "Student Group"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "student_group",
|
|
||||||
"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": "Student Group",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Student Group",
|
|
||||||
"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": "section_break_8",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_8",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "other_details",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Small Text",
|
||||||
"allow_on_submit": 0,
|
"label": "Other details"
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "other_details",
|
|
||||||
"fieldtype": "Small 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": "Other details",
|
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"istable": 1,
|
||||||
"hide_heading": 0,
|
"links": [],
|
||||||
"hide_toolbar": 0,
|
"modified": "2020-10-23 15:15:50.759657",
|
||||||
"idx": 0,
|
"modified_by": "Administrator",
|
||||||
"image_view": 0,
|
"module": "Education",
|
||||||
"in_create": 0,
|
"name": "Instructor Log",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
"permissions": [],
|
||||||
"istable": 1,
|
"quick_entry": 1,
|
||||||
"max_attachments": 0,
|
"restrict_to_domain": "Education",
|
||||||
"modified": "2018-11-04 03:38:30.902942",
|
"sort_field": "modified",
|
||||||
"modified_by": "Administrator",
|
"sort_order": "DESC",
|
||||||
"module": "Education",
|
"track_changes": 1
|
||||||
"name": "Instructor Log",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"restrict_to_domain": "Education",
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
@ -6,13 +6,13 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import get_link_to_form
|
from frappe.utils import get_link_to_form, getdate
|
||||||
from erpnext.education.api import get_student_group_students
|
from erpnext.education.api import get_student_group_students
|
||||||
|
|
||||||
|
|
||||||
class StudentAttendance(Document):
|
class StudentAttendance(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_mandatory()
|
self.validate_mandatory()
|
||||||
|
self.validate_date()
|
||||||
self.set_date()
|
self.set_date()
|
||||||
self.set_student_group()
|
self.set_student_group()
|
||||||
self.validate_student()
|
self.validate_student()
|
||||||
@ -27,6 +27,18 @@ class StudentAttendance(Document):
|
|||||||
frappe.throw(_('{0} or {1} is mandatory').format(frappe.bold('Student Group'),
|
frappe.throw(_('{0} or {1} is mandatory').format(frappe.bold('Student Group'),
|
||||||
frappe.bold('Course Schedule')), title=_('Mandatory Fields'))
|
frappe.bold('Course Schedule')), title=_('Mandatory Fields'))
|
||||||
|
|
||||||
|
def validate_date(self):
|
||||||
|
if not self.leave_application and getdate(self.date) > getdate():
|
||||||
|
frappe.throw(_('Attendance cannot be marked for future dates.'))
|
||||||
|
|
||||||
|
if self.student_group:
|
||||||
|
academic_year = frappe.db.get_value('Student Group', self.student_group, 'academic_year')
|
||||||
|
if academic_year:
|
||||||
|
year_start_date, year_end_date = frappe.db.get_value('Academic Year', academic_year, ['year_start_date', 'year_end_date'])
|
||||||
|
if year_start_date and year_end_date:
|
||||||
|
if getdate(self.date) < getdate(year_start_date) or getdate(self.date) > getdate(year_end_date):
|
||||||
|
frappe.throw(_('Attendance cannot be marked outside of Academic Year {0}').format(academic_year))
|
||||||
|
|
||||||
def set_student_group(self):
|
def set_student_group(self):
|
||||||
if self.course_schedule:
|
if self.course_schedule:
|
||||||
self.student_group = frappe.db.get_value('Course Schedule', self.course_schedule, 'student_group')
|
self.student_group = frappe.db.get_value('Course Schedule', self.course_schedule, 'student_group')
|
||||||
@ -63,6 +75,6 @@ class StudentAttendance(Document):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if attendance_record:
|
if attendance_record:
|
||||||
record = get_link_to_form('Attendance Record', attendance_record)
|
record = get_link_to_form('Student Attendance', attendance_record)
|
||||||
frappe.throw(_('Student Attendance record {0} already exists against the Student {1}')
|
frappe.throw(_('Student Attendance record {0} already exists against the Student {1}')
|
||||||
.format(record, frappe.bold(self.student)), title=_('Duplicate Entry'))
|
.format(record, frappe.bold(self.student)), title=_('Duplicate Entry'))
|
||||||
|
@ -52,6 +52,8 @@ frappe.ui.form.on('Student Attendance Tool', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
date: function(frm) {
|
date: function(frm) {
|
||||||
|
if (frm.doc.date > frappe.datetime.get_today())
|
||||||
|
frappe.throw(__("Cannot mark attendance for future dates."));
|
||||||
frm.trigger("student_group");
|
frm.trigger("student_group");
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -133,8 +135,8 @@ education.StudentsEditor = Class.extend({
|
|||||||
return !stud.disabled && !stud.checked;
|
return !stud.disabled && !stud.checked;
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.confirm(__("Do you want to update attendance?<br>Present: {0}\
|
frappe.confirm(__("Do you want to update attendance? <br> Present: {0} <br> Absent: {1}",
|
||||||
<br>Absent: {1}", [students_present.length, students_absent.length]),
|
[students_present.length, students_absent.length]),
|
||||||
function() { //ifyes
|
function() { //ifyes
|
||||||
if(!frappe.request.ajax_count) {
|
if(!frappe.request.ajax_count) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
@ -1,333 +1,118 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 1,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"allow_copy": 1,
|
||||||
"allow_import": 0,
|
"creation": "2016-11-16 17:12:46.437539",
|
||||||
"allow_rename": 0,
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"editable_grid": 1,
|
||||||
"creation": "2016-11-16 17:12:46.437539",
|
"engine": "InnoDB",
|
||||||
"custom": 0,
|
"field_order": [
|
||||||
"docstatus": 0,
|
"based_on",
|
||||||
"doctype": "DocType",
|
"group_based_on",
|
||||||
"document_type": "",
|
"column_break_2",
|
||||||
"editable_grid": 1,
|
"student_group",
|
||||||
"engine": "InnoDB",
|
"academic_year",
|
||||||
|
"academic_term",
|
||||||
|
"course_schedule",
|
||||||
|
"date",
|
||||||
|
"attendance",
|
||||||
|
"students_html"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "based_on",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Select",
|
||||||
"bold": 0,
|
"label": "Based On",
|
||||||
"collapsible": 0,
|
"options": "Student Group\nCourse Schedule"
|
||||||
"columns": 0,
|
},
|
||||||
"default": "",
|
|
||||||
"fieldname": "based_on",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"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": "Based On",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Student Group\nCourse Schedule",
|
|
||||||
"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": "Batch",
|
||||||
"allow_on_submit": 0,
|
"depends_on": "eval:doc.based_on == \"Student Group\"",
|
||||||
"bold": 0,
|
"fieldname": "group_based_on",
|
||||||
"collapsible": 0,
|
"fieldtype": "Select",
|
||||||
"columns": 0,
|
"label": "Group Based On",
|
||||||
"default": "Batch",
|
"options": "Batch\nCourse\nActivity"
|
||||||
"depends_on": "eval:doc.based_on == \"Student Group\"",
|
},
|
||||||
"fieldname": "group_based_on",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"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": "Group Based On",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Batch\nCourse\nActivity",
|
|
||||||
"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,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"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,
|
"depends_on": "eval:doc.based_on ==\"Student Group\"",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "student_group",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Student Group",
|
||||||
"depends_on": "eval:doc.based_on ==\"Student Group\"",
|
"options": "Student Group",
|
||||||
"fieldname": "student_group",
|
"reqd": 1
|
||||||
"fieldtype": "Link",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Student Group",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Student Group",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "eval:doc.based_on ==\"Course Schedule\"",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "course_schedule",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Course Schedule",
|
||||||
"depends_on": "eval:doc.based_on ==\"Course Schedule\"",
|
"options": "Course Schedule",
|
||||||
"fieldname": "course_schedule",
|
"reqd": 1
|
||||||
"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": "Course Schedule",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Course Schedule",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "eval:doc.based_on ==\"Student Group\"",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "date",
|
||||||
"bold": 0,
|
"fieldtype": "Date",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Date",
|
||||||
"depends_on": "eval:doc.based_on ==\"Student Group\"",
|
"reqd": 1
|
||||||
"fieldname": "date",
|
},
|
||||||
"fieldtype": "Date",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Date",
|
|
||||||
"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": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "eval: (doc.course_schedule \n|| (doc.student_group && doc.date))",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "attendance",
|
||||||
"bold": 0,
|
"fieldtype": "Section Break",
|
||||||
"collapsible": 0,
|
"label": "Attendance"
|
||||||
"columns": 0,
|
},
|
||||||
"depends_on": "eval: (doc.course_schedule \n|| (doc.student_group && doc.date))",
|
|
||||||
"fieldname": "attendance",
|
|
||||||
"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": "Attendance",
|
|
||||||
"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": "students_html",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "HTML",
|
||||||
"bold": 0,
|
"label": "Students HTML"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
{
|
||||||
"fieldname": "students_html",
|
"fetch_from": "student_group.academic_year",
|
||||||
"fieldtype": "HTML",
|
"fieldname": "academic_year",
|
||||||
"hidden": 0,
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 0,
|
"label": "Academic Year",
|
||||||
"ignore_xss_filter": 0,
|
"options": "Academic Year",
|
||||||
"in_filter": 0,
|
"read_only": 1
|
||||||
"in_global_search": 0,
|
},
|
||||||
"in_list_view": 0,
|
{
|
||||||
"in_standard_filter": 0,
|
"fetch_from": "student_group.academic_term",
|
||||||
"label": "Students HTML",
|
"fieldname": "academic_term",
|
||||||
"length": 0,
|
"fieldtype": "Link",
|
||||||
"no_copy": 0,
|
"label": "Academic Term",
|
||||||
"permlevel": 0,
|
"options": "Academic Term",
|
||||||
"precision": "",
|
"read_only": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"hide_toolbar": 1,
|
||||||
"hide_heading": 1,
|
"issingle": 1,
|
||||||
"hide_toolbar": 1,
|
"links": [],
|
||||||
"idx": 0,
|
"modified": "2020-10-23 17:52:28.078971",
|
||||||
"image_view": 0,
|
"modified_by": "Administrator",
|
||||||
"in_create": 0,
|
"module": "Education",
|
||||||
"is_submittable": 0,
|
"name": "Student Attendance Tool",
|
||||||
"issingle": 1,
|
"owner": "Administrator",
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2017-11-10 18:55:36.168044",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Education",
|
|
||||||
"name": "Student Attendance Tool",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"apply_user_permissions": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"role": "Instructor",
|
||||||
"create": 1,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Instructor",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"apply_user_permissions": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"role": "Academics User",
|
||||||
"create": 1,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Academics User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"restrict_to_domain": "Education",
|
||||||
"read_only": 0,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC"
|
||||||
"restrict_to_domain": "Education",
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -43,7 +43,8 @@
|
|||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Dosage",
|
"label": "Dosage",
|
||||||
"options": "Prescription Dosage"
|
"options": "Prescription Dosage",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "period",
|
"fieldname": "period",
|
||||||
@ -51,14 +52,16 @@
|
|||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Period",
|
"label": "Period",
|
||||||
"options": "Prescription Duration"
|
"options": "Prescription Duration",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "dosage_form",
|
"fieldname": "dosage_form",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"label": "Dosage Form",
|
"label": "Dosage Form",
|
||||||
"options": "Dosage Form"
|
"options": "Dosage Form",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_7",
|
"fieldname": "column_break_7",
|
||||||
@ -72,7 +75,7 @@
|
|||||||
"label": "Comment"
|
"label": "Comment"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "use_interval",
|
"depends_on": "usage_interval",
|
||||||
"fieldname": "interval",
|
"fieldname": "interval",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -80,6 +83,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
|
"depends_on": "usage_interval",
|
||||||
"fieldname": "update_schedule",
|
"fieldname": "update_schedule",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -99,12 +103,13 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "usage_interval",
|
"fieldname": "usage_interval",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
"label": "Dosage by Time Interval"
|
"label": "Dosage by Time Interval"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-26 17:02:42.741338",
|
"modified": "2020-09-30 23:32:09.495288",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Drug Prescription",
|
"name": "Drug Prescription",
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Inpatient Medication Entry', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
// Ignore cancellation of doctype on cancel all
|
||||||
|
frm.ignore_doctypes_on_cancel_all = ['Stock Entry'];
|
||||||
|
|
||||||
|
frm.set_query('item_code', () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_stock_item: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('drug_code', 'medication_orders', () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_stock_item: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_medication_orders: function(frm) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'get_medication_orders',
|
||||||
|
doc: frm.doc,
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Fetching Pending Medication Orders'),
|
||||||
|
callback: function() {
|
||||||
|
refresh_field('medication_orders');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "naming_series:",
|
||||||
|
"creation": "2020-09-25 14:13:20.111906",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"naming_series",
|
||||||
|
"company",
|
||||||
|
"column_break_3",
|
||||||
|
"posting_date",
|
||||||
|
"status",
|
||||||
|
"filters_section",
|
||||||
|
"item_code",
|
||||||
|
"assigned_to_practitioner",
|
||||||
|
"patient",
|
||||||
|
"practitioner",
|
||||||
|
"service_unit",
|
||||||
|
"column_break_11",
|
||||||
|
"from_date",
|
||||||
|
"to_date",
|
||||||
|
"from_time",
|
||||||
|
"to_time",
|
||||||
|
"select_medication_orders_section",
|
||||||
|
"get_medication_orders",
|
||||||
|
"medication_orders",
|
||||||
|
"section_break_18",
|
||||||
|
"update_stock",
|
||||||
|
"warehouse",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Naming Series",
|
||||||
|
"options": "HLC-IME-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Posting Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Status",
|
||||||
|
"options": "\nDraft\nSubmitted\nPending\nIn Process\nCompleted\nCancelled",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "filters_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Filters"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item Code (Drug)",
|
||||||
|
"options": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "update_stock",
|
||||||
|
"description": "Warehouse from where medication stock should be consumed",
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Medication Warehouse",
|
||||||
|
"mandatory_depends_on": "update_stock",
|
||||||
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "patient",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Patient",
|
||||||
|
"options": "Patient"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "service_unit",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Healthcare Service Unit",
|
||||||
|
"options": "Healthcare Service Unit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_11",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "from_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "From Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "to_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "To Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Inpatient Medication Entry",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "practitioner",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Healthcare Practitioner",
|
||||||
|
"options": "Healthcare Practitioner"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "select_medication_orders_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Medication Orders"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "medication_orders",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Inpatient Medication Orders",
|
||||||
|
"options": "Inpatient Medication Entry Detail",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.docstatus!==1",
|
||||||
|
"fieldname": "get_medication_orders",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Get Pending Medication Orders",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "assigned_to_practitioner",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Assigned To",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_18",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Stock Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "update_stock",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Update Stock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "from_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"label": "From Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "to_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"label": "To Time"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-09-30 23:40:45.528715",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Inpatient Medication Entry",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,273 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import flt, get_link_to_form, getdate, nowtime
|
||||||
|
from erpnext.stock.utils import get_latest_stock_qty
|
||||||
|
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
|
||||||
|
|
||||||
|
class InpatientMedicationEntry(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_medication_orders()
|
||||||
|
|
||||||
|
def get_medication_orders(self):
|
||||||
|
self.validate_datetime_filters()
|
||||||
|
|
||||||
|
# pull inpatient medication orders based on selected filters
|
||||||
|
orders = get_pending_medication_orders(self)
|
||||||
|
|
||||||
|
if orders:
|
||||||
|
self.add_mo_to_table(orders)
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
self.set('medication_orders', [])
|
||||||
|
frappe.msgprint(_('No pending medication orders found for selected criteria'))
|
||||||
|
|
||||||
|
def validate_datetime_filters(self):
|
||||||
|
if self.from_date and self.to_date:
|
||||||
|
self.validate_from_to_dates('from_date', 'to_date')
|
||||||
|
|
||||||
|
if self.from_date and getdate(self.from_date) > getdate():
|
||||||
|
frappe.throw(_('From Date cannot be after the current date.'))
|
||||||
|
|
||||||
|
if self.to_date and getdate(self.to_date) > getdate():
|
||||||
|
frappe.throw(_('To Date cannot be after the current date.'))
|
||||||
|
|
||||||
|
if self.from_time and self.from_time > nowtime():
|
||||||
|
frappe.throw(_('From Time cannot be after the current time.'))
|
||||||
|
|
||||||
|
if self.to_time and self.to_time > nowtime():
|
||||||
|
frappe.throw(_('To Time cannot be after the current time.'))
|
||||||
|
|
||||||
|
def add_mo_to_table(self, orders):
|
||||||
|
# Add medication orders in the child table
|
||||||
|
self.set('medication_orders', [])
|
||||||
|
|
||||||
|
for data in orders:
|
||||||
|
self.append('medication_orders', {
|
||||||
|
'patient': data.patient,
|
||||||
|
'patient_name': data.patient_name,
|
||||||
|
'inpatient_record': data.inpatient_record,
|
||||||
|
'service_unit': data.service_unit,
|
||||||
|
'datetime': "%s %s" % (data.date, data.time or "00:00:00"),
|
||||||
|
'drug_code': data.drug,
|
||||||
|
'drug_name': data.drug_name,
|
||||||
|
'dosage': data.dosage,
|
||||||
|
'dosage_form': data.dosage_form,
|
||||||
|
'against_imo': data.parent,
|
||||||
|
'against_imoe': data.name
|
||||||
|
})
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.validate_medication_orders()
|
||||||
|
success_msg = ""
|
||||||
|
if self.update_stock:
|
||||||
|
stock_entry = self.process_stock()
|
||||||
|
success_msg += _('Stock Entry {0} created and ').format(
|
||||||
|
frappe.bold(get_link_to_form('Stock Entry', stock_entry)))
|
||||||
|
|
||||||
|
self.update_medication_orders()
|
||||||
|
success_msg += _('Inpatient Medication Orders updated successfully')
|
||||||
|
frappe.msgprint(success_msg, title=_('Success'), indicator='green')
|
||||||
|
|
||||||
|
def validate_medication_orders(self):
|
||||||
|
for entry in self.medication_orders:
|
||||||
|
docstatus, is_completed = frappe.db.get_value('Inpatient Medication Order Entry', entry.against_imoe,
|
||||||
|
['docstatus', 'is_completed'])
|
||||||
|
|
||||||
|
if docstatus == 2:
|
||||||
|
frappe.throw(_('Row {0}: Cannot create Inpatient Medication Entry against cancelled Inpatient Medication Order {1}').format(
|
||||||
|
entry.idx, get_link_to_form(entry.against_imo)))
|
||||||
|
|
||||||
|
if is_completed:
|
||||||
|
frappe.throw(_('Row {0}: This Medication Order is already marked as completed').format(
|
||||||
|
entry.idx))
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.cancel_stock_entries()
|
||||||
|
self.update_medication_orders(on_cancel=True)
|
||||||
|
|
||||||
|
def process_stock(self):
|
||||||
|
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
|
||||||
|
if not allow_negative_stock:
|
||||||
|
self.check_stock_qty()
|
||||||
|
|
||||||
|
return self.make_stock_entry()
|
||||||
|
|
||||||
|
def update_medication_orders(self, on_cancel=False):
|
||||||
|
orders, order_entry_map = self.get_order_entry_map()
|
||||||
|
# mark completion status
|
||||||
|
is_completed = 1
|
||||||
|
if on_cancel:
|
||||||
|
is_completed = 0
|
||||||
|
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE `tabInpatient Medication Order Entry`
|
||||||
|
SET is_completed = %(is_completed)s
|
||||||
|
WHERE name IN %(orders)s
|
||||||
|
""", {'orders': orders, 'is_completed': is_completed})
|
||||||
|
|
||||||
|
# update status and completed orders count
|
||||||
|
for order, count in order_entry_map.items():
|
||||||
|
medication_order = frappe.get_doc('Inpatient Medication Order', order)
|
||||||
|
completed_orders = flt(count)
|
||||||
|
current_value = frappe.db.get_value('Inpatient Medication Order', order, 'completed_orders')
|
||||||
|
|
||||||
|
if on_cancel:
|
||||||
|
completed_orders = flt(current_value) - flt(count)
|
||||||
|
else:
|
||||||
|
completed_orders = flt(current_value) + flt(count)
|
||||||
|
|
||||||
|
medication_order.db_set('completed_orders', completed_orders)
|
||||||
|
medication_order.set_status()
|
||||||
|
|
||||||
|
def get_order_entry_map(self):
|
||||||
|
# for marking order completion status
|
||||||
|
orders = []
|
||||||
|
# orders mapped
|
||||||
|
order_entry_map = dict()
|
||||||
|
|
||||||
|
for entry in self.medication_orders:
|
||||||
|
orders.append(entry.against_imoe)
|
||||||
|
parent = entry.against_imo
|
||||||
|
if not order_entry_map.get(parent):
|
||||||
|
order_entry_map[parent] = 0
|
||||||
|
|
||||||
|
order_entry_map[parent] += 1
|
||||||
|
|
||||||
|
return orders, order_entry_map
|
||||||
|
|
||||||
|
def check_stock_qty(self):
|
||||||
|
from erpnext.stock.stock_ledger import NegativeStockError
|
||||||
|
|
||||||
|
drug_availability = dict()
|
||||||
|
for d in self.medication_orders:
|
||||||
|
if not drug_availability.get(d.drug_code):
|
||||||
|
drug_availability[d.drug_code] = 0
|
||||||
|
drug_availability[d.drug_code] += flt(d.dosage)
|
||||||
|
|
||||||
|
for drug, dosage in drug_availability.items():
|
||||||
|
available_qty = get_latest_stock_qty(drug, self.warehouse)
|
||||||
|
|
||||||
|
# validate qty
|
||||||
|
if flt(available_qty) < flt(dosage):
|
||||||
|
frappe.throw(_('Quantity not available for {0} in warehouse {1}').format(
|
||||||
|
frappe.bold(drug), frappe.bold(self.warehouse))
|
||||||
|
+ '<br><br>' + _('Available quantity is {0}, you need {1}').format(
|
||||||
|
frappe.bold(available_qty), frappe.bold(dosage))
|
||||||
|
+ '<br><br>' + _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.'),
|
||||||
|
NegativeStockError, title=_('Insufficient Stock'))
|
||||||
|
|
||||||
|
def make_stock_entry(self):
|
||||||
|
stock_entry = frappe.new_doc('Stock Entry')
|
||||||
|
stock_entry.purpose = 'Material Issue'
|
||||||
|
stock_entry.set_stock_entry_type()
|
||||||
|
stock_entry.from_warehouse = self.warehouse
|
||||||
|
stock_entry.company = self.company
|
||||||
|
stock_entry.inpatient_medication_entry = self.name
|
||||||
|
cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
|
||||||
|
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', self.company)
|
||||||
|
|
||||||
|
for entry in self.medication_orders:
|
||||||
|
se_child = stock_entry.append('items')
|
||||||
|
se_child.item_code = entry.drug_code
|
||||||
|
se_child.item_name = entry.drug_name
|
||||||
|
se_child.uom = frappe.db.get_value('Item', entry.drug_code, 'stock_uom')
|
||||||
|
se_child.stock_uom = se_child.uom
|
||||||
|
se_child.qty = flt(entry.dosage)
|
||||||
|
# in stock uom
|
||||||
|
se_child.conversion_factor = 1
|
||||||
|
se_child.cost_center = cost_center
|
||||||
|
se_child.expense_account = expense_account
|
||||||
|
# references
|
||||||
|
se_child.patient = entry.patient
|
||||||
|
se_child.inpatient_medication_entry_child = entry.name
|
||||||
|
|
||||||
|
stock_entry.submit()
|
||||||
|
return stock_entry.name
|
||||||
|
|
||||||
|
def cancel_stock_entries(self):
|
||||||
|
stock_entries = frappe.get_all('Stock Entry', {'inpatient_medication_entry': self.name})
|
||||||
|
for entry in stock_entries:
|
||||||
|
doc = frappe.get_doc('Stock Entry', entry.name)
|
||||||
|
doc.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
def get_pending_medication_orders(entry):
|
||||||
|
filters, values = get_filters(entry)
|
||||||
|
|
||||||
|
data = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
ip.inpatient_record, ip.patient, ip.patient_name,
|
||||||
|
entry.name, entry.parent, entry.drug, entry.drug_name,
|
||||||
|
entry.dosage, entry.dosage_form, entry.date, entry.time, entry.instructions
|
||||||
|
FROM
|
||||||
|
`tabInpatient Medication Order` ip
|
||||||
|
INNER JOIN
|
||||||
|
`tabInpatient Medication Order Entry` entry
|
||||||
|
ON
|
||||||
|
ip.name = entry.parent
|
||||||
|
WHERE
|
||||||
|
ip.docstatus = 1 and
|
||||||
|
ip.company = %(company)s and
|
||||||
|
entry.is_completed = 0
|
||||||
|
{0}
|
||||||
|
ORDER BY
|
||||||
|
entry.date, entry.time
|
||||||
|
""".format(filters), values, as_dict=1)
|
||||||
|
|
||||||
|
for doc in data:
|
||||||
|
inpatient_record = doc.inpatient_record
|
||||||
|
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
|
||||||
|
|
||||||
|
if entry.service_unit and doc.service_unit != entry.service_unit:
|
||||||
|
data.remove(doc)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_filters(entry):
|
||||||
|
filters = ''
|
||||||
|
values = dict(company=entry.company)
|
||||||
|
if entry.from_date:
|
||||||
|
filters += ' and entry.date >= %(from_date)s'
|
||||||
|
values['from_date'] = entry.from_date
|
||||||
|
|
||||||
|
if entry.to_date:
|
||||||
|
filters += ' and entry.date <= %(to_date)s'
|
||||||
|
values['to_date'] = entry.to_date
|
||||||
|
|
||||||
|
if entry.from_time:
|
||||||
|
filters += ' and entry.time >= %(from_time)s'
|
||||||
|
values['from_time'] = entry.from_time
|
||||||
|
|
||||||
|
if entry.to_time:
|
||||||
|
filters += ' and entry.time <= %(to_time)s'
|
||||||
|
values['to_time'] = entry.to_time
|
||||||
|
|
||||||
|
if entry.patient:
|
||||||
|
filters += ' and ip.patient = %(patient)s'
|
||||||
|
values['patient'] = entry.patient
|
||||||
|
|
||||||
|
if entry.practitioner:
|
||||||
|
filters += ' and ip.practitioner = %(practitioner)s'
|
||||||
|
values['practitioner'] = entry.practitioner
|
||||||
|
|
||||||
|
if entry.item_code:
|
||||||
|
filters += ' and entry.drug = %(item_code)s'
|
||||||
|
values['item_code'] = entry.item_code
|
||||||
|
|
||||||
|
if entry.assigned_to_practitioner:
|
||||||
|
filters += ' and ip._assign LIKE %(assigned_to)s'
|
||||||
|
values['assigned_to'] = '%' + entry.assigned_to_practitioner + '%'
|
||||||
|
|
||||||
|
return filters, values
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_healthcare_service_unit(inpatient_record):
|
||||||
|
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
|
||||||
|
return ip_record.inpatient_occupancies[-1].service_unit
|
@ -0,0 +1,16 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'against_imoe',
|
||||||
|
'internal_links': {
|
||||||
|
'Inpatient Medication Order': ['medication_orders', 'against_imo']
|
||||||
|
},
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Reference'),
|
||||||
|
'items': ['Inpatient Medication Order']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from frappe.utils import add_days, getdate, now_datetime
|
||||||
|
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||||
|
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||||
|
from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
|
||||||
|
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
|
||||||
|
|
||||||
|
class TestInpatientMedicationEntry(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Medication Order`""")
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Medication Entry`""")
|
||||||
|
self.patient = create_patient()
|
||||||
|
|
||||||
|
# Admit
|
||||||
|
ip_record = create_inpatient(self.patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
|
ip_record.save()
|
||||||
|
ip_record.reload()
|
||||||
|
service_unit = get_healthcare_service_unit()
|
||||||
|
admit_patient(ip_record, service_unit, now_datetime())
|
||||||
|
self.ip_record = ip_record
|
||||||
|
|
||||||
|
def test_filters_for_fetching_pending_mo(self):
|
||||||
|
ipmo = create_ipmo(self.patient)
|
||||||
|
ipmo.submit()
|
||||||
|
ipmo.reload()
|
||||||
|
|
||||||
|
date = add_days(getdate(), -1)
|
||||||
|
filters = frappe._dict(
|
||||||
|
from_date=date,
|
||||||
|
to_date=date,
|
||||||
|
from_time='',
|
||||||
|
to_time='',
|
||||||
|
item_code='Dextromethorphan',
|
||||||
|
patient=self.patient
|
||||||
|
)
|
||||||
|
|
||||||
|
ipme = create_ipme(filters, update_stock=0)
|
||||||
|
|
||||||
|
# 3 dosages per day
|
||||||
|
self.assertEqual(len(ipme.medication_orders), 3)
|
||||||
|
self.assertEqual(getdate(ipme.medication_orders[0].datetime), date)
|
||||||
|
|
||||||
|
def test_ipme_with_stock_update(self):
|
||||||
|
ipmo = create_ipmo(self.patient)
|
||||||
|
ipmo.submit()
|
||||||
|
ipmo.reload()
|
||||||
|
|
||||||
|
date = add_days(getdate(), -1)
|
||||||
|
filters = frappe._dict(
|
||||||
|
from_date=date,
|
||||||
|
to_date=date,
|
||||||
|
from_time='',
|
||||||
|
to_time='',
|
||||||
|
item_code='Dextromethorphan',
|
||||||
|
patient=self.patient
|
||||||
|
)
|
||||||
|
|
||||||
|
make_stock_entry()
|
||||||
|
ipme = create_ipme(filters, update_stock=1)
|
||||||
|
ipme.submit()
|
||||||
|
ipme.reload()
|
||||||
|
|
||||||
|
# test order completed
|
||||||
|
is_order_completed = frappe.db.get_value('Inpatient Medication Order Entry',
|
||||||
|
ipme.medication_orders[0].against_imoe, 'is_completed')
|
||||||
|
self.assertEqual(is_order_completed, 1)
|
||||||
|
|
||||||
|
# test stock entry
|
||||||
|
stock_entry = frappe.db.exists('Stock Entry', {'inpatient_medication_entry': ipme.name})
|
||||||
|
self.assertTrue(stock_entry)
|
||||||
|
|
||||||
|
# check references
|
||||||
|
stock_entry = frappe.get_doc('Stock Entry', stock_entry)
|
||||||
|
self.assertEqual(stock_entry.items[0].patient, self.patient)
|
||||||
|
self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# cleanup - Discharge
|
||||||
|
schedule_discharge(frappe.as_json({'patient': self.patient}))
|
||||||
|
self.ip_record.reload()
|
||||||
|
mark_invoiced_inpatient_occupancy(self.ip_record)
|
||||||
|
|
||||||
|
self.ip_record.reload()
|
||||||
|
discharge_patient(self.ip_record)
|
||||||
|
|
||||||
|
for entry in frappe.get_all('Inpatient Medication Entry'):
|
||||||
|
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
|
||||||
|
doc.cancel()
|
||||||
|
frappe.db.delete('Stock Entry', {'inpatient_medication_entry': doc.name})
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
for entry in frappe.get_all('Inpatient Medication Order'):
|
||||||
|
doc = frappe.get_doc('Inpatient Medication Order', entry.name)
|
||||||
|
doc.cancel()
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
def make_stock_entry():
|
||||||
|
frappe.db.set_value('Company', '_Test Company', {
|
||||||
|
'stock_adjustment_account': 'Stock Adjustment - _TC',
|
||||||
|
'default_inventory_account': 'Stock In Hand - _TC'
|
||||||
|
})
|
||||||
|
stock_entry = frappe.new_doc('Stock Entry')
|
||||||
|
stock_entry.stock_entry_type = 'Material Receipt'
|
||||||
|
stock_entry.company = '_Test Company'
|
||||||
|
stock_entry.to_warehouse = 'Stores - _TC'
|
||||||
|
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company')
|
||||||
|
se_child = stock_entry.append('items')
|
||||||
|
se_child.item_code = 'Dextromethorphan'
|
||||||
|
se_child.item_name = 'Dextromethorphan'
|
||||||
|
se_child.uom = 'Nos'
|
||||||
|
se_child.stock_uom = 'Nos'
|
||||||
|
se_child.qty = 6
|
||||||
|
se_child.t_warehouse = 'Stores - _TC'
|
||||||
|
# in stock uom
|
||||||
|
se_child.conversion_factor = 1.0
|
||||||
|
se_child.expense_account = expense_account
|
||||||
|
stock_entry.submit()
|
@ -0,0 +1,163 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-09-25 14:56:32.636569",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"patient",
|
||||||
|
"patient_name",
|
||||||
|
"inpatient_record",
|
||||||
|
"column_break_4",
|
||||||
|
"service_unit",
|
||||||
|
"datetime",
|
||||||
|
"medication_details_section",
|
||||||
|
"drug_code",
|
||||||
|
"drug_name",
|
||||||
|
"dosage",
|
||||||
|
"available_qty",
|
||||||
|
"dosage_form",
|
||||||
|
"column_break_10",
|
||||||
|
"instructions",
|
||||||
|
"references_section",
|
||||||
|
"against_imo",
|
||||||
|
"against_imoe"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "patient",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Patient",
|
||||||
|
"options": "Patient",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "patient.patient_name",
|
||||||
|
"fieldname": "patient_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Patient Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "drug_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Drug Code",
|
||||||
|
"options": "Item",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "drug_code.item_name",
|
||||||
|
"fieldname": "drug_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Drug Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 1,
|
||||||
|
"fieldname": "dosage",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Dosage",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dosage_form",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Dosage Form",
|
||||||
|
"options": "Dosage Form"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "patient.inpatient_record",
|
||||||
|
"fieldname": "inpatient_record",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Inpatient Record",
|
||||||
|
"options": "Inpatient Record",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "references_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "References"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "medication_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Medication Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_10",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 3,
|
||||||
|
"fieldname": "datetime",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Datetime",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "instructions",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Instructions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "service_unit",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Service Unit",
|
||||||
|
"options": "Healthcare Service Unit",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_imo",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Against Inpatient Medication Order",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Inpatient Medication Order",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_imoe",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Against Inpatient Medication Order Entry",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "available_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Available Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-09-30 14:48:23.648223",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Inpatient Medication Entry Detail",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class InpatientMedicationEntryDetail(Document):
|
||||||
|
pass
|
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Inpatient Medication Order', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
if (frm.doc.docstatus === 1) {
|
||||||
|
frm.trigger("show_progress");
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.events.show_medication_order_button(frm);
|
||||||
|
|
||||||
|
frm.set_query('patient', () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'inpatient_record': ['!=', '']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
show_medication_order_button: function(frm) {
|
||||||
|
frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide();
|
||||||
|
frm.fields_dict['medication_orders'].grid.add_custom_button(__('Add Medication Orders'), () => {
|
||||||
|
let d = new frappe.ui.Dialog({
|
||||||
|
title: __('Add Medication Orders'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: 'drug_code',
|
||||||
|
label: __('Drug'),
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Item',
|
||||||
|
reqd: 1,
|
||||||
|
"get_query": function () {
|
||||||
|
return {
|
||||||
|
filters: {'is_stock_item': 1}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'dosage',
|
||||||
|
label: __('Dosage'),
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Prescription Dosage',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'period',
|
||||||
|
label: __('Period'),
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Prescription Duration',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'dosage_form',
|
||||||
|
label: __('Dosage Form'),
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Dosage Form',
|
||||||
|
reqd: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primary_action_label: __('Add'),
|
||||||
|
primary_action: () => {
|
||||||
|
let values = d.get_values();
|
||||||
|
if (values) {
|
||||||
|
frm.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: 'add_order_entries',
|
||||||
|
args: {
|
||||||
|
order: values
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Adding Order Entries'),
|
||||||
|
callback: function() {
|
||||||
|
frm.refresh_field('medication_orders');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
show_progress: function(frm) {
|
||||||
|
let bars = [];
|
||||||
|
let message = '';
|
||||||
|
|
||||||
|
// completed sessions
|
||||||
|
let title = __('{0} medication orders completed', [frm.doc.completed_orders]);
|
||||||
|
if (frm.doc.completed_orders === 1) {
|
||||||
|
title = __('{0} medication order completed', [frm.doc.completed_orders]);
|
||||||
|
}
|
||||||
|
title += __(' out of {0}', [frm.doc.total_orders]);
|
||||||
|
|
||||||
|
bars.push({
|
||||||
|
'title': title,
|
||||||
|
'width': (frm.doc.completed_orders / frm.doc.total_orders * 100) + '%',
|
||||||
|
'progress_class': 'progress-bar-success'
|
||||||
|
});
|
||||||
|
if (bars[0].width == '0%') {
|
||||||
|
bars[0].width = '0.5%';
|
||||||
|
}
|
||||||
|
message = title;
|
||||||
|
frm.dashboard.add_progress(__('Status'), bars, message);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,196 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "naming_series:",
|
||||||
|
"creation": "2020-09-14 18:33:56.715736",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"patient_details_section",
|
||||||
|
"naming_series",
|
||||||
|
"patient_encounter",
|
||||||
|
"patient",
|
||||||
|
"patient_name",
|
||||||
|
"patient_age",
|
||||||
|
"inpatient_record",
|
||||||
|
"column_break_6",
|
||||||
|
"company",
|
||||||
|
"status",
|
||||||
|
"practitioner",
|
||||||
|
"start_date",
|
||||||
|
"end_date",
|
||||||
|
"medication_orders_section",
|
||||||
|
"medication_orders",
|
||||||
|
"section_break_16",
|
||||||
|
"total_orders",
|
||||||
|
"column_break_18",
|
||||||
|
"completed_orders",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "patient_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Patient Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Naming Series",
|
||||||
|
"options": "HLC-IMO-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "patient_encounter",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Patient Encounter",
|
||||||
|
"options": "Patient Encounter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "patient_encounter.patient",
|
||||||
|
"fieldname": "patient",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Patient",
|
||||||
|
"options": "Patient",
|
||||||
|
"read_only_depends_on": "patient_encounter",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "patient.patient_name",
|
||||||
|
"fieldname": "patient_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Patient Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "patient_age",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Patient Age",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "patient.inpatient_record",
|
||||||
|
"fieldname": "inpatient_record",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Inpatient Record",
|
||||||
|
"options": "Inpatient Record",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "patient_encounter.practitioner",
|
||||||
|
"fieldname": "practitioner",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Healthcare Practitioner",
|
||||||
|
"options": "Healthcare Practitioner",
|
||||||
|
"read_only_depends_on": "patient_encounter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Start Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "end_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "End Date",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.patient && doc.start_date",
|
||||||
|
"fieldname": "medication_orders_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Medication Orders"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "medication_orders",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Medication Orders",
|
||||||
|
"options": "Inpatient Medication Order Entry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Inpatient Medication Order",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "\nDraft\nSubmitted\nPending\nIn Process\nCompleted\nCancelled",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "section_break_16",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Other Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_orders",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Total Orders",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_18",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "completed_orders",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Completed Orders",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-09-30 21:53:27.128591",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Inpatient Medication Order",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"search_fields": "patient_encounter, patient",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"title_field": "patient",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import cstr
|
||||||
|
from erpnext.healthcare.doctype.patient_encounter.patient_encounter import get_prescription_dates
|
||||||
|
|
||||||
|
class InpatientMedicationOrder(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_inpatient()
|
||||||
|
self.validate_duplicate()
|
||||||
|
self.set_total_orders()
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.validate_inpatient()
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
|
def validate_inpatient(self):
|
||||||
|
if not self.inpatient_record:
|
||||||
|
frappe.throw(_('No Inpatient Record found against patient {0}').format(self.patient))
|
||||||
|
|
||||||
|
def validate_duplicate(self):
|
||||||
|
existing_mo = frappe.db.exists('Inpatient Medication Order', {
|
||||||
|
'patient_encounter': self.patient_encounter,
|
||||||
|
'docstatus': ('!=', 2),
|
||||||
|
'name': ('!=', self.name)
|
||||||
|
})
|
||||||
|
if existing_mo:
|
||||||
|
frappe.throw(_('An Inpatient Medication Order {0} against Patient Encounter {1} already exists.').format(
|
||||||
|
existing_mo, self.patient_encounter), frappe.DuplicateEntryError)
|
||||||
|
|
||||||
|
def set_total_orders(self):
|
||||||
|
self.db_set('total_orders', len(self.medication_orders))
|
||||||
|
|
||||||
|
def set_status(self):
|
||||||
|
status = {
|
||||||
|
"0": "Draft",
|
||||||
|
"1": "Submitted",
|
||||||
|
"2": "Cancelled"
|
||||||
|
}[cstr(self.docstatus or 0)]
|
||||||
|
|
||||||
|
if self.docstatus == 1:
|
||||||
|
if not self.completed_orders:
|
||||||
|
status = 'Pending'
|
||||||
|
elif self.completed_orders < self.total_orders:
|
||||||
|
status = 'In Process'
|
||||||
|
else:
|
||||||
|
status = 'Completed'
|
||||||
|
|
||||||
|
self.db_set('status', status)
|
||||||
|
|
||||||
|
def add_order_entries(self, order):
|
||||||
|
if order.get('drug_code'):
|
||||||
|
dosage = frappe.get_doc('Prescription Dosage', order.get('dosage'))
|
||||||
|
dates = get_prescription_dates(order.get('period'), self.start_date)
|
||||||
|
for date in dates:
|
||||||
|
for dose in dosage.dosage_strength:
|
||||||
|
entry = self.append('medication_orders')
|
||||||
|
entry.drug = order.get('drug_code')
|
||||||
|
entry.drug_name = frappe.db.get_value('Item', order.get('drug_code'), 'item_name')
|
||||||
|
entry.dosage = dose.strength
|
||||||
|
entry.dosage_form = order.get('dosage_form')
|
||||||
|
entry.date = date
|
||||||
|
entry.time = dose.strength_time
|
||||||
|
self.end_date = dates[-1]
|
||||||
|
return
|
@ -0,0 +1,16 @@
|
|||||||
|
frappe.listview_settings['Inpatient Medication Order'] = {
|
||||||
|
add_fields: ["status"],
|
||||||
|
filters: [["status", "!=", "Cancelled"]],
|
||||||
|
get_indicator: function(doc) {
|
||||||
|
if (doc.status === "Pending") {
|
||||||
|
return [__("Pending"), "orange", "status,=,Pending"];
|
||||||
|
|
||||||
|
} else if (doc.status === "In Process") {
|
||||||
|
return [__("In Process"), "blue", "status,=,In Process"];
|
||||||
|
|
||||||
|
} else if (doc.status === "Completed") {
|
||||||
|
return [__("Completed"), "green", "status,=,Completed"];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,150 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from frappe.utils import add_days, getdate, now_datetime
|
||||||
|
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||||
|
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||||
|
|
||||||
|
class TestInpatientMedicationOrder(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
|
self.patient = create_patient()
|
||||||
|
|
||||||
|
# Admit
|
||||||
|
ip_record = create_inpatient(self.patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
|
ip_record.save()
|
||||||
|
ip_record.reload()
|
||||||
|
service_unit = get_healthcare_service_unit()
|
||||||
|
admit_patient(ip_record, service_unit, now_datetime())
|
||||||
|
self.ip_record = ip_record
|
||||||
|
|
||||||
|
def test_order_creation(self):
|
||||||
|
ipmo = create_ipmo(self.patient)
|
||||||
|
ipmo.submit()
|
||||||
|
ipmo.reload()
|
||||||
|
|
||||||
|
# 3 dosages per day for 2 days
|
||||||
|
self.assertEqual(len(ipmo.medication_orders), 6)
|
||||||
|
self.assertEqual(ipmo.medication_orders[0].date, add_days(getdate(), -1))
|
||||||
|
|
||||||
|
prescription_dosage = frappe.get_doc('Prescription Dosage', '1-1-1')
|
||||||
|
for i in range(len(prescription_dosage.dosage_strength)):
|
||||||
|
self.assertEqual(ipmo.medication_orders[i].time, prescription_dosage.dosage_strength[i].strength_time)
|
||||||
|
|
||||||
|
self.assertEqual(ipmo.medication_orders[3].date, getdate())
|
||||||
|
|
||||||
|
def test_inpatient_validation(self):
|
||||||
|
# Discharge
|
||||||
|
schedule_discharge(frappe.as_json({'patient': self.patient}))
|
||||||
|
|
||||||
|
self.ip_record.reload()
|
||||||
|
mark_invoiced_inpatient_occupancy(self.ip_record)
|
||||||
|
|
||||||
|
self.ip_record.reload()
|
||||||
|
discharge_patient(self.ip_record)
|
||||||
|
|
||||||
|
ipmo = create_ipmo(self.patient)
|
||||||
|
# inpatient validation
|
||||||
|
self.assertRaises(frappe.ValidationError, ipmo.insert)
|
||||||
|
|
||||||
|
def test_status(self):
|
||||||
|
ipmo = create_ipmo(self.patient)
|
||||||
|
ipmo.submit()
|
||||||
|
ipmo.reload()
|
||||||
|
|
||||||
|
self.assertEqual(ipmo.status, 'Pending')
|
||||||
|
|
||||||
|
filters = frappe._dict(from_date=add_days(getdate(), -1), to_date=add_days(getdate(), -1), from_time='', to_time='')
|
||||||
|
ipme = create_ipme(filters)
|
||||||
|
ipme.submit()
|
||||||
|
ipmo.reload()
|
||||||
|
self.assertEqual(ipmo.status, 'In Process')
|
||||||
|
|
||||||
|
filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time='', to_time='')
|
||||||
|
ipme = create_ipme(filters)
|
||||||
|
ipme.submit()
|
||||||
|
ipmo.reload()
|
||||||
|
self.assertEqual(ipmo.status, 'Completed')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if frappe.db.get_value('Patient', self.patient, 'inpatient_record'):
|
||||||
|
# cleanup - Discharge
|
||||||
|
schedule_discharge(frappe.as_json({'patient': self.patient}))
|
||||||
|
self.ip_record.reload()
|
||||||
|
mark_invoiced_inpatient_occupancy(self.ip_record)
|
||||||
|
|
||||||
|
self.ip_record.reload()
|
||||||
|
discharge_patient(self.ip_record)
|
||||||
|
|
||||||
|
for entry in frappe.get_all('Inpatient Medication Entry'):
|
||||||
|
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
|
||||||
|
doc.cancel()
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
for entry in frappe.get_all('Inpatient Medication Order'):
|
||||||
|
doc = frappe.get_doc('Inpatient Medication Order', entry.name)
|
||||||
|
doc.cancel()
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
def create_dosage_form():
|
||||||
|
if not frappe.db.exists('Dosage Form', 'Tablet'):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Dosage Form',
|
||||||
|
'dosage_form': 'Tablet'
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
def create_drug(item=None):
|
||||||
|
if not item:
|
||||||
|
item = 'Dextromethorphan'
|
||||||
|
drug = frappe.db.exists('Item', {'item_code': 'Dextromethorphan'})
|
||||||
|
if not drug:
|
||||||
|
drug = frappe.get_doc({
|
||||||
|
'doctype': 'Item',
|
||||||
|
'item_code': 'Dextromethorphan',
|
||||||
|
'item_name': 'Dextromethorphan',
|
||||||
|
'item_group': 'Products',
|
||||||
|
'stock_uom': 'Nos',
|
||||||
|
'is_stock_item': 1,
|
||||||
|
'valuation_rate': 50,
|
||||||
|
'opening_stock': 20
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
def get_orders():
|
||||||
|
create_dosage_form()
|
||||||
|
create_drug()
|
||||||
|
return {
|
||||||
|
'drug_code': 'Dextromethorphan',
|
||||||
|
'drug_name': 'Dextromethorphan',
|
||||||
|
'dosage': '1-1-1',
|
||||||
|
'dosage_form': 'Tablet',
|
||||||
|
'period': '2 Day'
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_ipmo(patient):
|
||||||
|
orders = get_orders()
|
||||||
|
ipmo = frappe.new_doc('Inpatient Medication Order')
|
||||||
|
ipmo.patient = patient
|
||||||
|
ipmo.company = '_Test Company'
|
||||||
|
ipmo.start_date = add_days(getdate(), -1)
|
||||||
|
ipmo.add_order_entries(orders)
|
||||||
|
|
||||||
|
return ipmo
|
||||||
|
|
||||||
|
def create_ipme(filters, update_stock=0):
|
||||||
|
ipme = frappe.new_doc('Inpatient Medication Entry')
|
||||||
|
ipme.company = '_Test Company'
|
||||||
|
ipme.posting_date = getdate()
|
||||||
|
ipme.update_stock = update_stock
|
||||||
|
if update_stock:
|
||||||
|
ipme.warehouse = 'Stores - _TC'
|
||||||
|
for key, value in filters.items():
|
||||||
|
ipme.set(key, value)
|
||||||
|
ipme = ipme.get_medication_orders()
|
||||||
|
|
||||||
|
return ipme
|
||||||
|
|
@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-09-14 21:51:30.259164",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"drug",
|
||||||
|
"drug_name",
|
||||||
|
"dosage",
|
||||||
|
"dosage_form",
|
||||||
|
"instructions",
|
||||||
|
"column_break_4",
|
||||||
|
"date",
|
||||||
|
"time",
|
||||||
|
"is_completed"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "drug",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Drug",
|
||||||
|
"options": "Item",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "drug.item_name",
|
||||||
|
"fieldname": "drug_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Drug Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dosage",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Dosage",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dosage_form",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Dosage Form",
|
||||||
|
"options": "Dosage Form",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Time",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_completed",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Order Completed",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "instructions",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Instructions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-09-30 14:03:26.755925",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Inpatient Medication Order Entry",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class InpatientMedicationOrderEntry(Document):
|
||||||
|
pass
|
@ -83,6 +83,7 @@ def get_healthcare_service_unit():
|
|||||||
if not service_unit:
|
if not service_unit:
|
||||||
service_unit = frappe.new_doc("Healthcare Service Unit")
|
service_unit = frappe.new_doc("Healthcare Service Unit")
|
||||||
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
|
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
|
||||||
|
service_unit.company = "_Test Company"
|
||||||
service_unit.service_unit_type = get_service_unit_type()
|
service_unit.service_unit_type = get_service_unit_type()
|
||||||
service_unit.inpatient_occupancy = 1
|
service_unit.inpatient_occupancy = 1
|
||||||
service_unit.occupancy_status = "Vacant"
|
service_unit.occupancy_status = "Vacant"
|
||||||
|
@ -58,6 +58,14 @@ frappe.ui.form.on('Patient Encounter', {
|
|||||||
create_procedure(frm);
|
create_procedure(frm);
|
||||||
},'Create');
|
},'Create');
|
||||||
|
|
||||||
|
if (frm.doc.drug_prescription && frm.doc.inpatient_record && frm.doc.inpatient_status === "Admitted") {
|
||||||
|
frm.add_custom_button(__('Inpatient Medication Order'), function() {
|
||||||
|
frappe.model.open_mapped_doc({
|
||||||
|
method: 'erpnext.healthcare.doctype.patient_encounter.patient_encounter.make_ip_medication_order',
|
||||||
|
frm: frm
|
||||||
|
});
|
||||||
|
}, 'Create');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.set_query('patient', function() {
|
frm.set_query('patient', function() {
|
||||||
|
@ -6,8 +6,9 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cstr
|
from frappe.utils import cstr, getdate, add_days
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
class PatientEncounter(Document):
|
class PatientEncounter(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -22,20 +23,69 @@ class PatientEncounter(Document):
|
|||||||
insert_encounter_to_medical_record(self)
|
insert_encounter_to_medical_record(self)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
update_encounter_medical_record(self)
|
if self.therapies:
|
||||||
|
create_therapy_plan(self)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
if self.appointment:
|
if self.appointment:
|
||||||
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
|
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
|
||||||
delete_medical_record(self)
|
|
||||||
|
|
||||||
def on_submit(self):
|
if self.inpatient_record and self.drug_prescription:
|
||||||
create_therapy_plan(self)
|
delete_ip_medication_order(self)
|
||||||
|
|
||||||
|
delete_medical_record(self)
|
||||||
|
|
||||||
def set_title(self):
|
def set_title(self):
|
||||||
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
|
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
|
||||||
self.practitioner_name or self.practitioner)[:100]
|
self.practitioner_name or self.practitioner)[:100]
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_ip_medication_order(source_name, target_doc=None):
|
||||||
|
def set_missing_values(source, target):
|
||||||
|
target.start_date = source.encounter_date
|
||||||
|
for entry in source.drug_prescription:
|
||||||
|
if entry.drug_code:
|
||||||
|
dosage = frappe.get_doc('Prescription Dosage', entry.dosage)
|
||||||
|
dates = get_prescription_dates(entry.period, target.start_date)
|
||||||
|
for date in dates:
|
||||||
|
for dose in dosage.dosage_strength:
|
||||||
|
order = target.append('medication_orders')
|
||||||
|
order.drug = entry.drug_code
|
||||||
|
order.drug_name = entry.drug_name
|
||||||
|
order.dosage = dose.strength
|
||||||
|
order.instructions = entry.comment
|
||||||
|
order.dosage_form = entry.dosage_form
|
||||||
|
order.date = date
|
||||||
|
order.time = dose.strength_time
|
||||||
|
target.end_date = dates[-1]
|
||||||
|
|
||||||
|
doc = get_mapped_doc('Patient Encounter', source_name, {
|
||||||
|
'Patient Encounter': {
|
||||||
|
'doctype': 'Inpatient Medication Order',
|
||||||
|
'field_map': {
|
||||||
|
'name': 'patient_encounter',
|
||||||
|
'patient': 'patient',
|
||||||
|
'patient_name': 'patient_name',
|
||||||
|
'patient_age': 'patient_age',
|
||||||
|
'inpatient_record': 'inpatient_record',
|
||||||
|
'practitioner': 'practitioner',
|
||||||
|
'start_date': 'encounter_date'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, target_doc, set_missing_values)
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
def get_prescription_dates(period, start_date):
|
||||||
|
prescription_duration = frappe.get_doc('Prescription Duration', period)
|
||||||
|
days = prescription_duration.get_days()
|
||||||
|
dates = [start_date]
|
||||||
|
for i in range(1, days):
|
||||||
|
dates.append(add_days(getdate(start_date), i))
|
||||||
|
return dates
|
||||||
|
|
||||||
|
|
||||||
def create_therapy_plan(encounter):
|
def create_therapy_plan(encounter):
|
||||||
if len(encounter.therapies):
|
if len(encounter.therapies):
|
||||||
doc = frappe.new_doc('Therapy Plan')
|
doc = frappe.new_doc('Therapy Plan')
|
||||||
@ -51,6 +101,7 @@ def create_therapy_plan(encounter):
|
|||||||
encounter.db_set('therapy_plan', doc.name)
|
encounter.db_set('therapy_plan', doc.name)
|
||||||
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
|
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
|
||||||
|
|
||||||
|
|
||||||
def insert_encounter_to_medical_record(doc):
|
def insert_encounter_to_medical_record(doc):
|
||||||
subject = set_subject_field(doc)
|
subject = set_subject_field(doc)
|
||||||
medical_record = frappe.new_doc('Patient Medical Record')
|
medical_record = frappe.new_doc('Patient Medical Record')
|
||||||
@ -63,6 +114,7 @@ def insert_encounter_to_medical_record(doc):
|
|||||||
medical_record.reference_owner = doc.owner
|
medical_record.reference_owner = doc.owner
|
||||||
medical_record.save(ignore_permissions=True)
|
medical_record.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
def update_encounter_medical_record(encounter):
|
def update_encounter_medical_record(encounter):
|
||||||
medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
|
medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
|
||||||
|
|
||||||
@ -72,8 +124,17 @@ def update_encounter_medical_record(encounter):
|
|||||||
else:
|
else:
|
||||||
insert_encounter_to_medical_record(encounter)
|
insert_encounter_to_medical_record(encounter)
|
||||||
|
|
||||||
|
|
||||||
def delete_medical_record(encounter):
|
def delete_medical_record(encounter):
|
||||||
frappe.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name)
|
record = frappe.db.exists('Patient Medical Record', {'reference_name', encounter.name})
|
||||||
|
if record:
|
||||||
|
frappe.delete_doc('Patient Medical Record', record, force=1)
|
||||||
|
|
||||||
|
def delete_ip_medication_order(encounter):
|
||||||
|
record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
|
||||||
|
if record:
|
||||||
|
frappe.delete_doc('Inpatient Medication Order', record, force=1)
|
||||||
|
|
||||||
|
|
||||||
def set_subject_field(encounter):
|
def set_subject_field(encounter):
|
||||||
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
|
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
|
||||||
|
@ -5,12 +5,18 @@ def get_data():
|
|||||||
return {
|
return {
|
||||||
'fieldname': 'encounter',
|
'fieldname': 'encounter',
|
||||||
'non_standard_fieldnames': {
|
'non_standard_fieldnames': {
|
||||||
'Patient Medical Record': 'reference_name'
|
'Patient Medical Record': 'reference_name',
|
||||||
|
'Inpatient Medication Order': 'patient_encounter'
|
||||||
},
|
},
|
||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
'label': _('Records'),
|
'label': _('Records'),
|
||||||
'items': ['Vital Signs', 'Patient Medical Record']
|
'items': ['Vital Signs', 'Patient Medical Record']
|
||||||
},
|
},
|
||||||
]
|
{
|
||||||
|
'label': _('Orders'),
|
||||||
|
'items': ['Inpatient Medication Order']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'disable_create_buttons': ['Inpatient Medication Order']
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate, flt
|
||||||
from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
|
from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
|
||||||
from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session
|
from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice
|
||||||
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient
|
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient
|
||||||
|
|
||||||
class TestTherapyPlan(unittest.TestCase):
|
class TestTherapyPlan(unittest.TestCase):
|
||||||
@ -20,25 +20,45 @@ class TestTherapyPlan(unittest.TestCase):
|
|||||||
plan = create_therapy_plan()
|
plan = create_therapy_plan()
|
||||||
self.assertEquals(plan.status, 'Not Started')
|
self.assertEquals(plan.status, 'Not Started')
|
||||||
|
|
||||||
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab')
|
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
|
||||||
frappe.get_doc(session).submit()
|
frappe.get_doc(session).submit()
|
||||||
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
|
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
|
||||||
|
|
||||||
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab')
|
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
|
||||||
frappe.get_doc(session).submit()
|
frappe.get_doc(session).submit()
|
||||||
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
|
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
|
||||||
|
|
||||||
|
def test_therapy_plan_from_template(self):
|
||||||
|
patient = create_patient()
|
||||||
|
template = create_therapy_plan_template()
|
||||||
|
# check linked item
|
||||||
|
self.assertTrue(frappe.db.exists('Therapy Plan Template', {'linked_item': 'Complete Rehab'}))
|
||||||
|
|
||||||
def create_therapy_plan():
|
plan = create_therapy_plan(template)
|
||||||
|
# invoice
|
||||||
|
si = make_sales_invoice(plan.name, patient, '_Test Company', template)
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount')
|
||||||
|
self.assertEquals(si.items[0].amount, therapy_plan_template_amt)
|
||||||
|
|
||||||
|
|
||||||
|
def create_therapy_plan(template=None):
|
||||||
patient = create_patient()
|
patient = create_patient()
|
||||||
therapy_type = create_therapy_type()
|
therapy_type = create_therapy_type()
|
||||||
plan = frappe.new_doc('Therapy Plan')
|
plan = frappe.new_doc('Therapy Plan')
|
||||||
plan.patient = patient
|
plan.patient = patient
|
||||||
plan.start_date = getdate()
|
plan.start_date = getdate()
|
||||||
plan.append('therapy_plan_details', {
|
|
||||||
'therapy_type': therapy_type.name,
|
if template:
|
||||||
'no_of_sessions': 2
|
plan.therapy_plan_template = template
|
||||||
})
|
plan = plan.set_therapy_details_from_template()
|
||||||
|
else:
|
||||||
|
plan.append('therapy_plan_details', {
|
||||||
|
'therapy_type': therapy_type.name,
|
||||||
|
'no_of_sessions': 2
|
||||||
|
})
|
||||||
|
|
||||||
plan.save()
|
plan.save()
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
@ -55,3 +75,22 @@ def create_encounter(patient, medical_department, practitioner):
|
|||||||
encounter.save()
|
encounter.save()
|
||||||
encounter.submit()
|
encounter.submit()
|
||||||
return encounter
|
return encounter
|
||||||
|
|
||||||
|
def create_therapy_plan_template():
|
||||||
|
template_name = frappe.db.exists('Therapy Plan Template', 'Complete Rehab')
|
||||||
|
if not template_name:
|
||||||
|
therapy_type = create_therapy_type()
|
||||||
|
template = frappe.new_doc('Therapy Plan Template')
|
||||||
|
template.plan_name = template.item_code = template.item_name = 'Complete Rehab'
|
||||||
|
template.item_group = 'Services'
|
||||||
|
rate = frappe.db.get_value('Therapy Type', therapy_type.name, 'rate')
|
||||||
|
template.append('therapy_types', {
|
||||||
|
'therapy_type': therapy_type.name,
|
||||||
|
'no_of_sessions': 2,
|
||||||
|
'rate': rate,
|
||||||
|
'amount': 2 * flt(rate)
|
||||||
|
})
|
||||||
|
template.save()
|
||||||
|
template_name = template.name
|
||||||
|
|
||||||
|
return template_name
|
||||||
|
@ -37,7 +37,8 @@ frappe.ui.form.on('Therapy Plan', {
|
|||||||
args: {
|
args: {
|
||||||
therapy_plan: frm.doc.name,
|
therapy_plan: frm.doc.name,
|
||||||
patient: frm.doc.patient,
|
patient: frm.doc.patient,
|
||||||
therapy_type: data.therapy_type
|
therapy_type: data.therapy_type,
|
||||||
|
company: frm.doc.company
|
||||||
},
|
},
|
||||||
freeze: true,
|
freeze: true,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
@ -49,13 +50,53 @@ frappe.ui.form.on('Therapy Plan', {
|
|||||||
});
|
});
|
||||||
}, __('Select Therapy Type'), __('Create'));
|
}, __('Select Therapy Type'), __('Create'));
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
|
|
||||||
|
if (frm.doc.therapy_plan_template && !frm.doc.invoiced) {
|
||||||
|
frm.add_custom_button(__('Sales Invoice'), function() {
|
||||||
|
frm.trigger('make_sales_invoice');
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.therapy_plan_template) {
|
||||||
|
frappe.meta.get_docfield('Therapy Plan Detail', 'therapy_type', frm.doc.name).read_only = 1;
|
||||||
|
frappe.meta.get_docfield('Therapy Plan Detail', 'no_of_sessions', frm.doc.name).read_only = 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
make_sales_invoice: function(frm) {
|
||||||
|
frappe.call({
|
||||||
|
args: {
|
||||||
|
'reference_name': frm.doc.name,
|
||||||
|
'patient': frm.doc.patient,
|
||||||
|
'company': frm.doc.company,
|
||||||
|
'therapy_plan_template': frm.doc.therapy_plan_template
|
||||||
|
},
|
||||||
|
method: 'erpnext.healthcare.doctype.therapy_plan.therapy_plan.make_sales_invoice',
|
||||||
|
callback: function(r) {
|
||||||
|
var doclist = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
therapy_plan_template: function(frm) {
|
||||||
|
if (frm.doc.therapy_plan_template) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'set_therapy_details_from_template',
|
||||||
|
doc: frm.doc,
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Fetching Template Details'),
|
||||||
|
callback: function() {
|
||||||
|
refresh_field('therapy_plan_details');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
show_progress_for_therapies: function(frm) {
|
show_progress_for_therapies: function(frm) {
|
||||||
let bars = [];
|
let bars = [];
|
||||||
let message = '';
|
let message = '';
|
||||||
let added_min = false;
|
|
||||||
|
|
||||||
// completed sessions
|
// completed sessions
|
||||||
let title = __('{0} sessions completed', [frm.doc.total_sessions_completed]);
|
let title = __('{0} sessions completed', [frm.doc.total_sessions_completed]);
|
||||||
@ -71,7 +112,6 @@ frappe.ui.form.on('Therapy Plan', {
|
|||||||
});
|
});
|
||||||
if (bars[0].width == '0%') {
|
if (bars[0].width == '0%') {
|
||||||
bars[0].width = '0.5%';
|
bars[0].width = '0.5%';
|
||||||
added_min = 0.5;
|
|
||||||
}
|
}
|
||||||
message = title;
|
message = title;
|
||||||
frm.dashboard.add_progress(__('Status'), bars, message);
|
frm.dashboard.add_progress(__('Status'), bars, message);
|
||||||
|
@ -9,11 +9,13 @@
|
|||||||
"naming_series",
|
"naming_series",
|
||||||
"patient",
|
"patient",
|
||||||
"patient_name",
|
"patient_name",
|
||||||
|
"invoiced",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"company",
|
"company",
|
||||||
"status",
|
"status",
|
||||||
"start_date",
|
"start_date",
|
||||||
"section_break_3",
|
"section_break_3",
|
||||||
|
"therapy_plan_template",
|
||||||
"therapy_plan_details",
|
"therapy_plan_details",
|
||||||
"title",
|
"title",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
@ -46,6 +48,7 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Therapy Plan Details",
|
"label": "Therapy Plan Details",
|
||||||
"options": "Therapy Plan Detail",
|
"options": "Therapy Plan Detail",
|
||||||
|
"read_only_depends_on": "therapy_plan_template",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -105,11 +108,27 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company"
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "therapy_plan_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Therapy Plan Template",
|
||||||
|
"options": "Therapy Plan Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "invoiced",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Invoiced",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-25 14:38:53.649315",
|
"modified": "2020-10-23 01:27:42.128855",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Therapy Plan",
|
"name": "Therapy Plan",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import today
|
from frappe.utils import flt, today
|
||||||
|
|
||||||
class TherapyPlan(Document):
|
class TherapyPlan(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -33,13 +33,26 @@ class TherapyPlan(Document):
|
|||||||
self.db_set('total_sessions', total_sessions)
|
self.db_set('total_sessions', total_sessions)
|
||||||
self.db_set('total_sessions_completed', total_sessions_completed)
|
self.db_set('total_sessions_completed', total_sessions_completed)
|
||||||
|
|
||||||
|
def set_therapy_details_from_template(self):
|
||||||
|
# Add therapy types in the child table
|
||||||
|
self.set('therapy_plan_details', [])
|
||||||
|
therapy_plan_template = frappe.get_doc('Therapy Plan Template', self.therapy_plan_template)
|
||||||
|
|
||||||
|
for data in therapy_plan_template.therapy_types:
|
||||||
|
self.append('therapy_plan_details', {
|
||||||
|
'therapy_type': data.therapy_type,
|
||||||
|
'no_of_sessions': data.no_of_sessions
|
||||||
|
})
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_therapy_session(therapy_plan, patient, therapy_type):
|
def make_therapy_session(therapy_plan, patient, therapy_type, company):
|
||||||
therapy_type = frappe.get_doc('Therapy Type', therapy_type)
|
therapy_type = frappe.get_doc('Therapy Type', therapy_type)
|
||||||
|
|
||||||
therapy_session = frappe.new_doc('Therapy Session')
|
therapy_session = frappe.new_doc('Therapy Session')
|
||||||
therapy_session.therapy_plan = therapy_plan
|
therapy_session.therapy_plan = therapy_plan
|
||||||
|
therapy_session.company = company
|
||||||
therapy_session.patient = patient
|
therapy_session.patient = patient
|
||||||
therapy_session.therapy_type = therapy_type.name
|
therapy_session.therapy_type = therapy_type.name
|
||||||
therapy_session.duration = therapy_type.default_duration
|
therapy_session.duration = therapy_type.default_duration
|
||||||
@ -48,4 +61,39 @@ def make_therapy_session(therapy_plan, patient, therapy_type):
|
|||||||
|
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
therapy_session.start_date = today()
|
therapy_session.start_date = today()
|
||||||
return therapy_session.as_dict()
|
return therapy_session.as_dict()
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_sales_invoice(reference_name, patient, company, therapy_plan_template):
|
||||||
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
|
si = frappe.new_doc('Sales Invoice')
|
||||||
|
si.company = company
|
||||||
|
si.patient = patient
|
||||||
|
si.customer = frappe.db.get_value('Patient', patient, 'customer')
|
||||||
|
|
||||||
|
item = frappe.db.get_value('Therapy Plan Template', therapy_plan_template, 'linked_item')
|
||||||
|
price_list, price_list_currency = frappe.db.get_values('Price List', {'selling': 1}, ['name', 'currency'])[0]
|
||||||
|
args = {
|
||||||
|
'doctype': 'Sales Invoice',
|
||||||
|
'item_code': item,
|
||||||
|
'company': company,
|
||||||
|
'customer': si.customer,
|
||||||
|
'selling_price_list': price_list,
|
||||||
|
'price_list_currency': price_list_currency,
|
||||||
|
'plc_conversion_rate': 1.0,
|
||||||
|
'conversion_rate': 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
item_line = si.append('items', {})
|
||||||
|
item_details = get_item_details(args)
|
||||||
|
item_line.item_code = item
|
||||||
|
item_line.qty = 1
|
||||||
|
item_line.rate = item_details.price_list_rate
|
||||||
|
item_line.amount = flt(item_line.rate) * flt(item_line.qty)
|
||||||
|
item_line.reference_dt = 'Therapy Plan'
|
||||||
|
item_line.reference_dn = reference_name
|
||||||
|
item_line.description = item_details.description
|
||||||
|
|
||||||
|
si.set_missing_values(for_validate = True)
|
||||||
|
return si
|
||||||
|
@ -4,10 +4,18 @@ from frappe import _
|
|||||||
def get_data():
|
def get_data():
|
||||||
return {
|
return {
|
||||||
'fieldname': 'therapy_plan',
|
'fieldname': 'therapy_plan',
|
||||||
|
'non_standard_fieldnames': {
|
||||||
|
'Sales Invoice': 'reference_dn'
|
||||||
|
},
|
||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
'label': _('Therapy Sessions'),
|
'label': _('Therapy Sessions'),
|
||||||
'items': ['Therapy Session']
|
'items': ['Therapy Session']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Billing'),
|
||||||
|
'items': ['Sales Invoice']
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
'disable_create_buttons': ['Sales Invoice']
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-30 22:02:01.740109",
|
"modified": "2020-10-08 01:17:34.778028",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Therapy Plan Detail",
|
"name": "Therapy Plan Detail",
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestTherapyPlanTemplate(unittest.TestCase):
|
||||||
|
pass
|
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Therapy Plan Template', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
frm.set_query('therapy_type', 'therapy_types', () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'is_billable': 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
set_totals: function(frm) {
|
||||||
|
let total_sessions = 0;
|
||||||
|
let total_amount = 0.0;
|
||||||
|
frm.doc.therapy_types.forEach((d) => {
|
||||||
|
if (d.no_of_sessions) total_sessions += cint(d.no_of_sessions);
|
||||||
|
if (d.amount) total_amount += flt(d.amount);
|
||||||
|
});
|
||||||
|
frm.set_value('total_sessions', total_sessions);
|
||||||
|
frm.set_value('total_amount', total_amount);
|
||||||
|
frm.refresh_fields();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Therapy Plan Template Detail', {
|
||||||
|
therapy_type: function(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
frappe.call('frappe.client.get', {
|
||||||
|
doctype: 'Therapy Type',
|
||||||
|
name: row.therapy_type
|
||||||
|
}).then((res) => {
|
||||||
|
row.rate = res.message.rate;
|
||||||
|
if (!row.no_of_sessions)
|
||||||
|
row.no_of_sessions = 1;
|
||||||
|
row.amount = flt(row.rate) * cint(row.no_of_sessions);
|
||||||
|
frm.refresh_field('therapy_types');
|
||||||
|
frm.trigger('set_totals');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
no_of_sessions: function(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
row.amount = flt(row.rate) * cint(row.no_of_sessions);
|
||||||
|
frm.refresh_field('therapy_types');
|
||||||
|
frm.trigger('set_totals');
|
||||||
|
},
|
||||||
|
|
||||||
|
rate: function(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
row.amount = flt(row.rate) * cint(row.no_of_sessions);
|
||||||
|
frm.refresh_field('therapy_types');
|
||||||
|
frm.trigger('set_totals');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "field:plan_name",
|
||||||
|
"creation": "2020-09-22 17:51:38.861055",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"plan_name",
|
||||||
|
"linked_item_details_section",
|
||||||
|
"item_code",
|
||||||
|
"item_name",
|
||||||
|
"item_group",
|
||||||
|
"column_break_6",
|
||||||
|
"description",
|
||||||
|
"linked_item",
|
||||||
|
"therapy_types_section",
|
||||||
|
"therapy_types",
|
||||||
|
"section_break_11",
|
||||||
|
"total_sessions",
|
||||||
|
"column_break_13",
|
||||||
|
"total_amount"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "plan_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Plan Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "therapy_types_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Therapy Types"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "therapy_types",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Therapy Types",
|
||||||
|
"options": "Therapy Plan Template Detail",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "linked_item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Linked Item",
|
||||||
|
"options": "Item",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "linked_item_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Linked Item Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Item Code",
|
||||||
|
"reqd": 1,
|
||||||
|
"set_only_once": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Item Name",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item Group",
|
||||||
|
"options": "Item Group",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Item Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Amount",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_11",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_sessions",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Total Sessions",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_13",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-10-08 00:56:58.062105",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Therapy Plan Template",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import cint, flt
|
||||||
|
from erpnext.healthcare.doctype.therapy_type.therapy_type import make_item_price
|
||||||
|
|
||||||
|
class TherapyPlanTemplate(Document):
|
||||||
|
def after_insert(self):
|
||||||
|
self.create_item_from_template()
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
self.set_totals()
|
||||||
|
|
||||||
|
def on_update(self):
|
||||||
|
doc_before_save = self.get_doc_before_save()
|
||||||
|
if not doc_before_save: return
|
||||||
|
if doc_before_save.item_name != self.item_name or doc_before_save.item_group != self.item_group \
|
||||||
|
or doc_before_save.description != self.description:
|
||||||
|
self.update_item()
|
||||||
|
|
||||||
|
if doc_before_save.therapy_types != self.therapy_types:
|
||||||
|
self.update_item_price()
|
||||||
|
|
||||||
|
def set_totals(self):
|
||||||
|
total_sessions = 0
|
||||||
|
total_amount = 0
|
||||||
|
|
||||||
|
for entry in self.therapy_types:
|
||||||
|
total_sessions += cint(entry.no_of_sessions)
|
||||||
|
total_amount += flt(entry.amount)
|
||||||
|
|
||||||
|
self.total_sessions = total_sessions
|
||||||
|
self.total_amount = total_amount
|
||||||
|
|
||||||
|
def create_item_from_template(self):
|
||||||
|
uom = frappe.db.exists('UOM', 'Nos') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
|
||||||
|
|
||||||
|
item = frappe.get_doc({
|
||||||
|
'doctype': 'Item',
|
||||||
|
'item_code': self.item_code,
|
||||||
|
'item_name': self.item_name,
|
||||||
|
'item_group': self.item_group,
|
||||||
|
'description': self.description,
|
||||||
|
'is_sales_item': 1,
|
||||||
|
'is_service_item': 1,
|
||||||
|
'is_purchase_item': 0,
|
||||||
|
'is_stock_item': 0,
|
||||||
|
'show_in_website': 0,
|
||||||
|
'is_pro_applicable': 0,
|
||||||
|
'stock_uom': uom
|
||||||
|
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||||
|
|
||||||
|
make_item_price(item.name, self.total_amount)
|
||||||
|
self.db_set('linked_item', item.name)
|
||||||
|
|
||||||
|
def update_item(self):
|
||||||
|
item_doc = frappe.get_doc('Item', {'item_code': self.linked_item})
|
||||||
|
item_doc.item_name = self.item_name
|
||||||
|
item_doc.item_group = self.item_group
|
||||||
|
item_doc.description = self.description
|
||||||
|
item_doc.ignore_mandatory = True
|
||||||
|
item_doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
def update_item_price(self):
|
||||||
|
item_price = frappe.get_doc('Item Price', {'item_code': self.linked_item})
|
||||||
|
item_price.item_name = self.item_name
|
||||||
|
item_price.price_list_rate = self.total_amount
|
||||||
|
item_price.ignore_mandatory = True
|
||||||
|
item_price.save(ignore_permissions=True)
|
@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'therapy_plan_template',
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Therapy Plans'),
|
||||||
|
'items': ['Therapy Plan']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-10-07 23:04:44.373381",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"therapy_type",
|
||||||
|
"no_of_sessions",
|
||||||
|
"rate",
|
||||||
|
"amount"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "therapy_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Therapy Type",
|
||||||
|
"options": "Therapy Type",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "no_of_sessions",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "No of Sessions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Rate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Amount",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-10-07 23:46:54.296322",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Therapy Plan Template Detail",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class TherapyPlanTemplateDetail(Document):
|
||||||
|
pass
|
@ -92,7 +92,8 @@ frappe.ui.form.on('Therapy Session', {
|
|||||||
'start_date': data.message.appointment_date,
|
'start_date': data.message.appointment_date,
|
||||||
'start_time': data.message.appointment_time,
|
'start_time': data.message.appointment_time,
|
||||||
'service_unit': data.message.service_unit,
|
'service_unit': data.message.service_unit,
|
||||||
'company': data.message.company
|
'company': data.message.company,
|
||||||
|
'duration': data.message.duration
|
||||||
};
|
};
|
||||||
frm.set_value(values);
|
frm.set_value(values);
|
||||||
}
|
}
|
||||||
@ -107,6 +108,7 @@ frappe.ui.form.on('Therapy Session', {
|
|||||||
'start_date': '',
|
'start_date': '',
|
||||||
'start_time': '',
|
'start_time': '',
|
||||||
'service_unit': '',
|
'service_unit': '',
|
||||||
|
'duration': ''
|
||||||
};
|
};
|
||||||
frm.set_value(values);
|
frm.set_value(values);
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,8 @@
|
|||||||
"fieldname": "appointment",
|
"fieldname": "appointment",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Appointment",
|
"label": "Appointment",
|
||||||
"options": "Patient Appointment"
|
"options": "Patient Appointment",
|
||||||
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "patient",
|
"fieldname": "patient",
|
||||||
@ -90,7 +91,8 @@
|
|||||||
"fetch_from": "therapy_template.default_duration",
|
"fetch_from": "therapy_template.default_duration",
|
||||||
"fieldname": "duration",
|
"fieldname": "duration",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Duration"
|
"label": "Duration",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "location",
|
"fieldname": "location",
|
||||||
@ -220,7 +222,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-30 10:56:10.354268",
|
"modified": "2020-10-22 23:10:21.178644",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Therapy Session",
|
"name": "Therapy Session",
|
||||||
|
@ -4,16 +4,41 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
import datetime
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import get_time, flt
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cstr, getdate
|
from frappe.utils import cstr, getdate, get_link_to_form
|
||||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
|
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
|
||||||
|
|
||||||
class TherapySession(Document):
|
class TherapySession(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_duplicate()
|
||||||
self.set_total_counts()
|
self.set_total_counts()
|
||||||
|
|
||||||
|
def validate_duplicate(self):
|
||||||
|
end_time = datetime.datetime.combine(getdate(self.start_date), get_time(self.start_time)) \
|
||||||
|
+ datetime.timedelta(minutes=flt(self.duration))
|
||||||
|
|
||||||
|
overlaps = frappe.db.sql("""
|
||||||
|
select
|
||||||
|
name
|
||||||
|
from
|
||||||
|
`tabTherapy Session`
|
||||||
|
where
|
||||||
|
start_date=%s and name!=%s and docstatus!=2
|
||||||
|
and (practitioner=%s or patient=%s) and
|
||||||
|
((start_time<%s and start_time + INTERVAL duration MINUTE>%s) or
|
||||||
|
(start_time>%s and start_time<%s) or
|
||||||
|
(start_time=%s))
|
||||||
|
""", (self.start_date, self.name, self.practitioner, self.patient,
|
||||||
|
self.start_time, end_time.time(), self.start_time, end_time.time(), self.start_time))
|
||||||
|
|
||||||
|
if overlaps:
|
||||||
|
overlapping_details = _('Therapy Session overlaps with {0}').format(get_link_to_form('Therapy Session', overlaps[0][0]))
|
||||||
|
frappe.throw(overlapping_details, title=_('Therapy Sessions Overlapping'))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_sessions_count_in_therapy_plan()
|
self.update_sessions_count_in_therapy_plan()
|
||||||
insert_session_medical_record(self)
|
insert_session_medical_record(self)
|
||||||
|
@ -41,7 +41,7 @@ class TherapyType(Document):
|
|||||||
if self.rate:
|
if self.rate:
|
||||||
item_price = frappe.get_doc('Item Price', {'item_code': self.item})
|
item_price = frappe.get_doc('Item Price', {'item_code': self.item})
|
||||||
item_price.item_name = self.item_name
|
item_price.item_name = self.item_name
|
||||||
item_price.price_list_name = self.rate
|
item_price.price_list_rate = self.rate
|
||||||
item_price.ignore_mandatory = True
|
item_price.ignore_mandatory = True
|
||||||
item_price.save()
|
item_price.save()
|
||||||
|
|
||||||
|
@ -23,9 +23,9 @@ def get_healthcare_services_to_invoice(patient, company):
|
|||||||
items_to_invoice += get_lab_tests_to_invoice(patient, company)
|
items_to_invoice += get_lab_tests_to_invoice(patient, company)
|
||||||
items_to_invoice += get_clinical_procedures_to_invoice(patient, company)
|
items_to_invoice += get_clinical_procedures_to_invoice(patient, company)
|
||||||
items_to_invoice += get_inpatient_services_to_invoice(patient, company)
|
items_to_invoice += get_inpatient_services_to_invoice(patient, company)
|
||||||
|
items_to_invoice += get_therapy_plans_to_invoice(patient, company)
|
||||||
items_to_invoice += get_therapy_sessions_to_invoice(patient, company)
|
items_to_invoice += get_therapy_sessions_to_invoice(patient, company)
|
||||||
|
|
||||||
|
|
||||||
return items_to_invoice
|
return items_to_invoice
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +35,7 @@ def validate_customer_created(patient):
|
|||||||
msg += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name)
|
msg += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name)
|
||||||
frappe.throw(msg, title=_('Customer Not Found'))
|
frappe.throw(msg, title=_('Customer Not Found'))
|
||||||
|
|
||||||
|
|
||||||
def get_appointments_to_invoice(patient, company):
|
def get_appointments_to_invoice(patient, company):
|
||||||
appointments_to_invoice = []
|
appointments_to_invoice = []
|
||||||
patient_appointments = frappe.get_list(
|
patient_appointments = frappe.get_list(
|
||||||
@ -246,12 +247,44 @@ def get_inpatient_services_to_invoice(patient, company):
|
|||||||
return services_to_invoice
|
return services_to_invoice
|
||||||
|
|
||||||
|
|
||||||
|
def get_therapy_plans_to_invoice(patient, company):
|
||||||
|
therapy_plans_to_invoice = []
|
||||||
|
therapy_plans = frappe.get_list(
|
||||||
|
'Therapy Plan',
|
||||||
|
fields=['therapy_plan_template', 'name'],
|
||||||
|
filters={
|
||||||
|
'patient': patient.name,
|
||||||
|
'invoiced': 0,
|
||||||
|
'company': company,
|
||||||
|
'therapy_plan_template': ('!=', '')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for plan in therapy_plans:
|
||||||
|
therapy_plans_to_invoice.append({
|
||||||
|
'reference_type': 'Therapy Plan',
|
||||||
|
'reference_name': plan.name,
|
||||||
|
'service': frappe.db.get_value('Therapy Plan Template', plan.therapy_plan_template, 'linked_item')
|
||||||
|
})
|
||||||
|
|
||||||
|
return therapy_plans_to_invoice
|
||||||
|
|
||||||
|
|
||||||
def get_therapy_sessions_to_invoice(patient, company):
|
def get_therapy_sessions_to_invoice(patient, company):
|
||||||
therapy_sessions_to_invoice = []
|
therapy_sessions_to_invoice = []
|
||||||
|
therapy_plans = frappe.db.get_all('Therapy Plan', {'therapy_plan_template': ('!=', '')})
|
||||||
|
therapy_plans_created_from_template = []
|
||||||
|
for entry in therapy_plans:
|
||||||
|
therapy_plans_created_from_template.append(entry.name)
|
||||||
|
|
||||||
therapy_sessions = frappe.get_list(
|
therapy_sessions = frappe.get_list(
|
||||||
'Therapy Session',
|
'Therapy Session',
|
||||||
fields='*',
|
fields='*',
|
||||||
filters={'patient': patient.name, 'invoiced': 0, 'company': company}
|
filters={
|
||||||
|
'patient': patient.name,
|
||||||
|
'invoiced': 0,
|
||||||
|
'company': company,
|
||||||
|
'therapy_plan': ('not in', therapy_plans_created_from_template)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
for therapy in therapy_sessions:
|
for therapy in therapy_sessions:
|
||||||
if not therapy.appointment:
|
if not therapy.appointment:
|
||||||
@ -368,8 +401,8 @@ def validate_invoiced_on_submit(item):
|
|||||||
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')
|
||||||
if is_invoiced:
|
if is_invoiced:
|
||||||
frappe.throw(_('The item referenced by {0} - {1} is already invoiced'\
|
frappe.throw(_('The item referenced by {0} - {1} is already invoiced').format(
|
||||||
).format(item.reference_dt, item.reference_dn))
|
item.reference_dt, item.reference_dn))
|
||||||
|
|
||||||
|
|
||||||
def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field):
|
def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field):
|
||||||
|
@ -282,7 +282,8 @@ doc_events = {
|
|||||||
# to maintain data integrity we exempted payment entry. it will un-link when sales invoice get cancelled.
|
# to maintain data integrity we exempted payment entry. it will un-link when sales invoice get cancelled.
|
||||||
# if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
|
# if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
|
||||||
auto_cancel_exempted_doctypes= [
|
auto_cancel_exempted_doctypes= [
|
||||||
"Payment Entry"
|
"Payment Entry",
|
||||||
|
"Inpatient Medication Entry"
|
||||||
]
|
]
|
||||||
|
|
||||||
scheduler_events = {
|
scheduler_events = {
|
||||||
|
@ -56,23 +56,35 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.docstatus === 1) {
|
if (frm.doc.docstatus === 1) {
|
||||||
frm.trigger("show_progress");
|
frm.trigger("show_progress");
|
||||||
|
|
||||||
|
if (frm.doc.status !== "Completed") {
|
||||||
|
if (frm.doc.po_items && frm.doc.status !== "Closed") {
|
||||||
|
frm.add_custom_button(__("Work Order"), ()=> {
|
||||||
|
frm.trigger("make_work_order");
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
|
||||||
|
frm.add_custom_button(__("Material Request"), ()=> {
|
||||||
|
frm.trigger("make_material_request");
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.status === "Closed") {
|
||||||
|
frm.add_custom_button(__("Re-open"), function() {
|
||||||
|
frm.events.close_open_production_plan(frm, false);
|
||||||
|
}, __("Status"));
|
||||||
|
} else {
|
||||||
|
frm.add_custom_button(__("Close"), function() {
|
||||||
|
frm.events.close_open_production_plan(frm, true);
|
||||||
|
}, __("Status"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus === 1 && frm.doc.po_items
|
if (frm.doc.status !== "Closed") {
|
||||||
&& frm.doc.status != 'Completed') {
|
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
frm.add_custom_button(__("Work Order"), ()=> {
|
|
||||||
frm.trigger("make_work_order");
|
|
||||||
}, __('Create'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus === 1 && frm.doc.mr_items
|
|
||||||
&& !in_list(['Material Requested', 'Completed'], frm.doc.status)) {
|
|
||||||
frm.add_custom_button(__("Material Request"), ()=> {
|
|
||||||
frm.trigger("make_material_request");
|
|
||||||
}, __('Create'));
|
|
||||||
}
|
|
||||||
|
|
||||||
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
|
||||||
frm.trigger("material_requirement");
|
frm.trigger("material_requirement");
|
||||||
|
|
||||||
const projected_qty_formula = ` <table class="table table-bordered" style="background-color: #f9f9f9;">
|
const projected_qty_formula = ` <table class="table table-bordered" style="background-color: #f9f9f9;">
|
||||||
@ -121,6 +133,18 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
set_field_options("projected_qty_formula", projected_qty_formula);
|
set_field_options("projected_qty_formula", projected_qty_formula);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
close_open_production_plan: (frm, close=false) => {
|
||||||
|
frappe.call({
|
||||||
|
method: "set_status",
|
||||||
|
freeze: true,
|
||||||
|
doc: frm.doc,
|
||||||
|
args: {close : close},
|
||||||
|
callback: function() {
|
||||||
|
frm.reload_doc();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
make_work_order: function(frm) {
|
make_work_order: function(frm) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "make_work_order",
|
method: "make_work_order",
|
||||||
|
@ -275,7 +275,7 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled\nMaterial Requested",
|
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nClosed\nCancelled\nMaterial Requested",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -304,9 +304,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-calendar",
|
"icon": "fa fa-calendar",
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-03 00:25:25.934202",
|
"modified": "2020-10-26 13:00:54.335319",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
@ -219,13 +219,17 @@ class ProductionPlan(Document):
|
|||||||
filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
|
filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
|
||||||
frappe.delete_doc('Work Order', d.name)
|
frappe.delete_doc('Work Order', d.name)
|
||||||
|
|
||||||
def set_status(self):
|
def set_status(self, close=None):
|
||||||
self.status = {
|
self.status = {
|
||||||
0: 'Draft',
|
0: 'Draft',
|
||||||
1: 'Submitted',
|
1: 'Submitted',
|
||||||
2: 'Cancelled'
|
2: 'Cancelled'
|
||||||
}.get(self.docstatus)
|
}.get(self.docstatus)
|
||||||
|
|
||||||
|
if close:
|
||||||
|
self.db_set('status', 'Closed')
|
||||||
|
return
|
||||||
|
|
||||||
if self.total_produced_qty > 0:
|
if self.total_produced_qty > 0:
|
||||||
self.status = "In Process"
|
self.status = "In Process"
|
||||||
if self.total_produced_qty == self.total_planned_qty:
|
if self.total_produced_qty == self.total_planned_qty:
|
||||||
@ -235,6 +239,9 @@ class ProductionPlan(Document):
|
|||||||
self.update_ordered_status()
|
self.update_ordered_status()
|
||||||
self.update_requested_status()
|
self.update_requested_status()
|
||||||
|
|
||||||
|
if close is not None:
|
||||||
|
self.db_set('status', self.status)
|
||||||
|
|
||||||
def update_ordered_status(self):
|
def update_ordered_status(self):
|
||||||
update_status = False
|
update_status = False
|
||||||
for d in self.po_items:
|
for d in self.po_items:
|
||||||
@ -735,10 +742,12 @@ def get_items_for_material_requests(doc, warehouses=None):
|
|||||||
mr_items = new_mr_items
|
mr_items = new_mr_items
|
||||||
|
|
||||||
if not mr_items:
|
if not mr_items:
|
||||||
frappe.msgprint(_("""As raw materials projected quantity is more than required quantity,
|
to_enable = frappe.bold(_("Ignore Existing Projected Quantity"))
|
||||||
there is no need to create material request for the warehouse {0}.
|
warehouse = frappe.bold(doc.get('for_warehouse'))
|
||||||
Still if you want to make material request,
|
message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "<br><br>"
|
||||||
kindly enable <b>Ignore Existing Projected Quantity</b> checkbox""").format(doc.get('for_warehouse')))
|
message += _(" If you still want to proceed, please enable {0}.").format(to_enable)
|
||||||
|
|
||||||
|
frappe.msgprint(message, title=_("Note"))
|
||||||
|
|
||||||
return mr_items
|
return mr_items
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
frappe.listview_settings['Production Plan'] = {
|
frappe.listview_settings['Production Plan'] = {
|
||||||
add_fields: ["status"],
|
add_fields: ["status"],
|
||||||
filters: [["status", "!=", "Stopped"]],
|
filters: [["status", "!=", "Closed"]],
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
if(doc.status==="Submitted") {
|
if(doc.status==="Submitted") {
|
||||||
return [__("Not Started"), "orange", "status,=,Submitted"];
|
return [__("Not Started"), "orange", "status,=,Submitted"];
|
||||||
@ -10,7 +10,8 @@ frappe.listview_settings['Production Plan'] = {
|
|||||||
"In Process": "orange",
|
"In Process": "orange",
|
||||||
"Completed": "green",
|
"Completed": "green",
|
||||||
"Material Requested": "darkgrey",
|
"Material Requested": "darkgrey",
|
||||||
"Cancelled": "darkgrey"
|
"Cancelled": "darkgrey",
|
||||||
|
"Closed": "grey"
|
||||||
}[doc.status], "status,=," + doc.status];
|
}[doc.status], "status,=," + doc.status];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,30 +11,20 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Warehouse",
|
"label": "Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-02 10:37:16.650836",
|
"modified": "2020-10-26 12:55:00.778201",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan Material Request Warehouse",
|
"name": "Production Plan Material Request Warehouse",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [],
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
@ -731,3 +731,5 @@ erpnext.patches.v13_0.change_default_pos_print_format
|
|||||||
erpnext.patches.v13_0.set_youtube_video_id
|
erpnext.patches.v13_0.set_youtube_video_id
|
||||||
erpnext.patches.v13_0.print_uom_after_quantity_patch
|
erpnext.patches.v13_0.print_uom_after_quantity_patch
|
||||||
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
|
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
|
||||||
|
erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
|
||||||
|
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
|
from erpnext.domains.healthcare import data
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if 'Healthcare' not in frappe.get_active_domains():
|
||||||
|
return
|
||||||
|
|
||||||
|
if data['custom_fields']:
|
||||||
|
create_custom_fields(data['custom_fields'])
|
@ -53,7 +53,7 @@ def execute():
|
|||||||
# renamed reports from "Minutes to First Response for Issues" to "First Response Time for Issues". Same for Opportunity
|
# renamed reports from "Minutes to First Response for Issues" to "First Response Time for Issues". Same for Opportunity
|
||||||
for report in ['Minutes to First Response for Issues', 'Minutes to First Response for Opportunity']:
|
for report in ['Minutes to First Response for Issues', 'Minutes to First Response for Opportunity']:
|
||||||
if frappe.db.exists('Report', report):
|
if frappe.db.exists('Report', report):
|
||||||
frappe.delete_doc('Report', report)
|
frappe.delete_doc('Report', report, ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
def convert_to_seconds(value, unit):
|
def convert_to_seconds(value, unit):
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# MIT License. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("hr", "doctype", "employee")
|
||||||
|
|
||||||
|
if frappe.db.has_column("Employee", "reason_for_resignation"):
|
||||||
|
frappe.db.sql(""" UPDATE `tabEmployee`
|
||||||
|
SET reason_for_leaving = reason_for_resignation
|
||||||
|
WHERE status = 'Left' and reason_for_leaving is null and reason_for_resignation is not null
|
||||||
|
""")
|
||||||
|
|
@ -210,6 +210,7 @@
|
|||||||
[data-route="point-of-sale"] .item-summary-wrapper:last-child { border-bottom: none; }
|
[data-route="point-of-sale"] .item-summary-wrapper:last-child { border-bottom: none; }
|
||||||
[data-route="point-of-sale"] .total-summary-wrapper:last-child { border-bottom: none; }
|
[data-route="point-of-sale"] .total-summary-wrapper:last-child { border-bottom: none; }
|
||||||
[data-route="point-of-sale"] .invoices-container .invoice-wrapper:last-child { border-bottom: none; }
|
[data-route="point-of-sale"] .invoices-container .invoice-wrapper:last-child { border-bottom: none; }
|
||||||
|
[data-route="point-of-sale"] .new-btn { background-color: #5e64ff; color: white; border: none;}
|
||||||
[data-route="point-of-sale"] .summary-btns:last-child { margin-right: 0px; }
|
[data-route="point-of-sale"] .summary-btns:last-child { margin-right: 0px; }
|
||||||
[data-route="point-of-sale"] ::-webkit-scrollbar { width: 1px }
|
[data-route="point-of-sale"] ::-webkit-scrollbar { width: 1px }
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
|||||||
|
|
||||||
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;
|
||||||
|
let item_rate = item.rate;
|
||||||
if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
|
if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
|
||||||
effective_item_rate = item.blanket_order_rate;
|
effective_item_rate = item.blanket_order_rate;
|
||||||
}
|
}
|
||||||
@ -17,15 +18,17 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
|||||||
}
|
}
|
||||||
item.base_rate_with_margin = flt(item.rate_with_margin) * flt(this.frm.doc.conversion_rate);
|
item.base_rate_with_margin = flt(item.rate_with_margin) * flt(this.frm.doc.conversion_rate);
|
||||||
|
|
||||||
item.rate = flt(item.rate_with_margin , precision("rate", item));
|
item_rate = flt(item.rate_with_margin , precision("rate", item));
|
||||||
|
|
||||||
if(item.discount_percentage){
|
if(item.discount_percentage){
|
||||||
item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
|
item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.discount_amount) {
|
if (item.discount_amount) {
|
||||||
item.rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item));
|
item_rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frappe.model.set_value(item.doctype, item.name, "rate", item_rate);
|
||||||
},
|
},
|
||||||
|
|
||||||
calculate_taxes_and_totals: function(update_paid_amount) {
|
calculate_taxes_and_totals: function(update_paid_amount) {
|
||||||
@ -88,11 +91,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
|||||||
if(this.frm.doc.currency == company_currency) {
|
if(this.frm.doc.currency == company_currency) {
|
||||||
this.frm.set_value("conversion_rate", 1);
|
this.frm.set_value("conversion_rate", 1);
|
||||||
} else {
|
} else {
|
||||||
const err_message = __('{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}', [
|
const subs = [conversion_rate_label, this.frm.doc.currency, company_currency];
|
||||||
conversion_rate_label,
|
const err_message = __('{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}', subs);
|
||||||
this.frm.doc.currency,
|
|
||||||
company_currency
|
|
||||||
]);
|
|
||||||
frappe.throw(err_message);
|
frappe.throw(err_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,9 +352,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
|
|
||||||
let show_description = function(idx, exist = null) {
|
let show_description = function(idx, exist = null) {
|
||||||
if (exist) {
|
if (exist) {
|
||||||
scan_barcode_field.set_new_description(__('Row #{0}: Qty increased by 1', [idx]));
|
frappe.show_alert({
|
||||||
|
message: __('Row #{0}: Qty increased by 1', [idx]),
|
||||||
|
indicator: 'green'
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
scan_barcode_field.set_new_description(__('Row #{0}: Item added', [idx]));
|
frappe.show_alert({
|
||||||
|
message: __('Row #{0}: Item added', [idx]),
|
||||||
|
indicator: 'green'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,7 +371,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
}).then(r => {
|
}).then(r => {
|
||||||
const data = r && r.message;
|
const data = r && r.message;
|
||||||
if (!data || Object.keys(data).length === 0) {
|
if (!data || Object.keys(data).length === 0) {
|
||||||
scan_barcode_field.set_new_description(__('Cannot find Item with this barcode'));
|
frappe.show_alert({
|
||||||
|
message: __('Cannot find Item with this Barcode'),
|
||||||
|
indicator: 'red'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,7 +660,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
let child = frappe.model.add_child(me.frm.doc, "taxes");
|
let child = frappe.model.add_child(me.frm.doc, "taxes");
|
||||||
child.charge_type = "On Net Total";
|
child.charge_type = "On Net Total";
|
||||||
child.account_head = tax;
|
child.account_head = tax;
|
||||||
child.rate = rate;
|
child.rate = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1049,14 +1058,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
if(item.item_code && item.uom) {
|
if(item.item_code && item.uom) {
|
||||||
return this.frm.call({
|
return this.frm.call({
|
||||||
method: "erpnext.stock.get_item_details.get_conversion_factor",
|
method: "erpnext.stock.get_item_details.get_conversion_factor",
|
||||||
child: item,
|
|
||||||
args: {
|
args: {
|
||||||
item_code: item.item_code,
|
item_code: item.item_code,
|
||||||
uom: item.uom
|
uom: item.uom
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) {
|
if(!r.exc) {
|
||||||
me.conversion_factor(me.frm.doc, cdt, cdn);
|
frappe.model.set_value(cdt, cdn, 'conversion_factor', r.message.conversion_factor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -75,7 +75,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
fieldtype:'Float',
|
fieldtype:'Float',
|
||||||
read_only: me.has_batch && !me.has_serial_no,
|
read_only: me.has_batch && !me.has_serial_no,
|
||||||
label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'),
|
label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'),
|
||||||
default: 0
|
default: flt(me.item.stock_qty),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'auto_fetch_button',
|
fieldname: 'auto_fetch_button',
|
||||||
@ -91,7 +91,8 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
qty: qty,
|
qty: qty,
|
||||||
item_code: me.item_code,
|
item_code: me.item_code,
|
||||||
warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
|
warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
|
||||||
batch_no: me.item.batch_no || null
|
batch_no: me.item.batch_no || null,
|
||||||
|
posting_date: me.frm.doc.posting_date || me.frm.doc.transaction_date
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,11 +101,12 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
let records_length = auto_fetched_serial_numbers.length;
|
let records_length = auto_fetched_serial_numbers.length;
|
||||||
if (!records_length) {
|
if (!records_length) {
|
||||||
const warehouse = me.dialog.fields_dict.warehouse.get_value().bold();
|
const warehouse = me.dialog.fields_dict.warehouse.get_value().bold();
|
||||||
frappe.msgprint(__(`Serial numbers unavailable for Item ${me.item.item_code.bold()}
|
frappe.msgprint(
|
||||||
under warehouse ${warehouse}. Please try changing warehouse.`));
|
__('Serial numbers unavailable for Item {0} under warehouse {1}. Please try changing warehouse.', [me.item.item_code.bold(), warehouse])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (records_length < qty) {
|
if (records_length < qty) {
|
||||||
frappe.msgprint(__(`Fetched only ${records_length} available serial numbers.`));
|
frappe.msgprint(__('Fetched only {0} available serial numbers.', [records_length]));
|
||||||
}
|
}
|
||||||
let serial_no_list_field = this.dialog.fields_dict.serial_no;
|
let serial_no_list_field = this.dialog.fields_dict.serial_no;
|
||||||
numbers = auto_fetched_serial_numbers.join('\n');
|
numbers = auto_fetched_serial_numbers.join('\n');
|
||||||
@ -189,15 +191,12 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
}
|
}
|
||||||
if(this.has_batch && !this.has_serial_no) {
|
if(this.has_batch && !this.has_serial_no) {
|
||||||
if(values.batches.length === 0 || !values.batches) {
|
if(values.batches.length === 0 || !values.batches) {
|
||||||
frappe.throw(__("Please select batches for batched item "
|
frappe.throw(__("Please select batches for batched item {0}", [values.item_code]));
|
||||||
+ values.item_code));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
values.batches.map((batch, i) => {
|
values.batches.map((batch, i) => {
|
||||||
if(!batch.selected_qty || batch.selected_qty === 0 ) {
|
if(!batch.selected_qty || batch.selected_qty === 0 ) {
|
||||||
if (!this.show_dialog) {
|
if (!this.show_dialog) {
|
||||||
frappe.throw(__("Please select quantity on row " + (i+1)));
|
frappe.throw(__("Please select quantity on row {0}", [i+1]));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -206,9 +205,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
} else {
|
} else {
|
||||||
let serial_nos = values.serial_no || '';
|
let serial_nos = values.serial_no || '';
|
||||||
if (!serial_nos || !serial_nos.replace(/\s/g, '').length) {
|
if (!serial_nos || !serial_nos.replace(/\s/g, '').length) {
|
||||||
frappe.throw(__("Please enter serial numbers for serialized item "
|
frappe.throw(__("Please enter serial numbers for serialized item {0}", [values.item_code]));
|
||||||
+ values.item_code));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -355,8 +352,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
});
|
});
|
||||||
if (selected_batches.includes(val)) {
|
if (selected_batches.includes(val)) {
|
||||||
this.set_value("");
|
this.set_value("");
|
||||||
frappe.throw(__(`Batch ${val} already selected.`));
|
frappe.throw(__('Batch {0} already selected.', [val]));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (me.warehouse_details.name) {
|
if (me.warehouse_details.name) {
|
||||||
@ -375,8 +371,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.set_value("");
|
this.set_value("");
|
||||||
frappe.throw(__(`Please select a warehouse to get available
|
frappe.throw(__('Please select a warehouse to get available quantities'));
|
||||||
quantities`));
|
|
||||||
}
|
}
|
||||||
// e.stopImmediatePropagation();
|
// e.stopImmediatePropagation();
|
||||||
}
|
}
|
||||||
@ -411,8 +406,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
parseFloat(available_qty) < parseFloat(selected_qty)) {
|
parseFloat(available_qty) < parseFloat(selected_qty)) {
|
||||||
|
|
||||||
this.set_value('0');
|
this.set_value('0');
|
||||||
frappe.throw(__(`For transfer from source, selected quantity cannot be
|
frappe.throw(__('For transfer from source, selected quantity cannot be greater than available quantity'));
|
||||||
greater than available quantity`));
|
|
||||||
} else {
|
} else {
|
||||||
this.grid.refresh();
|
this.grid.refresh();
|
||||||
}
|
}
|
||||||
@ -451,20 +445,12 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.stock.doctype.serial_no.serial_no.get_pos_reserved_serial_nos",
|
method: "erpnext.stock.doctype.serial_no.serial_no.get_pos_reserved_serial_nos",
|
||||||
args: {
|
args: {
|
||||||
item_code: me.item_code,
|
filters: {
|
||||||
warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : ''
|
item_code: me.item_code,
|
||||||
|
warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).then((data) => {
|
}).then((data) => {
|
||||||
if (!data.message[1].length) {
|
|
||||||
this.showing_reserved_serial_nos_error = true;
|
|
||||||
const warehouse = me.dialog.fields_dict.warehouse.get_value().bold();
|
|
||||||
const d = frappe.msgprint(__(`Serial numbers unavailable for Item ${me.item.item_code.bold()}
|
|
||||||
under warehouse ${warehouse}. Please try changing warehouse.`));
|
|
||||||
d.get_close_btn().on('click', () => {
|
|
||||||
this.showing_reserved_serial_nos_error = false;
|
|
||||||
d.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
serial_no_filters['name'] = ["not in", data.message[0]]
|
serial_no_filters['name'] = ["not in", data.message[0]]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Review and Action",
|
"label": "Review and Action",
|
||||||
"links": "[\n {\n \"description\": \"Quality Review\",\n \"label\": \"Quality Review\",\n \"name\": \"Quality Review\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Action\",\n \"label\": \"Quality Action\",\n \"name\": \"Quality Action\",\n \"type\": \"doctype\"\n }\n]"
|
"links": "[\n {\n \"description\": \"Non Conformance\",\n \"label\": \"Non Conformance\",\n \"name\": \"Non Conformance\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Review\",\n \"label\": \"Quality Review\",\n \"name\": \"Quality Review\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Action\",\n \"label\": \"Quality Action\",\n \"name\": \"Quality Action\",\n \"type\": \"doctype\"\n }\n]"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"category": "Modules",
|
"category": "Modules",
|
||||||
@ -29,11 +29,11 @@
|
|||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Desk Page",
|
"doctype": "Desk Page",
|
||||||
"extends_another_page": 0,
|
"extends_another_page": 0,
|
||||||
"icon": "",
|
"hide_custom": 0,
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Quality",
|
"label": "Quality",
|
||||||
"modified": "2020-04-01 11:28:51.095012",
|
"modified": "2020-10-27 16:28:54.138055",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Quality Management",
|
"module": "Quality Management",
|
||||||
"name": "Quality",
|
"name": "Quality",
|
||||||
@ -47,6 +47,7 @@
|
|||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"doc_view": "Tree",
|
||||||
"label": "Quality Procedure",
|
"label": "Quality Procedure",
|
||||||
"link_to": "Quality Procedure",
|
"link_to": "Quality Procedure",
|
||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
@ -55,6 +56,33 @@
|
|||||||
"label": "Quality Inspection",
|
"label": "Quality Inspection",
|
||||||
"link_to": "Quality Inspection",
|
"link_to": "Quality Inspection",
|
||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#ff8989",
|
||||||
|
"doc_view": "",
|
||||||
|
"format": "{} Open",
|
||||||
|
"label": "Quality Review",
|
||||||
|
"link_to": "Quality Review",
|
||||||
|
"stats_filter": "{\"status\": \"Open\"}",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#ff8989",
|
||||||
|
"doc_view": "",
|
||||||
|
"format": "{} Open",
|
||||||
|
"label": "Quality Action",
|
||||||
|
"link_to": "Quality Action",
|
||||||
|
"stats_filter": "{\"status\": \"Open\"}",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#ff8989",
|
||||||
|
"doc_view": "",
|
||||||
|
"format": "{} Open",
|
||||||
|
"label": "Non Conformance",
|
||||||
|
"link_to": "Non Conformance",
|
||||||
|
"stats_filter": "{\"status\": \"Open\"}",
|
||||||
|
"type": "DocType"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user