Merge branch 'develop' into fixed-extra-trasnferred_qty_issue-develop

This commit is contained in:
Marica
2021-01-28 15:03:15 +05:30
committed by GitHub
260 changed files with 7759 additions and 2209 deletions

View File

@@ -132,16 +132,10 @@ def allow_regional(fn):
return caller return caller
def get_last_membership(): def get_last_membership(member):
'''Returns last membership if exists''' '''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type', last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1) dict(member=member, paid=1), order_by='to_date desc', limit=1)
return last_membership and last_membership[0] if last_membership:
return last_membership[0]
def is_member():
'''Returns true if the user is still a member'''
last_membership = get_last_membership()
if last_membership and getdate(last_membership.to_date) > getdate():
return True
return False

View File

@@ -254,7 +254,8 @@ def create_account(**kwargs):
account_name = kwargs.get('account_name'), account_name = kwargs.get('account_name'),
account_type = kwargs.get('account_type'), account_type = kwargs.get('account_type'),
parent_account = kwargs.get('parent_account'), parent_account = kwargs.get('parent_account'),
company = kwargs.get('company') company = kwargs.get('company'),
account_currency = kwargs.get('account_currency')
)) ))
account.save() account.save()

View File

@@ -2,7 +2,6 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension', { frappe.ui.form.on('Accounting Dimension', {
refresh: function(frm) { refresh: function(frm) {
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;

View File

@@ -203,7 +203,7 @@ def get_dimension_with_children(doctype, dimension):
return all_dimensions return all_dimensions
@frappe.whitelist() @frappe.whitelist()
def get_dimension_filters(): def get_dimensions(with_cost_center_and_project=False):
dimension_filters = frappe.db.sql(""" dimension_filters = frappe.db.sql("""
SELECT label, fieldname, document_type SELECT label, fieldname, document_type
FROM `tabAccounting Dimension` FROM `tabAccounting Dimension`
@@ -214,6 +214,18 @@ def get_dimension_filters():
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
WHERE c.parent = p.name""", as_dict=1) WHERE c.parent = p.name""", as_dict=1)
if with_cost_center_and_project:
dimension_filters.extend([
{
'fieldname': 'cost_center',
'document_type': 'Cost Center'
},
{
'fieldname': 'project',
'document_type': 'Project'
}
])
default_dimensions_map = {} default_dimensions_map = {}
for dimension in default_dimensions: for dimension in default_dimensions:
default_dimensions_map.setdefault(dimension.company, {}) default_dimensions_map.setdefault(dimension.company, {})

View File

@@ -11,37 +11,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import d
class TestAccountingDimension(unittest.TestCase): class TestAccountingDimension(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.set_user("Administrator") create_dimension()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
dimension = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Department",
}).insert()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 0
dimension1.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Location",
})
dimension1.append("dimension_defaults", {
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1
})
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
def test_dimension_against_sales_invoice(self): def test_dimension_against_sales_invoice(self):
si = create_sales_invoice(do_not_save=1) si = create_sales_invoice(do_not_save=1)
@@ -101,6 +71,38 @@ class TestAccountingDimension(unittest.TestCase):
def tearDown(self): def tearDown(self):
disable_dimension() disable_dimension()
def create_dimension():
frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Department",
}).insert()
else:
dimension = frappe.get_doc("Accounting Dimension", "Department")
dimension.disabled = 0
dimension.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Location",
})
dimension1.append("dimension_defaults", {
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1
})
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
def disable_dimension(): def disable_dimension():
dimension1 = frappe.get_doc("Accounting Dimension", "Department") dimension1 = frappe.get_doc("Accounting Dimension", "Department")

View File

@@ -0,0 +1,82 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension Filter', {
refresh: function(frm, cdt, cdn) {
if (frm.doc.accounting_dimension) {
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
}
let help_content =
`<table class="table table-bordered" style="background-color: #f9f9f9;">
<tr><td>
<p>
<i class="fa fa-hand-right"></i>
{{__('Note: On checking Is Mandatory the accounting dimension will become mandatory against that specific account for all accounting transactions')}}
</p>
</td></tr>
</table>`;
frm.set_df_property('dimension_filter_help', 'options', help_content);
},
onload: function(frm) {
frm.set_query('applicable_on_account', 'accounts', function() {
return {
filters: {
'company': frm.doc.company
}
};
});
frappe.db.get_list('Accounting Dimension',
{fields: ['document_type']}).then((res) => {
let options = ['Cost Center', 'Project'];
res.forEach((dimension) => {
options.push(dimension.document_type);
});
frm.set_df_property('accounting_dimension', 'options', options);
});
frm.trigger('setup_filters');
},
setup_filters: function(frm) {
let filters = {};
if (frm.doc.accounting_dimension) {
frappe.model.with_doctype(frm.doc.accounting_dimension, function() {
if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
filters['is_group'] = 0;
}
if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) {
filters['company'] = frm.doc.company;
}
frm.set_query('dimension_value', 'dimensions', function() {
return {
filters: filters
};
});
});
}
},
accounting_dimension: function(frm) {
frm.clear_table("dimensions");
let row = frm.add_child("dimensions");
row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions");
frm.trigger('setup_filters');
},
});
frappe.ui.form.on('Allowed Dimension', {
dimensions_add: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions");
}
});

View File

@@ -0,0 +1,134 @@
{
"actions": [],
"autoname": "format:{accounting_dimension}-{#####}",
"creation": "2020-11-08 18:28:11.906146",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"accounting_dimension",
"disabled",
"column_break_2",
"company",
"allow_or_restrict",
"section_break_4",
"accounts",
"column_break_6",
"dimensions",
"section_break_10",
"dimension_filter_help"
],
"fields": [
{
"fieldname": "accounting_dimension",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Accounting Dimension",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hide_border": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "allow_or_restrict",
"fieldtype": "Select",
"label": "Allow Or Restrict Dimension",
"options": "Allow\nRestrict",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "accounts",
"fieldtype": "Table",
"label": "Applicable On Account",
"options": "Applicable On Account",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.accounting_dimension",
"fieldname": "dimensions",
"fieldtype": "Table",
"label": "Applicable Dimension",
"options": "Allowed Dimension",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dimension_filter_help",
"fieldtype": "HTML",
"label": "Dimension Filter Help",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-12-16 15:27:23.659285",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Dimension Filter",
"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
}

View File

@@ -0,0 +1,61 @@
# -*- 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 _, scrub
from frappe.model.document import Document
class AccountingDimensionFilter(Document):
def validate(self):
self.validate_applicable_accounts()
def validate_applicable_accounts(self):
accounts = frappe.db.sql(
"""
SELECT a.applicable_on_account as account
FROM `tabApplicable On Account` a, `tabAccounting Dimension Filter` d
WHERE d.name = a.parent
and d.name != %s
and d.accounting_dimension = %s
""", (self.name, self.accounting_dimension), as_dict=1)
account_list = [d.account for d in accounts]
for account in self.get('accounts'):
if account.applicable_on_account in account_list:
frappe.throw(_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
account.idx, frappe.bold(account.applicable_on_account), frappe.bold(self.accounting_dimension)))
def get_dimension_filter_map():
filters = frappe.db.sql("""
SELECT
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, a.is_mandatory
FROM
`tabApplicable On Account` a, `tabAllowed Dimension` d,
`tabAccounting Dimension Filter` p
WHERE
p.name = a.parent
AND p.disabled = 0
AND p.name = d.parent
""", as_dict=1)
dimension_filter_map = {}
for f in filters:
f.fieldname = scrub(f.accounting_dimension)
build_map(dimension_filter_map, f.fieldname, f.applicable_on_account, f.dimension_value,
f.allow_or_restrict, f.is_mandatory)
return dimension_filter_map
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
map_object.setdefault((dimension, account), {
'allowed_dimensions': [],
'is_mandatory': is_mandatory,
'allow_or_restrict': allow_or_restrict
})
map_object[(dimension, account)]['allowed_dimensions'].append(filter_value)

View File

@@ -0,0 +1,99 @@
# -*- 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 erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
class TestAccountingDimensionFilter(unittest.TestCase):
def setUp(self):
create_dimension()
create_accounting_dimension_filter()
self.invoice_list = []
def test_allowed_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
si.items[0].cost_center = 'Main - _TC'
si.department = 'Accounts - _TC'
si.location = 'Block 1'
si.save()
self.assertRaises(InvalidAccountDimensionError, si.submit)
self.invoice_list.append(si)
def test_mandatory_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
si.department = ''
si.location = 'Block 1'
# Test with no department for Sales Account
si.items[0].department = ''
si.items[0].cost_center = '_Test Cost Center 2 - _TC'
si.save()
self.assertRaises(MandatoryAccountDimensionError, si.submit)
self.invoice_list.append(si)
def tearDown(self):
disable_dimension_filter()
disable_dimension()
for si in self.invoice_list:
si.load_from_db()
if si.docstatus == 1:
si.cancel()
def create_accounting_dimension_filter():
if not frappe.db.get_value('Accounting Dimension Filter',
{'accounting_dimension': 'Cost Center'}):
frappe.get_doc({
'doctype': 'Accounting Dimension Filter',
'accounting_dimension': 'Cost Center',
'allow_or_restrict': 'Allow',
'company': '_Test Company',
'accounts': [{
'applicable_on_account': 'Sales - _TC',
}],
'dimensions': [{
'accounting_dimension': 'Cost Center',
'dimension_value': '_Test Cost Center 2 - _TC'
}]
}).insert()
else:
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
doc.disabled = 0
doc.save()
if not frappe.db.get_value('Accounting Dimension Filter',
{'accounting_dimension': 'Department'}):
frappe.get_doc({
'doctype': 'Accounting Dimension Filter',
'accounting_dimension': 'Department',
'allow_or_restrict': 'Allow',
'company': '_Test Company',
'accounts': [{
'applicable_on_account': 'Sales - _TC',
'is_mandatory': 1
}],
'dimensions': [{
'accounting_dimension': 'Department',
'dimension_value': 'Accounts - _TC'
}]
}).insert()
else:
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
doc.disabled = 0
doc.save()
def disable_dimension_filter():
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
doc.disabled = 1
doc.save()
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
doc.disabled = 1
doc.save()

View File

@@ -21,6 +21,7 @@
"book_asset_depreciation_entry_automatically", "book_asset_depreciation_entry_automatically",
"add_taxes_from_item_tax_template", "add_taxes_from_item_tax_template",
"automatically_fetch_payment_terms", "automatically_fetch_payment_terms",
"delete_linked_ledger_entries",
"deferred_accounting_settings_section", "deferred_accounting_settings_section",
"automatically_process_deferred_accounting_entry", "automatically_process_deferred_accounting_entry",
"book_deferred_entries_based_on", "book_deferred_entries_based_on",
@@ -219,6 +220,12 @@
"fieldtype": "Select", "fieldtype": "Select",
"label": "Book Deferred Entries Based On", "label": "Book Deferred Entries Based On",
"options": "Days\nMonths" "options": "Days\nMonths"
},
{
"default": "0",
"fieldname": "delete_linked_ledger_entries",
"fieldtype": "Check",
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@@ -226,7 +233,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-10-13 11:32:52.268826", "modified": "2021-01-05 13:04:00.118892",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -0,0 +1,43 @@
{
"actions": [],
"creation": "2020-11-08 18:22:36.001131",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"accounting_dimension",
"dimension_value"
],
"fields": [
{
"fieldname": "accounting_dimension",
"fieldtype": "Link",
"label": "Accounting Dimension",
"options": "DocType",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dimension_value",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"options": "accounting_dimension",
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-11-23 09:56:19.744200",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Allowed Dimension",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -0,0 +1,46 @@
{
"actions": [],
"creation": "2020-11-08 18:20:00.944449",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"applicable_on_account",
"is_mandatory"
],
"fields": [
{
"fieldname": "applicable_on_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Accounts",
"options": "Account",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"columns": 2,
"default": "0",
"fieldname": "is_mandatory",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Mandatory",
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-11-22 19:55:13.324136",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Applicable On Account",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide('erpnext.integrations');
frappe.ui.form.on('Bank', { frappe.ui.form.on('Bank', {
onload: function(frm) { onload: function(frm) {
@@ -20,7 +21,12 @@ frappe.ui.form.on('Bank', {
frm.set_df_property('address_and_contact', 'hidden', 0); frm.set_df_property('address_and_contact', 'hidden', 0);
frappe.contacts.render_address_and_contact(frm); frappe.contacts.render_address_and_contact(frm);
} }
}, if (frm.doc.plaid_access_token) {
frm.add_custom_button(__('Refresh Plaid Link'), () => {
new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token);
});
}
}
}); });
@@ -41,3 +47,78 @@ let add_fields_to_mapping_table = function (frm) {
frm.fields_dict.bank_transaction_mapping.grid.refresh(); frm.fields_dict.bank_transaction_mapping.grid.refresh();
}; };
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
constructor(access_token) {
this.access_token = access_token;
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config();
}
async init_config() {
this.plaid_env = await frappe.db.get_single_value('Plaid Settings', 'plaid_env');
this.token = await this.get_link_token_for_update();
this.init_plaid();
}
async get_link_token_for_update() {
const token = frappe.xcall(
'erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update',
{ access_token: this.access_token }
)
if (!token) {
frappe.throw(__('Cannot retrieve link token for update. Check Error Log for more information'));
}
return token;
}
init_plaid() {
const me = this;
me.loadScript(me.plaidUrl)
.then(() => {
me.onScriptLoaded(me);
})
.then(() => {
if (me.linkHandler) {
me.linkHandler.open();
}
})
.catch((error) => {
me.onScriptError(error);
});
}
loadScript(src) {
return new Promise(function (resolve, reject) {
if (document.querySelector("script[src='" + src + "']")) {
resolve();
return;
}
const el = document.createElement('script');
el.type = 'text/javascript';
el.async = true;
el.src = src;
el.addEventListener('load', resolve);
el.addEventListener('error', reject);
el.addEventListener('abort', reject);
document.head.appendChild(el);
});
}
onScriptLoaded(me) {
me.linkHandler = Plaid.create({
env: me.plaid_env,
token: me.token,
onSuccess: me.plaid_success
});
}
onScriptError(error) {
frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
console.log(error);
}
plaid_success(token, response) {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
}
};

View File

@@ -1,24 +1,9 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Budget', { frappe.ui.form.on('Budget', {
onload: function(frm) { onload: function(frm) {
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company
}
}
})
frm.set_query("project", function() {
return {
filters: {
company: frm.doc.company
}
}
})
frm.set_query("account", "accounts", function() { frm.set_query("account", "accounts", function() {
return { return {
filters: { filters: {
@@ -26,16 +11,18 @@ frappe.ui.form.on('Budget', {
report_type: "Profit and Loss", report_type: "Profit and Loss",
is_group: 0 is_group: 0
} }
} };
}) });
frm.set_query("monthly_distribution", function() { frm.set_query("monthly_distribution", function() {
return { return {
filters: { filters: {
fiscal_year: frm.doc.fiscal_year fiscal_year: frm.doc.fiscal_year
} }
} };
}) });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@@ -122,8 +122,10 @@ class TestBudget(unittest.TestCase):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
project = frappe.get_value("Project", {"project_name": "_Test Project"})
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate()) "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project=project, posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@@ -147,8 +149,11 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
project = frappe.get_value("Project", {"project_name": "_Test Project"})
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate()) "_Test Bank - _TC", 250000, "_Test Cost Center - _TC",
project=project, posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@@ -184,9 +189,11 @@ class TestBudget(unittest.TestCase):
if month > 9: if month > 9:
month = 9 month = 9
project = frappe.get_value("Project", {"project_name": "_Test Project"})
for i in range(month + 1): for i in range(month + 1):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project") "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True,
project=project)
self.assertTrue(frappe.db.get_value("GL Entry", self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name})) {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
@@ -289,7 +296,7 @@ def make_budget(**args):
budget = frappe.new_doc("Budget") budget = frappe.new_doc("Budget")
if budget_against == "Project": if budget_against == "Project":
budget.project = "_Test Project" budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
else: else:
budget.cost_center =cost_center or "_Test Cost Center - _TC" budget.cost_center =cost_center or "_Test Cost Center - _TC"

View File

@@ -11,8 +11,10 @@ from frappe.model.meta import get_field_precision
from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency from erpnext.exceptions import InvalidAccountCurrency, InvalidAccountDimensionError, MandatoryAccountDimensionError
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
from six import iteritems
exclude_from_linked_with = True exclude_from_linked_with = True
class GLEntry(Document): class GLEntry(Document):
@@ -39,6 +41,7 @@ class GLEntry(Document):
if not from_repost: if not from_repost:
self.validate_account_details(adv_adj) self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs() self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_frozen_account(self.account, adv_adj) validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj) validate_balance_type(self.account, adv_adj)
@@ -76,11 +79,9 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.account)) .format(self.voucher_type, self.voucher_no, self.account))
def validate_dimensions_for_pl_and_bs(self): def validate_dimensions_for_pl_and_bs(self):
account_type = frappe.db.get_value("Account", self.account, "report_type") account_type = frappe.db.get_value("Account", self.account, "report_type")
for dimension in get_checks_for_pl_and_bs_accounts(): for dimension in get_checks_for_pl_and_bs_accounts():
if account_type == "Profit and Loss" \ if account_type == "Profit and Loss" \
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled: and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
if not self.get(dimension.fieldname): if not self.get(dimension.fieldname):
@@ -93,6 +94,25 @@ class GLEntry(Document):
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.") frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
.format(dimension.label, self.account)) .format(dimension.label, self.account))
def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
for key, value in iteritems(dimension_filter_map):
dimension = key[0]
account = key[1]
if self.account == account:
if value['is_mandatory'] and not self.get(dimension):
frappe.throw(_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), MandatoryAccountDimensionError)
if value['allow_or_restrict'] == 'Allow':
if self.get(dimension) and self.get(dimension) not in value['allowed_dimensions']:
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
else:
if self.get(dimension) and self.get(dimension) in value['allowed_dimensions']:
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
def check_pl_account(self): def check_pl_account(self):
if self.is_opening=='Yes' and \ if self.is_opening=='Yes' and \
@@ -137,9 +157,10 @@ class GLEntry(Document):
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}") frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) .format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if self.cost_center and _check_is_group(): if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""") and self.cost_center and _check_is_group():
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
def validate_party(self): def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party) validate_party_frozen_disabled(self.party_type, self.party)
@@ -149,7 +170,7 @@ class GLEntry(Document):
account_currency = get_account_currency(self.account) account_currency = get_account_currency(self.account)
if not self.account_currency: if not self.account_currency:
self.account_currency = company_currency self.account_currency = account_currency or company_currency
if account_currency != self.account_currency: if account_currency != self.account_currency:
frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}") frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")

View File

@@ -120,6 +120,8 @@ frappe.ui.form.on("Journal Entry", {
} }
} }
}); });
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
}, },
voucher_type: function(frm){ voucher_type: function(frm){
@@ -197,6 +199,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
this.load_defaults(); this.load_defaults();
this.setup_queries(); this.setup_queries();
this.setup_balance_formatter(); this.setup_balance_formatter();
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}, },
onload_post_render: function() { onload_post_render: function() {
@@ -222,15 +225,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
return erpnext.journal_entry.account_query(me.frm); return erpnext.journal_entry.account_query(me.frm);
}); });
me.frm.set_query("cost_center", "accounts", function(doc, cdt, cdn) {
return {
filters: {
company: me.frm.doc.company,
is_group: 0
}
};
});
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) { me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
@@ -406,6 +400,8 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
} }
} }
cur_frm.cscript.update_totals(doc); cur_frm.cscript.update_totals(doc);
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'accounts');
}, },
}); });

View File

@@ -299,15 +299,20 @@ class TestJournalEntry(unittest.TestCase):
def test_jv_with_project(self): def test_jv_with_project(self):
from erpnext.projects.doctype.project.test_project import make_project from erpnext.projects.doctype.project.test_project import make_project
if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
project = make_project({ project = make_project({
'project_name': 'Journal Entry Project', 'project_name': 'Journal Entry Project',
'project_template_name': 'Test Project Template', 'project_template_name': 'Test Project Template',
'start_date': '2020-01-01' 'start_date': '2020-01-01'
}) })
project_name = project.name
else:
project_name = frappe.get_value("Project", {"project_name": "_Test Project"})
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
for d in jv.accounts: for d in jv.accounts:
d.project = project.project_name d.project = project_name
jv.voucher_type = "Bank Entry" jv.voucher_type = "Bank Entry"
jv.multi_currency = 0 jv.multi_currency = 0
jv.cheque_no = "112233" jv.cheque_no = "112233"
@@ -317,10 +322,10 @@ class TestJournalEntry(unittest.TestCase):
expected_values = { expected_values = {
"_Test Cash - _TC": { "_Test Cash - _TC": {
"project": project.project_name "project": project_name
}, },
"_Test Bank - _TC": { "_Test Bank - _TC": {
"project": project.project_name "project": project_name
} }
} }

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Loyalty Program', { frappe.ui.form.on('Loyalty Program', {
setup: function(frm) { setup: function(frm) {
var help_content = var help_content =
@@ -46,20 +48,17 @@ frappe.ui.form.on('Loyalty Program', {
}; };
}); });
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company
}
};
});
frm.set_value("company", frappe.defaults.get_user_default("Company")); frm.set_value("company", frappe.defaults.get_user_default("Company"));
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function(frm) {
if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) { if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules.")); frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
} }
},
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
} }
}); });

View File

@@ -36,6 +36,8 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message); frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
frm.page.set_indicator(__('In Progress'), 'orange'); frm.page.set_indicator(__('In Progress'), 'orange');
}); });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function(frm) {
@@ -100,6 +102,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
} }
}) })
} }
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
}, },
invoice_type: function(frm) { invoice_type: function(frm) {

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
{% include "erpnext/public/js/controllers/accounts.js" %} {% include "erpnext/public/js/controllers/accounts.js" %}
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Payment Entry', { frappe.ui.form.on('Payment Entry', {
onload: function(frm) { onload: function(frm) {
@@ -8,6 +9,8 @@ frappe.ui.form.on('Payment Entry', {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null); if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null); if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
} }
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
setup: function(frm) { setup: function(frm) {
@@ -88,15 +91,6 @@ frappe.ui.form.on('Payment Entry', {
} }
}); });
frm.set_query("cost_center", "deductions", function() {
return {
filters: {
"is_group": 0,
"company": frm.doc.company
}
}
});
frm.set_query("reference_doctype", "references", function() { frm.set_query("reference_doctype", "references", function() {
if (frm.doc.party_type=="Customer") { if (frm.doc.party_type=="Customer") {
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"]; var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
@@ -167,6 +161,7 @@ frappe.ui.form.on('Payment Entry', {
company: function(frm) { company: function(frm) {
frm.events.hide_unhide_fields(frm); frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm); frm.events.set_dynamic_labels(frm);
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
}, },
contact_person: function(frm) { contact_person: function(frm) {

View File

@@ -8,7 +8,7 @@ from frappe import _
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions, from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
get_dimension_filters) get_dimensions)
class PeriodClosingVoucher(AccountsController): class PeriodClosingVoucher(AccountsController):
def validate(self): def validate(self):
@@ -58,7 +58,7 @@ class PeriodClosingVoucher(AccountsController):
for dimension in accounting_dimensions: for dimension in accounting_dimensions:
dimension_fields.append('t1.{0}'.format(dimension)) dimension_fields.append('t1.{0}'.format(dimension))
dimension_filters, default_dimensions = get_dimension_filters() dimension_filters, default_dimensions = get_dimensions()
pl_accounts = self.get_pl_balances(dimension_fields) pl_accounts = self.get_pl_balances(dimension_fields)

View File

@@ -57,6 +57,8 @@ frappe.ui.form.on('POS Profile', {
} }
}; };
}); });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function(frm) {
@@ -67,6 +69,7 @@ frappe.ui.form.on('POS Profile', {
company: function(frm) { company: function(frm) {
frm.trigger("toggle_display_account_head"); frm.trigger("toggle_display_account_head");
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
}, },
toggle_display_account_head: function(frm) { toggle_display_account_head: function(frm) {

View File

@@ -26,6 +26,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
}; };
}); });
}, },
company: function() {
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
},
onload: function() { onload: function() {
this._super(); this._super();
@@ -41,6 +46,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
if (this.frm.doc.supplier && this.frm.doc.__islocal) { if (this.frm.doc.supplier && this.frm.doc.__islocal) {
this.frm.trigger('supplier'); this.frm.trigger('supplier');
} }
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}, },
refresh: function(doc) { refresh: function(doc) {
@@ -268,8 +275,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
supplier: function() { supplier: function() {
var me = this; var me = this;
if(this.frm.updating_party_details)
// Do not update if inter company reference is there as the details will already be updated
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
return; return;
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details", erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
{ {
posting_date: this.frm.doc.posting_date, posting_date: this.frm.doc.posting_date,
@@ -511,15 +521,6 @@ frappe.ui.form.on("Purchase Invoice", {
} }
} }
} }
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0
}
};
});
}, },
onload: function(frm) { onload: function(frm) {

View File

@@ -57,8 +57,8 @@
"set_warehouse", "set_warehouse",
"rejected_warehouse", "rejected_warehouse",
"col_break_warehouse", "col_break_warehouse",
"set_from_warehouse",
"is_subcontracted", "is_subcontracted",
"supplier_warehouse",
"items_section", "items_section",
"update_stock", "update_stock",
"scan_barcode", "scan_barcode",
@@ -515,6 +515,7 @@
}, },
{ {
"depends_on": "update_stock", "depends_on": "update_stock",
"description": "Sets 'Accepted Warehouse' in each row of the items table.",
"fieldname": "set_warehouse", "fieldname": "set_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Set Accepted Warehouse", "label": "Set Accepted Warehouse",
@@ -543,17 +544,6 @@
"options": "No\nYes", "options": "No\nYes",
"print_hide": 1 "print_hide": 1
}, },
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
"no_copy": 1,
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
"width": "50px"
},
{ {
"fieldname": "items_section", "fieldname": "items_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@@ -1232,7 +1222,9 @@
"fieldname": "inter_company_invoice_reference", "fieldname": "inter_company_invoice_reference",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Inter Company Invoice Reference", "label": "Inter Company Invoice Reference",
"no_copy": 1,
"options": "Sales Invoice", "options": "Sales Invoice",
"print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{ {
@@ -1356,13 +1348,25 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Represents Company", "label": "Represents Company",
"options": "Company" "options": "Company"
},
{
"depends_on": "eval:doc.update_stock && (doc.is_subcontracted==\"Yes\" || doc.is_internal_supplier)",
"description": "Sets 'From Warehouse' in each row of the items table.",
"fieldname": "set_from_warehouse",
"fieldtype": "Link",
"label": "Set From Warehouse",
"no_copy": 1,
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
"width": "50px"
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-12-11 12:46:12.796378", "modified": "2020-12-26 20:49:03.305063",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -511,8 +511,8 @@ class PurchaseInvoice(BuyingController):
voucher_wise_stock_value = {} voucher_wise_stock_value = {}
if self.update_stock: if self.update_stock:
for d in frappe.get_all('Stock Ledger Entry', for d in frappe.get_all('Stock Ledger Entry',
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}): fields = ["voucher_detail_no", "stock_value_difference", "warehouse"], filters={'voucher_no': self.name}):
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference) voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
valuation_tax_accounts = [d.account_head for d in self.get("taxes") valuation_tax_accounts = [d.account_head for d in self.get("taxes")
if d.category in ('Valuation', 'Total and Valuation') if d.category in ('Valuation', 'Total and Valuation')
@@ -563,6 +563,7 @@ class PurchaseInvoice(BuyingController):
) )
else: else:
if not self.is_internal_transfer():
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": item.expense_account, "account": item.expense_account,
@@ -582,7 +583,8 @@ class PurchaseInvoice(BuyingController):
"against": item.expense_account, "against": item.expense_account,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount), "credit": flt(amount["base_amount"]),
"credit_in_account_currency": flt(amount["amount"]),
"project": item.project or self.project "project": item.project or self.project
}, item=item)) }, item=item))
@@ -624,6 +626,7 @@ class PurchaseInvoice(BuyingController):
if expense_booked_in_pr: if expense_booked_in_pr:
expense_account = service_received_but_not_billed_account expense_account = service_received_but_not_billed_account
if not self.is_internal_transfer():
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": expense_account, "account": expense_account,
"against": self.supplier, "against": self.supplier,
@@ -795,10 +798,10 @@ class PurchaseInvoice(BuyingController):
# Stock ledger value is not matching with the warehouse amount # Stock ledger value is not matching with the warehouse amount
if (self.update_stock and voucher_wise_stock_value.get(item.name) and if (self.update_stock and voucher_wise_stock_value.get(item.name) and
warehouse_debit_amount != flt(voucher_wise_stock_value.get(item.name), net_amt_precision)): warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)):
cost_of_goods_sold_account = self.get_company_default("default_expense_account") cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_amount = flt(voucher_wise_stock_value.get(item.name), net_amt_precision) stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
stock_adjustment_amt = warehouse_debit_amount - stock_amount stock_adjustment_amt = warehouse_debit_amount - stock_amount
gl_entries.append( gl_entries.append(

View File

@@ -426,26 +426,31 @@ class TestPurchaseInvoice(unittest.TestCase):
) )
def test_total_purchase_cost_for_project(self): def test_total_purchase_cost_for_project(self):
make_project({'project_name':'_Test Project'}) if not frappe.db.exists("Project", {"project_name": "_Test Project for Purchase"}):
project = make_project({'project_name':'_Test Project for Purchase'})
else:
project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount) existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""") from `tabPurchase Invoice Item`
where project = '{0}'
and docstatus=1""".format(project.name))
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0 existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project="_Test Project") pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
existing_purchase_cost + 15000) existing_purchase_cost + 15000)
pi1 = make_purchase_invoice(qty=10, project="_Test Project") pi1 = make_purchase_invoice(qty=10, project=project.name)
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
existing_purchase_cost + 15500) existing_purchase_cost + 15500)
pi1.cancel() pi1.cancel()
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
existing_purchase_cost + 15000) existing_purchase_cost + 15000)
pi.cancel() pi.cancel()
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost) self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost)
def test_return_purchase_invoice_with_perpetual_inventory(self): def test_return_purchase_invoice_with_perpetual_inventory(self):
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1", pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
@@ -860,17 +865,17 @@ class TestPurchaseInvoice(unittest.TestCase):
}) })
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1) pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
pi.items[0].project = item_project.project_name pi.items[0].project = item_project.name
pi.project = project.project_name pi.project = project.name
pi.submit() pi.submit()
expected_values = { expected_values = {
"Creditors - _TC": { "Creditors - _TC": {
"project": project.project_name "project": project.name
}, },
"_Test Account Cost for Goods Sold - _TC": { "_Test Account Cost for Goods Sold - _TC": {
"project": item_project.project_name "project": item_project.name
} }
} }

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-05-22 12:43:10", "creation": "2013-05-22 12:43:10",
"doctype": "DocType", "doctype": "DocType",
@@ -87,6 +88,7 @@
"po_detail", "po_detail",
"purchase_receipt", "purchase_receipt",
"pr_detail", "pr_detail",
"sales_invoice_item",
"item_weight_details", "item_weight_details",
"weight_per_unit", "weight_per_unit",
"total_weight", "total_weight",
@@ -553,8 +555,8 @@
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"label": "Brand", "label": "Brand",
"print_hide": 1, "options": "Brand",
"options": "Brand" "print_hide": 1
}, },
{ {
"fetch_from": "item_code.item_group", "fetch_from": "item_code.item_group",
@@ -562,9 +564,9 @@
"fieldname": "item_group", "fieldname": "item_group",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Item Group", "label": "Item Group",
"options": "Item Group",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1
"options": "Item Group"
}, },
{ {
"description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges", "description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges",
@@ -759,10 +761,11 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
"fieldname": "from_warehouse", "fieldname": "from_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Supplier Warehouse", "label": "From Warehouse",
"options": "Warehouse" "options": "Warehouse"
}, },
{ {
@@ -779,11 +782,20 @@
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "sales_invoice_item",
"fieldtype": "Data",
"label": "Sales Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2020-08-20 11:48:01.398356", "links": [],
"modified": "2020-12-26 17:20:36.415791",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@@ -5,14 +5,17 @@
cur_frm.pformat.print_heading = 'Invoice'; cur_frm.pformat.print_heading = 'Invoice';
{% include 'erpnext/selling/sales_common.js' %}; {% include 'erpnext/selling/sales_common.js' %};
frappe.provide("erpnext.accounts"); frappe.provide("erpnext.accounts");
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
setup: function(doc) { setup: function(doc) {
this.setup_posting_date_time_check(); this.setup_posting_date_time_check();
this._super(doc); this._super(doc);
}, },
company: function() {
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
},
onload: function() { onload: function() {
var me = this; var me = this;
this._super(); this._super();
@@ -33,6 +36,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.frm.refresh_fields(); me.frm.refresh_fields();
} }
erpnext.queries.setup_warehouse_query(this.frm); erpnext.queries.setup_warehouse_query(this.frm);
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}, },
refresh: function(doc, dt, dn) { refresh: function(doc, dt, dn) {
@@ -126,16 +130,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.set_default_print_format(); this.set_default_print_format();
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) { if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
frappe.model.with_doc("Customer", me.frm.doc.customer, function() { let internal = me.frm.doc.is_internal_customer;
var customer = frappe.model.get_doc("Customer", me.frm.doc.customer); if (internal) {
var internal = customer.is_internal_customer; let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Invoice" :
var disabled = customer.disabled; "Inter Company Purchase Invoice";
if (internal == 1 && disabled == 0) {
me.frm.add_custom_button("Inter Company Invoice", function() { me.frm.add_custom_button(button_label, function() {
me.make_inter_company_invoice(); me.make_inter_company_invoice();
}, __('Create')); }, __('Create'));
} }
});
} }
}, },
@@ -571,15 +574,6 @@ frappe.ui.form.on('Sales Invoice', {
}; };
}); });
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0
}
};
});
frm.set_query("unrealized_profit_loss_account", function() { frm.set_query("unrealized_profit_loss_account", function() {
return { return {
filters: { filters: {

View File

@@ -60,6 +60,8 @@
"ignore_pricing_rule", "ignore_pricing_rule",
"sec_warehouse", "sec_warehouse",
"set_warehouse", "set_warehouse",
"column_break_55",
"set_target_warehouse",
"items_section", "items_section",
"update_stock", "update_stock",
"scan_barcode", "scan_barcode",
@@ -1969,13 +1971,24 @@
"label": "Represents Company", "label": "Represents Company",
"options": "Company", "options": "Company",
"read_only": 1 "read_only": 1
},
{
"fieldname": "column_break_55",
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.is_internal_customer && doc.update_stock",
"fieldname": "set_target_warehouse",
"fieldtype": "Link",
"label": "Set Target Warehouse",
"options": "Warehouse"
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-12-11 12:48:31.769958", "modified": "2020-12-25 22:57:32.555067",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -6,7 +6,7 @@ import frappe, erpnext
import frappe.defaults import frappe.defaults
from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date, get_party_details
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
@@ -21,6 +21,8 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \ from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.deferred_revenue import validate_service_stop_date
from frappe.model.utils import get_fetch_values
from frappe.contacts.doctype.address.address import get_address_display
from erpnext.healthcare.utils import manage_invoice_submit_cancel from erpnext.healthcare.utils import manage_invoice_submit_cancel
@@ -1534,7 +1536,7 @@ def validate_inter_company_transaction(doc, doctype):
details = get_inter_company_details(doc, doctype) details = get_inter_company_details(doc, doctype)
price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1}) valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
if not valid_price_list: if not valid_price_list and not doc.is_internal_transfer():
frappe.throw(_("Selected Price List should have buying and selling fields checked.")) frappe.throw(_("Selected Price List should have buying and selling fields checked."))
party = details.get("party") party = details.get("party")
@@ -1557,6 +1559,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if doctype in ["Sales Invoice", "Sales Order"]: if doctype in ["Sales Invoice", "Sales Order"]:
source_doc = frappe.get_doc(doctype, source_name) source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order" target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
source_document_warehouse_field = 'target_warehouse' source_document_warehouse_field = 'target_warehouse'
target_document_warehouse_field = 'from_warehouse' target_document_warehouse_field = 'from_warehouse'
else: else:
@@ -1570,6 +1573,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
def set_missing_values(source, target): def set_missing_values(source, target):
target.run_method("set_missing_values") target.run_method("set_missing_values")
set_purchase_references(target)
def update_details(source_doc, target_doc, source_parent): def update_details(source_doc, target_doc, source_parent):
target_doc.inter_company_invoice_reference = source_doc.name target_doc.inter_company_invoice_reference = source_doc.name
@@ -1577,19 +1581,38 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency') currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
target_doc.company = details.get("company") target_doc.company = details.get("company")
target_doc.supplier = details.get("party") target_doc.supplier = details.get("party")
target_doc.is_internal_supplier = 1
target_doc.ignore_pricing_rule = 1
target_doc.buying_price_list = source_doc.selling_price_list target_doc.buying_price_list = source_doc.selling_price_list
# Invert Addresses
update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
if currency: if currency:
target_doc.currency = currency target_doc.currency = currency
update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
doctype=target_doc.doctype, party_address=target_doc.supplier_address,
company_address=target_doc.shipping_address)
else: else:
currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency') currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
target_doc.company = details.get("company") target_doc.company = details.get("company")
target_doc.customer = details.get("party") target_doc.customer = details.get("party")
target_doc.selling_price_list = source_doc.buying_price_list target_doc.selling_price_list = source_doc.buying_price_list
update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
if currency: if currency:
target_doc.currency = currency target_doc.currency = currency
update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
doctype=target_doc.doctype, party_address=target_doc.customer_address,
company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
item_field_map = { item_field_map = {
"doctype": target_doctype + " Item", "doctype": target_doctype + " Item",
"field_no_map": [ "field_no_map": [
@@ -1597,25 +1620,33 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
"expense_account", "expense_account",
"cost_center", "cost_center",
"warehouse" "warehouse"
] ],
"field_map": {
'rate': 'rate',
}
} }
if doctype in ["Sales Invoice", "Sales Order"]:
item_field_map["field_map"].update({
"name": target_detail_field,
})
if source_doc.get('update_stock'): if source_doc.get('update_stock'):
item_field_map.update({ item_field_map["field_map"].update({
'field_map': {
source_document_warehouse_field: target_document_warehouse_field, source_document_warehouse_field: target_document_warehouse_field,
'batch_no': 'batch_no', 'batch_no': 'batch_no',
'serial_no': 'serial_no' 'serial_no': 'serial_no'
}
}) })
doclist = get_mapped_doc(doctype, source_name, { doclist = get_mapped_doc(doctype, source_name, {
doctype: { doctype: {
"doctype": target_doctype, "doctype": target_doctype,
"postprocess": update_details, "postprocess": update_details,
"set_target_warehouse": "set_from_warehouse",
"field_no_map": [ "field_no_map": [
"taxes_and_charges" "taxes_and_charges",
"set_warehouse",
"shipping_address"
] ]
}, },
doctype +" Item": item_field_map doctype +" Item": item_field_map
@@ -1624,6 +1655,110 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
return doclist return doclist
def set_purchase_references(doc):
# add internal PO or PR links if any
if doc.is_internal_transfer():
if doc.doctype == 'Purchase Receipt':
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
if so_item_map:
pd_item_map, parent_child_map, warehouse_map = \
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
update_pr_items(doc, so_item_map, pd_item_map, parent_child_map, warehouse_map)
elif doc.doctype == 'Purchase Invoice':
dn_item_map, so_item_map = get_sales_invoice_details(doc.inter_company_invoice_reference)
# First check for Purchase receipt
if list(dn_item_map.values()):
pd_item_map, parent_child_map, warehouse_map = \
get_pd_details('Purchase Receipt Item', dn_item_map, 'delivery_note_item')
update_pi_items(doc, 'pr_detail', 'purchase_receipt',
dn_item_map, pd_item_map, parent_child_map, warehouse_map)
if list(so_item_map.values()):
pd_item_map, parent_child_map, warehouse_map = \
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
update_pi_items(doc, 'po_detail', 'purchase_order',
so_item_map, pd_item_map, parent_child_map, warehouse_map)
def update_pi_items(doc, detail_field, parent_field, sales_item_map,
purchase_item_map, parent_child_map, warehouse_map):
for item in doc.get('items'):
item.set(detail_field, purchase_item_map.get(sales_item_map.get(item.sales_invoice_item)))
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
if doc.update_stock:
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
for item in doc.get('items'):
item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
def get_delivery_note_details(internal_reference):
so_item_map = {}
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
filters={'parent': internal_reference})
for d in si_item_details:
so_item_map.setdefault(d.name, d.so_detail)
return so_item_map
def get_sales_invoice_details(internal_reference):
dn_item_map = {}
so_item_map = {}
si_item_details = frappe.get_all('Sales Invoice Item', fields=['name', 'so_detail',
'dn_detail'], filters={'parent': internal_reference})
for d in si_item_details:
if d.dn_detail:
dn_item_map.setdefault(d.name, d.dn_detail)
if d.so_detail:
so_item_map.setdefault(d.name, d.so_detail)
return dn_item_map, so_item_map
def get_pd_details(doctype, sd_detail_map, sd_detail_field):
pd_item_map = {}
accepted_warehouse_map = {}
parent_child_map = {}
pd_item_details = frappe.get_all(doctype,
fields=[sd_detail_field, 'name', 'warehouse', 'parent'], filters={sd_detail_field: ('in', list(sd_detail_map.values()))})
for d in pd_item_details:
pd_item_map.setdefault(d.get(sd_detail_field), d.name)
parent_child_map.setdefault(d.get(sd_detail_field), d.parent)
accepted_warehouse_map.setdefault(d.get(sd_detail_field), d.warehouse)
return pd_item_map, parent_child_map, accepted_warehouse_map
def update_taxes(doc, party=None, party_type=None, company=None, doctype=None, party_address=None,
company_address=None, shipping_address_name=None, master_doctype=None):
# Update Party Details
party_details = get_party_details(party=party, party_type=party_type, company=company,
doctype=doctype, party_address=party_address, company_address=company_address,
shipping_address=shipping_address_name)
# Update taxes and charges if any
doc.taxes_and_charges = party_details.get('taxes_and_charges')
doc.set('taxes', party_details.get('taxes'))
def update_address(doc, address_field, address_display_field, address_name):
doc.set(address_field, address_name)
fetch_values = get_fetch_values(doc.doctype, address_field, address_name)
for key, value in fetch_values.items():
doc.set(key, value)
doc.set(address_display_field, get_address_display(doc.get(address_field)))
@frappe.whitelist() @frappe.whitelist()
def get_loyalty_programs(customer): def get_loyalty_programs(customer):
''' sets applicable loyalty program to the customer or returns a list of applicable programs ''' ''' sets applicable loyalty program to the customer or returns a list of applicable programs '''

View File

@@ -22,6 +22,7 @@ from erpnext.regional.india.utils import get_ewb_data
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
from erpnext.stock.utils import get_incoming_rate
class TestSalesInvoice(unittest.TestCase): class TestSalesInvoice(unittest.TestCase):
def make(self): def make(self):
@@ -1573,17 +1574,17 @@ class TestSalesInvoice(unittest.TestCase):
}) })
sales_invoice = create_sales_invoice(do_not_save=1) sales_invoice = create_sales_invoice(do_not_save=1)
sales_invoice.items[0].project = item_project.project_name sales_invoice.items[0].project = item_project.name
sales_invoice.project = project.project_name sales_invoice.project = project.name
sales_invoice.submit() sales_invoice.submit()
expected_values = { expected_values = {
"Debtors - _TC": { "Debtors - _TC": {
"project": project.project_name "project": project.name
}, },
"Sales - _TC": { "Sales - _TC": {
"project": item_project.project_name "project": item_project.name
} }
} }
@@ -1770,59 +1771,82 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(target_doc.company, "_Test Company 1") self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier") self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
# def test_internal_transfer_gl_entry(self): def test_internal_transfer_gl_entry(self):
# ## Create internal transfer account ## Create internal transfer account
# account = create_account(account_name="Unrealized Profit", account = create_account(account_name="Unrealized Profit",
# parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory") parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
# frappe.db.set_value('Company', '_Test Company with perpetual inventory', frappe.db.set_value('Company', '_Test Company with perpetual inventory',
# 'unrealized_profit_loss_account', account) 'unrealized_profit_loss_account', account)
# customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory", customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
# "_Test Company with perpetual inventory") "_Test Company with perpetual inventory")
# create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory", create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
# "_Test Company with perpetual inventory") "_Test Company with perpetual inventory")
# si = create_sales_invoice( si = create_sales_invoice(
# company = "_Test Company with perpetual inventory", company = "_Test Company with perpetual inventory",
# customer = customer, customer = customer,
# debit_to = "Debtors - TCP1", debit_to = "Debtors - TCP1",
# warehouse = "Stores - TCP1", warehouse = "Stores - TCP1",
# income_account = "Sales - TCP1", income_account = "Sales - TCP1",
# expense_account = "Cost of Goods Sold - TCP1", expense_account = "Cost of Goods Sold - TCP1",
# cost_center = "Main - TCP1", cost_center = "Main - TCP1",
# currency = "INR", currency = "INR",
# do_not_save = 1 do_not_save = 1
# ) )
# si.selling_price_list = "_Test Price List Rest of the World" si.selling_price_list = "_Test Price List Rest of the World"
# si.update_stock = 1 si.update_stock = 1
# si.items[0].target_warehouse = 'Work In Progress - TCP1' si.items[0].target_warehouse = 'Work In Progress - TCP1'
# add_taxes(si) add_taxes(si)
# si.save() si.save()
# si.submit()
# target_doc = make_inter_company_transaction("Sales Invoice", si.name) rate = 0.0
# target_doc.company = '_Test Company with perpetual inventory' for d in si.get('items'):
# target_doc.items[0].warehouse = 'Finished Goods - TCP1' rate = get_incoming_rate({
# add_taxes(target_doc) "item_code": d.item_code,
# target_doc.save() "warehouse": d.warehouse,
# target_doc.submit() "posting_date": si.posting_date,
"posting_time": si.posting_time,
"qty": -1 * flt(d.get('stock_qty')),
"serial_no": d.serial_no,
"company": si.company,
"voucher_type": 'Sales Invoice',
"voucher_no": si.name,
"allow_zero_valuation": d.get("allow_zero_valuation")
}, raise_error_if_no_rate=False)
# si_gl_entries = [ rate = flt(rate, 2)
# ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
# ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
# ]
# check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1)) si.submit()
# pi_gl_entries = [ target_doc = make_inter_company_transaction("Sales Invoice", si.name)
# ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()], target_doc.company = '_Test Company with perpetual inventory'
# ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()] target_doc.items[0].warehouse = 'Finished Goods - TCP1'
# ] add_taxes(target_doc)
target_doc.save()
target_doc.submit()
# check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1)) tax_amount = flt(rate * (12/100), 2)
si_gl_entries = [
["_Test Account Excise Duty - TCP1", 0.0, tax_amount, nowdate()],
["Unrealized Profit - TCP1", tax_amount, 0.0, nowdate()]
]
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
pi_gl_entries = [
["_Test Account Excise Duty - TCP1", tax_amount , 0.0, nowdate()],
["Unrealized Profit - TCP1", 0.0, tax_amount, nowdate()]
]
# Sale and Purchase both should be at valuation rate
self.assertEqual(si.items[0].rate, rate)
self.assertEqual(target_doc.items[0].rate, rate)
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
def test_eway_bill_json(self): def test_eway_bill_json(self):
si = make_sales_invoice_for_ewaybill() si = make_sales_invoice_for_ewaybill()
@@ -1861,23 +1885,6 @@ class TestSalesInvoice(unittest.TestCase):
def test_einvoice_json(self): def test_einvoice_json(self):
from erpnext.regional.india.e_invoice.utils import make_einvoice from erpnext.regional.india.e_invoice.utils import make_einvoice
customer_gstin = '27AACCM7806M1Z3'
customer_gstin_dtls = {
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
}
company_gstin = '27AAECE4835E1ZR'
company_gstin_dtls = {
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
}
# set cache gstin details to avoid fetching details which will require connection to GSP servers
frappe.local.gstin_cache = {}
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
si = make_sales_invoice_for_ewaybill() si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####' si.naming_series = 'INV-2020-.#####'
si.items = [] si.items = []
@@ -1930,12 +1937,12 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(value_details['SgstVal'], total_item_sgst_value) self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
self.assertEqual(value_details['IgstVal'], total_item_igst_value) self.assertEqual(value_details['IgstVal'], total_item_igst_value)
self.assertEqual( calculated_invoice_value = \
value_details['TotInvVal'], value_details['AssVal'] + value_details['CgstVal'] \
value_details['AssVal'] + value_details['CgstVal'] + value_details['SgstVal'] + value_details['IgstVal'] \
+ value_details['SgstVal'] + value_details['IgstVal']
+ value_details['OthChrg'] - value_details['Discount'] + value_details['OthChrg'] - value_details['Discount']
)
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
self.assertEqual(value_details['TotInvVal'], si.base_grand_total) self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
self.assertTrue(einvoice['EwbDtls']) self.assertTrue(einvoice['EwbDtls'])

View File

@@ -565,11 +565,12 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"depends_on": "eval: parent.is_internal_customer && parent.update_stock",
"fieldname": "target_warehouse", "fieldname": "target_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Customer Warehouse (Optional)", "label": "Target Warehouse",
"no_copy": 1, "no_copy": 1,
"options": "Warehouse", "options": "Warehouse",
"print_hide": 1 "print_hide": 1
@@ -815,7 +816,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-09-23 19:59:04.879322", "modified": "2020-12-26 17:25:04.090630",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",

View File

@@ -34,6 +34,9 @@ def valdiate_taxes_and_charges_template(doc):
validate_disabled(doc) validate_disabled(doc)
# Validate with existing taxes and charges template for unique tax category
validate_for_tax_category(doc)
for tax in doc.get("taxes"): for tax in doc.get("taxes"):
validate_taxes_and_charges(tax) validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, doc) validate_inclusive_tax(tax, doc)
@@ -41,3 +44,7 @@ def valdiate_taxes_and_charges_template(doc):
def validate_disabled(doc): def validate_disabled(doc):
if doc.is_default and doc.disabled: if doc.is_default and doc.disabled:
frappe.throw(_("Disabled template must not be default template")) frappe.throw(_("Disabled template must not be default template"))
def validate_for_tax_category(doc):
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))

View File

@@ -1,16 +1,18 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on('Shipping Rule', { frappe.provide('erpnext.accounts.dimensions');
refresh: function(frm) {
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company
}
}
})
frappe.ui.form.on('Shipping Rule', {
onload: function(frm) {
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
refresh: function(frm) {
frm.set_query("account", function() { frm.set_query("account", function() {
return { return {
filters: { filters: {

View File

@@ -152,7 +152,7 @@
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td> <td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td> <td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td> <td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td> <td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td> <td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td> <td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
</tr> </tr>

View File

@@ -49,12 +49,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
elif d.po_detail: elif d.po_detail:
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, [])) purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
expense_account = d.expense_account or aii_account_map.get(d.company) expense_account = d.unrealized_profit_loss_account or d.expense_account \
or aii_account_map.get(d.company)
row = { row = {
'item_code': d.item_code, 'item_code': d.item_code,
'item_name': item_record.item_name, 'item_name': item_record.item_name if item_record else d.item_name,
'item_group': item_record.item_group, 'item_group': item_record.item_group if item_record else d.item_group,
'description': d.description, 'description': d.description,
'invoice': d.parent, 'invoice': d.parent,
'posting_date': d.posting_date, 'posting_date': d.posting_date,
@@ -315,7 +316,9 @@ def get_items(filters, additional_query_columns):
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice`.unrealized_profit_loss_account,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,

View File

@@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
'company': d.company, 'company': d.company,
'sales_order': d.sales_order, 'sales_order': d.sales_order,
'delivery_note': d.delivery_note, 'delivery_note': d.delivery_note,
'income_account': d.income_account, 'income_account': d.unrealized_profit_loss_account or d.income_account,
'cost_center': d.cost_center, 'cost_center': d.cost_center,
'stock_qty': d.stock_qty, 'stock_qty': d.stock_qty,
'stock_uom': d.stock_uom 'stock_uom': d.stock_uom
@@ -379,6 +379,7 @@ def get_items(filters, additional_query_columns):
select select
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent, `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.unrealized_profit_loss_account,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,

View File

@@ -59,23 +59,111 @@ def validate_filters(filters):
def get_columns(filters): def get_columns(filters):
return [ return [
_("Payment Document") + ":: 100", {
_("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140", "fieldname": "payment_document",
_("Party Type") + "::100", "label": _("Payment Document Type"),
_("Party") + ":Dynamic Link/Party Type:140", "fieldtype": "Data",
_("Posting Date") + ":Date:100", "width": 100
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"), },
_("Invoice Posting Date") + ":Date:130", {
_("Payment Due Date") + ":Date:130", "fieldname": "payment_entry",
_("Debit") + ":Currency:120", "label": _("Payment Document"),
_("Credit") + ":Currency:120", "fieldtype": "Dynamic Link",
_("Remarks") + "::150", "options": "payment_document",
_("Age") +":Int:40", "width": 160
"0-30:Currency:100", },
"30-60:Currency:100", {
"60-90:Currency:100", "fieldname": "party_type",
_("90-Above") + ":Currency:100", "label": _("Party Type"),
_("Delay in payment (Days)") + "::150" "fieldtype": "Data",
"width": 100
},
{
"fieldname": "party",
"label": _("Party"),
"fieldtype": "Dynamic Link",
"options": "party_type",
"width": 160
},
{
"fieldname": "posting_date",
"label": _("Posting Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "invoice",
"label": _("Invoice"),
"fieldtype": "Link",
"options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice",
"width": 160
},
{
"fieldname": "invoice_posting_date",
"label": _("Invoice Posting Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "due_date",
"label": _("Payment Due Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "debit",
"label": _("Debit"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "credit",
"label": _("Credit"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "remarks",
"label": _("Remarks"),
"fieldtype": "Data",
"width": 200
},
{
"fieldname": "age",
"label": _("Age"),
"fieldtype": "Int",
"width": 50
},
{
"fieldname": "range1",
"label": "0-30",
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range2",
"label": "30-60",
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range3",
"label": "60-90",
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range4",
"label": _("90 Above"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "delay_in_payment",
"label": _("Delay in payment (Days)"),
"fieldtype": "Int",
"width": 100
}
] ]
def get_conditions(filters): def get_conditions(filters):

View File

@@ -14,13 +14,15 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if not filters: filters = {} if not filters: filters = {}
invoice_list = get_invoices(filters, additional_query_columns) invoice_list = get_invoices(filters, additional_query_columns)
columns, expense_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns) columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts \
= get_columns(invoice_list, additional_table_columns)
if not invoice_list: if not invoice_list:
msgprint(_("No record found")) msgprint(_("No record found"))
return columns, invoice_list return columns, invoice_list
invoice_expense_map = get_invoice_expense_map(invoice_list) invoice_expense_map = get_invoice_expense_map(invoice_list)
internal_invoice_map = get_internal_invoice_map(invoice_list)
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list, invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
invoice_expense_map, expense_accounts) invoice_expense_map, expense_accounts)
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list) invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
@@ -52,10 +54,17 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
# map expense values # map expense values
base_net_total = 0 base_net_total = 0
for expense_acc in expense_accounts: for expense_acc in expense_accounts:
if inv.is_internal_supplier and inv.company == inv.represents_company:
expense_amount = 0
else:
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc)) expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
base_net_total += expense_amount base_net_total += expense_amount
row.append(expense_amount) row.append(expense_amount)
# Add amount in unrealized account
for account in unrealized_profit_loss_accounts:
row.append(flt(internal_invoice_map.get((inv.name, account))))
# net total # net total
row.append(base_net_total or inv.base_net_total) row.append(base_net_total or inv.base_net_total)
@@ -96,7 +105,8 @@ def get_columns(invoice_list, additional_table_columns):
"width": 80 "width": 80
} }
] ]
expense_accounts = tax_accounts = expense_columns = tax_columns = [] expense_accounts = tax_accounts = expense_columns = tax_columns = unrealized_profit_loss_accounts = \
unrealized_profit_loss_account_columns = []
if invoice_list: if invoice_list:
expense_accounts = frappe.db.sql_list("""select distinct expense_account expense_accounts = frappe.db.sql_list("""select distinct expense_account
@@ -112,17 +122,25 @@ def get_columns(invoice_list, additional_table_columns):
and parent in (%s) order by account_head""" % and parent in (%s) order by account_head""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
and ifnull(unrealized_profit_loss_account, '') != ''
order by unrealized_profit_loss_account""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts] expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
for account in tax_accounts: for account in tax_accounts:
if account not in expense_accounts: if account not in expense_accounts:
tax_columns.append(account + ":Currency/currency:120") tax_columns.append(account + ":Currency/currency:120")
columns = columns + expense_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \ columns = columns + expense_columns + unrealized_profit_loss_account_columns + \
[_("Net Total") + ":Currency/currency:120"] + tax_columns + \
[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120", [_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"] _("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
return columns, expense_accounts, tax_accounts return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
def get_conditions(filters): def get_conditions(filters):
conditions = "" conditions = ""
@@ -199,6 +217,19 @@ def get_invoice_expense_map(invoice_list):
return invoice_expense_map return invoice_expense_map
def get_internal_invoice_map(invoice_list):
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
base_net_total as amount from `tabPurchase Invoice` where name in (%s)
and is_internal_supplier = 1 and company = represents_company""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
internal_invoice_map = {}
for d in unrealized_amount_details:
if d.unrealized_profit_loss_account:
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
return internal_invoice_map
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts): def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
tax_details = frappe.db.sql(""" tax_details = frappe.db.sql("""
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount) select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)

View File

@@ -15,13 +15,14 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
if not filters: filters = frappe._dict({}) if not filters: filters = frappe._dict({})
invoice_list = get_invoices(filters, additional_query_columns) invoice_list = get_invoices(filters, additional_query_columns)
columns, income_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns) columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(invoice_list, additional_table_columns)
if not invoice_list: if not invoice_list:
msgprint(_("No record found")) msgprint(_("No record found"))
return columns, invoice_list return columns, invoice_list
invoice_income_map = get_invoice_income_map(invoice_list) invoice_income_map = get_invoice_income_map(invoice_list)
internal_invoice_map = get_internal_invoice_map(invoice_list)
invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list, invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
invoice_income_map, income_accounts) invoice_income_map, income_accounts)
#Cost Center & Warehouse Map #Cost Center & Warehouse Map
@@ -70,12 +71,22 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
# map income values # map income values
base_net_total = 0 base_net_total = 0
for income_acc in income_accounts: for income_acc in income_accounts:
if inv.is_internal_customer and inv.company == inv.represents_company:
income_amount = 0
else:
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc)) income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
base_net_total += income_amount base_net_total += income_amount
row.update({ row.update({
frappe.scrub(income_acc): income_amount frappe.scrub(income_acc): income_amount
}) })
# Add amount in unrealized account
for account in unrealized_profit_loss_accounts:
row.update({
frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account)))
})
# net total # net total
row.update({'net_total': base_net_total or inv.base_net_total}) row.update({'net_total': base_net_total or inv.base_net_total})
@@ -230,6 +241,8 @@ def get_columns(invoice_list, additional_table_columns):
tax_accounts = [] tax_accounts = []
income_columns = [] income_columns = []
tax_columns = [] tax_columns = []
unrealized_profit_loss_accounts = []
unrealized_profit_loss_account_columns = []
if invoice_list: if invoice_list:
income_accounts = frappe.db.sql_list("""select distinct income_account income_accounts = frappe.db.sql_list("""select distinct income_account
@@ -243,12 +256,18 @@ def get_columns(invoice_list, additional_table_columns):
and parent in (%s) order by account_head""" % and parent in (%s) order by account_head""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
from `tabSales Invoice` where docstatus = 1 and name in (%s)
and ifnull(unrealized_profit_loss_account, '') != ''
order by unrealized_profit_loss_account""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
for account in income_accounts: for account in income_accounts:
income_columns.append({ income_columns.append({
"label": account, "label": account,
"fieldname": frappe.scrub(account), "fieldname": frappe.scrub(account),
"fieldtype": "Currency", "fieldtype": "Currency",
"options": 'currency', "options": "currency",
"width": 120 "width": 120
}) })
@@ -258,7 +277,16 @@ def get_columns(invoice_list, additional_table_columns):
"label": account, "label": account,
"fieldname": frappe.scrub(account), "fieldname": frappe.scrub(account),
"fieldtype": "Currency", "fieldtype": "Currency",
"options": 'currency', "options": "currency",
"width": 120
})
for account in unrealized_profit_loss_accounts:
unrealized_profit_loss_account_columns.append({
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": "currency",
"width": 120 "width": 120
}) })
@@ -266,7 +294,7 @@ def get_columns(invoice_list, additional_table_columns):
"label": _("Net Total"), "label": _("Net Total"),
"fieldname": "net_total", "fieldname": "net_total",
"fieldtype": "Currency", "fieldtype": "Currency",
"options": 'currency', "options": "currency",
"width": 120 "width": 120
}] }]
@@ -301,9 +329,10 @@ def get_columns(invoice_list, additional_table_columns):
} }
] ]
columns = columns + income_columns + net_total_column + tax_columns + total_columns columns = columns + income_columns + unrealized_profit_loss_account_columns + \
net_total_column + tax_columns + total_columns
return columns, income_accounts, tax_accounts return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
def get_conditions(filters): def get_conditions(filters):
conditions = "" conditions = ""
@@ -368,7 +397,8 @@ def get_invoices(filters, additional_query_columns):
return frappe.db.sql(""" return frappe.db.sql("""
select name, posting_date, debit_to, project, customer, select name, posting_date, debit_to, project, customer,
customer_name, owner, remarks, territory, tax_id, customer_group, customer_name, owner, remarks, territory, tax_id, customer_group,
base_net_total, base_grand_total, base_rounded_total, outstanding_amount {0} base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
is_internal_customer, represents_company, company {0}
from `tabSales Invoice` from `tabSales Invoice`
where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') % where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') %
conditions, filters, as_dict=1) conditions, filters, as_dict=1)
@@ -385,6 +415,19 @@ def get_invoice_income_map(invoice_list):
return invoice_income_map return invoice_income_map
def get_internal_invoice_map(invoice_list):
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
base_net_total as amount from `tabSales Invoice` where name in (%s)
and is_internal_customer = 1 and company = represents_company""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
internal_invoice_map = {}
for d in unrealized_amount_details:
if d.unrealized_profit_loss_account:
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
return internal_invoice_map
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts): def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
tax_details = frappe.db.sql("""select parent, account_head, tax_details = frappe.db.sql("""select parent, account_head,
sum(base_tax_amount_after_discount_amount) as tax_amount sum(base_tax_amount_after_discount_amount) as tax_amount

View File

@@ -48,7 +48,7 @@ class CropCycle(Document):
def import_disease_tasks(self, disease, start_date): def import_disease_tasks(self, disease, start_date):
disease_doc = frappe.get_doc('Disease', disease) disease_doc = frappe.get_doc('Disease', disease)
self.create_task(disease_doc.treatment_task, self.name, start_date) self.create_task(disease_doc.treatment_task, self.project, start_date)
def create_project(self, period, crop_tasks): def create_project(self, period, crop_tasks):
project = frappe.get_doc({ project = frappe.get_doc({

View File

@@ -71,4 +71,4 @@ def check_task_creation():
def check_project_creation(): def check_project_creation():
return True if frappe.db.exists('Project', 'Basil from seed 2017') else False return True if frappe.db.exists('Project', {'project_name': 'Basil from seed 2017'}) else False

View File

@@ -2,6 +2,7 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.asset"); frappe.provide("erpnext.asset");
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Asset', { frappe.ui.form.on('Asset', {
onload: function(frm) { onload: function(frm) {
@@ -32,13 +33,11 @@ frappe.ui.form.on('Asset', {
}; };
}); });
frm.set_query("cost_center", function() { erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
return { },
"filters": {
"company": frm.doc.company, company: function(frm) {
} erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
};
});
}, },
setup: function(frm) { setup: function(frm) {

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Asset Value Adjustment', { frappe.ui.form.on('Asset Value Adjustment', {
setup: function(frm) { setup: function(frm) {
frm.add_fetch('company', 'cost_center', 'cost_center'); frm.add_fetch('company', 'cost_center', 'cost_center');
@@ -13,11 +15,19 @@ frappe.ui.form.on('Asset Value Adjustment', {
} }
}); });
}, },
onload: function(frm) { onload: function(frm) {
if(frm.is_new() && frm.doc.asset) { if(frm.is_new() && frm.doc.asset) {
frm.trigger("set_current_asset_value"); frm.trigger("set_current_asset_value");
} }
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
asset: function(frm) { asset: function(frm) {
frm.trigger("set_current_asset_value"); frm.trigger("set_current_asset_value");
}, },

View File

@@ -2,7 +2,7 @@
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.buying"); frappe.provide("erpnext.buying");
frappe.provide("erpnext.accounts.dimensions");
{% include 'erpnext/public/js/controllers/buying.js' %}; {% include 'erpnext/public/js/controllers/buying.js' %};
frappe.ui.form.on("Purchase Order", { frappe.ui.form.on("Purchase Order", {
@@ -30,6 +30,10 @@ frappe.ui.form.on("Purchase Order", {
}, },
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
onload: function(frm) { onload: function(frm) {
set_schedule_date(frm); set_schedule_date(frm);
if (!frm.doc.transaction_date){ if (!frm.doc.transaction_date){
@@ -39,6 +43,8 @@ frappe.ui.form.on("Purchase Order", {
erpnext.queries.setup_queries(frm, "Warehouse", function() { erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc); return erpnext.queries.warehouse(frm.doc);
}); });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
} }
}); });
@@ -158,16 +164,16 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if (doc.docstatus === 1 && !doc.inter_company_order_reference) { if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
let me = this; let me = this;
frappe.model.with_doc("Supplier", me.frm.doc.supplier, () => { let internal = me.frm.doc.is_internal_supplier;
let supplier = frappe.model.get_doc("Supplier", me.frm.doc.supplier); if (internal) {
let internal = supplier.is_internal_supplier; let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Sales Order" :
let disabled = supplier.disabled; "Inter Company Sales Order";
if (internal === 1 && disabled === 0) {
me.frm.add_custom_button("Inter Company Order", function() { me.frm.add_custom_button(button_label, function() {
me.make_inter_company_order(me.frm); me.make_inter_company_order(me.frm);
}, __('Create')); }, __('Create'));
} }
});
} }
} }
@@ -347,7 +353,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
make_purchase_receipt: function() { make_purchase_receipt: function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt", method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
frm: cur_frm frm: cur_frm,
freeze_message: __("Creating Purchase Receipt ...")
}) })
}, },
@@ -374,7 +381,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
material_request_type: "Purchase", material_request_type: "Purchase",
docstatus: 1, docstatus: 1,
status: ["!=", "Stopped"], status: ["!=", "Stopped"],
per_ordered: ["<", 99.99], per_ordered: ["<", 100],
company: me.frm.doc.company company: me.frm.doc.company
} }
}) })

View File

@@ -134,6 +134,8 @@
"ref_sq", "ref_sq",
"column_break_74", "column_break_74",
"party_account_currency", "party_account_currency",
"is_internal_supplier",
"represents_company",
"inter_company_order_reference" "inter_company_order_reference"
], ],
"fields": [ "fields": [
@@ -1101,13 +1103,28 @@
{ {
"fieldname": "items_col_break", "fieldname": "items_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"default": "0",
"fetch_from": "supplier.is_internal_supplier",
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier"
},
{
"fetch_from": "supplier.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-12-03 16:46:44.229351", "modified": "2021-01-20 22:07:23.487138",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@@ -123,8 +123,8 @@ class PurchaseOrder(BuyingController):
if self.is_subcontracted == "Yes": if self.is_subcontracted == "Yes":
for item in self.items: for item in self.items:
if not item.bom: if not item.bom:
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}"\ frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}")
.format(item.item_code, item.idx))) .format(item.item_code, item.idx))
def get_schedule_dates(self): def get_schedule_dates(self):
for d in self.get('items'): for d in self.get('items'):

View File

@@ -224,7 +224,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
material_request_type: "Purchase", material_request_type: "Purchase",
docstatus: 1, docstatus: 1,
status: ["!=", "Stopped"], status: ["!=", "Stopped"],
per_ordered: ["<", 99.99], per_ordered: ["<", 100],
company: me.frm.doc.company company: me.frm.doc.company
} }
}) })
@@ -280,7 +280,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
material_request_type: "Purchase", material_request_type: "Purchase",
docstatus: 1, docstatus: 1,
status: ["!=", "Stopped"], status: ["!=", "Stopped"],
per_ordered: ["<", 99.99] per_ordered: ["<", 100]
} }
}); });
dialog.hide(); dialog.hide();

View File

@@ -52,7 +52,10 @@ class Supplier(TransactionBase):
self.validate_internal_supplier() self.validate_internal_supplier()
def validate_internal_supplier(self): def validate_internal_supplier(self):
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"): internal_supplier = frappe.db.get_value("Supplier",
{"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
if internal_supplier:
frappe.throw(_("Internal Supplier for company {0} already exists").format( frappe.throw(_("Internal Supplier for company {0} already exists").format(
frappe.bold(self.represents_company))) frappe.bold(self.represents_company)))

View File

@@ -44,7 +44,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
material_request_type: "Purchase", material_request_type: "Purchase",
docstatus: 1, docstatus: 1,
status: ["!=", "Stopped"], status: ["!=", "Stopped"],
per_ordered: ["<", 99.99], per_ordered: ["<", 100],
company: me.frm.doc.company company: me.frm.doc.company
} }
}) })

View File

@@ -35,9 +35,10 @@ def update_last_purchase_rate(doc, is_submit):
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
# update last purchsae rate # update last purchsae rate
if last_purchase_rate: frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate))
frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
(flt(last_purchase_rate), d.item_code))
def validate_for_items(doc): def validate_for_items(doc):
items = [] items = []

View File

@@ -75,6 +75,9 @@ class AccountsController(TransactionBase):
self.ensure_supplier_is_not_blocked() self.ensure_supplier_is_not_blocked()
self.validate_date_with_fiscal_year() self.validate_date_with_fiscal_year()
self.validate_inter_company_reference()
self.set_incoming_rate()
if self.meta.get_field("currency"): if self.meta.get_field("currency"):
self.calculate_taxes_and_totals() self.calculate_taxes_and_totals()
@@ -119,6 +122,12 @@ class AccountsController(TransactionBase):
def before_cancel(self): def before_cancel(self):
validate_einvoice_fields(self) validate_einvoice_fields(self)
def on_trash(self):
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
frappe.db.sql("delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
frappe.db.sql("delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
def validate_deferred_start_and_end_date(self): def validate_deferred_start_and_end_date(self):
for d in self.items: for d in self.items:
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"): if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
@@ -206,6 +215,17 @@ class AccountsController(TransactionBase):
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company, validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
self.meta.get_label(date_field), self) self.meta.get_label(date_field), self)
def validate_inter_company_reference(self):
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
return
if self.is_internal_transfer():
if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
or self.get('inter_company_order_reference')):
msg = _("Internal Sale or Delivery Reference missing. ")
msg += _("Please create purchase from internal sale or delivery document itself")
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
def validate_due_date(self): def validate_due_date(self):
if self.get('is_pos'): return if self.get('is_pos'): return
@@ -448,8 +468,10 @@ class AccountsController(TransactionBase):
account_currency = get_account_currency(gl_dict.account) account_currency = get_account_currency(gl_dict.account)
if gl_dict.account and self.doctype not in ["Journal Entry", if gl_dict.account and self.doctype not in ["Journal Entry",
"Period Closing Voucher", "Payment Entry"]: "Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
self.validate_account_currency(gl_dict.account, account_currency) self.validate_account_currency(gl_dict.account, account_currency)
if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]:
set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"), set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
self.company_currency) self.company_currency)
@@ -962,9 +984,9 @@ class AccountsController(TransactionBase):
It will an internal transfer if its an internal customer and representation It will an internal transfer if its an internal customer and representation
company is same as billing company company is same as billing company
""" """
if self.doctype == 'Sales Invoice': if self.doctype in ('Sales Invoice', 'Delivery Note', 'Sales Order'):
internal_party_field = 'is_internal_customer' internal_party_field = 'is_internal_customer'
else: elif self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
internal_party_field = 'is_internal_supplier' internal_party_field = 'is_internal_supplier'
if self.get(internal_party_field) and (self.represents_company == self.company): if self.get(internal_party_field) and (self.represents_company == self.company):

View File

@@ -44,7 +44,6 @@ class BuyingController(StockController):
self.validate_items() self.validate_items()
self.set_qty_as_per_stock_uom() self.set_qty_as_per_stock_uom()
self.validate_stock_or_nonstock_items() self.validate_stock_or_nonstock_items()
self.update_tax_category_for_internal_transfer()
self.validate_warehouse() self.validate_warehouse()
self.validate_from_warehouse() self.validate_from_warehouse()
self.set_supplier_address() self.set_supplier_address()
@@ -100,11 +99,6 @@ class BuyingController(StockController):
msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items') msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
self.update_tax_category(msg) self.update_tax_category(msg)
def update_tax_category_for_internal_transfer(self):
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
self.update_tax_category(msg)
def update_tax_category(self, msg): def update_tax_category(self, msg):
tax_for_valuation = [d for d in self.get("taxes") tax_for_valuation = [d for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]] if d.category in ["Valuation", "Valuation and Total"]]
@@ -224,6 +218,48 @@ class BuyingController(StockController):
else: else:
item.valuation_rate = 0.0 item.valuation_rate = 0.0
def set_incoming_rate(self):
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
return
ref_doctype_map = {
"Purchase Order": "Sales Order Item",
"Purchase Receipt": "Delivery Note Item",
"Purchase Invoice": "Sales Invoice Item",
}
ref_doctype = ref_doctype_map.get(self.doctype)
items = self.get("items")
for d in items:
if not cint(self.get("is_return")):
# Get outgoing rate based on original item cost based on valuation method
if not d.get(frappe.scrub(ref_doctype)):
outgoing_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.get('from_warehouse'),
"posting_date": self.get('posting_date') or self.get('transation_date'),
"posting_time": self.get('posting_time'),
"qty": -1 * flt(d.get('stock_qty')),
"serial_no": d.get('serial_no'),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation")
}, raise_error_if_no_rate=False)
rate = flt(outgoing_rate * d.conversion_factor, d.precision('rate'))
else:
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), 'rate')
if self.is_internal_transfer():
if rate != d.rate:
d.rate = rate
d.discount_percentage = 0
d.discount_amount = 0
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
.format(d.idx), alert=1)
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True): def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
supplied_items_cost = 0.0 supplied_items_cost = 0.0
for d in self.get("supplied_items"): for d in self.get("supplied_items"):
@@ -336,7 +372,7 @@ class BuyingController(StockController):
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)
consumed_serial_nos = raw_material_data.get('serial_nos', '') consumed_serial_nos = raw_material_data.get('serial_no', '')
consumed_batch_nos = raw_material_data.get('batch_nos', '') consumed_batch_nos = raw_material_data.get('batch_nos', '')
transferred_qty = raw_material.qty transferred_qty = raw_material.qty
@@ -559,6 +595,8 @@ class BuyingController(StockController):
from_warehouse_sle = self.get_sl_entries(d, { from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty, "actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse, "warehouse": d.from_warehouse,
"outgoing_rate": d.rate,
"recalculate_rate": 1,
"dependant_sle_voucher_detail_no": d.name "dependant_sle_voucher_detail_no": d.name
}) })

View File

@@ -493,6 +493,41 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
'company': filters.get("company", "") 'company': filters.get("company", "")
}) })
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters):
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
dimension_filters = get_dimension_filter_map()
dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
query_filters = []
meta = frappe.get_meta(doctype)
if meta.is_tree:
query_filters.append(['is_group', '=', 0])
if meta.has_field('company'):
query_filters.append(['company', '=', filters.get('company')])
if txt:
query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt])
if dimension_filters:
if dimension_filters['allow_or_restrict'] == 'Allow':
query_selector = 'in'
else:
query_selector = 'not in'
if len(dimension_filters['allowed_dimensions']) == 1:
dimensions = tuple(dimension_filters['allowed_dimensions'] * 2)
else:
dimensions = tuple(dimension_filters['allowed_dimensions'])
query_filters.append(['name', query_selector, dimensions])
output = frappe.get_all(doctype, filters=query_filters)
result = [d.name for d in output]
return [(d,) for d in set(result)]
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
@@ -620,6 +655,34 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(query, filters) return frappe.db.sql(query, filters)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters):
query = """
select name
from `tabHealthcare Service Unit`
where
is_group = 0
and company = {company}
and name like {txt}""".format(
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
if filters and filters.get('inpatient_record'):
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
# if the patient is admitted, then appointments should be allowed against the admission service unit,
# inspite of it being an Inpatient Occupancy service unit
if service_unit:
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
else:
query += " and allow_appointments = 1"
else:
query += " and allow_appointments = 1"
return frappe.db.sql(query, filters)
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def get_tax_template(doctype, txt, searchfield, start, page_len, filters): def get_tax_template(doctype, txt, searchfield, start, page_len, filters):

View File

@@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form from frappe.utils import cint, flt, cstr, get_link_to_form, nowtime
from frappe import _, throw from frappe import _, throw
from erpnext.stock.get_item_details import get_bin_details from erpnext.stock.get_item_details import get_bin_details
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate
@@ -49,7 +49,6 @@ class SellingController(StockController):
self.set_customer_address() self.set_customer_address()
self.validate_for_duplicate_items() self.validate_for_duplicate_items()
self.validate_target_warehouse() self.validate_target_warehouse()
self.set_incoming_rate()
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
@@ -312,7 +311,7 @@ class SellingController(StockController):
sales_order.update_reserved_qty(so_item_rows) sales_order.update_reserved_qty(so_item_rows)
def set_incoming_rate(self): def set_incoming_rate(self):
if self.doctype not in ("Delivery Note", "Sales Invoice"): if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
return return
items = self.get("items") + (self.get("packed_items") or []) items = self.get("items") + (self.get("packed_items") or [])
@@ -322,15 +321,26 @@ class SellingController(StockController):
d.incoming_rate = get_incoming_rate({ d.incoming_rate = get_incoming_rate({
"item_code": d.item_code, "item_code": d.item_code,
"warehouse": d.warehouse, "warehouse": d.warehouse,
"posting_date": self.posting_date, "posting_date": self.get('posting_date') or self.get('transaction_date'),
"posting_time": self.posting_time, "posting_time": self.get('posting_time') or nowtime(),
"qty": -1*flt(d.qty), "qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
"serial_no": d.serial_no, "serial_no": d.get('serial_no'),
"company": self.company, "company": self.company,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_no": self.name, "voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation") "allow_zero_valuation": d.get("allow_zero_valuation")
}, raise_error_if_no_rate=False) }, raise_error_if_no_rate=False)
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate'))
if d.rate != rate:
d.rate = rate
d.discount_percentage = 0
d.discount_amount = 0
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
.format(d.idx), alert=1)
elif self.get("return_against"): elif self.get("return_against"):
# Get incoming rate of return entry from reference document # Get incoming rate of return entry from reference document
# based on original item cost as per valuation method # based on original item cost as per valuation method

View File

@@ -6,6 +6,7 @@ import frappe, erpnext
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
from frappe import _ from frappe import _
import frappe.defaults import frappe.defaults
from collections import defaultdict
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@@ -23,6 +24,8 @@ class StockController(AccountsController):
self.validate_inspection() self.validate_inspection()
self.validate_serialized_batch() self.validate_serialized_batch()
self.validate_customer_provided_item() self.validate_customer_provided_item()
self.validate_internal_transfer()
self.validate_putaway_capacity()
def make_gl_entries(self, gl_entries=None, from_repost=False): def make_gl_entries(self, gl_entries=None, from_repost=False):
if self.docstatus == 2: if self.docstatus == 2:
@@ -72,6 +75,7 @@ class StockController(AccountsController):
warehouse_with_no_account = [] warehouse_with_no_account = []
precision = frappe.get_precision("GL Entry", "debit_in_account_currency") precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
for item_row in voucher_details: for item_row in voucher_details:
sle_list = sle_map.get(item_row.name) sle_list = sle_map.get(item_row.name)
if sle_list: if sle_list:
for sle in sle_list: for sle in sle_list:
@@ -391,6 +395,84 @@ class StockController(AccountsController):
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1 d.allow_zero_valuation_rate = 1
def validate_internal_transfer(self):
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
and self.is_internal_transfer():
self.validate_in_transit_warehouses()
self.validate_multi_currency()
self.validate_packed_items()
def validate_in_transit_warehouses(self):
if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note':
for item in self.get('items'):
if not item.target_warehouse:
frappe.throw(_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx))
if (self.doctype == 'Purchase Invoice' and self.get('update_stock')) or self.doctype == 'Purchase Receipt':
for item in self.get('items'):
if not item.from_warehouse:
frappe.throw(_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx))
def validate_multi_currency(self):
if self.currency != self.company_currency:
frappe.throw(_("Internal transfers can only be done in company's default currency"))
def validate_packed_items(self):
if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'):
frappe.throw(_("Packed Items cannot be transferred internally"))
def validate_putaway_capacity(self):
# if over receipt is attempted while 'apply putaway rule' is disabled
# and if rule was applied on the transaction, validate it.
from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
"Stock Reconciliation")
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
valid_doctype = False
if valid_doctype:
rule_map = defaultdict(dict)
for item in self.get("items"):
warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
rule = frappe.db.get_value("Putaway Rule",
{
"item_code": item.get("item_code"),
"warehouse": item.get(warehouse_field)
},
["name", "disable"], as_dict=True)
if rule:
if rule.get("disabled"): continue # dont validate for disabled rule
if self.doctype == "Stock Reconciliation":
stock_qty = flt(item.qty)
else:
stock_qty = flt(item.transfer_qty) if self.doctype == "Stock Entry" else flt(item.stock_qty)
rule_name = rule.get("name")
if not rule_map[rule_name]:
rule_map[rule_name]["warehouse"] = item.get(warehouse_field)
rule_map[rule_name]["item"] = item.get("item_code")
rule_map[rule_name]["qty_put"] = 0
rule_map[rule_name]["capacity"] = get_available_putaway_capacity(rule_name)
rule_map[rule_name]["qty_put"] += flt(stock_qty)
for rule, values in rule_map.items():
if flt(values["qty_put"]) > flt(values["capacity"]):
message = self.prepare_over_receipt_message(rule, values)
frappe.throw(msg=message, title=_("Over Receipt"))
def prepare_over_receipt_message(self, rule, values):
message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
.format(
frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
)
message += "<br><br>"
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
return message
def repost_future_sle_and_gle(self): def repost_future_sle_and_gle(self):
args = frappe._dict({ args = frappe._dict({
"posting_date": self.posting_date, "posting_date": self.posting_date,

View File

@@ -10,6 +10,7 @@ from erpnext.controllers.accounts_controller import validate_conversion_rate, \
validate_taxes_and_charges, validate_inclusive_tax validate_taxes_and_charges, validate_inclusive_tax
from erpnext.stock.get_item_details import _get_item_tax_template from erpnext.stock.get_item_details import _get_item_tax_template
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
class calculate_taxes_and_totals(object): class calculate_taxes_and_totals(object):
def __init__(self, doc): def __init__(self, doc):
@@ -758,3 +759,35 @@ def get_rounded_tax_amount(itemised_tax, precision):
for taxes in itemised_tax.values(): for taxes in itemised_tax.values():
for tax_account in taxes: for tax_account in taxes:
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision) taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
class init_landed_taxes_and_totals(object):
def __init__(self, doc):
self.doc = doc
self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
self.set_account_currency()
self.set_exchange_rate()
self.set_amounts_in_company_currency()
def set_account_currency(self):
company_currency = erpnext.get_company_currency(self.doc.company)
for d in self.doc.get(self.tax_field):
if not d.account_currency:
account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
d.account_currency = account_currency or company_currency
def set_exchange_rate(self):
company_currency = erpnext.get_company_currency(self.doc.company)
for d in self.doc.get(self.tax_field):
if d.account_currency == company_currency:
d.exchange_rate = 1
elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date:
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
account_currency=d.account_currency, company=self.doc.company)
if not d.exchange_rate:
frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
def set_amounts_in_company_currency(self):
for d in self.doc.get(self.tax_field):
d.amount = flt(d.amount, d.precision("amount"))
d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))

View File

@@ -8,12 +8,12 @@
"is_mandatory": 0, "is_mandatory": 0,
"is_single": 0, "is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2020-05-14 17:38:27.496696", "modified": "2021-01-21 15:28:52.483839",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Create Opportunity", "name": "Create Opportunity",
"owner": "Administrator", "owner": "Administrator",
"reference_document": "Opportunity", "reference_document": "Opportunity",
"show_full_form": 0, "show_full_form": 1,
"title": "Create Opportunity", "title": "Create Opportunity",
"validate_action": 1 "validate_action": 1
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Fee Schedule', { frappe.ui.form.on('Fee Schedule', {
setup: function(frm) { setup: function(frm) {
frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account'); frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account');
@@ -8,6 +9,10 @@ frappe.ui.form.on('Fee Schedule', {
frm.add_fetch('fee_structure', 'cost_center', 'cost_center'); frm.add_fetch('fee_structure', 'cost_center', 'cost_center');
}, },
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
onload: function(frm) { onload: function(frm) {
frm.set_query('receivable_account', function(doc) { frm.set_query('receivable_account', function(doc) {
return { return {
@@ -50,6 +55,8 @@ frappe.ui.form.on('Fee Schedule', {
} }
} }
}); });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Fee Structure', { frappe.ui.form.on('Fee Structure', {
setup: function(frm) { setup: function(frm) {
frm.add_fetch('company', 'default_receivable_account', 'receivable_account'); frm.add_fetch('company', 'default_receivable_account', 'receivable_account');
@@ -8,6 +10,10 @@ frappe.ui.form.on('Fee Structure', {
frm.add_fetch('company', 'cost_center', 'cost_center'); frm.add_fetch('company', 'cost_center', 'cost_center');
}, },
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
onload: function(frm) { onload: function(frm) {
frm.set_query('academic_term', function() { frm.set_query('academic_term', function() {
return { return {
@@ -35,6 +41,8 @@ frappe.ui.form.on('Fee Structure', {
} }
}; };
}); });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on("Fees", { frappe.ui.form.on("Fees", {
setup: function(frm) { setup: function(frm) {
@@ -9,6 +10,10 @@ frappe.ui.form.on("Fees", {
frm.add_fetch("fee_structure", "cost_center", "cost_center"); frm.add_fetch("fee_structure", "cost_center", "cost_center");
}, },
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
onload: function(frm) { onload: function(frm) {
frm.set_query("academic_term", function() { frm.set_query("academic_term", function() {
return{ return{
@@ -45,6 +50,8 @@ frappe.ui.form.on("Fees", {
if (!frm.doc.posting_date) { if (!frm.doc.posting_date) {
frm.doc.posting_date = frappe.datetime.get_today(); frm.doc.posting_date = frappe.datetime.get_today();
} }
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@@ -30,13 +30,10 @@ class PlaidConnector():
access_token = response["access_token"] access_token = response["access_token"]
return access_token return access_token
def get_link_token(self): def get_token_request(self, update_mode=False):
country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"] country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"]
token_request = { args = {
"client_name": self.client_name, "client_name": self.client_name,
"client_id": self.settings.plaid_client_id,
"secret": self.settings.plaid_secret,
"products": self.products,
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020) # only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en", "language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
"country_codes": country_codes, "country_codes": country_codes,
@@ -45,6 +42,20 @@ class PlaidConnector():
} }
} }
if update_mode:
args["access_token"] = self.access_token
else:
args.update({
"client_id": self.settings.plaid_client_id,
"secret": self.settings.plaid_secret,
"products": self.products,
})
return args
def get_link_token(self, update_mode=False):
token_request = self.get_token_request(update_mode)
try: try:
response = self.client.LinkToken.create(token_request) response = self.client.LinkToken.create(token_request)
except InvalidRequestError: except InvalidRequestError:

View File

@@ -12,9 +12,25 @@ frappe.ui.form.on('Plaid Settings', {
refresh: function (frm) { refresh: function (frm) {
if (frm.doc.enabled) { if (frm.doc.enabled) {
frm.add_custom_button('Link a new bank account', () => { frm.add_custom_button(__('Link a new bank account'), () => {
new erpnext.integrations.plaidLink(frm); new erpnext.integrations.plaidLink(frm);
}); });
frm.add_custom_button(__("Sync Now"), () => {
frappe.call({
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
freeze: true,
callback: () => {
let bank_transaction_link = '<a href="#List/Bank Transaction">Bank Transaction</a>';
frappe.msgprint({
title: __("Sync Started"),
message: __("The sync has started in the background, please check the {0} list for new records.", [bank_transaction_link]),
alert: 1
});
}
});
}).addClass("btn-primary");
} }
} }
}); });
@@ -30,10 +46,18 @@ erpnext.integrations.plaidLink = class plaidLink {
this.product = ["auth", "transactions"]; this.product = ["auth", "transactions"];
this.plaid_env = this.frm.doc.plaid_env; this.plaid_env = this.frm.doc.plaid_env;
this.client_name = frappe.boot.sitename; this.client_name = frappe.boot.sitename;
this.token = await this.frm.call("get_link_token").then(resp => resp.message); this.token = await this.get_link_token();
this.init_plaid(); this.init_plaid();
} }
async get_link_token() {
const token = await this.frm.call("get_link_token").then(resp => resp.message);
if (!token) {
frappe.throw(__('Cannot retrieve link token. Check Error Log for more information'));
}
return token;
}
init_plaid() { init_plaid() {
const me = this; const me = this;
me.loadScript(me.plaidUrl) me.loadScript(me.plaidUrl)
@@ -78,8 +102,8 @@ erpnext.integrations.plaidLink = class plaidLink {
} }
onScriptError(error) { onScriptError(error) {
frappe.msgprint("There was an issue connecting to Plaid's authentication server"); frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
frappe.msgprint(error); console.log(error);
} }
plaid_success(token, response) { plaid_success(token, response) {

View File

@@ -166,7 +166,6 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True) related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True)
access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token") access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
account_id = related_bank[0].integration_id account_id = related_bank[0].integration_id
else: else:
access_token = frappe.db.get_value("Bank", bank, "plaid_access_token") access_token = frappe.db.get_value("Bank", bank, "plaid_access_token")
account_id = None account_id = None
@@ -228,9 +227,14 @@ def new_bank_transaction(transaction):
def automatic_synchronization(): def automatic_synchronization():
settings = frappe.get_doc("Plaid Settings", "Plaid Settings") settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
if settings.enabled == 1 and settings.automatic_sync == 1: if settings.enabled == 1 and settings.automatic_sync == 1:
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"]) enqueue_synchronization()
@frappe.whitelist()
def enqueue_synchronization():
plaid_accounts = frappe.get_all("Bank Account",
filters={"integration_id": ["!=", ""]},
fields=["name", "bank"])
for plaid_account in plaid_accounts: for plaid_account in plaid_accounts:
frappe.enqueue( frappe.enqueue(
@@ -238,3 +242,8 @@ def automatic_synchronization():
bank=plaid_account.bank, bank=plaid_account.bank,
bank_account=plaid_account.name bank_account=plaid_account.name
) )
@frappe.whitelist()
def get_link_token_for_update(access_token):
plaid = PlaidConnector(access_token)
return plaid.get_link_token(update_mode=True)

View File

@@ -6,3 +6,5 @@ class PartyFrozen(frappe.ValidationError): pass
class InvalidAccountCurrency(frappe.ValidationError): pass class InvalidAccountCurrency(frappe.ValidationError): pass
class InvalidCurrency(frappe.ValidationError): pass class InvalidCurrency(frappe.ValidationError): pass
class PartyDisabled(frappe.ValidationError):pass class PartyDisabled(frappe.ValidationError):pass
class InvalidAccountDimensionError(frappe.ValidationError): pass
class MandatoryAccountDimensionError(frappe.ValidationError): pass

View File

@@ -100,7 +100,6 @@ class ClinicalProcedure(Document):
allow_start = self.set_actual_qty() allow_start = self.set_actual_qty()
if allow_start: if allow_start:
self.db_set('status', 'In Progress') self.db_set('status', 'In Progress')
insert_clinical_procedure_to_medical_record(self)
return 'success' return 'success'
return 'insufficient stock' return 'insufficient stock'
@@ -247,21 +246,3 @@ def make_procedure(source_name, target_doc=None):
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doc return doc
def insert_clinical_procedure_to_medical_record(doc):
subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
if subject and doc.notes:
subject += '<br/>' + doc.notes
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.start_date
medical_record.reference_doctype = 'Clinical Procedure'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)

View File

@@ -19,6 +19,7 @@
"valid_days", "valid_days",
"inpatient_settings_section", "inpatient_settings_section",
"allow_discharge_despite_unbilled_services", "allow_discharge_despite_unbilled_services",
"do_not_bill_inpatient_encounters",
"healthcare_service_items", "healthcare_service_items",
"inpatient_visit_charge_item", "inpatient_visit_charge_item",
"op_consulting_charge_item", "op_consulting_charge_item",
@@ -315,11 +316,17 @@
"fieldname": "allow_discharge_despite_unbilled_services", "fieldname": "allow_discharge_despite_unbilled_services",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Allow Discharge Despite Unbilled Healthcare Services" "label": "Allow Discharge Despite Unbilled Healthcare Services"
},
{
"default": "0",
"fieldname": "do_not_bill_inpatient_encounters",
"fieldtype": "Check",
"label": "Do Not Bill Patient Encounters for Inpatients"
} }
], ],
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-01-04 10:19:22.329272", "modified": "2021-01-13 09:04:35.877700",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Healthcare Settings", "name": "Healthcare Settings",

View File

@@ -264,7 +264,7 @@ def get_filters(entry):
def get_current_healthcare_service_unit(inpatient_record): def get_current_healthcare_service_unit(inpatient_record):
ip_record = frappe.get_doc('Inpatient Record', inpatient_record) ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
if ip_record.inpatient_occupancies: if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies:
return ip_record.inpatient_occupancies[-1].service_unit return ip_record.inpatient_occupancies[-1].service_unit
return return

View File

@@ -8,6 +8,8 @@ import unittest
from frappe.utils import now_datetime, today from frappe.utils import now_datetime, today
from frappe.utils.make_random import get_random from frappe.utils.make_random import get_random
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter
from erpnext.healthcare.utils import get_encounters_to_invoice
class TestInpatientRecord(unittest.TestCase): class TestInpatientRecord(unittest.TestCase):
def test_admit_and_discharge(self): def test_admit_and_discharge(self):
@@ -42,7 +44,7 @@ class TestInpatientRecord(unittest.TestCase):
def test_allow_discharge_despite_unbilled_services(self): def test_allow_discharge_despite_unbilled_services(self):
frappe.db.sql("""delete from `tabInpatient Record`""") frappe.db.sql("""delete from `tabInpatient Record`""")
setup_inpatient_settings() setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=1)
patient = create_patient() patient = create_patient()
# Schedule Admission # Schedule Admission
ip_record = create_inpatient(patient) ip_record = create_inpatient(patient)
@@ -64,6 +66,35 @@ class TestInpatientRecord(unittest.TestCase):
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record")) self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status")) self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=0)
def test_do_not_bill_patient_encounters_for_inpatients(self):
frappe.db.sql("""delete from `tabInpatient Record`""")
setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=1)
patient = create_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
# Admit
service_unit = get_healthcare_service_unit()
admit_patient(ip_record, service_unit, now_datetime())
# Patient Encounter
patient_encounter = create_patient_encounter()
encounters = get_encounters_to_invoice(patient, "_Test Company")
encounter_ids = [entry.reference_name for entry in encounters]
self.assertFalse(patient_encounter.name in encounter_ids)
# Discharge
schedule_discharge(frappe.as_json({"patient": patient}))
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
mark_invoiced_inpatient_occupancy(ip_record)
discharge_patient(ip_record)
setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=0)
def test_validate_overlap_admission(self): def test_validate_overlap_admission(self):
frappe.db.sql("""delete from `tabInpatient Record`""") frappe.db.sql("""delete from `tabInpatient Record`""")
@@ -89,9 +120,9 @@ def mark_invoiced_inpatient_occupancy(ip_record):
ip_record.save(ignore_permissions = True) ip_record.save(ignore_permissions = True)
def setup_inpatient_settings(): def setup_inpatient_settings(key, value):
settings = frappe.get_single("Healthcare Settings") settings = frappe.get_single("Healthcare Settings")
settings.allow_discharge_despite_unbilled_services = 1 settings.set(key, value)
settings.save() settings.save()
@@ -111,11 +142,15 @@ def create_inpatient(patient):
return inpatient_record return inpatient_record
def get_healthcare_service_unit(): def get_healthcare_service_unit(unit_name=None):
if not unit_name:
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1}) service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
else:
service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name})
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 = unit_name or "Test Service Unit Ip Occupancy"
service_unit.company = "_Test Company" 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

View File

@@ -359,6 +359,7 @@
{ {
"fieldname": "normal_test_items", "fieldname": "normal_test_items",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Normal Test Result",
"options": "Normal Test Result", "options": "Normal Test Result",
"print_hide": 1 "print_hide": 1
}, },
@@ -380,6 +381,7 @@
{ {
"fieldname": "sensitivity_test_items", "fieldname": "sensitivity_test_items",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Sensitivity Test Result",
"options": "Sensitivity Test Result", "options": "Sensitivity Test Result",
"print_hide": 1, "print_hide": 1,
"report_hide": 1 "report_hide": 1
@@ -529,6 +531,7 @@
{ {
"fieldname": "descriptive_test_items", "fieldname": "descriptive_test_items",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Descriptive Test Result",
"options": "Descriptive Test Result", "options": "Descriptive Test Result",
"print_hide": 1, "print_hide": 1,
"report_hide": 1 "report_hide": 1
@@ -549,13 +552,14 @@
{ {
"fieldname": "organism_test_items", "fieldname": "organism_test_items",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Organism Test Result",
"options": "Organism Test Result", "options": "Organism Test Result",
"print_hide": 1 "print_hide": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-07-30 18:18:38.516215", "modified": "2020-11-30 11:04:17.195848",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Lab Test", "name": "Lab Test",

View File

@@ -17,11 +17,9 @@ class LabTest(Document):
self.validate_result_values() self.validate_result_values()
self.db_set('submitted_date', getdate()) self.db_set('submitted_date', getdate())
self.db_set('status', 'Completed') self.db_set('status', 'Completed')
insert_lab_test_to_medical_record(self)
def on_cancel(self): def on_cancel(self):
self.db_set('status', 'Cancelled') self.db_set('status', 'Cancelled')
delete_lab_test_from_medical_record(self)
self.reload() self.reload()
def on_update(self): def on_update(self):
@@ -330,60 +328,6 @@ def get_employee_by_user_id(user_id):
return frappe.get_doc('Employee', emp_id) return frappe.get_doc('Employee', emp_id)
return None return None
def insert_lab_test_to_medical_record(doc):
table_row = False
subject = cstr(doc.lab_test_name)
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '<br>'
if doc.normal_test_items:
item = doc.normal_test_items[0]
comment = ''
if item.lab_test_comment:
comment = str(item.lab_test_comment)
table_row = frappe.bold(_('Lab Test Conducted: ')) + item.lab_test_name
if item.lab_test_event:
table_row += frappe.bold(_('Lab Test Event: ')) + item.lab_test_event
if item.result_value:
table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
if item.normal_range:
table_row += ' ' + _('Normal Range: ') + item.normal_range
table_row += ' ' + comment
elif doc.descriptive_test_items:
item = doc.descriptive_test_items[0]
if item.lab_test_particulars and item.result_value:
table_row = item.lab_test_particulars + ' ' + item.result_value
elif doc.sensitivity_test_items:
item = doc.sensitivity_test_items[0]
if item.antibiotic and item.antibiotic_sensitivity:
table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity
if table_row:
subject += '<br>' + table_row
if doc.lab_test_comment:
subject += '<br>' + cstr(doc.lab_test_comment)
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.result_date
medical_record.reference_doctype = 'Lab Test'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions = True)
def delete_lab_test_from_medical_record(self):
medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name))
if medical_record_id and medical_record_id[0][0]:
frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])
@frappe.whitelist() @frappe.whitelist()
def get_lab_test_prescribed(patient): def get_lab_test_prescribed(patient):

View File

@@ -33,10 +33,10 @@ frappe.ui.form.on('Patient Appointment', {
frm.set_query('service_unit', function() { frm.set_query('service_unit', function() {
return { return {
query: 'erpnext.controllers.queries.get_healthcare_service_units',
filters: { filters: {
'is_group': false, company: frm.doc.company,
'allow_appointments': true, inpatient_record: frm.doc.inpatient_record
'company': frm.doc.company
} }
}; };
}); });

View File

@@ -18,6 +18,7 @@ from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_pr
class PatientAppointment(Document): class PatientAppointment(Document):
def validate(self): def validate(self):
self.validate_overlaps() self.validate_overlaps()
self.validate_service_unit()
self.set_appointment_datetime() self.set_appointment_datetime()
self.validate_customer_created() self.validate_customer_created()
self.set_status() self.set_status()
@@ -68,6 +69,19 @@ class PatientAppointment(Document):
overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4]) overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
frappe.throw(overlapping_details, title=_('Appointments Overlapping')) frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
def validate_service_unit(self):
if self.inpatient_record and self.service_unit:
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
is_inpatient_occupancy_unit = frappe.db.get_value('Healthcare Service Unit', self.service_unit,
'inpatient_occupancy')
service_unit = get_current_healthcare_service_unit(self.inpatient_record)
if is_inpatient_occupancy_unit and service_unit != self.service_unit:
msg = _('Patient {0} is not admitted in the service unit {1}').format(frappe.bold(self.patient), frappe.bold(self.service_unit)) + '<br>'
msg += _('Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted.')
frappe.throw(msg, title=_('Invalid Healthcare Service Unit'))
def set_appointment_datetime(self): def set_appointment_datetime(self):
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00") self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")

View File

@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
from frappe.utils import nowdate, add_days from frappe.utils import nowdate, add_days, now_datetime
from frappe.utils.make_random import get_random from frappe.utils.make_random import get_random
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
@@ -78,6 +78,59 @@ class TestPatientAppointment(unittest.TestCase):
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled') self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
def test_appointment_booking_for_admission_service_unit(self):
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
frappe.db.sql("""delete from `tabInpatient Record`""")
patient, medical_department, practitioner = create_healthcare_docs()
patient = create_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
# Admit
service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
admit_patient(ip_record, service_unit, now_datetime())
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit)
self.assertEqual(appointment.service_unit, service_unit)
# Discharge
schedule_discharge(frappe.as_json({'patient': patient}))
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
mark_invoiced_inpatient_occupancy(ip_record1)
discharge_patient(ip_record1)
def test_invalid_healthcare_service_unit_validation(self):
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
frappe.db.sql("""delete from `tabInpatient Record`""")
patient, medical_department, practitioner = create_healthcare_docs()
patient = create_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
# Admit
service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
admit_patient(ip_record, service_unit, now_datetime())
appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment')
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0)
self.assertRaises(frappe.exceptions.ValidationError, appointment.save)
# Discharge
schedule_discharge(frappe.as_json({'patient': patient}))
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
mark_invoiced_inpatient_occupancy(ip_record1)
discharge_patient(ip_record1)
def create_healthcare_docs(): def create_healthcare_docs():
patient = create_patient() patient = create_patient()
@@ -125,7 +178,7 @@ def create_encounter(appointment):
encounter.submit() encounter.submit()
return encounter return encounter
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0): def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1):
item = create_healthcare_service_items() item = create_healthcare_service_items()
frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item) frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item) frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
@@ -136,11 +189,14 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
appointment.appointment_date = appointment_date appointment.appointment_date = appointment_date
appointment.company = '_Test Company' appointment.company = '_Test Company'
appointment.duration = 15 appointment.duration = 15
if service_unit:
appointment.service_unit = service_unit
if invoice: if invoice:
appointment.mode_of_payment = 'Cash' appointment.mode_of_payment = 'Cash'
appointment.paid_amount = 500 appointment.paid_amount = 500
if procedure_template: if procedure_template:
appointment.procedure_template = create_clinical_procedure_template().get('name') appointment.procedure_template = create_clinical_procedure_template().get('name')
if save:
appointment.save(ignore_permissions=True) appointment.save(ignore_permissions=True)
return appointment return appointment
@@ -152,6 +208,7 @@ def create_healthcare_service_items():
item.item_name = 'Consulting Charges' item.item_name = 'Consulting Charges'
item.item_group = 'Services' item.item_group = 'Services'
item.is_stock_item = 0 item.is_stock_item = 0
item.stock_uom = 'Nos'
item.save() item.save()
return item.name return item.name

View File

@@ -210,7 +210,7 @@
{ {
"fieldname": "drug_prescription", "fieldname": "drug_prescription",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Items", "label": "Drug Prescription",
"options": "Drug Prescription" "options": "Drug Prescription"
}, },
{ {
@@ -328,7 +328,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-16 21:00:08.644531", "modified": "2020-11-30 10:39:00.783119",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Patient Encounter", "name": "Patient Encounter",

View File

@@ -17,10 +17,6 @@ class PatientEncounter(Document):
def on_update(self): def on_update(self):
if self.appointment: if self.appointment:
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed') frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
update_encounter_medical_record(self)
def after_insert(self):
insert_encounter_to_medical_record(self)
def on_submit(self): def on_submit(self):
if self.therapies: if self.therapies:
@@ -33,8 +29,6 @@ class PatientEncounter(Document):
if self.inpatient_record and self.drug_prescription: if self.inpatient_record and self.drug_prescription:
delete_ip_medication_order(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]
@@ -102,61 +96,7 @@ def create_therapy_plan(encounter):
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):
subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.encounter_date
medical_record.reference_doctype = 'Patient Encounter'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)
def update_encounter_medical_record(encounter):
medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
if medical_record_id and medical_record_id[0][0]:
subject = set_subject_field(encounter)
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
else:
insert_encounter_to_medical_record(encounter)
def delete_medical_record(encounter):
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): def delete_ip_medication_order(encounter):
record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name}) record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
if record: if record:
frappe.delete_doc('Inpatient Medication Order', record, force=1) frappe.delete_doc('Inpatient Medication Order', record, force=1)
def set_subject_field(encounter):
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
if encounter.symptoms:
subject += frappe.bold(_('Symptoms: ')) + '<br>'
for entry in encounter.symptoms:
subject += cstr(entry.complaint) + '<br>'
else:
subject += frappe.bold(_('No Symptoms')) + '<br>'
if encounter.diagnosis:
subject += frappe.bold(_('Diagnosis: ')) + '<br>'
for entry in encounter.diagnosis:
subject += cstr(entry.diagnosis) + '<br>'
else:
subject += frappe.bold(_('No Diagnosis')) + '<br>'
if encounter.drug_prescription:
subject += '<br>' + _('Drug(s) Prescribed.')
if encounter.lab_test_prescription:
subject += '<br>' + _('Test(s) Prescribed.')
if encounter.procedure_prescription:
subject += '<br>' + _('Procedure(s) Prescribed.')
return subject

View File

@@ -0,0 +1,55 @@
{
"actions": [],
"creation": "2020-11-25 13:40:23.054469",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"document_type",
"date_fieldname",
"add_edit_fields",
"selected_fields"
],
"fields": [
{
"fieldname": "document_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Document Type",
"options": "DocType",
"reqd": 1
},
{
"fieldname": "selected_fields",
"fieldtype": "Code",
"label": "Selected Fields",
"read_only": 1
},
{
"fieldname": "add_edit_fields",
"fieldtype": "Button",
"in_list_view": 1,
"label": "Add / Edit Fields"
},
{
"fieldname": "date_fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Date Fieldname",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-11-30 13:54:37.474671",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient History Custom Document Type",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -0,0 +1,133 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Patient History Settings', {
refresh: function(frm) {
frm.set_query('document_type', 'custom_doctypes', () => {
return {
filters: {
custom: 1,
is_submittable: 1,
module: 'Healthcare',
}
};
});
},
field_selector: function(frm, doc, standard=1) {
let document_fields = [];
if (doc.selected_fields)
document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname);
frm.call({
method: 'get_doctype_fields',
doc: frm.doc,
args: {
document_type: doc.document_type,
fields: document_fields
},
freeze: true,
callback: function(r) {
if (r.message) {
let doctype = 'Patient History Custom Document Type';
if (standard)
doctype = 'Patient History Standard Document Type';
frm.events.show_field_selector_dialog(frm, doc, doctype, r.message);
}
}
});
},
show_field_selector_dialog: function(frm, doc, doctype, doc_fields) {
let d = new frappe.ui.Dialog({
title: __('{0} Fields', [__(doc.document_type)]),
fields: [
{
label: __('Select Fields'),
fieldtype: 'MultiCheck',
fieldname: 'fields',
options: doc_fields,
columns: 2
}
]
});
d.$body.prepend(`
<div class="columns-search">
<input type="text" placeholder="${__('Search')}" data-element="search" class="form-control input-xs">
</div>`
);
frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area');
d.set_primary_action(__('Save'), () => {
let values = d.get_values().fields;
let selected_fields = [];
frappe.model.with_doctype(doc.document_type, function() {
for (let idx in values) {
let value = values[idx];
let field = frappe.get_meta(doc.document_type).fields.filter((df) => df.fieldname == value)[0];
if (field) {
selected_fields.push({
label: field.label,
fieldname: field.fieldname,
fieldtype: field.fieldtype
});
}
}
d.refresh();
frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields));
});
d.hide();
});
d.show();
},
get_date_field_for_dt: function(frm, row) {
frm.call({
method: 'get_date_field_for_dt',
doc: frm.doc,
args: {
document_type: row.document_type
},
callback: function(data) {
if (data.message) {
frappe.model.set_value('Patient History Custom Document Type',
row.name, 'date_fieldname', data.message);
}
}
});
}
});
frappe.ui.form.on('Patient History Custom Document Type', {
document_type: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.document_type) {
frm.events.get_date_field_for_dt(frm, row);
}
},
add_edit_fields: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.document_type) {
frm.events.field_selector(frm, row, 0);
}
}
});
frappe.ui.form.on('Patient History Standard Document Type', {
add_edit_fields: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.document_type) {
frm.events.field_selector(frm, row);
}
}
});

View File

@@ -0,0 +1,55 @@
{
"actions": [],
"creation": "2020-11-25 13:41:37.675518",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"standard_doctypes",
"section_break_2",
"custom_doctypes"
],
"fields": [
{
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"fieldname": "custom_doctypes",
"fieldtype": "Table",
"label": "Custom Document Types",
"options": "Patient History Custom Document Type"
},
{
"fieldname": "standard_doctypes",
"fieldtype": "Table",
"label": "Standard Document Types",
"options": "Patient History Standard Document Type",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2020-11-25 13:43:38.511771",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient History Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,188 @@
# -*- 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
import json
from frappe import _
from frappe.utils import cstr, cint
from frappe.model.document import Document
from erpnext.healthcare.page.patient_history.patient_history import get_patient_history_doctypes
class PatientHistorySettings(Document):
def validate(self):
self.validate_submittable_doctypes()
self.validate_date_fieldnames()
def validate_submittable_doctypes(self):
for entry in self.custom_doctypes:
if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')):
msg = _('Row #{0}: Document Type {1} is not submittable. ').format(
entry.idx, frappe.bold(entry.document_type))
msg += _('Patient Medical Record can only be created for submittable document types.')
frappe.throw(msg)
def validate_date_fieldnames(self):
for entry in self.custom_doctypes:
field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname)
if not field:
frappe.throw(_('Row #{0}: No such Field named {1} found in the Document Type {2}.').format(
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
if field.fieldtype not in ['Date', 'Datetime']:
frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
def get_doctype_fields(self, document_type, fields):
multicheck_fields = []
doc_fields = frappe.get_meta(document_type).fields
for field in doc_fields:
if field.fieldtype not in frappe.model.no_value_fields or \
field.fieldtype in frappe.model.table_fields and not field.hidden:
multicheck_fields.append({
'label': field.label,
'value': field.fieldname,
'checked': 1 if field.fieldname in fields else 0
})
return multicheck_fields
def get_date_field_for_dt(self, document_type):
meta = frappe.get_meta(document_type)
date_fields = meta.get('fields', {
'fieldtype': ['in', ['Date', 'Datetime']]
})
if date_fields:
return date_fields[0].get('fieldname')
def create_medical_record(doc, method=None):
medical_record_required = validate_medical_record_required(doc)
if not medical_record_required:
return
if frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }):
return
subject = set_subject_field(doc)
date_field = get_date_field(doc.doctype)
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.get(date_field)
medical_record.reference_doctype = doc.doctype
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)
def update_medical_record(doc, method=None):
medical_record_required = validate_medical_record_required(doc)
if not medical_record_required:
return
medical_record_id = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
if medical_record_id:
subject = set_subject_field(doc)
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
else:
create_medical_record(doc)
def delete_medical_record(doc, method=None):
medical_record_required = validate_medical_record_required(doc)
if not medical_record_required:
return
record = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
if record:
frappe.delete_doc('Patient Medical Record', record, force=1)
def set_subject_field(doc):
from frappe.utils.formatters import format_value
meta = frappe.get_meta(doc.doctype)
subject = ''
patient_history_fields = get_patient_history_fields(doc)
for entry in patient_history_fields:
fieldname = entry.get('fieldname')
if entry.get('fieldtype') == 'Table' and doc.get(fieldname):
formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname))
subject += frappe.bold(_(entry.get('label')) + ': ') + '<br>' + cstr(formatted_value) + '<br>'
else:
if doc.get(fieldname):
formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc)
subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + '<br>'
return subject
def get_date_field(doctype):
dt = get_patient_history_config_dt(doctype)
return frappe.db.get_value(dt, { 'document_type': doctype }, 'date_fieldname')
def get_patient_history_fields(doc):
dt = get_patient_history_config_dt(doc.doctype)
patient_history_fields = frappe.db.get_value(dt, { 'document_type': doc.doctype }, 'selected_fields')
if patient_history_fields:
return json.loads(patient_history_fields)
def get_formatted_value_for_table_field(items, df):
child_meta = frappe.get_meta(df.options)
table_head = ''
table_row = ''
html = ''
create_head = True
for item in items:
table_row += '<tr>'
for cdf in child_meta.fields:
if cdf.in_list_view:
if create_head:
table_head += '<td>' + cdf.label + '</td>'
if item.get(cdf.fieldname):
table_row += '<td>' + str(item.get(cdf.fieldname)) + '</td>'
else:
table_row += '<td></td>'
create_head = False
table_row += '</tr>'
html += "<table class='table table-condensed table-bordered'>" + table_head + table_row + "</table>"
return html
def get_patient_history_config_dt(doctype):
if frappe.db.get_value('DocType', doctype, 'custom'):
return 'Patient History Custom Document Type'
else:
return 'Patient History Standard Document Type'
def validate_medical_record_required(doc):
if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard \
or get_module(doc) != 'Healthcare':
return False
if doc.doctype not in get_patient_history_doctypes():
return False
return True
def get_module(doc):
module = doc.meta.module
if not module:
module = frappe.db.get_value('DocType', doc.doctype, 'module')
return module

View File

@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
import json
from frappe.utils import getdate
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
class TestPatientHistorySettings(unittest.TestCase):
def setUp(self):
dt = create_custom_doctype()
settings = frappe.get_single("Patient History Settings")
settings.append("custom_doctypes", {
"document_type": dt.name,
"date_fieldname": "date",
"selected_fields": json.dumps([{
"label": "Date",
"fieldname": "date",
"fieldtype": "Date"
},
{
"label": "Rating",
"fieldname": "rating",
"fieldtype": "Rating"
},
{
"label": "Feedback",
"fieldname": "feedback",
"fieldtype": "Small Text"
}])
})
settings.save()
def test_custom_doctype_medical_record(self):
# tests for medical record creation of standard doctypes in test_patient_medical_record.py
patient = create_patient()
doc = create_doc(patient)
# check for medical record
medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name})
self.assertTrue(medical_rec)
medical_rec = frappe.get_doc("Patient Medical Record", medical_rec)
expected_subject = "<b>Date: </b>{0}<br><b>Rating: </b>3<br><b>Feedback: </b>Test Patient History Settings<br>".format(
frappe.utils.format_date(getdate()))
self.assertEqual(medical_rec.subject, expected_subject)
self.assertEqual(medical_rec.patient, patient)
self.assertEqual(medical_rec.communication_date, getdate())
def create_custom_doctype():
if not frappe.db.exists("DocType", "Test Patient Feedback"):
doc = frappe.get_doc({
"doctype": "DocType",
"module": "Healthcare",
"custom": 1,
"is_submittable": 1,
"fields": [{
"label": "Date",
"fieldname": "date",
"fieldtype": "Date"
},
{
"label": "Patient",
"fieldname": "patient",
"fieldtype": "Link",
"options": "Patient"
},
{
"label": "Rating",
"fieldname": "rating",
"fieldtype": "Rating"
},
{
"label": "Feedback",
"fieldname": "feedback",
"fieldtype": "Small Text"
}],
"permissions": [{
"role": "System Manager",
"read": 1
}],
"name": "Test Patient Feedback",
})
doc.insert()
return doc
else:
return frappe.get_doc("DocType", "Test Patient Feedback")
def create_doc(patient):
doc = frappe.get_doc({
"doctype": "Test Patient Feedback",
"patient": patient,
"date": getdate(),
"rating": 3,
"feedback": "Test Patient History Settings"
}).insert()
doc.submit()
return doc

View File

@@ -0,0 +1,57 @@
{
"actions": [],
"creation": "2020-11-25 13:39:36.014814",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"document_type",
"date_fieldname",
"add_edit_fields",
"selected_fields"
],
"fields": [
{
"fieldname": "document_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Document Type",
"options": "DocType",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "selected_fields",
"fieldtype": "Code",
"label": "Selected Fields",
"read_only": 1
},
{
"fieldname": "add_edit_fields",
"fieldtype": "Button",
"in_list_view": 1,
"label": "Add / Edit Fields"
},
{
"fieldname": "date_fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Date Fieldname",
"read_only": 1,
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-11-30 13:54:56.773325",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient History Standard Document Type",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -18,6 +18,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
patient, medical_department, practitioner = create_healthcare_docs() patient, medical_department, practitioner = create_healthcare_docs()
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
encounter = create_encounter(appointment) encounter = create_encounter(appointment)
# check for encounter # check for encounter
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name}) medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name})
self.assertTrue(medical_rec) self.assertTrue(medical_rec)

View File

@@ -41,7 +41,6 @@ class TherapySession(Document):
def on_submit(self): def on_submit(self):
self.update_sessions_count_in_therapy_plan() self.update_sessions_count_in_therapy_plan()
insert_session_medical_record(self)
def on_update(self): def on_update(self):
if self.appointment: if self.appointment:
@@ -142,23 +141,3 @@ def get_therapy_item(therapy, item):
item.reference_dt = 'Therapy Session' item.reference_dt = 'Therapy Session'
item.reference_dn = therapy.name item.reference_dn = therapy.name
return item return item
def insert_session_medical_record(doc):
subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
if doc.therapy_plan:
subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.start_date
medical_record.reference_doctype = 'Therapy Session'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)

View File

@@ -12,47 +12,7 @@ class VitalSigns(Document):
def validate(self): def validate(self):
self.set_title() self.set_title()
def on_submit(self):
insert_vital_signs_to_medical_record(self)
def on_cancel(self):
delete_vital_signs_from_medical_record(self)
def set_title(self): def set_title(self):
self.title = _('{0} on {1}').format(self.patient_name or self.patient, self.title = _('{0} on {1}').format(self.patient_name or self.patient,
frappe.utils.format_date(self.signs_date))[:100] frappe.utils.format_date(self.signs_date))[:100]
def insert_vital_signs_to_medical_record(doc):
subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.signs_date
medical_record.reference_doctype = 'Vital Signs'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.flags.ignore_mandatory = True
medical_record.save(ignore_permissions=True)
def delete_vital_signs_from_medical_record(doc):
medical_record = frappe.db.get_value('Patient Medical Record', {'reference_name': doc.name})
if medical_record:
frappe.delete_doc('Patient Medical Record', medical_record)
def set_subject_field(doc):
subject = ''
if doc.temperature:
subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
if doc.pulse:
subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
if doc.respiratory_rate:
subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
if doc.bp:
subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
if doc.bmi:
subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
if doc.nutrition_note:
subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
return subject

View File

@@ -109,6 +109,11 @@
padding-right: 0px; padding-right: 0px;
} }
.patient-history-filter {
margin-left: 35px;
width: 25%;
}
#page-medical_record .plot-wrapper { #page-medical_record .plot-wrapper {
padding: 20px 15px; padding: 20px 15px;
border-bottom: 1px solid #d1d8dd; border-bottom: 1px solid #d1d8dd;

View File

@@ -1,6 +1,5 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="col-sm-3"> <div class="col-sm-3">
<p class="text-center">{%= __("Select Patient") %}</p>
<p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p> <p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p>
<div class="patient_details" style="z-index=0"></div> <div class="patient_details" style="z-index=0"></div>
</div> </div>
@@ -11,6 +10,13 @@
<div id="chart" class="col-sm-12 patient_vital_charts"> <div id="chart" class="col-sm-12 patient_vital_charts">
</div> </div>
</div> </div>
<div class="header-separator col-sm-12 d-flex border-bottom py-3" style="display:none"></div>
<div class="row">
<div class="col-sm-12 d-flex">
<div class="patient-history-filter doctype-filter"></div>
<div class="patient-history-filter date-filter"></div>
</div>
</div>
<div class="col-sm-12 patient_documents_list"> <div class="col-sm-12 patient_documents_list">
</div> </div>
<div class="col-sm-12 text-center py-3"> <div class="col-sm-12 text-center py-3">

View File

@@ -1,33 +1,38 @@
frappe.provide("frappe.patient_history"); frappe.provide('frappe.patient_history');
frappe.pages['patient_history'].on_page_load = function(wrapper) { frappe.pages['patient_history'].on_page_load = function(wrapper) {
var me = this; let me = this;
var page = frappe.ui.make_app_page({ let page = frappe.ui.make_app_page({
parent: wrapper, parent: wrapper,
title: 'Patient History', title: 'Patient History',
single_column: true single_column: true
}); });
frappe.breadcrumbs.add("Healthcare"); frappe.breadcrumbs.add('Healthcare');
let pid = ''; let pid = '';
page.main.html(frappe.render_template("patient_history", {})); page.main.html(frappe.render_template('patient_history', {}));
var patient = frappe.ui.form.make_control({ page.main.find('.header-separator').hide();
parent: page.main.find(".patient"),
let patient = frappe.ui.form.make_control({
parent: page.main.find('.patient'),
df: { df: {
fieldtype: "Link", fieldtype: 'Link',
options: "Patient", options: 'Patient',
fieldname: "patient", fieldname: 'patient',
placeholder: __('Select Patient'),
only_select: true,
change: function() { change: function() {
if(pid != patient.get_value() && patient.get_value()){ let patient_id = patient.get_value();
if (pid != patient_id && patient_id) {
me.start = 0; me.start = 0;
me.page.main.find(".patient_documents_list").html(""); me.page.main.find('.patient_documents_list').html('');
get_documents(patient.get_value(), me); setup_filters(patient_id, me);
show_patient_info(patient.get_value(), me); get_documents(patient_id, me);
show_patient_vital_charts(patient.get_value(), me, "bp", "mmHg", "Blood Pressure"); show_patient_info(patient_id, me);
show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure');
} }
pid = patient.get_value(); pid = patient_id;
} }
}, },
only_input: true,
}); });
patient.refresh(); patient.refresh();
@@ -35,77 +40,145 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) {
patient.set_value(frappe.route_options.patient); patient.set_value(frappe.route_options.patient);
} }
this.page.main.on("click", ".btn-show-chart", function() { this.page.main.on('click', '.btn-show-chart', function() {
var btn_show_id = $(this).attr("data-show-chart-id"), pts = $(this).attr("data-pts"); let btn_show_id = $(this).attr('data-show-chart-id'), pts = $(this).attr('data-pts');
var title = $(this).attr("data-title"); let title = $(this).attr('data-title');
show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title); show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title);
}); });
this.page.main.on("click", ".btn-more", function() { this.page.main.on('click', '.btn-more', function() {
var doctype = $(this).attr("data-doctype"), docname = $(this).attr("data-docname"); let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname');
if(me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched') == "1"){ if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') {
me.page.main.find("."+docname).hide(); me.page.main.find('.'+docname).hide();
me.page.main.find("."+docname).parent().find('.document-html').show(); me.page.main.find('.'+docname).parent().find('.document-html').show();
} else { } else {
if (doctype && docname) { if (doctype && docname) {
let exclude = ["patient", "patient_name", 'patient_sex', "encounter_date"]; let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date'];
frappe.call({ frappe.call({
method: "erpnext.healthcare.utils.render_doc_as_html", method: 'erpnext.healthcare.utils.render_doc_as_html',
args:{ args:{
doctype: doctype, doctype: doctype,
docname: docname, docname: docname,
exclude_fields: exclude exclude_fields: exclude
}, },
freeze: true,
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
me.page.main.find("."+docname).hide(); me.page.main.find('.' + docname).hide();
me.page.main.find("."+docname).parent().find('.document-html').html(r.message.html+"\
<div align='center'><a class='btn octicon octicon-chevron-up btn-default btn-xs\ me.page.main.find('.' + docname).parent().find('.document-html').html(
btn-less' data-doctype='"+doctype+"' data-docname='"+docname+"'></a></div>"); `${r.message.html}
me.page.main.find("."+docname).parent().find('.document-html').show(); <div align='center'>
me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched', "1"); <a class='btn octicon octicon-chevron-up btn-default btn-xs btn-less'
data-doctype='${doctype}'
data-docname='${docname}'>
</a>
</div>
`);
me.page.main.find('.' + docname).parent().find('.document-html').show();
me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1');
}
} }
},
freeze: true
}); });
} }
} }
}); });
this.page.main.on("click", ".btn-less", function() { this.page.main.on('click', '.btn-less', function() {
var docname = $(this).attr("data-docname"); let docname = $(this).attr('data-docname');
me.page.main.find("."+docname).parent().find('.document-id').show(); me.page.main.find('.' + docname).parent().find('.document-id').show();
me.page.main.find("."+docname).parent().find('.document-html').hide(); me.page.main.find('.' + docname).parent().find('.document-html').hide();
}); });
me.start = 0; me.start = 0;
me.page.main.on("click", ".btn-get-records", function(){ me.page.main.on('click', '.btn-get-records', function() {
get_documents(patient.get_value(), me); get_documents(patient.get_value(), me);
}); });
}; };
var get_documents = function(patient, me){ let setup_filters = function(patient, me) {
frappe.call({ $('.doctype-filter').empty();
"method": "erpnext.healthcare.page.patient_history.patient_history.get_feed", frappe.xcall(
args: { 'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes'
).then(document_types => {
let doctype_filter = frappe.ui.form.make_control({
parent: $('.doctype-filter'),
df: {
fieldtype: 'MultiSelectList',
fieldname: 'document_type',
placeholder: __('Select Document Type'),
input_class: 'input-xs',
change: () => {
me.start = 0;
me.page.main.find('.patient_documents_list').html('');
get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value());
},
get_data: () => {
return document_types.map(document_type => {
return {
description: document_type,
value: document_type
};
});
},
}
});
doctype_filter.refresh();
$('.date-filter').empty();
let date_range_field = frappe.ui.form.make_control({
df: {
fieldtype: 'DateRange',
fieldname: 'date_range',
placeholder: __('Date Range'),
input_class: 'input-xs',
change: () => {
let selected_date_range = date_range_field.get_value();
if (selected_date_range && selected_date_range.length === 2) {
me.start = 0;
me.page.main.find('.patient_documents_list').html('');
get_documents(patient, me, doctype_filter.get_value(), selected_date_range);
}
}
},
parent: $('.date-filter')
});
date_range_field.refresh();
});
};
let get_documents = function(patient, me, document_types="", selected_date_range="") {
let filters = {
name: patient, name: patient,
start: me.start, start: me.start,
page_length: 20 page_length: 20
}, };
if (document_types)
filters['document_types'] = document_types;
if (selected_date_range)
filters['date_range'] = selected_date_range;
frappe.call({
'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed',
args: filters,
callback: function(r) { callback: function(r) {
var data = r.message; let data = r.message;
if (data.length) { if (data.length) {
add_to_records(me, data); add_to_records(me, data);
} else { } else {
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>"); me.page.main.find('.patient_documents_list').append(`
me.page.main.find(".btn-get-records").hide(); <div class='text-muted' align='center'>
<br><br>${__('No more records..')}<br><br>
</div>`);
me.page.main.find('.btn-get-records').hide();
} }
} }
}); });
}; };
var add_to_records = function(me, data){ let add_to_records = function(me, data) {
var details = "<ul class='nav nav-pills nav-stacked'>"; let details = "<ul class='nav nav-pills nav-stacked'>";
var i; let i;
for (i=0; i<data.length; i++) { for (i=0; i<data.length; i++) {
if (data[i].reference_doctype) { if (data[i].reference_doctype) {
let label = ''; let label = '';
@@ -113,29 +186,40 @@ var add_to_records = function(me, data){
label += "<br/>" + data[i].subject; label += "<br/>" + data[i].subject;
} }
data[i] = add_date_separator(data[i]); data[i] = add_date_separator(data[i]);
if (frappe.user_info(data[i].owner).image) { if (frappe.user_info(data[i].owner).image) {
data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image); data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
} } else {
else{
data[i].imgsrc = false; data[i].imgsrc = false;
} }
var time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
time_line_heading += data[i].reference_doctype + " - "+ data[i].reference_name; let time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
details += `<li data-toggle='pill' class='patient_doc_menu' time_line_heading += data[i].reference_doctype + " - " +
`<a onclick="frappe.set_route('Form', '${data[i].reference_doctype}', '${data[i].reference_name}');">
${data[i].reference_name}
</a>`;
details += `
<li data-toggle='pill' class='patient_doc_menu'
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'> data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
<div class='col-sm-12 d-flex border-bottom py-3'>`; <div class='col-sm-12 d-flex border-bottom py-3'>`;
if (data[i].imgsrc) { if (data[i].imgsrc) {
details += `<span class='mr-3'> details += `
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'> <span class='mr-3'>
</img> <img class='avtar' src='${data[i].imgsrc}' width='32' height='32'></img>
</span>`; </span>`;
} else { } else {
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'><div align='center' class='standard-image' details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
style='background-color: #fafbfc;'>${data[i].practitioner ? data[i].practitioner.charAt(0) : "U"}</div></span>`; <div align='center' class='standard-image' style='background-color: #fafbfc;'>
${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'}
</div>
</span>`;
} }
details += `<div class='d-flex flex-column width-full'> details += `<div class='d-flex flex-column width-full'>
<div> <div>
`+time_line_heading+` on `+time_line_heading+`
<span> <span>
${data[i].date_sep} ${data[i].date_sep}
</span> </span>
@@ -156,133 +240,150 @@ var add_to_records = function(me, data){
</li>`; </li>`;
} }
} }
details += "</ul>";
me.page.main.find(".patient_documents_list").append(details); details += '</ul>';
me.page.main.find('.patient_documents_list').append(details);
me.start += data.length; me.start += data.length;
if (data.length === 20) { if (data.length === 20) {
me.page.main.find(".btn-get-records").show(); me.page.main.find(".btn-get-records").show();
} else { } else {
me.page.main.find(".btn-get-records").hide(); me.page.main.find(".btn-get-records").hide();
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>"); me.page.main.find(".patient_documents_list").append(`
<div class='text-muted' align='center'>
<br><br>${__('No more records..')}<br><br>
</div>`);
} }
}; };
var add_date_separator = function(data) { let add_date_separator = function(data) {
var date = frappe.datetime.str_to_obj(data.creation); let date = frappe.datetime.str_to_obj(data.communication_date);
let pdate = '';
let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
if (diff < 1) { if (diff < 1) {
var pdate = 'Today'; pdate = __('Today');
} else if (diff < 2) { } else if (diff < 2) {
pdate = 'Yesterday'; pdate = __('Yesterday');
} else { } else {
pdate = frappe.datetime.global_date_format(date); pdate = __('on ') + frappe.datetime.global_date_format(date);
} }
data.date_sep = pdate; data.date_sep = pdate;
return data; return data;
}; };
var show_patient_info = function(patient, me){ let show_patient_info = function(patient, me) {
frappe.call({ frappe.call({
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail", 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
args: { args: {
patient: patient patient: patient
}, },
callback: function(r) { callback: function(r) {
var data = r.message; let data = r.message;
var details = ""; let details = '';
if (data.image) { if (data.image) {
details += "<div><img class='thumbnail' width=75% src='"+data.image+"'></div>"; details += `<div><img class='thumbnail' width=75% src='${data.image}'></div>`;
} }
details += "<b>" + data.patient_name +"</b><br>" + data.sex;
if(data.email) details += "<br>" + data.email; details += `<b> ${data.patient_name} </b><br> ${data.sex}`;
if(data.mobile) details += "<br>" + data.mobile; if (data.email) details += `<br> ${data.email}`;
if(data.occupation) details += "<br><br><b>Occupation :</b> " + data.occupation; if (data.mobile) details += `<br> ${data.mobile}`;
if(data.blood_group) details += "<br><b>Blood group : </b> " + data.blood_group; if (data.occupation) details += `<br><br><b> ${__('Occupation')} : </b> ${data.occupation}`;
if(data.allergies) details += "<br><br><b>Allergies : </b> "+ data.allergies.replace("\n", "<br>"); if (data.blood_group) details += `<br><b> ${__('Blood Group')} : </b> ${data.blood_group}`;
if(data.medication) details += "<br><b>Medication : </b> "+ data.medication.replace("\n", "<br>"); if (data.allergies) details += `<br><br><b> ${__('Allerigies')} : </b> ${data.allergies.replace("\n", ", ")}`;
if(data.alcohol_current_use) details += "<br><br><b>Alcohol use : </b> "+ data.alcohol_current_use; if (data.medication) details += `<br><b> ${__('Medication')} : </b> ${data.medication.replace("\n", ", ")}`;
if(data.alcohol_past_use) details += "<br><b>Alcohol past use : </b> "+ data.alcohol_past_use; if (data.alcohol_current_use) details += `<br><br><b> ${__('Alcohol use')} : </b> ${data.alcohol_current_use}`;
if(data.tobacco_current_use) details += "<br><b>Tobacco use : </b> "+ data.tobacco_current_use; if (data.alcohol_past_use) details += `<br><b> ${__('Alcohol past use')} : </b> ${data.alcohol_past_use}`;
if(data.tobacco_past_use) details += "<br><b>Tobacco past use : </b> "+ data.tobacco_past_use; if (data.tobacco_current_use) details += `<br><b> ${__('Tobacco use')} : </b> ${data.tobacco_current_use}`;
if(data.medical_history) details += "<br><br><b>Medical history : </b> "+ data.medical_history.replace("\n", "<br>"); if (data.tobacco_past_use) details += `<br><b> ${__('Tobacco past use')} : </b> ${data.tobacco_past_use}`;
if(data.surgical_history) details += "<br><b>Surgical history : </b> "+ data.surgical_history.replace("\n", "<br>"); if (data.medical_history) details += `<br><br><b> ${__('Medical history')} : </b> ${data.medical_history.replace("\n", ", ")}`;
if(data.surrounding_factors) details += "<br><br><b>Occupational hazards : </b> "+ data.surrounding_factors.replace("\n", "<br>"); if (data.surgical_history) details += `<br><b> ${__('Surgical history')} : </b> ${data.surgical_history.replace("\n", ", ")}`;
if(data.other_risk_factors) details += "<br><b>Other risk factors : </b> " + data.other_risk_factors.replace("\n", "<br>"); if (data.surrounding_factors) details += `<br><br><b> ${__('Occupational hazards')} : </b> ${data.surrounding_factors.replace("\n", ", ")}`;
if(data.patient_details) details += "<br><br><b>More info : </b> " + data.patient_details.replace("\n", "<br>"); if (data.other_risk_factors) details += `<br><b> ${__('Other risk factors')} : </b> ${data.other_risk_factors.replace("\n", ", ")}`;
if (data.patient_details) details += `<br><br><b> ${__('More info')} : </b> ${data.patient_details.replace("\n", ", ")}`;
if (details) { if (details) {
details = "<div style='padding-left:10px; font-size:13px;' align='center'>" + details + "</div>"; details = `<div style='padding-left:10px; font-size:13px;' align='left'>` + details + `</div>`;
} }
me.page.main.find(".patient_details").html(details); me.page.main.find('.patient_details').html(details);
} }
}); });
}; };
var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) { let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
frappe.call({ frappe.call({
method: "erpnext.healthcare.utils.get_patient_vitals", method: 'erpnext.healthcare.utils.get_patient_vitals',
args:{ args:{
patient: patient patient: patient
}, },
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
var show_chart_btns_html = "<div style='padding-top:5px;'><a class='btn btn-default btn-xs btn-show-chart' \ let show_chart_btns_html = `
data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>Blood Pressure</a>\ <div style='padding-top:10px;'>
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' \ <a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>
data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>Respiratory/Pulse Rate</a>\ ${__('Blood Pressure')}
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' \ </a>
data-pts='°C or °F' data-title='Temperature'>Temperature</a>\ <a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' \ ${__('Respiratory/Pulse Rate')}
data-pts='' data-title='BMI'>BMI</a></div>"; </a>
me.page.main.find(".show_chart_btns").html(show_chart_btns_html); <a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' data-pts='°C or °F' data-title='Temperature'>
var data = r.message; ${__('Temperature')}
</a>
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' data-pts='' data-title='BMI'>
${__('BMI')}
</a>
</div>`;
me.page.main.find('.show_chart_btns').html(show_chart_btns_html);
let data = r.message;
let labels = [], datasets = []; let labels = [], datasets = [];
let bp_systolic = [], bp_diastolic = [], temperature = []; let bp_systolic = [], bp_diastolic = [], temperature = [];
let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = []; let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
for(var i=0; i<data.length; i++){
labels.push(data[i].signs_date+"||"+data[i].signs_time); for (let i=0; i<data.length; i++) {
if(btn_show_id=="bp"){ labels.push(data[i].signs_date+'||'+data[i].signs_time);
if (btn_show_id === 'bp') {
bp_systolic.push(data[i].bp_systolic); bp_systolic.push(data[i].bp_systolic);
bp_diastolic.push(data[i].bp_diastolic); bp_diastolic.push(data[i].bp_diastolic);
} }
if(btn_show_id=="temperature"){ if (btn_show_id === 'temperature') {
temperature.push(data[i].temperature); temperature.push(data[i].temperature);
} }
if(btn_show_id=="pulse_rate"){ if (btn_show_id === 'pulse_rate') {
pulse.push(data[i].pulse); pulse.push(data[i].pulse);
respiratory_rate.push(data[i].respiratory_rate); respiratory_rate.push(data[i].respiratory_rate);
} }
if(btn_show_id=="bmi"){ if (btn_show_id === 'bmi') {
bmi.push(data[i].bmi); bmi.push(data[i].bmi);
height.push(data[i].height); height.push(data[i].height);
weight.push(data[i].weight); weight.push(data[i].weight);
} }
} }
if(btn_show_id=="temperature"){ if (btn_show_id === 'temperature') {
datasets.push({name: "Temperature", values: temperature, chartType:'line'}); datasets.push({name: 'Temperature', values: temperature, chartType: 'line'});
} }
if(btn_show_id=="bmi"){ if (btn_show_id === 'bmi') {
datasets.push({name: "BMI", values: bmi, chartType:'line'}); datasets.push({name: 'BMI', values: bmi, chartType: 'line'});
datasets.push({name: "Height", values: height, chartType:'line'}); datasets.push({name: 'Height', values: height, chartType: 'line'});
datasets.push({name: "Weight", values: weight, chartType:'line'}); datasets.push({name: 'Weight', values: weight, chartType: 'line'});
} }
if(btn_show_id=="bp"){ if (btn_show_id === 'bp') {
datasets.push({name: "BP Systolic", values: bp_systolic, chartType:'line'}); datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'});
datasets.push({name: "BP Diastolic", values: bp_diastolic, chartType:'line'}); datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'});
} }
if(btn_show_id=="pulse_rate"){ if (btn_show_id === 'pulse_rate') {
datasets.push({name: "Heart Rate / Pulse", values: pulse, chartType:'line'}); datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'});
datasets.push({name: "Respiratory Rate", values: respiratory_rate, chartType:'line'}); datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'});
} }
new frappe.Chart( ".patient_vital_charts", { new frappe.Chart('.patient_vital_charts', {
data: { data: {
labels: labels, labels: labels,
datasets: datasets datasets: datasets
}, },
title: title, title: title,
type: 'axis-mixed', // 'axis-mixed', 'bar', 'line', 'pie', 'percentage' type: 'axis-mixed',
height: 200, height: 200,
colors: ['purple', '#ffa3ef', 'light-blue'], colors: ['purple', '#ffa3ef', 'light-blue'],
@@ -291,9 +392,11 @@ var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
formatTooltipY: d => d + ' ' + pts, formatTooltipY: d => d + ' ' + pts,
} }
}); });
me.page.main.find('.header-separator').show();
} else { } else {
me.page.main.find(".patient_vital_charts").html(""); me.page.main.find('.patient_vital_charts').html('');
me.page.main.find(".show_chart_btns").html(""); me.page.main.find('.show_chart_btns').html('');
me.page.main.find('.header-separator').hide();
} }
} }
}); });

View File

@@ -4,36 +4,70 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import json
from frappe.utils import cint from frappe.utils import cint
from erpnext.healthcare.utils import render_docs_as_html from erpnext.healthcare.utils import render_docs_as_html
@frappe.whitelist() @frappe.whitelist()
def get_feed(name, start=0, page_length=20): def get_feed(name, document_types=None, date_range=None, start=0, page_length=20):
"""get feed""" """get feed"""
result = frappe.db.sql("""select name, owner, creation, filters = get_filters(name, document_types, date_range)
reference_doctype, reference_name, subject
from `tabPatient Medical Record` result = frappe.db.get_all('Patient Medical Record',
where patient=%(patient)s fields=['name', 'owner', 'communication_date',
order by creation desc 'reference_doctype', 'reference_name', 'subject'],
limit %(start)s, %(page_length)s""", filters=filters,
{ order_by='communication_date DESC',
"patient": name, limit=cint(page_length),
"start": cint(start), start=cint(start)
"page_length": cint(page_length) )
}, as_dict=True)
return result return result
def get_filters(name, document_types=None, date_range=None):
filters = {'patient': name}
if document_types:
document_types = json.loads(document_types)
if len(document_types):
filters['reference_doctype'] = ['IN', document_types]
if date_range:
try:
date_range = json.loads(date_range)
if date_range:
filters['communication_date'] = ['between', [date_range[0], date_range[1]]]
except json.decoder.JSONDecodeError:
pass
return filters
@frappe.whitelist() @frappe.whitelist()
def get_feed_for_dt(doctype, docname): def get_feed_for_dt(doctype, docname):
"""get feed""" """get feed"""
result = frappe.db.sql("""select name, owner, modified, creation, result = frappe.db.get_all('Patient Medical Record',
reference_doctype, reference_name, subject fields=['name', 'owner', 'communication_date',
from `tabPatient Medical Record` 'reference_doctype', 'reference_name', 'subject'],
where reference_name=%(docname)s and reference_doctype=%(doctype)s filters={
order by creation desc""", 'reference_doctype': doctype,
{ 'reference_name': docname
"docname": docname, },
"doctype": doctype order_by='communication_date DESC'
}, as_dict=True) )
return result return result
@frappe.whitelist()
def get_patient_history_doctypes():
document_types = []
settings = frappe.get_single("Patient History Settings")
for entry in settings.standard_doctypes:
document_types.append(entry.document_type)
for entry in settings.custom_doctypes:
document_types.append(entry.document_type)
return document_types

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