Browse Source

Merge branch 'develop' into youtube-analytics

develop
gavin 4 years ago
committed by GitHub
parent
commit
d935b2753f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 17
      erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py
  3. 4
      erpnext/accounts/doctype/item_tax_template/item_tax_template.json
  4. 6
      erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json
  5. 15
      erpnext/accounts/doctype/payment_entry/payment_entry.json
  6. 2
      erpnext/accounts/doctype/payment_entry/payment_entry.py
  7. 4
      erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
  8. 11
      erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
  9. 29
      erpnext/accounts/doctype/pos_profile/pos_profile.py
  10. 8
      erpnext/accounts/doctype/pos_settings/pos_settings.js
  11. 2
      erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
  12. 2
      erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
  13. 2
      erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
  14. 2
      erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
  15. 18
      erpnext/accounts/doctype/sales_invoice/sales_invoice.py
  16. 38
      erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
  17. 2
      erpnext/accounts/report/accounts_receivable/accounts_receivable.js
  18. 10
      erpnext/accounts/report/accounts_receivable/accounts_receivable.py
  19. 16
      erpnext/assets/doctype/asset/asset.js
  20. 9
      erpnext/assets/doctype/asset/asset.py
  21. 18
      erpnext/assets/doctype/asset/test_asset.py
  22. 420
      erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
  23. 6
      erpnext/assets/doctype/asset_movement/test_asset_movement.py
  24. 5
      erpnext/buying/doctype/purchase_order/purchase_order.json
  25. 74
      erpnext/buying/doctype/purchase_order/test_purchase_order.py
  26. 4
      erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
  27. 4
      erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
  28. 85
      erpnext/controllers/accounts_controller.py
  29. 7
      erpnext/controllers/queries.py
  30. 1
      erpnext/controllers/selling_controller.py
  31. 20
      erpnext/education/doctype/student/student.js
  32. 3
      erpnext/education/doctype/student/student.json
  33. 13
      erpnext/education/doctype/student_applicant/student_applicant.js
  34. 1507
      erpnext/education/doctype/student_applicant/student_applicant.json
  35. 103
      erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
  36. 57
      erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
  37. 11
      erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json
  38. 50
      erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
  39. 19
      erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
  40. 4
      erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
  41. 4
      erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json
  42. 33
      erpnext/healthcare/doctype/lab_test/lab_test.js
  43. 25
      erpnext/healthcare/doctype/lab_test/lab_test.json
  44. 60
      erpnext/healthcare/doctype/lab_test/lab_test.py
  45. 17
      erpnext/healthcare/doctype/lab_test/lab_test_list.js
  46. 202
      erpnext/healthcare/doctype/lab_test/test_lab_test.py
  47. 5
      erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json
  48. 21
      erpnext/healthcare/doctype/lab_test_template/lab_test_template.json
  49. 37
      erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
  50. 13
      erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py
  51. 2
      erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js
  52. 6
      erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
  53. 4
      erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json
  54. 20
      erpnext/healthcare/doctype/sample_collection/sample_collection.js
  55. 85
      erpnext/healthcare/doctype/sample_collection/sample_collection.json
  56. 7
      erpnext/healthcare/doctype/sample_collection/sample_collection.py
  57. 39
      erpnext/healthcare/report/lab_test_report/lab_test_report.js
  58. 39
      erpnext/healthcare/report/lab_test_report/lab_test_report.json
  59. 209
      erpnext/healthcare/report/lab_test_report/lab_test_report.py
  60. 4
      erpnext/hr/doctype/appraisal/appraisal.json
  61. 4
      erpnext/hr/doctype/appraisal_goal/appraisal_goal.json
  62. 4
      erpnext/hr/doctype/appraisal_template/appraisal_template.json
  63. 4
      erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.json
  64. 4
      erpnext/hr/doctype/attendance/attendance.json
  65. 4
      erpnext/hr/doctype/expense_claim/expense_claim.json
  66. 4
      erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
  67. 4
      erpnext/hr/doctype/expense_claim_type/expense_claim_type.json
  68. 4
      erpnext/hr/doctype/upload_attendance/upload_attendance.json
  69. 4
      erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json
  70. 6
      erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json
  71. 4
      erpnext/hub_node/doctype/hub_user/hub_user.json
  72. 4
      erpnext/hub_node/doctype/hub_users/hub_users.json
  73. 4
      erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json
  74. 2
      erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
  75. 4
      erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
  76. 4
      erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
  77. 407
      erpnext/non_profit/doctype/donor/donor.json
  78. 3
      erpnext/non_profit/doctype/member/member.json
  79. 8
      erpnext/non_profit/doctype/membership/membership.json
  80. 708
      erpnext/non_profit/doctype/volunteer/volunteer.json
  81. 1
      erpnext/patches.txt
  82. 5
      erpnext/patches/v12_0/move_plaid_settings_to_doctype.py
  83. 4
      erpnext/projects/doctype/dependent_task/dependent_task.json
  84. 4
      erpnext/public/js/conf.js
  85. 2
      erpnext/public/js/controllers/taxes_and_totals.js
  86. 2
      erpnext/public/js/controllers/transaction.js
  87. 28
      erpnext/public/js/utils.js
  88. 6
      erpnext/public/js/utils/party.js
  89. 0
      erpnext/regional/germany/utils/__init__.py
  90. 0
      erpnext/regional/germany/utils/datev/__init__.py
  91. 75
      erpnext/regional/germany/utils/datev/datev_constants.py
  92. 174
      erpnext/regional/germany/utils/datev/datev_csv.py
  93. 282
      erpnext/regional/report/datev/datev.py
  94. 18
      erpnext/regional/report/datev/test_datev.py
  95. 4
      erpnext/selling/doctype/industry_type/industry_type.json
  96. 4
      erpnext/selling/doctype/product_bundle/product_bundle.json
  97. 93
      erpnext/selling/doctype/sales_order/test_sales_order.py
  98. 16
      erpnext/selling/page/point_of_sale/pos_payment.js
  99. 2
      erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
  100. 2
      erpnext/selling/report/sales_analytics/sales_analytics.js

2
README.md

@ -16,7 +16,7 @@
ERPNext as a monolith includes the following areas for managing businesses:
1. [Accounting](https://erpnext.com/open-source-accounting)
1. [Inventory](https://erpnext.com/distribution/inventory-management-system)
1. [Warehouse Management](https://erpnext.com/distribution/warehouse-management-system)
1. [CRM](https://erpnext.com/open-source-crm)
1. [Sales](https://erpnext.com/open-source-sales-purchase)
1. [Purchase](https://erpnext.com/open-source-sales-purchase)

17
erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py

@ -55,7 +55,7 @@ class BankStatementTransactionEntry(Document):
def populate_payment_entries(self):
if self.bank_statement is None: return
filename = self.bank_statement.split("/")[-1]
file_url = self.bank_statement
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
frappe.throw(_("Transactions already retreived from the statement"))
@ -65,7 +65,7 @@ class BankStatementTransactionEntry(Document):
if self.bank_settings:
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
statement_headers = self.get_statement_headers()
transactions = get_transaction_entries(filename, statement_headers)
transactions = get_transaction_entries(file_url, statement_headers)
for entry in transactions:
date = entry[statement_headers["Date"]].strip()
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
@ -398,20 +398,21 @@ def get_transaction_info(headers, header_index, row):
transaction[header] = ""
return transaction
def get_transaction_entries(filename, headers):
def get_transaction_entries(file_url, headers):
header_index = {}
rows, transactions = [], []
if (filename.lower().endswith("xlsx")):
if (file_url.lower().endswith("xlsx")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(file_id=filename)
elif (filename.lower().endswith("csv")):
rows = read_xlsx_file_from_attached_file(file_url=file_url)
elif (file_url.lower().endswith("csv")):
from frappe.utils.csvutils import read_csv_content
_file = frappe.get_doc("File", {"file_name": filename})
_file = frappe.get_doc("File", {"file_url": file_url})
filepath = _file.get_full_path()
with open(filepath,'rb') as csvfile:
rows = read_csv_content(csvfile.read())
elif (filename.lower().endswith("xls")):
elif (file_url.lower().endswith("xls")):
filename = file_url.split("/")[-1]
rows = get_rows_from_xls_file(filename)
else:
frappe.throw(_("Only .csv and .xlsx files are supported currently"))

4
erpnext/accounts/doctype/item_tax_template/item_tax_template.json

@ -38,8 +38,8 @@
"reqd": 1
}
],
"modified": "2020-06-18 20:27:42.615842",
"modified_by": "ahmad@havenir.com",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template",
"owner": "Administrator",

6
erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json

@ -45,11 +45,11 @@
],
"icon": "fa fa-credit-card",
"idx": 1,
"modified": "2019-08-14 14:58:42.079115",
"modified_by": "sammish.thundiyil@gmail.com",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Mode of Payment",
"owner": "harshada@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"create": 1,

15
erpnext/accounts/doctype/payment_entry/payment_entry.json

@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2016-06-01 14:38:51.012597",
@ -63,6 +64,7 @@
"cost_center",
"section_break_12",
"status",
"custom_remarks",
"remarks",
"column_break_16",
"letter_head",
@ -462,7 +464,8 @@
"fieldname": "remarks",
"fieldtype": "Small Text",
"label": "Remarks",
"no_copy": 1
"no_copy": 1,
"read_only_depends_on": "eval:doc.custom_remarks == 0"
},
{
"fieldname": "column_break_16",
@ -573,10 +576,18 @@
"label": "Status",
"options": "\nDraft\nSubmitted\nCancelled",
"read_only": 1
},
{
"default": "0",
"fieldname": "custom_remarks",
"fieldtype": "Check",
"label": "Custom Remarks"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"modified": "2019-12-08 13:02:30.016610",
"links": [],
"modified": "2020-09-02 13:39:43.383705",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

2
erpnext/accounts/doctype/payment_entry/payment_entry.py

@ -453,7 +453,7 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
def set_remarks(self):
if self.remarks: return
if self.custom_remarks: return
if self.payment_type=="Internal Transfer":
remarks = [_("Amount {0} {1} transferred from {2} to {3}")

4
erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json

@ -291,11 +291,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:49.089450",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Period Closing Voucher",
"owner": "jai@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"amend": 1,

11
erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py

@ -5,13 +5,14 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint
from frappe.utils import cint, get_link_to_form
from frappe.model.document import Document
from erpnext.controllers.status_updater import StatusUpdater
class POSOpeningEntry(StatusUpdater):
def validate(self):
self.validate_pos_profile_and_cashier()
self.validate_payment_method_account()
self.set_status()
def validate_pos_profile_and_cashier(self):
@ -20,6 +21,14 @@ class POSOpeningEntry(StatusUpdater):
if not cint(frappe.db.get_value("User", self.user, "enabled")):
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
def validate_payment_method_account(self):
for d in self.balance_details:
account = frappe.db.get_value("Mode of Payment Account",
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
if not account:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
def on_submit(self):
self.set_status(update=True)

29
erpnext/accounts/doctype/pos_profile/pos_profile.py

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import msgprint, _
from frappe.utils import cint, now
from frappe.utils import cint, now, get_link_to_form
from six import iteritems
from frappe.model.document import Document
@ -13,7 +13,7 @@ class POSProfile(Document):
self.validate_default_profile()
self.validate_all_link_fields()
self.validate_duplicate_groups()
self.check_default_payment()
self.validate_payment_methods()
def validate_default_profile(self):
for row in self.applicable_for_users:
@ -52,14 +52,23 @@ class POSProfile(Document):
if len(customer_groups) != len(set(customer_groups)):
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
def check_default_payment(self):
if self.payments:
default_mode_of_payment = [d.default for d in self.payments if d.default]
if not default_mode_of_payment:
frappe.throw(_("Set default mode of payment"))
if len(default_mode_of_payment) > 1:
frappe.throw(_("Multiple default mode of payment is not allowed"))
def validate_payment_methods(self):
if not self.payments:
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
default_mode_of_payment = [d.default for d in self.payments if d.default]
if not default_mode_of_payment:
frappe.throw(_("Please select a default mode of payment"))
if len(default_mode_of_payment) > 1:
frappe.throw(_("You can only select one mode of payment as default"))
for d in self.payments:
account = frappe.db.get_value("Mode of Payment Account",
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
if not account:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
def on_update(self):
self.set_defaults()

8
erpnext/accounts/doctype/pos_settings/pos_settings.js

@ -7,10 +7,10 @@ frappe.ui.form.on('POS Settings', {
},
get_invoice_fields: function(frm) {
frappe.model.with_doctype("Sales Invoice", () => {
var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
frappe.model.with_doctype("POS Invoice", () => {
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
d.fieldtype === 'Table') {
['Table', 'Button'].includes(d.fieldtype)) {
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
} else {
return null;
@ -25,7 +25,7 @@ frappe.ui.form.on('POS Settings', {
frappe.ui.form.on("POS Field", {
fieldname: function(frm, doctype, name) {
var doc = frappe.get_doc(doctype, name);
var df = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
var df = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
return doc.fieldname == d.fieldname ? d : null;
})[0];

2
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js

@ -28,7 +28,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
// Trigger supplier event on load if supplier is available
// The reason for this is PI can be created from PR or PO and supplier is pre populated
if (this.frm.doc.supplier) {
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
this.frm.trigger('supplier');
}
},

2
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py

@ -405,8 +405,6 @@ class PurchaseInvoice(BuyingController):
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
def make_gl_entries(self, gl_entries=None):
if not self.grand_total:
return
if not gl_entries:
gl_entries = self.get_gl_entries()

2
erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json

@ -210,7 +210,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-03-12 14:53:47.679439",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",

2
erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json

@ -78,7 +78,7 @@
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges Template",
"owner": "wasim@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"email": 1,

18
erpnext/accounts/doctype/sales_invoice/sales_invoice.py

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe, erpnext
import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date
from frappe.model.mapper import get_mapped_doc
@ -1372,7 +1372,7 @@ def get_bank_cash_account(mode_of_payment, company):
{"parent": mode_of_payment, "company": company}, "default_account")
if not account:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
.format(mode_of_payment))
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
return {
"account": account
}
@ -1612,18 +1612,16 @@ def update_multi_mode_option(doc, pos_profile):
payment.type = payment_mode.type
doc.set('payments', [])
if not pos_profile or not pos_profile.get('payments'):
for payment_mode in get_all_mode_of_payments(doc):
append_payment(payment_mode)
return
for pos_payment_method in pos_profile.get('payments'):
pos_payment_method = pos_payment_method.as_dict()
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
if payment_mode:
payment_mode[0].default = pos_payment_method.default
append_payment(payment_mode[0])
if not payment_mode:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
.format(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)), title=_("Missing Account"))
payment_mode[0].default = pos_payment_method.default
append_payment(payment_mode[0])
def get_all_mode_of_payments(doc):
return frappe.db.sql("""

38
erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js

@ -72,7 +72,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
check_plaid_status() {
const me = this;
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
if (r && r.enabled == "1") {
if (r && r.enabled === "1") {
me.plaid_status = "active"
} else {
me.plaid_status = "inactive"
@ -139,7 +139,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
}
make() {
const me = this;
const me = this;
new frappe.ui.FileUploader({
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
allow_multiple: 0,
@ -214,31 +214,35 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync {
init_config() {
const me = this;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
.then(result => {
me.plaid_env = result.plaid_env;
me.plaid_public_key = result.plaid_public_key;
me.client_name = result.client_name;
me.sync_transactions()
})
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
.then(result => {
me.plaid_env = result.plaid_env;
me.client_name = result.client_name;
me.link_token = result.link_token;
me.sync_transactions();
})
}
sync_transactions() {
const me = this;
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
bank: v['bank'],
bank: r.bank,
bank_account: me.parent.bank_account,
freeze: true
})
.then((result) => {
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
let result_title = (result && result.length > 0)
? __("{0} bank transaction(s) created", [result.length])
: __("This bank account is already synchronized");
let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
</div>`
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
</div>`
this.parent.$main_section.append(result_msg)
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
})
})
}
@ -384,7 +388,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
})
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
{ bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
).then((result) => {
me.make_dialog(result)
})

2
erpnext/accounts/report/accounts_receivable/accounts_receivable.js

@ -34,7 +34,7 @@ frappe.query_reports["Accounts Receivable"] = {
filters: {
'company': company
}
}
};
}
},
{

10
erpnext/accounts/report/accounts_receivable/accounts_receivable.py

@ -617,9 +617,19 @@ class ReceivablePayableReport(object):
elif party_type_field=="supplier":
self.add_supplier_filters(conditions, values)
if self.filters.cost_center:
self.get_cost_center_conditions(conditions)
self.add_accounting_dimensions_filters(conditions, values)
return " and ".join(conditions), values
def get_cost_center_conditions(self, conditions):
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
cost_center_list = [center.name for center in frappe.get_list("Cost Center", filters = {'lft': (">=", lft), 'rgt': ("<=", rgt)})]
cost_center_string = '", "'.join(cost_center_list)
conditions.append('cost_center in ("{0}")'.format(cost_center_string))
def get_order_by_condition(self):
if self.filters.get('group_by_party'):
return "order by party, posting_date"

16
erpnext/assets/doctype/asset/asset.js

@ -252,13 +252,6 @@ frappe.ui.form.on('Asset', {
})
},
available_for_use_date: function(frm) {
$.each(frm.doc.finance_books || [], function(i, d) {
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
});
refresh_field("finance_books");
},
is_existing_asset: function(frm) {
frm.trigger("toggle_reference_doc");
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
@ -438,6 +431,15 @@ frappe.ui.form.on('Asset Finance Book', {
}
frappe.flags.dont_change_rate = false;
},
depreciation_start_date: function(frm, cdt, cdn) {
const book = locals[cdt][cdn];
if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
book.depreciation_start_date = "";
frm.refresh_field("finance_books");
}
}
});

9
erpnext/assets/doctype/asset/asset.py

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext, math, json
from frappe import _
from six import string_types
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days, get_last_day
from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \
@ -83,6 +83,11 @@ class Asset(AccountsController):
if not self.available_for_use_date:
frappe.throw(_("Available for use date is required"))
for d in self.finance_books:
if d.depreciation_start_date == self.available_for_use_date:
frappe.throw(_("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(d.idx),
title=_("Incorrect Date"))
def set_missing_values(self):
if not self.asset_category:
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
@ -294,7 +299,7 @@ class Asset(AccountsController):
if not row.depreciation_start_date:
if not self.available_for_use_date:
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
row.depreciation_start_date = self.available_for_use_date
row.depreciation_start_date = get_last_day(self.available_for_use_date)
if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0

18
erpnext/assets/doctype/asset/test_asset.py

@ -371,19 +371,18 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = nowdate()
asset.purchase_date = nowdate()
asset.available_for_use_date = '2020-01-01'
asset.purchase_date = '2020-01-01'
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": nowdate()
"total_number_of_depreciations": 10,
"frequency_of_depreciation": 1
})
asset.insert()
asset.submit()
post_depreciation_entries(date=add_months(nowdate(), 10))
post_depreciation_entries(date=add_months('2020-01-01', 4))
scrap_asset(asset.name)
@ -392,9 +391,9 @@ class TestAsset(unittest.TestCase):
self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = (
("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0)
)
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
@ -466,8 +465,7 @@ class TestAsset(unittest.TestCase):
"expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06"
"frequency_of_depreciation": 10
})
asset.insert()
accumulated_depreciation_after_full_schedule = \

420
erpnext/assets/doctype/asset_finance_book/asset_finance_book.json

@ -1,347 +1,99 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-05-08 14:44:37.095570",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"creation": "2018-05-08 14:44:37.095570",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"finance_book",
"depreciation_method",
"total_number_of_depreciations",
"column_break_5",
"frequency_of_depreciation",
"depreciation_start_date",
"expected_value_after_useful_life",
"value_after_depreciation",
"rate_of_depreciation"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "finance_book",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Finance Book",
"length": 0,
"no_copy": 0,
"options": "Finance Book",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "finance_book",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Finance Book",
"options": "Finance Book"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "depreciation_method",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Depreciation Method",
"length": 0,
"no_copy": 0,
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "depreciation_method",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Depreciation Method",
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_number_of_depreciations",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Number of Depreciations",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "total_number_of_depreciations",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Total Number of Depreciations",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "frequency_of_depreciation",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Frequency of Depreciation (Months)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "frequency_of_depreciation",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Frequency of Depreciation (Months)",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:parent.doctype == 'Asset'",
"fetch_if_empty": 0,
"fieldname": "depreciation_start_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Depreciation Start Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"depends_on": "eval:parent.doctype == 'Asset'",
"fieldname": "depreciation_start_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Depreciation Posting Date",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"depends_on": "eval:parent.doctype == 'Asset'",
"fetch_if_empty": 0,
"fieldname": "expected_value_after_useful_life",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Expected Value After Useful Life",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "0",
"depends_on": "eval:parent.doctype == 'Asset'",
"fieldname": "expected_value_after_useful_life",
"fieldtype": "Currency",
"label": "Expected Value After Useful Life",
"options": "Company:company:default_currency"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "value_after_depreciation",
"fieldtype": "Currency",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Value After Depreciation",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "value_after_depreciation",
"fieldtype": "Currency",
"hidden": 1,
"label": "Value After Depreciation",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
"description": "In Percentage",
"fetch_if_empty": 0,
"fieldname": "rate_of_depreciation",
"fieldtype": "Percent",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Rate of Depreciation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
"description": "In Percentage",
"fieldname": "rate_of_depreciation",
"fieldtype": "Percent",
"label": "Rate of Depreciation"
}
],
"has_web_view": 0,
"hide_toolbar": 0,
"idx": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-04-09 19:45:14.523488",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-09-16 12:11:30.631788",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

6
erpnext/assets/doctype/asset_movement/test_asset_movement.py

@ -32,8 +32,7 @@ class TestAssetMovement(unittest.TestCase):
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06"
"frequency_of_depreciation": 10
})
if asset.docstatus == 0:
@ -82,8 +81,7 @@ class TestAssetMovement(unittest.TestCase):
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06"
"frequency_of_depreciation": 10
})
if asset.docstatus == 0:
asset.submit()

5
erpnext/buying/doctype/purchase_order/purchase_order.json

@ -1084,7 +1084,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2020-07-31 14:13:44.610190",
"modified": "2020-09-14 14:36:12.418690",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@ -1135,5 +1135,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "supplier",
"title_field": "supplier"
"title_field": "supplier",
"track_changes": 1
}

74
erpnext/buying/doctype/purchase_order/test_purchase_order.py

@ -89,7 +89,7 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
def test_update_child_qty_rate(self):
def test_update_child(self):
mr = make_material_request(qty=10)
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
@ -119,7 +119,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
def test_add_new_item_in_update_child_qty_rate(self):
def test_update_child_adding_new_item(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
po.save()
@ -145,7 +145,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(po.status, 'To Receive and Bill')
def test_remove_item_in_update_child_qty_rate(self):
def test_update_child_removing_item(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
po.save()
@ -185,7 +185,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
def test_update_child_qty_rate_perm(self):
def test_update_child_perm(self):
po = create_purchase_order(item_code= "_Test Item", qty=4)
user = 'test@example.com'
@ -202,6 +202,72 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
frappe.set_user("Administrator")
def test_update_child_with_tax_template(self):
tax_template = "_Test Account Excise Duty @ 10"
item = "_Test Item Home Desktop 100"
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
item_doc = frappe.get_doc("Item", item)
item_doc.append("taxes", {
"item_tax_template": tax_template,
"valid_from": nowdate()
})
item_doc.save()
else:
# update valid from
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template})
po = create_purchase_order(item_code=item, qty=1, do_not_save=1)
po.append("taxes", {
"account_head": "_Test Account Excise Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"doctype": "Purchase Taxes and Charges",
"rate": 10
})
po.insert()
po.submit()
self.assertEqual(po.taxes[0].tax_amount, 50)
self.assertEqual(po.taxes[0].total, 550)
items = json.dumps([
{'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
{'item_code' : item, 'rate' : 100, 'qty' : 1} # added item
])
update_child_qty_rate('Purchase Order', items, po.name)
po.reload()
self.assertEqual(po.taxes[0].tax_amount, 60)
self.assertEqual(po.taxes[0].total, 660)
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template})
def test_update_child_uom_conv_factor_change(self):
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
trans_item = json.dumps([{
'item_code': po.get("items")[0].item_code,
'rate': po.get("items")[0].rate,
'qty': po.get("items")[0].qty,
'uom': "_Test UOM 1",
'conversion_factor': 2,
'docname': po.get("items")[0].name
}])
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
total_reqd_qty_after_change = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
def test_update_qty(self):
po = create_purchase_order()

4
erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json

@ -148,11 +148,11 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-03-12 15:43:53.862897",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item Supplied",
"owner": "dhanalekshmi@webnotestech.com",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"

4
erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json

@ -188,11 +188,11 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-04-10 18:09:33.997618",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Receipt Item Supplied",
"owner": "wasim@webnotestech.com",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",

85
erpnext/controllers/accounts_controller.py

@ -7,6 +7,7 @@ import json
from frappe import _, throw
from frappe.utils import (today, flt, cint, fmt_money, formatdate,
getdate, add_days, add_months, get_last_day, nowdate, get_link_to_form)
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied, WorkflowPermissionError
from erpnext.stock.get_item_details import get_conversion_factor, get_item_details
from erpnext.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
@ -19,7 +20,7 @@ from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_t
from erpnext.exceptions import InvalidCurrency
from six import text_type
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
from erpnext.stock.get_item_details import get_item_warehouse
from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
@ -1157,6 +1158,18 @@ def get_supplier_block_status(party_name):
}
return info
def set_child_tax_template_and_map(item, child_item, parent_doc):
args = {
'item_code': item.item_code,
'posting_date': parent_doc.transaction_date,
'tax_category': parent_doc.get('tax_category'),
'company': parent_doc.get('company')
}
child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
if child_item.get("item_tax_template"):
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
"""
Returns a Sales Order Item child item containing the default values
@ -1168,8 +1181,10 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
child_item.item_name = item.item_name
child_item.description = item.description
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.uom = item.stock_uom
child_item.uom = trans_item.get("uom") or item.stock_uom
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
set_child_tax_template_and_map(item, child_item, p_doc)
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
@ -1188,13 +1203,15 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
child_item.item_name = item.item_name
child_item.description = item.description
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.uom = item.stock_uom
child_item.uom = trans_item.get("uom") or item.stock_uom
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation
set_child_tax_template_and_map(item, child_item, p_doc)
return child_item
def check_and_delete_children(parent, data):
def validate_and_delete_children(parent, data):
deleted_children = []
updated_item_names = [d.get("docname") for d in data]
for item in parent.items:
@ -1221,18 +1238,37 @@ def check_and_delete_children(parent, data):
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
def check_permissions(doc, perm_type='create'):
def check_doc_permissions(doc, perm_type='create'):
try:
doc.check_permission(perm_type)
except:
action = "add" if perm_type == 'create' else "update"
frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
except frappe.PermissionError:
actions = { 'create': 'add', 'write': 'update', 'cancel': 'remove' }
frappe.throw(_("You do not have permissions to {} items in a {}.")
.format(actions[perm_type], parent_doctype), title=_("Insufficient Permissions"))
def validate_workflow_conditions(doc):
workflow = get_workflow_name(doc.doctype)
if not workflow:
return
workflow_doc = frappe.get_doc("Workflow", workflow)
current_state = doc.get(workflow_doc.workflow_state_field)
roles = frappe.get_roles()
transitions = []
for transition in workflow_doc.transitions:
if transition.next_state == current_state and transition.allowed in roles:
if not is_transition_condition_satisfied(transition, doc):
continue
transitions.append(transition.as_dict())
if not transitions:
frappe.throw(_("You do not have workflow access to update this document."), title=_("Insufficient Workflow Permissions"))
def get_new_child_item(item_row):
if parent_doctype == "Sales Order":
return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
if parent_doctype == "Purchase Order":
return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults
return new_child_function(parent_doctype, parent_doctype_name, child_docname, item_row)
def validate_quantity(child_item, d):
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
@ -1246,21 +1282,23 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
check_and_delete_children(parent, data)
check_doc_permissions(parent, 'cancel')
validate_and_delete_children(parent, data)
for d in data:
new_child_flag = False
if not d.get("docname"):
new_child_flag = True
check_permissions(parent, 'create')
check_doc_permissions(parent, 'create')
child_item = get_new_child_item(d)
else:
check_permissions(parent, 'write')
check_doc_permissions(parent, 'write')
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
prev_uom, new_uom = child_item.get("uom"), d.get("uom")
if parent_doctype == 'Sales Order':
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
@ -1269,9 +1307,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
rate_unchanged = prev_rate == new_rate
qty_unchanged = prev_qty == new_qty
uom_unchanged = prev_uom == new_uom
conversion_factor_unchanged = prev_con_fac == new_con_fac
date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged:
if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
continue
validate_quantity(child_item, d)
@ -1291,6 +1330,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
else:
child_item.conversion_factor = flt(d.get('conversion_factor'))
if d.get("uom"):
child_item.uom = d.get("uom")
conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(d.get('conversion_factor')) or conversion_factor
if d.get("delivery_date") and parent_doctype == 'Sales Order':
child_item.delivery_date = d.get('delivery_date')
@ -1356,12 +1400,17 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
parent.update_receiving_percentage()
if parent.is_subcontracted == "Yes":
parent.update_reserved_qty_for_subcontract()
parent.create_raw_materials_supplied("supplied_items")
parent.save()
else:
parent.update_reserved_qty()
parent.update_project()
parent.update_prevdoc_status('submit')
parent.update_delivery_status()
parent.reload()
validate_workflow_conditions(parent)
parent.update_blanket_order()
parent.update_billing_percentage()
parent.set_status()

7
erpnext/controllers/queries.py

@ -165,9 +165,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
AND company = %(company)s
AND account_currency = %(currency)s
AND `{searchfield}` LIKE %(txt)s
{mcond}
ORDER BY idx DESC, name
LIMIT %(offset)s, %(limit)s
""".format(account_type_condition=account_type_condition, searchfield=searchfield),
""".format(
account_type_condition=account_type_condition,
searchfield=searchfield,
mcond=get_match_cond(doctype)
),
dict(
account_types=filters.get("account_type"),
company=filters.get("company"),

1
erpnext/controllers/selling_controller.py

@ -81,6 +81,7 @@ class SellingController(StockController):
party_details = _get_party_details(customer,
ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype, company=self.company,
posting_date=self.get('posting_date'),
fetch_payment_terms_template=fetch_payment_terms_template,
party_address=self.customer_address, shipping_address=self.shipping_address_name)
if not self.meta.get_field("sales_team"):

20
erpnext/education/doctype/student/student.js

@ -3,15 +3,15 @@
frappe.ui.form.on('Student', {
setup: function(frm) {
frm.add_fetch("guardian", "guardian_name", "guardian_name");
frm.add_fetch("student", "title", "full_name");
frm.add_fetch("student", "gender", "gender");
frm.add_fetch("student", "date_of_birth", "date_of_birth");
frm.add_fetch('guardian', 'guardian_name', 'guardian_name');
frm.add_fetch('student', 'title', 'full_name');
frm.add_fetch('student', 'gender', 'gender');
frm.add_fetch('student', 'date_of_birth', 'date_of_birth');
frm.set_query("student", "siblings", function(doc, cdt, cdn) {
frm.set_query('student', 'siblings', function(doc) {
return {
"filters": {
"name": ["!=", doc.name]
'filters': {
'name': ['!=', doc.name]
}
};
})
@ -25,6 +25,12 @@ frappe.ui.form.on('Student', {
{party_type:'Student', party:frm.doc.name});
});
}
frappe.db.get_value('Education Settings', {name: 'Education Settings'}, 'user_creation_skip', (r) => {
if (cint(r.user_creation_skip) !== 1) {
frm.set_df_property('student_email_id', 'reqd', 1);
}
});
}
});

3
erpnext/education/doctype/student/student.json

@ -102,7 +102,6 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Student Email Address",
"reqd": 1,
"unique": 1
},
{
@ -255,7 +254,7 @@
],
"image_field": "image",
"links": [],
"modified": "2020-07-23 18:14:06.366442",
"modified": "2020-09-07 19:28:08.914568",
"modified_by": "Administrator",
"module": "Education",
"name": "Student",

13
erpnext/education/doctype/student_applicant/student_applicant.js

@ -7,7 +7,7 @@ frappe.ui.form.on("Student Applicant", {
},
refresh: function(frm) {
if(frm.doc.application_status== "Applied" && frm.doc.docstatus== 1 ) {
if (frm.doc.application_status==="Applied" && frm.doc.docstatus===1 ) {
frm.add_custom_button(__("Approve"), function() {
frm.set_value("application_status", "Approved");
frm.save_or_update();
@ -20,10 +20,11 @@ frappe.ui.form.on("Student Applicant", {
}, 'Actions');
}
if(frm.doc.application_status== "Approved" && frm.doc.docstatus== 1 ) {
if (frm.doc.application_status === "Approved" && frm.doc.docstatus === 1) {
frm.add_custom_button(__("Enroll"), function() {
frm.events.enroll(frm)
}).addClass("btn-primary");
frm.add_custom_button(__("Reject"), function() {
frm.set_value("application_status", "Rejected");
frm.save_or_update();
@ -35,7 +36,13 @@ frappe.ui.form.on("Student Applicant", {
frappe.hide_msgprint(true);
frappe.show_progress(__("Enrolling student"), data.progress[0],data.progress[1]);
}
})
});
frappe.db.get_value("Education Settings", {name: "Education Settings"}, "user_creation_skip", (r) => {
if (cint(r.user_creation_skip) !== 1) {
frm.set_df_property("student_email_id", "reqd", 1);
}
});
},
enroll: function(frm) {

1507
erpnext/education/doctype/student_applicant/student_applicant.json

File diff suppressed because it is too large

103
erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py

@ -2,81 +2,90 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe import _
from frappe.utils.password import get_decrypted_password
from plaid import Client
from plaid.errors import APIError, ItemError
import plaid
import requests
from plaid.errors import APIError, ItemError, InvalidRequestError
import frappe
import requests
from frappe import _
class PlaidConnector():
def __init__(self, access_token=None):
plaid_settings = frappe.get_single("Plaid Settings")
self.config = {
"plaid_client_id": plaid_settings.plaid_client_id,
"plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'),
"plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": plaid_settings.plaid_env
}
self.client = Client(client_id=self.config.get("plaid_client_id"),
secret=self.config.get("plaid_secret"),
public_key=self.config.get("plaid_public_key"),
environment=self.config.get("plaid_env")
)
self.access_token = access_token
self.settings = frappe.get_single("Plaid Settings")
self.products = ["auth", "transactions"]
self.client_name = frappe.local.site
self.client = plaid.Client(
client_id=self.settings.plaid_client_id,
secret=self.settings.get_password("plaid_secret"),
environment=self.settings.plaid_env,
api_version="2019-05-29"
)
def get_access_token(self, public_token):
if public_token is None:
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
response = self.client.Item.public_token.exchange(public_token)
access_token = response['access_token']
access_token = response["access_token"]
return access_token
def get_link_token(self):
token_request = {
"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)
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
"country_codes": ["US", "CA", "FR", "IE", "NL", "ES", "GB"],
"user": {
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
}
}
try:
response = self.client.LinkToken.create(token_request)
except InvalidRequestError:
frappe.log_error(frappe.get_traceback(), _("Plaid invalid request error"))
frappe.msgprint(_("Please check your Plaid client ID and secret values"))
except APIError as e:
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
frappe.throw(_(str(e)), title=_("Authentication Failed"))
else:
return response["link_token"]
def auth(self):
try:
self.client.Auth.get(self.access_token)
print("Authentication successful.....")
except ItemError as e:
if e.code == 'ITEM_LOGIN_REQUIRED':
pass
else:
if e.code == "ITEM_LOGIN_REQUIRED":
pass
except APIError as e:
if e.code == 'PLANNED_MAINTENANCE':
pass
else:
if e.code == "PLANNED_MAINTENANCE":
pass
except requests.Timeout:
pass
except Exception as e:
print(e)
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'})
frappe.throw(_(str(e)), title=_("Authentication Failed"))
def get_transactions(self, start_date, end_date, account_id=None):
try:
self.auth()
if account_id:
account_ids = [account_id]
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
self.auth()
kwargs = dict(
access_token=self.access_token,
start_date=start_date,
end_date=end_date
)
if account_id:
kwargs.update(dict(account_ids=[account_id]))
else:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date)
transactions = response['transactions']
while len(transactions) < response['total_transactions']:
try:
response = self.client.Transactions.get(**kwargs)
transactions = response["transactions"]
while len(transactions) < response["total_transactions"]:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
transactions.extend(response['transactions'])
transactions.extend(response["transactions"])
return transactions
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))

57
erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js

@ -4,14 +4,14 @@
frappe.provide("erpnext.integrations");
frappe.ui.form.on('Plaid Settings', {
enabled: function(frm) {
enabled: function (frm) {
frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
frm.toggle_reqd('plaid_secret', frm.doc.enabled);
frm.toggle_reqd('plaid_public_key', frm.doc.enabled);
frm.toggle_reqd('plaid_env', frm.doc.enabled);
},
refresh: function(frm) {
if(frm.doc.enabled) {
refresh: function (frm) {
if (frm.doc.enabled) {
frm.add_custom_button('Link a new bank account', () => {
new erpnext.integrations.plaidLink(frm);
});
@ -22,17 +22,16 @@ frappe.ui.form.on('Plaid Settings', {
erpnext.integrations.plaidLink = class plaidLink {
constructor(parent) {
this.frm = parent;
this.product = ["transactions", "auth"];
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config();
}
init_config() {
const me = this;
me.plaid_env = me.frm.doc.plaid_env;
me.plaid_public_key = me.frm.doc.plaid_public_key;
me.client_name = frappe.boot.sitename;
me.init_plaid();
async init_config() {
this.product = ["auth", "transactions"];
this.plaid_env = this.frm.doc.plaid_env;
this.client_name = frappe.boot.sitename;
this.token = await this.frm.call("get_link_token").then(resp => resp.message);
this.init_plaid();
}
init_plaid() {
@ -69,17 +68,17 @@ erpnext.integrations.plaidLink = class plaidLink {
}
onScriptLoaded(me) {
me.linkHandler = window.Plaid.create({
me.linkHandler = Plaid.create({
clientName: me.client_name,
product: me.product,
env: me.plaid_env,
key: me.plaid_public_key,
onSuccess: me.plaid_success,
product: me.product
token: me.token,
onSuccess: me.plaid_success
});
}
onScriptError(error) {
frappe.msgprint('There was an issue loading the link-initialize.js script');
frappe.msgprint("There was an issue connecting to Plaid's authentication server");
frappe.msgprint(error);
}
@ -87,21 +86,25 @@ erpnext.integrations.plaidLink = class plaidLink {
const me = this;
frappe.prompt({
fieldtype:"Link",
fieldtype: "Link",
options: "Company",
label:__("Company"),
fieldname:"company",
reqd:1
label: __("Company"),
fieldname: "company",
reqd: 1
}, (data) => {
me.company = data.company;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
.then((result) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response,
bank: result, company: me.company});
})
.then(() => {
frappe.show_alert({message:__("Bank accounts added"), indicator:'green'});
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {
token: token,
response: response
}).then((result) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {
response: response,
bank: result,
company: me.company
});
}).then(() => {
frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' });
});
}, __("Select a company"), __("Continue"));
}
};

11
erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json

@ -1,5 +1,4 @@
{
"actions": [],
"creation": "2018-10-25 10:02:48.656165",
"doctype": "DocType",
"editable_grid": 1,
@ -12,7 +11,6 @@
"plaid_client_id",
"plaid_secret",
"column_break_7",
"plaid_public_key",
"plaid_env"
],
"fields": [
@ -41,12 +39,6 @@
"in_list_view": 1,
"label": "Plaid Secret"
},
{
"fieldname": "plaid_public_key",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Plaid Public Key"
},
{
"fieldname": "plaid_env",
"fieldtype": "Select",
@ -69,8 +61,7 @@
}
],
"issingle": 1,
"links": [],
"modified": "2020-02-07 15:21:11.616231",
"modified": "2020-09-12 02:31:44.542385",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Plaid Settings",

50
erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py

@ -2,30 +2,36 @@
# Copyright (c) 2018, 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.model.document import Document
import frappe
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
from frappe.utils import getdate, formatdate, today, add_months
from frappe import _
from frappe.desk.doctype.tag.tag import add_tag
from frappe.model.document import Document
from frappe.utils import add_months, formatdate, getdate, today
class PlaidSettings(Document):
pass
@staticmethod
def get_link_token():
plaid = PlaidConnector()
return plaid.get_link_token()
@frappe.whitelist()
def plaid_configuration():
def get_plaid_configuration():
if frappe.db.get_single_value("Plaid Settings", "enabled"):
plaid_settings = frappe.get_single("Plaid Settings")
return {
"plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": plaid_settings.plaid_env,
"link_token": plaid_settings.get_link_token(),
"client_name": frappe.local.site
}
else:
return "disabled"
return "disabled"
@frappe.whitelist()
def add_institution(token, response):
@ -33,6 +39,7 @@ def add_institution(token, response):
plaid = PlaidConnector()
access_token = plaid.get_access_token(token)
bank = None
if not frappe.db.exists("Bank", response["institution"]["name"]):
try:
@ -44,7 +51,6 @@ def add_institution(token, response):
bank.insert()
except Exception:
frappe.throw(frappe.get_traceback())
else:
bank = frappe.get_doc("Bank", response["institution"]["name"])
bank.plaid_access_token = access_token
@ -52,6 +58,7 @@ def add_institution(token, response):
return bank
@frappe.whitelist()
def add_bank_accounts(response, bank, company):
try:
@ -92,9 +99,8 @@ def add_bank_accounts(response, bank, company):
new_account.insert()
result.append(new_account.name)
except frappe.UniqueValidationError:
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name))
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
except Exception:
frappe.throw(frappe.get_traceback())
@ -103,6 +109,7 @@ def add_bank_accounts(response, bank, company):
return result
def add_account_type(account_type):
try:
frappe.get_doc({
@ -122,10 +129,11 @@ def add_account_subtype(account_subtype):
except Exception:
frappe.throw(frappe.get_traceback())
@frappe.whitelist()
def sync_transactions(bank, bank_account):
'''Sync transactions based on the last integration date as the start date, after the sync is completed
add the transaction date of the oldest transaction as the last integration date'''
"""Sync transactions based on the last integration date as the start date, after sync is completed
add the transaction date of the oldest transaction as the last integration date."""
last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
if last_transaction_date:
start_date = formatdate(last_transaction_date, "YYYY-MM-dd")
@ -147,10 +155,10 @@ def sync_transactions(bank, bank_account):
len(result), bank_account, start_date, end_date))
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
access_token = None
@ -168,6 +176,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
return transactions
def new_bank_transaction(transaction):
result = []
@ -182,8 +191,8 @@ def new_bank_transaction(transaction):
status = "Pending" if transaction["pending"] == "True" else "Settled"
tags = []
try:
tags = []
tags += transaction["category"]
tags += ["Plaid Cat. {}".format(transaction["category_id"])]
except KeyError:
@ -216,6 +225,7 @@ def new_bank_transaction(transaction):
return result
def automatic_synchronization():
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
@ -223,4 +233,8 @@ def automatic_synchronization():
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
for plaid_account in plaid_accounts:
frappe.enqueue("erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions", bank=plaid_account.bank, bank_account=plaid_account.name)
frappe.enqueue(
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
bank=plaid_account.bank,
bank_account=plaid_account.name
)

19
erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py

@ -1,14 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import json
import unittest
import frappe
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import plaid_configuration, add_account_type, add_account_subtype, new_bank_transaction, add_bank_accounts
import json
from frappe.utils.response import json_handler
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import (
add_account_subtype, add_account_type, add_bank_accounts,
new_bank_transaction, get_plaid_configuration)
from frappe.utils.response import json_handler
class TestPlaidSettings(unittest.TestCase):
def setUp(self):
@ -31,7 +34,7 @@ class TestPlaidSettings(unittest.TestCase):
def test_plaid_disabled(self):
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
self.assertTrue(plaid_configuration() == "disabled")
self.assertTrue(get_plaid_configuration() == "disabled")
def test_add_account_type(self):
add_account_type("brokerage")
@ -64,7 +67,7 @@ class TestPlaidSettings(unittest.TestCase):
'mask': '0000',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking'
}],
}],
'institution': {
'institution_id': 'ins_6',
'name': 'Citi'
@ -100,7 +103,7 @@ class TestPlaidSettings(unittest.TestCase):
'mask': '0000',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking'
}],
}],
'institution': {
'institution_id': 'ins_6',
'name': 'Citi'
@ -152,4 +155,4 @@ class TestPlaidSettings(unittest.TestCase):
new_bank_transaction(transactions)
self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1)
self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1)

4
erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json

@ -258,8 +258,8 @@
}
],
"issingle": 1,
"modified": "2020-05-28 12:32:11.384757",
"modified_by": "umair@erpnext.com",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Shopify Settings",
"owner": "Administrator",

4
erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json

@ -117,12 +117,12 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-05-08 13:45:57.226530",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Schedule Time Slot",
"name_case": "",
"owner": "rmehta@gmail.com",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,

33
erpnext/healthcare/doctype/lab_test/lab_test.js

@ -34,10 +34,10 @@ frappe.ui.form.on('Lab Test', {
if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') {
frm.add_custom_button(__('Approve'), function () {
status_update(1, frm);
});
}, __('Actions'));
frm.add_custom_button(__('Reject'), function () {
status_update(0, frm);
});
}, __('Actions'));
}
}
@ -179,23 +179,6 @@ var show_lab_tests = function (frm, lab_test_list) {
d.show();
};
cur_frm.cscript.custom_before_submit = function (doc) {
if (doc.normal_test_items) {
for (let result in doc.normal_test_items) {
if (!doc.normal_test_items[result].result_value && !doc.normal_test_items[result].allow_blank && doc.normal_test_items[result].require_result_value) {
frappe.throw(__('Please input all required result values'));
}
}
}
if (doc.descriptive_test_items) {
for (let result in doc.descriptive_test_items) {
if (!doc.descriptive_test_items[result].result_value && !doc.descriptive_test_items[result].allow_blank && doc.descriptive_test_items[result].require_result_value) {
frappe.throw(__('Please input all required result values'));
}
}
}
};
var make_dialog = function (frm, emailed, printed) {
var number = frm.doc.mobile;
@ -203,7 +186,7 @@ var make_dialog = function (frm, emailed, printed) {
title: 'Send SMS',
width: 400,
fields: [
{ fieldname: 'sms_type', fieldtype: 'Select', label: 'Type', options: ['Emailed', 'Printed'] },
{ fieldname: 'result_format', fieldtype: 'Select', label: 'Result Format', options: ['Emailed', 'Printed'] },
{ fieldname: 'number', fieldtype: 'Data', label: 'Mobile Number', reqd: 1 },
{ fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 }
],
@ -217,22 +200,22 @@ var make_dialog = function (frm, emailed, printed) {
dialog.hide();
}
});
if (frm.doc.report_preference == 'Print') {
if (frm.doc.report_preference === 'Print') {
dialog.set_values({
'sms_type': 'Printed',
'result_format': 'Printed',
'number': number,
'message': printed
});
} else {
dialog.set_values({
'sms_type': 'Emailed',
'result_format': 'Emailed',
'number': number,
'message': emailed
});
}
var fd = dialog.fields_dict;
$(fd.sms_type.input).change(function () {
if (dialog.get_value('sms_type') == 'Emailed') {
$(fd.result_format.input).change(function () {
if (dialog.get_value('result_format') === 'Emailed') {
dialog.set_values({
'number': number,
'message': emailed

25
erpnext/healthcare/doctype/lab_test/lab_test.json

@ -84,7 +84,7 @@
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "LP-",
"options": "HLC-LAB-.YYYY.-",
"print_hide": 1,
"report_hide": 1,
"reqd": 1
@ -197,11 +197,10 @@
{
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Draft\nCompleted\nApproved\nRejected\nCancelled",
"print_hide": 1,
"read_only": 1,
"report_hide": 1,
"search_index": 1
},
{
@ -249,8 +248,8 @@
{
"fieldname": "result_date",
"fieldtype": "Date",
"hidden": 1,
"label": "Result Date",
"read_only": 1,
"search_index": 1
},
{
@ -354,7 +353,8 @@
},
{
"fieldname": "sb_normal",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Compound Test Result"
},
{
"fieldname": "normal_test_items",
@ -369,11 +369,13 @@
{
"depends_on": "descriptive_toggle",
"fieldname": "organisms_section",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Organism Test Result"
},
{
"fieldname": "sb_sensitivity",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Sensitivity Test Result"
},
{
"fieldname": "sensitivity_test_items",
@ -383,8 +385,10 @@
"report_hide": 1
},
{
"collapsible": 1,
"fieldname": "sb_comments",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Comments"
},
{
"fieldname": "lab_test_comment",
@ -531,7 +535,8 @@
},
{
"fieldname": "sb_descriptive",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Descriptive Test Result"
},
{
"default": "0",
@ -550,7 +555,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-07-16 13:35:24.811062",
"modified": "2020-07-30 18:18:38.516215",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test",

60
erpnext/healthcare/doctype/lab_test/lab_test.py

@ -6,23 +6,24 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate, cstr
from frappe.utils import getdate, cstr, get_link_to_form
class LabTest(Document):
def validate(self):
if not self.is_new():
self.set_secondary_uom_result()
def on_submit(self):
self.validate_result_values()
self.db_set('submitted_date', getdate())
self.db_set('status', 'Completed')
insert_lab_test_to_medical_record(self)
def on_cancel(self):
delete_lab_test_from_medical_record(self)
self.db_set('status', 'Cancelled')
delete_lab_test_from_medical_record(self)
self.reload()
def validate(self):
if not self.is_new():
self.set_secondary_uom_result()
def on_update(self):
if self.sensitivity_test_items:
sensitivity = sorted(self.sensitivity_test_items, key=lambda x: x.antibiotic_sensitivity)
@ -51,7 +52,20 @@ class LabTest(Document):
item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor)
except:
item.secondary_uom_result = ''
frappe.msgprint(_('Result for Secondary UOM not calculated for row #{0}'.format(item.idx)), title = _('Warning'))
frappe.msgprint(_('Row #{0}: Result for Secondary UOM not calculated'.format(item.idx)), title = _('Warning'))
def validate_result_values(self):
if self.normal_test_items:
for item in self.normal_test_items:
if not item.result_value and not item.allow_blank and item.require_result_value:
frappe.throw(_('Row #{0}: Please enter the result value for {1}').format(
item.idx, frappe.bold(item.lab_test_name)), title=_('Mandatory Results'))
if self.descriptive_test_items:
for item in self.descriptive_test_items:
if not item.result_value and not item.allow_blank and item.require_result_value:
frappe.throw(_('Row #{0}: Please enter the result value for {1}').format(
item.idx, frappe.bold(item.lab_test_particulars)), title=_('Mandatory Results'))
def create_test_from_template(lab_test):
@ -89,7 +103,7 @@ def create_multiple(doctype, docname):
lab_test_created = create_lab_test_from_encounter(docname)
if lab_test_created:
frappe.msgprint(_('Lab Test(s) {0} created'.format(lab_test_created)))
frappe.msgprint(_('Lab Test(s) {0} created successfully').format(lab_test_created), indicator='green')
else:
frappe.msgprint(_('No Lab Tests created'))
@ -211,8 +225,9 @@ def create_sample_doc(template, patient, invoice, company = None):
'docstatus': 0,
'sample': template.sample
})
if sample_exists:
# Update Sample Collection by adding quantity
# update sample collection by adding quantity
sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0])
quantity = int(sample_collection.sample_qty) + int(template.sample_qty)
if template.sample_details:
@ -238,7 +253,7 @@ def create_sample_doc(template, patient, invoice, company = None):
sample_collection.company = company
if template.sample_details:
sample_collection.sample_details = 'Test :' + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details
sample_collection.sample_details = _('Test :') + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details
sample_collection.save(ignore_permissions=True)
return sample_collection
@ -248,26 +263,31 @@ def create_sample_collection(lab_test, template, patient, invoice):
sample_collection = create_sample_doc(template, patient, invoice, lab_test.company)
if sample_collection:
lab_test.sample = sample_collection.name
sample_collection_doc = get_link_to_form('Sample Collection', sample_collection.name)
frappe.msgprint(_('Sample Collection {0} has been created').format(sample_collection_doc),
title=_('Sample Collection'), indicator='green')
return lab_test
def load_result_format(lab_test, template, prescription, invoice):
if template.lab_test_template_type == 'Single':
create_normals(template, lab_test)
elif template.lab_test_template_type == 'Compound':
create_compounds(template, lab_test, False)
elif template.lab_test_template_type == 'Descriptive':
create_descriptives(template, lab_test)
elif template.lab_test_template_type == 'Grouped':
# Iterate for each template in the group and create one result for all.
for lab_test_group in template.lab_test_groups:
# Template_in_group = None
if lab_test_group.lab_test_template:
template_in_group = frappe.get_doc('Lab Test Template',
lab_test_group.lab_test_template)
template_in_group = frappe.get_doc('Lab Test Template', lab_test_group.lab_test_template)
if template_in_group:
if template_in_group.lab_test_template_type == 'Single':
create_normals(template_in_group, lab_test)
elif template_in_group.lab_test_template_type == 'Compound':
normal_heading = lab_test.append('normal_test_items')
normal_heading.lab_test_name = template_in_group.lab_test_name
@ -275,6 +295,7 @@ def load_result_format(lab_test, template, prescription, invoice):
normal_heading.allow_blank = 1
normal_heading.template = template_in_group.name
create_compounds(template_in_group, lab_test, True)
elif template_in_group.lab_test_template_type == 'Descriptive':
descriptive_heading = lab_test.append('descriptive_test_items')
descriptive_heading.lab_test_name = template_in_group.lab_test_name
@ -282,6 +303,7 @@ def load_result_format(lab_test, template, prescription, invoice):
descriptive_heading.allow_blank = 1
descriptive_heading.template = template_in_group.name
create_descriptives(template_in_group, lab_test)
else: # Lab Test Group - Add New Line
normal = lab_test.append('normal_test_items')
normal.lab_test_name = lab_test_group.group_event
@ -292,6 +314,7 @@ def load_result_format(lab_test, template, prescription, invoice):
normal.allow_blank = lab_test_group.allow_blank
normal.require_result_value = 1
normal.template = template.name
if template.lab_test_template_type != 'No Result':
if prescription:
lab_test.prescription = prescription
@ -302,9 +325,10 @@ def load_result_format(lab_test, template, prescription, invoice):
@frappe.whitelist()
def get_employee_by_user_id(user_id):
emp_id = frappe.db.get_value('Employee', { 'user_id': user_id })
employee = frappe.get_doc('Employee', emp_id)
return employee
emp_id = frappe.db.exists('Employee', { 'user_id': user_id })
if emp_id:
return frappe.get_doc('Employee', emp_id)
return None
def insert_lab_test_to_medical_record(doc):
table_row = False
@ -325,7 +349,7 @@ def insert_lab_test_to_medical_record(doc):
table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
if item.normal_range:
table_row += ' ' + _('Normal Range:') + item.normal_range
table_row += ' ' + _('Normal Range: ') + item.normal_range
table_row += ' ' + comment
elif doc.descriptive_test_items:
@ -356,7 +380,7 @@ def insert_lab_test_to_medical_record(doc):
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))
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])

17
erpnext/healthcare/doctype/lab_test/lab_test_list.js

@ -3,13 +3,16 @@
*/
frappe.listview_settings['Lab Test'] = {
add_fields: ['name', 'status', 'invoiced'],
filters: [['docstatus', '=', '0']],
filters: [['docstatus', '=', '1']],
get_indicator: function (doc) {
if (doc.status == 'Approved') {
return [__('Approved'), 'green', 'status, = ,Approved'];
}
if (doc.status == 'Rejected') {
if (doc.status === 'Approved') {
return [__('Approved'), 'green', 'status, =, Approved'];
} else if (doc.status === 'Rejected') {
return [__('Rejected'), 'orange', 'status, =, Rejected'];
} else if (doc.status === 'Completed') {
return [__('Completed'), 'green', 'status, =, Completed'];
} else if (doc.status === 'Cancelled') {
return [__('Cancelled'), 'red', 'status, =, Cancelled'];
}
},
onload: function (listview) {
@ -21,7 +24,7 @@ frappe.listview_settings['Lab Test'] = {
var create_multiple_dialog = function (listview) {
var dialog = new frappe.ui.Dialog({
title: 'Create Multiple Lab Test',
title: 'Create Multiple Lab Tests',
width: 100,
fields: [
{ fieldtype: 'Link', label: 'Patient', fieldname: 'patient', options: 'Patient', reqd: 1 },
@ -41,7 +44,7 @@ var create_multiple_dialog = function (listview) {
}
}
],
primary_action_label: __('Create Lab Test'),
primary_action_label: __('Create'),
primary_action: function () {
frappe.call({
method: 'erpnext.healthcare.doctype.lab_test.lab_test.create_multiple',

202
erpnext/healthcare/doctype/lab_test/test_lab_test.py

@ -3,8 +3,204 @@
# See license.txt
from __future__ import unicode_literals
import unittest
# test_records = frappe.get_test_records('Lab Test')
import frappe
from frappe.utils import getdate, nowtime
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
from erpnext.healthcare.doctype.patient_medical_record.test_patient_medical_record import create_lab_test_template as create_blood_test_template
class TestLabTest(unittest.TestCase):
pass
def test_lab_test_item(self):
lab_template = create_lab_test_template()
self.assertTrue(frappe.db.exists('Item', lab_template.item))
self.assertEqual(frappe.db.get_value('Item Price', {'item_code':lab_template.item}, 'price_list_rate'), lab_template.lab_test_rate)
lab_template.disabled = 1
lab_template.save()
self.assertEquals(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1)
lab_template.reload()
lab_template.disabled = 0
lab_template.save()
def test_descriptive_lab_test(self):
lab_template = create_lab_test_template()
# blank result value not allowed as per template
lab_test = create_lab_test(lab_template)
lab_test.descriptive_test_items[0].result_value = 12
lab_test.descriptive_test_items[2].result_value = 1
lab_test.save()
self.assertRaises(frappe.ValidationError, lab_test.submit)
def test_sample_collection(self):
frappe.db.set_value('Healthcare Settings', 'Healthcare Settings', 'create_sample_collection_for_lab_test', 1)
lab_template = create_lab_test_template()
lab_test = create_lab_test(lab_template)
lab_test.descriptive_test_items[0].result_value = 12
lab_test.descriptive_test_items[1].result_value = 1
lab_test.descriptive_test_items[2].result_value = 2.3
lab_test.save()
# check sample collection created
self.assertTrue(frappe.db.exists('Sample Collection', {'sample': lab_template.sample}))
frappe.db.set_value('Healthcare Settings', 'Healthcare Settings', 'create_sample_collection_for_lab_test', 0)
lab_test = create_lab_test(lab_template)
lab_test.descriptive_test_items[0].result_value = 12
lab_test.descriptive_test_items[1].result_value = 1
lab_test.descriptive_test_items[2].result_value = 2.3
lab_test.save()
# sample collection should not be created
lab_test.reload()
self.assertEquals(lab_test.sample, None)
def test_create_lab_tests_from_sales_invoice(self):
sales_invoice = create_sales_invoice()
create_multiple('Sales Invoice', sales_invoice.name)
sales_invoice.reload()
self.assertIsNotNone(sales_invoice.items[0].reference_dn)
self.assertIsNotNone(sales_invoice.items[1].reference_dn)
def test_create_lab_tests_from_patient_encounter(self):
patient_encounter = create_patient_encounter()
create_multiple('Patient Encounter', patient_encounter.name)
patient_encounter.reload()
self.assertTrue(patient_encounter.lab_test_prescription[0].lab_test_created)
self.assertTrue(patient_encounter.lab_test_prescription[0].lab_test_created)
def create_lab_test_template(test_sensitivity=0, sample_collection=1):
medical_department = create_medical_department()
if frappe.db.exists('Lab Test Template', 'Insulin Resistance'):
return frappe.get_doc('Lab Test Template', 'Insulin Resistance')
template = frappe.new_doc('Lab Test Template')
template.lab_test_name = 'Insulin Resistance'
template.lab_test_template_type = 'Descriptive'
template.lab_test_code = 'Insulin Resistance'
template.lab_test_group = 'Services'
template.department = medical_department
template.is_billable = 1
template.lab_test_description = 'Insulin Resistance'
template.lab_test_rate = 2000
for entry in ['FBS', 'Insulin', 'IR']:
template.append('descriptive_test_templates', {
'particulars': entry,
'allow_blank': 1 if entry=='IR' else 0
})
if test_sensitivity:
template.sensitivity = 1
if sample_collection:
template.sample = create_lab_test_sample()
template.sample_qty = 5.0
template.save()
return template
def create_medical_department():
medical_department = frappe.db.exists('Medical Department', '_Test Medical Department')
if not medical_department:
medical_department = frappe.new_doc('Medical Department')
medical_department.department = '_Test Medical Department'
medical_department.save()
medical_department = medical_department.name
return medical_department
def create_lab_test(lab_template):
patient = create_patient()
lab_test = frappe.new_doc('Lab Test')
lab_test.template = lab_template.name
lab_test.patient = patient
lab_test.patient_sex = 'Female'
lab_test.save()
return lab_test
def create_lab_test_sample():
blood_sample = frappe.db.exists('Lab Test Sample', 'Blood Sample')
if blood_sample:
return blood_sample
sample = frappe.new_doc('Lab Test Sample')
sample.sample = 'Blood Sample'
sample.sample_uom = 'U/ml'
sample.save()
return sample.name
def create_sales_invoice():
patient = create_patient()
medical_department = create_medical_department()
insulin_resistance_template = create_lab_test_template()
blood_test_template = create_blood_test_template(medical_department)
sales_invoice = frappe.new_doc('Sales Invoice')
sales_invoice.patient = patient
sales_invoice.customer = frappe.db.get_value('Patient', patient, 'customer')
sales_invoice.due_date = getdate()
sales_invoice.company = '_Test Company'
sales_invoice.debit_to = get_receivable_account('_Test Company')
tests = [insulin_resistance_template, blood_test_template]
for entry in tests:
sales_invoice.append('items', {
'item_code': entry.item,
'item_name': entry.lab_test_name,
'description': entry.lab_test_description,
'qty': 1,
'uom': 'Nos',
'conversion_factor': 1,
'income_account': get_income_account(None, '_Test Company'),
'rate': entry.lab_test_rate,
'amount': entry.lab_test_rate
})
sales_invoice.set_missing_values()
sales_invoice.submit()
return sales_invoice
def create_patient_encounter():
patient = create_patient()
medical_department = create_medical_department()
insulin_resistance_template = create_lab_test_template()
blood_test_template = create_blood_test_template(medical_department)
patient_encounter = frappe.new_doc('Patient Encounter')
patient_encounter.patient = patient
patient_encounter.practitioner = create_practitioner()
patient_encounter.encounter_date = getdate()
patient_encounter.encounter_time = nowtime()
tests = [insulin_resistance_template, blood_test_template]
for entry in tests:
patient_encounter.append('lab_test_prescription', {
'lab_test_code': entry.item,
'lab_test_name': entry.lab_test_name
})
patient_encounter.submit()
return patient_encounter
def create_practitioner():
practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner')
if not practitioner:
practitioner = frappe.new_doc('Healthcare Practitioner')
practitioner.first_name = '_Test Healthcare Practitioner'
practitioner.gender = 'Female'
practitioner.op_consulting_charge = 500
practitioner.inpatient_visit_charge = 500
practitioner.save(ignore_permissions=True)
practitioner = practitioner.name
return practitioner

5
erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json

@ -93,7 +93,8 @@
"depends_on": "secondary_uom",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "Conversion Factor"
"label": "Conversion Factor",
"mandatory_depends_on": "secondary_uom"
},
{
"default": "0",
@ -106,7 +107,7 @@
],
"istable": 1,
"links": [],
"modified": "2020-06-24 10:59:01.921924",
"modified": "2020-07-30 12:36:03.082391",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test Group Template",

21
erpnext/healthcare/doctype/lab_test_template/lab_test_template.json

@ -34,14 +34,15 @@
"descriptive_test_templates",
"section_break_group",
"lab_test_groups",
"medical_coding_section",
"medical_code_standard",
"medical_code",
"sb_sample_collection",
"sample",
"sample_uom",
"sample_qty",
"column_break_33",
"sample_details",
"medical_coding_section",
"medical_code",
"medical_code_standard",
"worksheet_section",
"worksheet_instructions",
"result_legend_section",
@ -112,7 +113,7 @@
{
"default": "1",
"depends_on": "eval:doc.lab_test_template_type != 'Grouped'",
"description": "If unchecked, the item wont be appear in Sales Invoice, but can be used in group test creation. ",
"description": "If unchecked, the item will not be available in Sales Invoices for billing but can be used in group test creation. ",
"fieldname": "is_billable",
"fieldtype": "Check",
"label": "Is Billable",
@ -128,6 +129,7 @@
"mandatory_depends_on": "eval:doc.is_billable == 1"
},
{
"collapsible": 1,
"fieldname": "medical_coding_section",
"fieldtype": "Section Break",
"label": "Medical Coding"
@ -184,7 +186,7 @@
"depends_on": "eval:doc.lab_test_template_type == 'Descriptive'",
"fieldname": "section_break_special",
"fieldtype": "Section Break",
"label": "Descriptive"
"label": "Descriptive Test"
},
{
"default": "0",
@ -196,7 +198,7 @@
"depends_on": "eval:doc.lab_test_template_type == 'Grouped'",
"fieldname": "section_break_group",
"fieldtype": "Section Break",
"label": "Group"
"label": "Group Tests"
},
{
"fieldname": "lab_test_groups",
@ -217,7 +219,6 @@
"no_copy": 1
},
{
"collapsible": 1,
"fieldname": "sb_sample_collection",
"fieldtype": "Section Break",
"label": "Sample Collection"
@ -311,10 +312,14 @@
"fieldname": "descriptive_test_templates",
"fieldtype": "Table",
"options": "Descriptive Test Template"
},
{
"fieldname": "column_break_33",
"fieldtype": "Column Break"
}
],
"links": [],
"modified": "2020-07-13 12:57:09.925436",
"modified": "2020-07-30 14:32:40.449818",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test Template",

37
erpnext/healthcare/doctype/lab_test_template/lab_test_template.py

@ -15,7 +15,8 @@ class LabTestTemplate(Document):
def validate(self):
if self.is_billable and (not self.lab_test_rate or self.lab_test_rate <= 0.0):
frappe.throw(_("Standard Selling Rate should be greater than zero."))
frappe.throw(_('Standard Selling Rate should be greater than zero.'))
self.validate_conversion_factor()
self.enable_disable_item()
@ -42,7 +43,9 @@ class LabTestTemplate(Document):
# Remove template reference from item and disable item
if self.item:
try:
frappe.delete_doc('Item', self.item)
item = self.item
self.db_set('item', '')
frappe.delete_doc('Item', item)
except Exception:
frappe.throw(_('Not permitted. Please disable the Lab Test Template'))
@ -63,26 +66,26 @@ class LabTestTemplate(Document):
'standard_rate': self.lab_test_rate,
'description': self.lab_test_description
})
item.save()
item.flags.ignore_mandatory = True
item.save(ignore_permissions=True)
def item_price_exists(self):
item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code})
if item_price:
return item_price[0][0]
else:
return False
return False
def validate_conversion_factor(self):
if self.lab_test_template_type == "Single" and self.secondary_uom and not self.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory"))
if self.lab_test_template_type == "Compound":
if self.lab_test_template_type == 'Single' and self.secondary_uom and not self.conversion_factor:
frappe.throw(_('Conversion Factor is mandatory'))
if self.lab_test_template_type == 'Compound':
for item in self.normal_test_templates:
if item.secondary_uom and not item.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory"))
if self.lab_test_template_type == "Grouped":
frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(item.idx))
if self.lab_test_template_type == 'Grouped':
for group in self.lab_test_groups:
if group.template_or_new_line == "Add New Line" and group.secondary_uom and not group.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory"))
if group.template_or_new_line == 'Add New Line' and group.secondary_uom and not group.conversion_factor:
frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(group.idx))
def create_item_from_template(doc):
@ -101,9 +104,9 @@ def create_item_from_template(doc):
'include_item_in_manufacturing': 0,
'show_in_website': 0,
'is_pro_applicable': 0,
'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled,
'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled,
'stock_uom': uom
}).insert(ignore_permissions = True, ignore_mandatory = True)
}).insert(ignore_permissions=True, ignore_mandatory=True)
# Insert item price
if doc.is_billable and doc.lab_test_rate != 0.0:
@ -123,7 +126,7 @@ def make_item_price(item, price_list_name, item_price):
'price_list': price_list_name,
'item_code': item,
'price_list_rate': item_price
}).insert(ignore_permissions = True, ignore_mandatory = True)
}).insert(ignore_permissions=True, ignore_mandatory=True)
@frappe.whitelist()
def change_test_code_from_template(lab_test_code, doc):
@ -132,8 +135,8 @@ def change_test_code_from_template(lab_test_code, doc):
if frappe.db.exists({'doctype': 'Item', 'item_code': lab_test_code}):
frappe.throw(_('Lab Test Item {0} already exist').format(lab_test_code))
else:
rename_doc('Item', doc.name, lab_test_code, ignore_permissions = True)
rename_doc('Item', doc.name, lab_test_code, ignore_permissions=True)
frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_code', lab_test_code)
frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_name', lab_test_code)
rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions = True)
rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions=True)
return lab_test_code

13
erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py

@ -0,0 +1,13 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'template',
'transactions': [
{
'label': _('Lab Tests'),
'items': ['Lab Test']
}
]
}

2
erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js

@ -3,5 +3,5 @@
*/
frappe.listview_settings['Lab Test Template'] = {
add_fields: ['lab_test_name', 'lab_test_code', 'lab_test_rate'],
filters: [['disabled', '=', 0]]
filters: [['disabled', '=', 'No']]
};

6
erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py

@ -34,7 +34,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
self.assertTrue(medical_rec)
template = create_lab_test_template(medical_department)
lab_test = create_lab_test(template, patient)
lab_test = create_lab_test(template.name, patient)
# check for lab test
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': lab_test.name})
self.assertTrue(medical_rec)
@ -66,7 +66,7 @@ def create_vital_signs(appointment):
def create_lab_test_template(medical_department):
if frappe.db.exists('Lab Test Template', 'Blood Test'):
return 'Blood Test'
return frappe.get_doc('Lab Test Template', 'Blood Test')
template = frappe.new_doc('Lab Test Template')
template.lab_test_name = 'Blood Test'
@ -76,7 +76,7 @@ def create_lab_test_template(medical_department):
template.is_billable = 1
template.lab_test_rate = 2000
template.save()
return template.name
return template
def create_lab_test(template, patient):
lab_test = frappe.new_doc('Lab Test')

4
erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json

@ -44,11 +44,11 @@
}
],
"links": [],
"modified": "2020-01-31 12:21:45.975488",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Practitioner Schedule",
"owner": "rmehta@gmail.com",
"owner": "Administrator",
"permissions": [
{
"create": 1,

20
erpnext/healthcare/doctype/sample_collection/sample_collection.js

@ -3,29 +3,29 @@
frappe.ui.form.on('Sample Collection', {
refresh: function(frm) {
if(frappe.defaults.get_default("create_sample_collection_for_lab_test")){
frm.add_custom_button(__("View Lab Tests"), function() {
frappe.route_options = {"sample": frm.doc.name};
frappe.set_route("List", "Lab Test");
if (frappe.defaults.get_default('create_sample_collection_for_lab_test')) {
frm.add_custom_button(__('View Lab Tests'), function() {
frappe.route_options = {'sample': frm.doc.name};
frappe.set_route('List', 'Lab Test');
});
}
}
});
frappe.ui.form.on("Sample Collection", "patient", function(frm) {
frappe.ui.form.on('Sample Collection', 'patient', function(frm) {
if(frm.doc.patient){
frappe.call({
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail",
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
args: {
patient: frm.doc.patient
},
callback: function (data) {
var age = null;
if(data.message.dob){
if (data.message.dob){
age = calculate_age(data.message.dob);
}
frappe.model.set_value(frm.doctype,frm.docname, "patient_age", age);
frappe.model.set_value(frm.doctype,frm.docname, "patient_sex", data.message.sex);
frappe.model.set_value(frm.doctype,frm.docname, 'patient_age', age);
frappe.model.set_value(frm.doctype,frm.docname, 'patient_sex', data.message.sex);
}
});
}
@ -36,5 +36,5 @@ var calculate_age = function(birth) {
var age = new Date();
age.setTime(ageMS);
var years = age.getFullYear() - 1970;
return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)";
return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)';
};

85
erpnext/healthcare/doctype/sample_collection/sample_collection.json

@ -9,8 +9,10 @@
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
"patient_details_section",
"naming_series",
"patient",
"patient_name",
"patient_age",
"patient_sex",
"column_break_4",
@ -25,15 +27,17 @@
"collected_by",
"collected_time",
"num_print",
"amended_from",
"section_break_15",
"sample_details"
"sample_details",
"amended_from"
],
"fields": [
{
"fetch_from": "patient.inpatient_record",
"fieldname": "inpatient_record",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Inpatient Record",
"options": "Inpatient Record",
"read_only": 1
@ -42,6 +46,8 @@
"bold": 1,
"fieldname": "naming_series",
"fieldtype": "Select",
"hide_days": 1,
"hide_seconds": 1,
"label": "Series",
"no_copy": 1,
"options": "HLC-SC-.YYYY.-",
@ -52,6 +58,8 @@
"default": "0",
"fieldname": "invoiced",
"fieldtype": "Check",
"hide_days": 1,
"hide_seconds": 1,
"label": "Invoiced",
"no_copy": 1,
"read_only": 1,
@ -61,41 +69,60 @@
"fetch_from": "inpatient_record.patient",
"fieldname": "patient",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"ignore_user_permissions": 1,
"in_standard_filter": 1,
"label": "Patient",
"options": "Patient",
"reqd": 1,
"search_index": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1
},
{
"fieldname": "patient_age",
"fieldtype": "Data",
"label": "Age"
"hide_days": 1,
"hide_seconds": 1,
"label": "Age",
"read_only": 1
},
{
"fetch_from": "patient.sex",
"fieldname": "patient_sex",
"fieldtype": "Data",
"label": "Gender"
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Gender",
"options": "Gender",
"read_only": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"in_standard_filter": 1,
"label": "Company",
"options": "Company"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Sample Details"
},
{
"fieldname": "sample",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"ignore_user_permissions": 1,
"in_list_view": 1,
"in_standard_filter": 1,
@ -108,16 +135,23 @@
"fetch_from": "sample.sample_uom",
"fieldname": "sample_uom",
"fieldtype": "Data",
"hide_days": 1,
"hide_seconds": 1,
"in_list_view": 1,
"label": "UOM"
"label": "UOM",
"read_only": 1
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1
},
{
"fieldname": "collected_by",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"ignore_user_permissions": 1,
"label": "Collected By",
"options": "User"
@ -125,20 +159,27 @@
{
"fieldname": "collected_time",
"fieldtype": "Datetime",
"label": "Collected Time"
"hide_days": 1,
"hide_seconds": 1,
"label": "Collected On"
},
{
"allow_on_submit": 1,
"default": "1",
"description": "Number of prints required for labelling the samples",
"fieldname": "num_print",
"fieldtype": "Int",
"label": "No. of print",
"hide_days": 1,
"hide_seconds": 1,
"label": "No. of prints",
"print_hide": 1,
"report_hide": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Amended From",
"no_copy": 1,
"options": "Sample Collection",
@ -147,25 +188,43 @@
},
{
"fieldname": "section_break_15",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1
},
{
"default": "0",
"fieldname": "sample_qty",
"fieldtype": "Float",
"hide_days": 1,
"hide_seconds": 1,
"in_list_view": 1,
"label": "Quantity"
},
{
"fieldname": "sample_details",
"fieldtype": "Long Text",
"hide_days": 1,
"hide_seconds": 1,
"ignore_xss_filter": 1,
"label": "Collection Details"
},
{
"fieldname": "patient_details_section",
"fieldtype": "Section Break",
"label": "Patient Details"
},
{
"fetch_from": "patient.patient_name",
"fieldname": "patient_name",
"fieldtype": "Data",
"label": "Patient Name",
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-05-25 14:36:46.990469",
"modified": "2020-07-30 16:53:13.076104",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Sample Collection",

7
erpnext/healthcare/doctype/sample_collection/sample_collection.py

@ -3,7 +3,12 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.utils import flt
from frappe import _
class SampleCollection(Document):
pass
def validate(self):
if flt(self.sample_qty) <= 0:
frappe.throw(_('Sample Quantity cannot be negative or 0'), title=_('Invalid Quantity'))

39
erpnext/healthcare/report/lab_test_report/lab_test_report.js

@ -4,29 +4,54 @@
frappe.query_reports["Lab Test Report"] = {
"filters": [
{
"fieldname":"from_date",
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": frappe.datetime.now_date(),
"width": "80"
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
"reqd": 1
},
{
"fieldname":"to_date",
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": frappe.datetime.now_date()
"default": frappe.datetime.now_date(),
"reqd": 1
},
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"default": frappe.defaults.get_default("Company"),
"options": "Company"
},
{
"fieldname": "template",
"label": __("Lab Test Template"),
"fieldtype": "Link",
"options": "Lab Test Template"
},
{
"fieldname":"patient",
"fieldname": "patient",
"label": __("Patient"),
"fieldtype": "Link",
"options": "Patient"
},
{
"fieldname":"department",
"fieldname": "department",
"label": __("Medical Department"),
"fieldtype": "Link",
"options": "Medical Department"
},
{
"fieldname": "status",
"label": __("Status"),
"fieldtype": "Select",
"options": "\nCompleted\nApproved\nRejected"
},
{
"fieldname": "invoiced",
"label": __("Invoiced"),
"fieldtype": "Check"
}
]
};

39
erpnext/healthcare/report/lab_test_report/lab_test_report.json

@ -1,30 +1,31 @@
{
"add_total_row": 1,
"creation": "2013-04-23 18:15:29",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 1,
"is_standard": "Yes",
"modified": "2018-08-06 11:41:50.218737",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test Report",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Lab Test",
"report_name": "Lab Test Report",
"report_type": "Script Report",
"add_total_row": 0,
"creation": "2013-04-23 18:15:29",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 1,
"is_standard": "Yes",
"modified": "2020-07-30 18:53:20.102873",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test Report",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Lab Test",
"report_name": "Lab Test Report",
"report_type": "Script Report",
"roles": [
{
"role": "Laboratory User"
},
},
{
"role": "Nursing User"
},
},
{
"role": "LabTest Approver"
},
},
{
"role": "Healthcare Administrator"
}

209
erpnext/healthcare/report/lab_test_report/lab_test_report.py

@ -8,51 +8,204 @@ from frappe import msgprint, _
def execute(filters=None):
if not filters: filters = {}
lab_test_list = get_lab_test(filters)
data, columns = [], []
columns = get_columns()
lab_test_list = get_lab_tests(filters)
if not lab_test_list:
msgprint(_("No record found"))
msgprint(_('No records found'))
return columns, lab_test_list
data = []
for lab_test in lab_test_list:
row = [ lab_test.lab_test_name, lab_test.patient, lab_test.practitioner, lab_test.invoiced, lab_test.status, lab_test.result_date, lab_test.department]
row = frappe._dict({
'test': lab_test.name,
'template': lab_test.template,
'company': lab_test.company,
'patient': lab_test.patient,
'patient_name': lab_test.patient_name,
'practitioner': lab_test.practitioner,
'employee': lab_test.employee,
'status': lab_test.status,
'invoiced': lab_test.invoiced,
'result_date': lab_test.result_date,
'department': lab_test.department
})
data.append(row)
return columns, data
chart = get_chart_data(data)
report_summary = get_report_summary(data)
return columns, data, None, chart, report_summary
def get_columns():
columns = [
_("Test") + ":Data:120",
_("Patient") + ":Link/Patient:180",
_("Healthcare Practitioner") + ":Link/Healthcare Practitioner:120",
_("Invoiced") + ":Check:100",
_("Status") + ":Data:120",
_("Result Date") + ":Date:120",
_("Department") + ":Data:120",
return [
{
'fieldname': 'test',
'label': _('Lab Test'),
'fieldtype': 'Link',
'options': 'Lab Test',
'width': '120'
},
{
'fieldname': 'template',
'label': _('Lab Test Template'),
'fieldtype': 'Link',
'options': 'Lab Test Template',
'width': '120'
},
{
'fieldname': 'company',
'label': _('Company'),
'fieldtype': 'Link',
'options': 'Company',
'width': '120'
},
{
'fieldname': 'patient',
'label': _('Patient'),
'fieldtype': 'Link',
'options': 'Patient',
'width': '120'
},
{
'fieldname': 'patient_name',
'label': _('Patient Name'),
'fieldtype': 'Data',
'width': '120'
},
{
'fieldname': 'employee',
'label': _('Lab Technician'),
'fieldtype': 'Link',
'options': 'Employee',
'width': '120'
},
{
'fieldname': 'status',
'label': _('Status'),
'fieldtype': 'Data',
'width': '100'
},
{
'fieldname': 'invoiced',
'label': _('Invoiced'),
'fieldtype': 'Check',
'width': '100'
},
{
'fieldname': 'result_date',
'label': _('Result Date'),
'fieldtype': 'Date',
'width': '100'
},
{
'fieldname': 'practitioner',
'label': _('Requesting Practitioner'),
'fieldtype': 'Link',
'options': 'Healthcare Practitioner',
'width': '120'
},
{
'fieldname': 'department',
'label': _('Medical Department'),
'fieldtype': 'Link',
'options': 'Medical Department',
'width': '100'
}
]
return columns
def get_lab_tests(filters):
conditions = get_conditions(filters)
data = frappe.get_all(
doctype='Lab Test',
fields=['name', 'template', 'company', 'patient', 'patient_name', 'practitioner', 'employee', 'status', 'invoiced', 'result_date', 'department'],
filters=conditions,
order_by='submitted_date desc'
)
return data
def get_conditions(filters):
conditions = ""
conditions = {
'docstatus': ('=', 1)
}
if filters.get('from_date') and filters.get('to_date'):
conditions['result_date'] = ('between', (filters.get('from_date'), filters.get('to_date')))
filters.pop('from_date')
filters.pop('to_date')
if filters.get("patient"):
conditions += "and patient = %(patient)s"
if filters.get("from_date"):
conditions += "and result_date >= %(from_date)s"
if filters.get("to_date"):
conditions += " and result_date <= %(to_date)s"
if filters.get("department"):
conditions += " and department = %(department)s"
for key, value in filters.items():
if filters.get(key):
conditions[key] = value
return conditions
def get_lab_test(filters):
conditions = get_conditions(filters)
return frappe.db.sql("""select name, patient, lab_test_name, patient_name, status, result_date, practitioner, invoiced, department
from `tabLab Test`
where docstatus<2 %s order by submitted_date desc, name desc""" %
conditions, filters, as_dict=1)
def get_chart_data(data):
if not data:
return None
labels = ['Completed', 'Approved', 'Rejected']
status_wise_data = {
'Completed': 0,
'Approved': 0,
'Rejected': 0
}
datasets = []
for entry in data:
status_wise_data[entry.status] += 1
datasets.append({
'name': 'Lab Test Status',
'values': [status_wise_data.get('Completed'), status_wise_data.get('Approved'), status_wise_data.get('Rejected')]
})
chart = {
'data': {
'labels': labels,
'datasets': datasets
},
'type': 'donut',
'height': 300,
}
return chart
def get_report_summary(data):
if not data:
return None
total_lab_tests = len(data)
invoiced_lab_tests, unbilled_lab_tests = 0, 0
for entry in data:
if entry.invoiced:
invoiced_lab_tests += 1
else:
unbilled_lab_tests += 1
return [
{
'value': total_lab_tests,
'indicator': 'Blue',
'label': 'Total Lab Tests',
'datatype': 'Int',
},
{
'value': invoiced_lab_tests,
'indicator': 'Green',
'label': 'Invoiced Lab Tests',
'datatype': 'Int',
},
{
'value': unbilled_lab_tests,
'indicator': 'Red',
'label': 'Unbilled Lab Tests',
'datatype': 'Int',
}
]

4
erpnext/hr/doctype/appraisal/appraisal.json

@ -696,11 +696,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-30 11:28:08.401459",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal",
"owner": "ashwini@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"amend": 0,

4
erpnext/hr/doctype/appraisal_goal/appraisal_goal.json

@ -207,11 +207,11 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:27:57.897071",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal Goal",
"owner": "ashwini@webnotestech.com",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,

4
erpnext/hr/doctype/appraisal_template/appraisal_template.json

@ -113,11 +113,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-12-13 12:37:56.937023",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal Template",
"owner": "ashwini@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"amend": 0,

4
erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.json

@ -78,11 +78,11 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:27:57.979215",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal Template Goal",
"owner": "ashwini@webnotestech.com",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,

4
erpnext/hr/doctype/attendance/attendance.json

@ -205,11 +205,11 @@
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-05-29 13:51:37.177231",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Attendance",
"owner": "ashwini@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"cancel": 1,

4
erpnext/hr/doctype/expense_claim/expense_claim.json

@ -371,12 +371,12 @@
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-06-15 12:43:04.099803",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
"name_case": "Title Case",
"owner": "harshada@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"amend": 1,

4
erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json

@ -120,11 +120,11 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-05-11 18:54:35.601592",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Detail",
"owner": "harshada@webnotestech.com",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"

4
erpnext/hr/doctype/expense_claim_type/expense_claim_type.json

@ -48,11 +48,11 @@
"icon": "fa fa-flag",
"idx": 1,
"links": [],
"modified": "2019-12-11 13:38:59.534034",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Type",
"owner": "harshada@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"create": 1,

4
erpnext/hr/doctype/upload_attendance/upload_attendance.json

@ -229,11 +229,11 @@
"issingle": 1,
"istable": 0,
"max_attachments": 1,
"modified": "2017-11-14 12:51:34.980103",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Upload Attendance",
"owner": "harshada@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"amend": 0,

4
erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json

@ -40,8 +40,8 @@
"mapping_name": "Company to Hub Company",
"mapping_type": "Push",
"migration_id_field": "hub_sync_id",
"modified": "2018-02-14 15:57:05.571142",
"modified_by": "achilles@erpnext.com",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"name": "Company to Hub Company",
"owner": "Administrator",
"page_length": 10,

6
erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json

@ -21,10 +21,10 @@
"mapping_name": "Hub Message to Lead",
"mapping_type": "Pull",
"migration_id_field": "hub_sync_id",
"modified": "2018-02-14 15:57:05.606597",
"modified_by": "achilles@erpnext.com",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"name": "Hub Message to Lead",
"owner": "frappetest@gmail.com",
"owner": "Administrator",
"page_length": 10,
"remote_objectname": "Hub Message",
"remote_primary_key": "name"

4
erpnext/hub_node/doctype/hub_user/hub_user.json

@ -121,8 +121,8 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-09-01 13:56:07.816894",
"modified_by": "netchamp@rawcoderz.com",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Hub Node",
"name": "Hub User",
"name_case": "",

4
erpnext/hub_node/doctype/hub_users/hub_users.json

@ -54,12 +54,12 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-03-06 04:41:17.916243",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Hub Node",
"name": "Hub Users",
"name_case": "",
"owner": "test1@example.com",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,

4
erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json

@ -352,12 +352,12 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2019-02-01 14:21:16.729848",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Hub Node",
"name": "Marketplace Settings",
"name_case": "",
"owner": "netchamp@rawcoderz.com",
"owner": "Administrator",
"permissions": [
{
"amend": 0,

2
erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json

@ -813,7 +813,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 14:44:33.670332",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Schedule",

4
erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json

@ -1001,11 +1001,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2020-07-15 14:44:44.911402",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit",
"owner": "ashwini@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"amend": 1,

4
erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json

@ -125,11 +125,11 @@
],
"idx": 1,
"istable": 1,
"modified": "2019-10-03 14:55:52.786805",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit Purpose",
"owner": "ashwini@webnotestech.com",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",

407
erpnext/non_profit/doctype/donor/donor.json

@ -1,336 +1,105 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:email",
"beta": 0,
"creation": "2017-09-19 16:20:27.510196",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"allow_rename": 1,
"autoname": "field:email",
"creation": "2017-09-19 16:20:27.510196",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"donor_name",
"column_break_5",
"donor_type",
"email",
"image",
"address_contacts",
"address_html",
"column_break_9",
"contact_html"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "donor_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Donor Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "donor_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Donor Name",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "donor_type",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Donor Type",
"length": 0,
"no_copy": 0,
"options": "Donor Type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "donor_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Donor Type",
"options": "Donor Type",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "email",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Email",
"reqd": 1,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Image",
"no_copy": 1,
"print_hide": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address and Contact",
"length": 0,
"no_copy": 0,
"options": "fa fa-map-marker",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"depends_on": "eval:!doc.__islocal;",
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"label": "Address and Contact",
"options": "fa fa-map-marker"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "address_html",
"fieldtype": "HTML",
"label": "Address HTML"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "contact_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldname": "contact_html",
"fieldtype": "HTML",
"label": "Contact HTML"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_field": "image",
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-01-22 15:53:35.059946",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Donor",
"name_case": "",
"owner": "Administrator",
],
"image_field": "image",
"links": [],
"modified": "2020-09-16 23:46:04.083274",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Donor",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Non Profit",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "donor_name",
"track_changes": 1,
"track_seen": 0
],
"quick_entry": 1,
"restrict_to_domain": "Non Profit",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "donor_name",
"track_changes": 1
}

3
erpnext/non_profit/doctype/member/member.json

@ -111,6 +111,7 @@
"options": "Supplier"
},
{
"depends_on": "eval:!doc.__islocal;",
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"label": "Address and Contact",
@ -177,7 +178,7 @@
],
"image_field": "image",
"links": [],
"modified": "2020-08-06 10:06:01.153564",
"modified": "2020-09-16 23:44:13.596948",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Member",

8
erpnext/non_profit/doctype/membership/membership.json

@ -90,9 +90,9 @@
},
{
"fieldname": "currency",
"fieldtype": "Select",
"fieldtype": "Link",
"label": "Currency",
"options": "USD\nINR"
"options": "Currency"
},
{
"fieldname": "amount",
@ -126,7 +126,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-07-31 13:57:02.328995",
"modified": "2020-09-19 14:28:11.532696",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership",
@ -161,4 +161,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

708
erpnext/non_profit/doctype/volunteer/volunteer.json

@ -1,580 +1,148 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:email",
"beta": 0,
"creation": "2017-09-19 16:16:45.676019",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"allow_rename": 1,
"autoname": "field:email",
"creation": "2017-09-19 16:16:45.676019",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"volunteer_name",
"column_break_5",
"volunteer_type",
"email",
"image",
"address_contacts",
"address_html",
"column_break_9",
"contact_html",
"volunteer_availability_and_skills_details",
"availability",
"availability_timeslot",
"column_break_12",
"volunteer_skills",
"section_break_15",
"note"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "volunteer_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Volunteer Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "volunteer_type",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Volunteer Type",
"length": 0,
"no_copy": 0,
"options": "Volunteer Type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"fieldname": "volunteer_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Volunteer Name",
"reqd": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "volunteer_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Volunteer Type",
"options": "Volunteer Type",
"reqd": 1
},
{
"fieldname": "email",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Email",
"reqd": 1,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address and Contact",
"length": 0,
"no_copy": 0,
"options": "fa fa-map-marker",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "contact_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "volunteer_availability_and_skills_details",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Availability and Skills",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "availability",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Availability",
"length": 0,
"no_copy": 0,
"options": "\nWeekly\nWeekdays\nWeekends",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "availability_timeslot",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Availability Timeslot",
"length": 0,
"no_copy": 0,
"options": "\nMorning\nAfternoon\nEvening\nAnytime",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "volunteer_skills",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Volunteer Skills",
"length": 0,
"no_copy": 0,
"options": "Volunteer Skill",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_15",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "note",
"fieldtype": "Long Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Note",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Image",
"no_copy": 1,
"print_hide": 1
},
{
"depends_on": "eval:!doc.__islocal;",
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"label": "Address and Contact",
"options": "fa fa-map-marker"
},
{
"fieldname": "address_html",
"fieldtype": "HTML",
"label": "Address HTML"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "contact_html",
"fieldtype": "HTML",
"label": "Contact HTML"
},
{
"fieldname": "volunteer_availability_and_skills_details",
"fieldtype": "Section Break",
"label": "Availability and Skills"
},
{
"fieldname": "availability",
"fieldtype": "Select",
"label": "Availability",
"options": "\nWeekly\nWeekdays\nWeekends"
},
{
"fieldname": "availability_timeslot",
"fieldtype": "Select",
"label": "Availability Timeslot",
"options": "\nMorning\nAfternoon\nEvening\nAnytime"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"fieldname": "volunteer_skills",
"fieldtype": "Table",
"label": "Volunteer Skills",
"options": "Volunteer Skill"
},
{
"fieldname": "section_break_15",
"fieldtype": "Section Break"
},
{
"fieldname": "note",
"fieldtype": "Long Text",
"label": "Note"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_field": "image",
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-04 03:36:25.776211",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Volunteer",
"name_case": "",
"owner": "Administrator",
],
"image_field": "image",
"links": [],
"modified": "2020-09-16 23:45:15.595952",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Volunteer",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Non Profit",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "volunteer_name",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"quick_entry": 1,
"restrict_to_domain": "Non Profit",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "volunteer_name",
"track_changes": 1
}

1
erpnext/patches.txt

@ -690,6 +690,7 @@ erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation
erpnext.patches.v13_0.update_old_loans
erpnext.patches.v12_0.set_serial_no_status #2020-05-21
erpnext.patches.v12_0.update_price_list_currency_in_bom
execute:frappe.reload_doctype('Dashboard')
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25

5
erpnext/patches/v12_0/move_plaid_settings_to_doctype.py

@ -4,17 +4,16 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("erpnext_integrations", "doctype", "plaid_settings")
plaid_settings = frappe.get_single("Plaid Settings")
if plaid_settings.enabled:
if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env \
and frappe.conf.plaid_public_key and frappe.conf.plaid_secret):
if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env and frappe.conf.plaid_secret):
plaid_settings.enabled = 0
else:
plaid_settings.update({
"plaid_client_id": frappe.conf.plaid_client_id,
"plaid_public_key": frappe.conf.plaid_public_key,
"plaid_env": frappe.conf.plaid_env,
"plaid_secret": frappe.conf.plaid_secret
})

4
erpnext/projects/doctype/dependent_task/dependent_task.json

@ -48,8 +48,8 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-10-26 17:37:25.638190",
"modified_by": "rohitw1991@gmail.com",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Projects",
"name": "Dependent Task",
"name_case": "",

4
erpnext/public/js/conf.js

@ -11,7 +11,9 @@ $.extend(frappe.breadcrumbs.preferred, {
"Territory": "Selling",
"Sales Person": "Selling",
"Sales Partner": "Selling",
"Brand": "Stock"
"Brand": "Stock",
"Maintenance Schedule": "Support",
"Maintenance Visit": "Support"
});
$.extend(frappe.breadcrumbs.module_map, {

2
erpnext/public/js/controllers/taxes_and_totals.js

@ -595,7 +595,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
$.each(actual_taxes_dict, function(key, value) {
if (value) total_actual_tax += value;
});
return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total"));
}
},

2
erpnext/public/js/controllers/transaction.js

@ -417,7 +417,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges",
me.frm.doc.name);
if (!this.frm.doc.taxes_and_charges && this.frm.doc.taxes) {
if (!this.frm.doc.taxes_and_charges && this.frm.doc.taxes && this.frm.doc.taxes.length > 0) {
return;
}

28
erpnext/public/js/utils.js

@ -466,6 +466,33 @@ erpnext.utils.update_child_items = function(opts) {
read_only: 0,
disabled: 0,
label: __('Item Code')
}, {
fieldtype:'Link',
fieldname:'uom',
options: 'UOM',
read_only: 0,
label: __('UOM'),
reqd: 1,
onchange: function () {
frappe.call({
method: "erpnext.stock.get_item_details.get_conversion_factor",
args: { item_code: this.doc.item_code, uom: this.value },
callback: r => {
if(!r.exc) {
if (this.doc.conversion_factor == r.message.conversion_factor) return;
const docname = this.doc.docname;
dialog.fields_dict.trans_items.df.data.some(doc => {
if (doc.docname == docname) {
doc.conversion_factor = r.message.conversion_factor;
dialog.fields_dict.trans_items.grid.refresh();
return true;
}
})
}
}
});
}
}, {
fieldtype:'Float',
fieldname:"qty",
@ -546,6 +573,7 @@ erpnext.utils.update_child_items = function(opts) {
"conversion_factor": d.conversion_factor,
"qty": d.qty,
"rate": d.rate,
"uom": d.uom
});
this.data = dialog.fields_dict.trans_items.df.data;
dialog.fields_dict.trans_items.grid.refresh();

6
erpnext/public/js/utils/party.js

@ -224,6 +224,10 @@ erpnext.utils.set_taxes = function(frm, triggered_from_field) {
party = frm.doc.party_name;
}
if (!frm.doc.company) {
frappe.throw(__("Kindly select the company first"));
}
frappe.call({
method: "erpnext.accounts.party.set_taxes",
args: {
@ -292,4 +296,4 @@ erpnext.utils.get_shipping_address = function(frm, callback){
} else {
frappe.msgprint(__("Select company first"));
}
}
}

0
erpnext/regional/germany/utils/__init__.py

0
erpnext/regional/germany/utils/datev/__init__.py

75
erpnext/regional/report/datev/datev_constants.py → erpnext/regional/germany/utils/datev/datev_constants.py

@ -460,80 +460,8 @@ ACCOUNT_NAME_COLUMNS = [
"Sprach-ID"
]
QUERY_REPORT_COLUMNS = [
{
"label": "Umsatz (ohne Soll/Haben-Kz)",
"fieldname": "Umsatz (ohne Soll/Haben-Kz)",
"fieldtype": "Currency",
"width": 100
},
{
"label": "Soll/Haben-Kennzeichen",
"fieldname": "Soll/Haben-Kennzeichen",
"fieldtype": "Data",
"width": 100
},
{
"label": "Konto",
"fieldname": "Konto",
"fieldtype": "Data",
"width": 100
},
{
"label": "Gegenkonto (ohne BU-Schlüssel)",
"fieldname": "Gegenkonto (ohne BU-Schlüssel)",
"fieldtype": "Data",
"width": 100
},
{
"label": "Belegdatum",
"fieldname": "Belegdatum",
"fieldtype": "Date",
"width": 100
},
{
"label": "Belegfeld 1",
"fieldname": "Belegfeld 1",
"fieldtype": "Data",
"width": 150
},
{
"label": "Buchungstext",
"fieldname": "Buchungstext",
"fieldtype": "Text",
"width": 300
},
{
"label": "Beleginfo - Art 1",
"fieldname": "Beleginfo - Art 1",
"fieldtype": "Link",
"options": "DocType",
"width": 100
},
{
"label": "Beleginfo - Inhalt 1",
"fieldname": "Beleginfo - Inhalt 1",
"fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 1",
"width": 150
},
{
"label": "Beleginfo - Art 2",
"fieldname": "Beleginfo - Art 2",
"fieldtype": "Link",
"options": "DocType",
"width": 100
},
{
"label": "Beleginfo - Inhalt 2",
"fieldname": "Beleginfo - Inhalt 2",
"fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 2",
"width": 150
}
]
class DataCategory():
"""Field of the CSV Header."""
DEBTORS_CREDITORS = "16"
@ -542,6 +470,7 @@ class DataCategory():
POSTING_TEXT_CONSTANTS = "67"
class FormatName():
"""Field of the CSV Header, corresponds to DataCategory."""
DEBTORS_CREDITORS = "Debitoren/Kreditoren"

174
erpnext/regional/germany/utils/datev/datev_csv.py

@ -0,0 +1,174 @@
# coding: utf-8
from __future__ import unicode_literals
import datetime
import zipfile
from csv import QUOTE_NONNUMERIC
from six import BytesIO
import six
import frappe
import pandas as pd
from frappe import _
from .datev_constants import DataCategory
def get_datev_csv(data, filters, csv_class):
"""
Fill in missing columns and return a CSV in DATEV Format.
For automatic processing, DATEV requires the first line of the CSV file to
hold meta data such as the length of account numbers oder the category of
the data.
Arguments:
data -- array of dictionaries
filters -- dict
csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS
"""
empty_df = pd.DataFrame(columns=csv_class.COLUMNS)
data_df = pd.DataFrame.from_records(data)
result = empty_df.append(data_df, sort=True)
if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS:
result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES:
result['Sprach-ID'] = 'de-DE'
data = result.to_csv(
# Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
sep=str(';'),
# European decimal seperator
decimal=',',
# Windows "ANSI" encoding
encoding='latin_1',
# format date as DDMM
date_format='%d%m',
# Windows line terminator
line_terminator='\r\n',
# Do not number rows
index=False,
# Use all columns defined above
columns=csv_class.COLUMNS,
# Quote most fields, even currency values with "," separator
quoting=QUOTE_NONNUMERIC
)
if not six.PY2:
data = data.encode('latin_1')
header = get_header(filters, csv_class)
header = ';'.join(header).encode('latin_1')
# 1st Row: Header with meta data
# 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
# 3rd - nth Row: Data (Nutzdaten)
return header + b'\r\n' + data
def get_header(filters, csv_class):
description = filters.get('voucher_type', csv_class.FORMAT_NAME)
company = filters.get('company')
datev_settings = frappe.get_doc('DATEV Settings', {'client': company})
default_currency = frappe.get_value('Company', company, 'default_currency')
coa = frappe.get_value('Company', company, 'chart_of_accounts')
coa_short_code = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '')
header = [
# DATEV format
# "DTVF" = created by DATEV software,
# "EXTF" = created by other software
'"EXTF"',
# version of the DATEV format
# 141 = 1.41,
# 510 = 5.10,
# 720 = 7.20
'700',
csv_class.DATA_CATEGORY,
'"%s"' % csv_class.FORMAT_NAME,
# Format version (regarding format name)
csv_class.FORMAT_VERSION,
# Generated on
datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '000',
# Imported on -- stays empty
'',
# Origin. Any two symbols, will be replaced by "SV" on import.
'"EN"',
# I = Exported by
'"%s"' % frappe.session.user,
# J = Imported by -- stays empty
'',
# K = Tax consultant number (Beraternummer)
datev_settings.get('consultant_number', '0000000'),
# L = Tax client number (Mandantennummer)
datev_settings.get('client_number', '00000'),
# M = Start of the fiscal year (Wirtschaftsjahresbeginn)
frappe.utils.formatdate(frappe.defaults.get_user_default('year_start_date'), 'yyyyMMdd'),
# N = Length of account numbers (Sachkontenlänge)
datev_settings.get('account_number_length', '4'),
# O = Transaction batch start date (YYYYMMDD)
frappe.utils.formatdate(filters.get('from_date'), 'yyyyMMdd') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# P = Transaction batch end date (YYYYMMDD)
frappe.utils.formatdate(filters.get('to_date'), 'yyyyMMdd') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# Q = Description (for example, "Sales Invoice") Max. 30 chars
'"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# R = Diktatkürzel
'',
# S = Buchungstyp
# 1 = Transaction batch (Finanzbuchführung),
# 2 = Annual financial statement (Jahresabschluss)
'1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# T = Rechnungslegungszweck
# 0 oder leer = vom Rechnungslegungszweck unabhängig
# 50 = Handelsrecht
# 30 = Steuerrecht
# 64 = IFRS
# 40 = Kalkulatorik
# 11 = Reserviert
# 12 = Reserviert
'0' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# U = Festschreibung
# TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
'0',
# V = Default currency, for example, "EUR"
'"%s"' % default_currency if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# reserviert
'',
# Derivatskennzeichen
'',
# reserviert
'',
# reserviert
'',
# SKR
'"%s"' % coa_short_code,
# Branchen-Lösungs-ID
'',
# reserviert
'',
# reserviert
'',
# Anwendungsinformation (Verarbeitungskennzeichen der abgebenden Anwendung)
''
]
return header
def download_csv_files_as_zip(csv_data_list):
"""
Put CSV files in a zip archive and send that to the client.
Params:
csv_data_list -- list of dicts [{'file_name': 'EXTF_Buchunsstapel.zip', 'csv_data': get_datev_csv()}]
"""
zip_buffer = BytesIO()
datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
for csv_file in csv_data_list:
datev_zip.writestr(csv_file.get('file_name'), csv_file.get('csv_data'))
datev_zip.close()
frappe.response['filecontent'] = zip_buffer.getvalue()
frappe.response['filename'] = 'DATEV.zip'
frappe.response['type'] = 'binary'

282
erpnext/regional/report/datev/datev.py

@ -9,31 +9,91 @@ Provide a report and downloadable CSV according to the German DATEV format.
"""
from __future__ import unicode_literals
import datetime
import json
import zipfile
import six
import frappe
import pandas as pd
from frappe import _
from csv import QUOTE_NONNUMERIC
from six import BytesIO
from six import string_types
from .datev_constants import DataCategory
from .datev_constants import Transactions
from .datev_constants import DebtorsCreditors
from .datev_constants import AccountNames
from .datev_constants import QUERY_REPORT_COLUMNS
from erpnext.regional.germany.utils.datev.datev_csv import download_csv_files_as_zip, get_datev_csv
from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames
COLUMNS = [
{
"label": "Umsatz (ohne Soll/Haben-Kz)",
"fieldname": "Umsatz (ohne Soll/Haben-Kz)",
"fieldtype": "Currency",
"width": 100
},
{
"label": "Soll/Haben-Kennzeichen",
"fieldname": "Soll/Haben-Kennzeichen",
"fieldtype": "Data",
"width": 100
},
{
"label": "Konto",
"fieldname": "Konto",
"fieldtype": "Data",
"width": 100
},
{
"label": "Gegenkonto (ohne BU-Schlüssel)",
"fieldname": "Gegenkonto (ohne BU-Schlüssel)",
"fieldtype": "Data",
"width": 100
},
{
"label": "Belegdatum",
"fieldname": "Belegdatum",
"fieldtype": "Date",
"width": 100
},
{
"label": "Belegfeld 1",
"fieldname": "Belegfeld 1",
"fieldtype": "Data",
"width": 150
},
{
"label": "Buchungstext",
"fieldname": "Buchungstext",
"fieldtype": "Text",
"width": 300
},
{
"label": "Beleginfo - Art 1",
"fieldname": "Beleginfo - Art 1",
"fieldtype": "Link",
"options": "DocType",
"width": 100
},
{
"label": "Beleginfo - Inhalt 1",
"fieldname": "Beleginfo - Inhalt 1",
"fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 1",
"width": 150
},
{
"label": "Beleginfo - Art 2",
"fieldname": "Beleginfo - Art 2",
"fieldtype": "Link",
"options": "DocType",
"width": 100
},
{
"label": "Beleginfo - Inhalt 2",
"fieldname": "Beleginfo - Inhalt 2",
"fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 2",
"width": 150
}
]
def execute(filters=None):
"""Entry point for frappe."""
validate(filters)
result = get_transactions(filters, as_dict=0)
columns = QUERY_REPORT_COLUMNS
return columns, result
return COLUMNS, get_transactions(filters, as_dict=0)
def validate(filters):
@ -240,146 +300,8 @@ def get_account_names(filters):
""", filters, as_dict=1)
def get_datev_csv(data, filters, csv_class):
"""
Fill in missing columns and return a CSV in DATEV Format.
For automatic processing, DATEV requires the first line of the CSV file to
hold meta data such as the length of account numbers oder the category of
the data.
Arguments:
data -- array of dictionaries
filters -- dict
csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS
"""
empty_df = pd.DataFrame(columns=csv_class.COLUMNS)
data_df = pd.DataFrame.from_records(data)
result = empty_df.append(data_df, sort=True)
if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS:
result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES:
result['Sprach-ID'] = 'de-DE'
data = result.to_csv(
# Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
sep=str(';'),
# European decimal seperator
decimal=',',
# Windows "ANSI" encoding
encoding='latin_1',
# format date as DDMM
date_format='%d%m',
# Windows line terminator
line_terminator='\r\n',
# Do not number rows
index=False,
# Use all columns defined above
columns=csv_class.COLUMNS,
# Quote most fields, even currency values with "," separator
quoting=QUOTE_NONNUMERIC
)
if not six.PY2:
data = data.encode('latin_1')
header = get_header(filters, csv_class)
header = ';'.join(header).encode('latin_1')
# 1st Row: Header with meta data
# 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
# 3rd - nth Row: Data (Nutzdaten)
return header + b'\r\n' + data
def get_header(filters, csv_class):
description = filters.get('voucher_type', csv_class.FORMAT_NAME)
header = [
# DATEV format
# "DTVF" = created by DATEV software,
# "EXTF" = created by other software
'"EXTF"',
# version of the DATEV format
# 141 = 1.41,
# 510 = 5.10,
# 720 = 7.20
'700',
csv_class.DATA_CATEGORY,
'"%s"' % csv_class.FORMAT_NAME,
# Format version (regarding format name)
csv_class.FORMAT_VERSION,
# Generated on
datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '000',
# Imported on -- stays empty
'',
# Origin. Any two symbols, will be replaced by "SV" on import.
'"EN"',
# I = Exported by
'"%s"' % frappe.session.user,
# J = Imported by -- stays empty
'',
# K = Tax consultant number (Beraternummer)
filters.get('consultant_number', '0000000'),
# L = Tax client number (Mandantennummer)
filters.get('client_number', '00000'),
# M = Start of the fiscal year (Wirtschaftsjahresbeginn)
frappe.utils.formatdate(frappe.defaults.get_user_default("year_start_date"), "yyyyMMdd"),
# N = Length of account numbers (Sachkontenlänge)
'%d' % filters.get('acc_len', 4),
# O = Transaction batch start date (YYYYMMDD)
frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# P = Transaction batch end date (YYYYMMDD)
frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# Q = Description (for example, "Sales Invoice") Max. 30 chars
'"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# R = Diktatkürzel
'',
# S = Buchungstyp
# 1 = Transaction batch (Finanzbuchführung),
# 2 = Annual financial statement (Jahresabschluss)
'1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# T = Rechnungslegungszweck
# 0 oder leer = vom Rechnungslegungszweck unabhängig
# 50 = Handelsrecht
# 30 = Steuerrecht
# 64 = IFRS
# 40 = Kalkulatorik
# 11 = Reserviert
# 12 = Reserviert
'0' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# U = Festschreibung
# TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
'0',
# V = Default currency, for example, "EUR"
'"%s"' % filters.get('default_currency', 'EUR') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# reserviert
'',
# Derivatskennzeichen
'',
# reserviert
'',
# reserviert
'',
# SKR
'"%s"' % filters.get('skr', '04'),
# Branchen-Lösungs-ID
'',
# reserviert
'',
# reserviert
'',
# Anwendungsinformation (Verarbeitungskennzeichen der abgebenden Anwendung)
''
]
return header
@frappe.whitelist()
def download_datev_csv(filters=None):
def download_datev_csv(filters):
"""
Provide accounting entries for download in DATEV format.
@ -400,38 +322,26 @@ def download_datev_csv(filters=None):
coa = frappe.get_value('Company', filters.get('company'), 'chart_of_accounts')
filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '')
# set account number length
account_numbers = frappe.get_list('Account', fields=['account_number'], filters={'is_group': 0, 'account_number': ('!=', '')})
filters['acc_len'] = max([len(a.account_number) for a in account_numbers])
filters['consultant_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'consultant_number')
filters['client_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'client_number')
filters['default_currency'] = frappe.get_value('Company', filters.get('company'), 'default_currency')
# This is where my zip will be written
zip_buffer = BytesIO()
# This is my zip file
datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
transactions = get_transactions(filters)
transactions_csv = get_datev_csv(transactions, filters, csv_class=Transactions)
datev_zip.writestr('EXTF_Buchungsstapel.csv', transactions_csv)
account_names = get_account_names(filters)
account_names_csv = get_datev_csv(account_names, filters, csv_class=AccountNames)
datev_zip.writestr('EXTF_Kontenbeschriftungen.csv', account_names_csv)
customers = get_customers(filters)
customers_csv = get_datev_csv(customers, filters, csv_class=DebtorsCreditors)
datev_zip.writestr('EXTF_Kunden.csv', customers_csv)
suppliers = get_suppliers(filters)
suppliers_csv = get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors)
datev_zip.writestr('EXTF_Lieferanten.csv', suppliers_csv)
# You must call close() before exiting your program or essential records will not be written.
datev_zip.close()
frappe.response['filecontent'] = zip_buffer.getvalue()
frappe.response['filename'] = 'DATEV.zip'
frappe.response['type'] = 'binary'
download_csv_files_as_zip([
{
'file_name': 'EXTF_Buchungsstapel.csv',
'csv_data': get_datev_csv(transactions, filters, csv_class=Transactions)
},
{
'file_name': 'EXTF_Kontenbeschriftungen.csv',
'csv_data': get_datev_csv(account_names, filters, csv_class=AccountNames)
},
{
'file_name': 'EXTF_Kunden.csv',
'csv_data': get_datev_csv(customers, filters, csv_class=DebtorsCreditors)
},
{
'file_name': 'EXTF_Lieferanten.csv',
'csv_data': get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors)
},
])

18
erpnext/regional/report/datev/test_datev.py

@ -1,32 +1,22 @@
# coding=utf-8
from __future__ import unicode_literals
import os
import json
import zipfile
import frappe
from six import BytesIO
from unittest import TestCase
import frappe
from frappe.utils import getdate, today, now_datetime, cstr
from frappe.test_runner import make_test_objects
from frappe.utils import today, now_datetime, cstr
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
from erpnext.regional.report.datev.datev import validate
from erpnext.regional.report.datev.datev import get_transactions
from erpnext.regional.report.datev.datev import get_customers
from erpnext.regional.report.datev.datev import get_suppliers
from erpnext.regional.report.datev.datev import get_account_names
from erpnext.regional.report.datev.datev import get_datev_csv
from erpnext.regional.report.datev.datev import get_header
from erpnext.regional.report.datev.datev import download_datev_csv
from erpnext.regional.report.datev.datev_constants import DataCategory
from erpnext.regional.report.datev.datev_constants import Transactions
from erpnext.regional.report.datev.datev_constants import DebtorsCreditors
from erpnext.regional.report.datev.datev_constants import AccountNames
from erpnext.regional.report.datev.datev_constants import QUERY_REPORT_COLUMNS
from erpnext.regional.germany.utils.datev.datev_csv import get_datev_csv, get_header
from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames
def make_company(company_name, abbr):
if not frappe.db.exists("Company", company_name):

4
erpnext/selling/doctype/industry_type/industry_type.json

@ -49,11 +49,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-25 05:24:25.881361",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Selling",
"name": "Industry Type",
"owner": "harshada@webnotestech.com",
"owner": "Administrator",
"permissions": [
{
"amend": 0,

4
erpnext/selling/doctype/product_bundle/product_bundle.json

@ -238,8 +238,8 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-10-18 14:23:06.538568",
"modified_by": "tundebabzy@gmail.com",
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Selling",
"name": "Product Bundle",
"owner": "Administrator",

93
erpnext/selling/doctype/sales_order/test_sales_order.py

@ -318,7 +318,7 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20)
def test_add_new_item_in_update_child_qty_rate(self):
def test_update_child_adding_new_item(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4)
make_sales_invoice(so.name)
@ -338,7 +338,7 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(so.get("items")[-1].amount, 1400)
self.assertEqual(so.status, 'To Deliver and Bill')
def test_remove_item_in_update_child_qty_rate(self):
def test_update_child_removing_item(self):
so = make_sales_order(**{
"item_list": [{
"item_code": '_Test Item',
@ -381,7 +381,7 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(so.status, 'To Deliver and Bill')
def test_update_child_qty_rate(self):
def test_update_child(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4)
make_sales_invoice(so.name)
@ -402,7 +402,7 @@ class TestSalesOrder(unittest.TestCase):
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
def test_update_child_qty_rate_perm(self):
def test_update_child_perm(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
user = 'test@example.com'
@ -417,9 +417,44 @@ class TestSalesOrder(unittest.TestCase):
# add new item
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
test_user.remove_roles("Accounts User")
frappe.set_user("Administrator")
def test_update_child_qty_rate_with_workflow(self):
from frappe.model.workflow import apply_workflow
def test_update_child_qty_rate_product_bundle(self):
workflow = make_sales_order_workflow()
so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
apply_workflow(so, 'Approve')
frappe.set_user("Administrator")
user = 'test@example.com'
test_user = frappe.get_doc('User', user)
test_user.add_roles("Sales User", "Test Junior Approver")
frappe.set_user(user)
# user shouldn't be able to edit since grand_total will become > 200 if qty is doubled
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 150, 'qty' : 2, 'docname': so.items[0].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Sales Order', trans_item, so.name)
frappe.set_user("Administrator")
user2 = 'test2@example.com'
test_user2 = frappe.get_doc('User', user2)
test_user2.add_roles("Sales User", "Test Approver")
frappe.set_user(user2)
# Test Approver is allowed to edit with grand_total > 200
update_child_qty_rate("Sales Order", trans_item, so.name)
so.reload()
self.assertEqual(so.items[0].qty, 2)
frappe.set_user("Administrator")
test_user.remove_roles("Sales User", "Test Junior Approver", "Test Approver")
test_user2.remove_roles("Sales User", "Test Junior Approver", "Test Approver")
workflow.is_active = 0
workflow.save()
def test_update_child_product_bundle(self):
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Product Bundle Item"):
bundle_item = make_item("_Product Bundle Item", {"is_stock_item": 0})
@ -439,6 +474,20 @@ class TestSalesOrder(unittest.TestCase):
so.reload()
self.assertEqual(so.packed_items[0].qty, 4)
# test uom and conversion factor change
update_uom_conv_factor = json.dumps([{
'item_code': so.get("items")[0].item_code,
'rate': so.get("items")[0].rate,
'qty': so.get("items")[0].qty,
'uom': "_Test UOM 1",
'conversion_factor': 2,
'docname': so.get("items")[0].name
}])
update_child_qty_rate('Sales Order', update_uom_conv_factor, so.name)
so.reload()
self.assertEqual(so.packed_items[0].qty, 8)
def test_warehouse_user(self):
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
@ -973,3 +1022,37 @@ def get_reserved_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"):
"reserved_qty"))
test_dependencies = ["Currency Exchange"]
def make_sales_order_workflow():
if frappe.db.exists('Workflow', 'SO Test Workflow'):
doc = frappe.get_doc("Workflow", "SO Test Workflow")
doc.set("is_active", 1)
doc.save()
return doc
frappe.get_doc(dict(doctype='Role', role_name='Test Junior Approver')).insert(ignore_if_duplicate=True)
frappe.get_doc(dict(doctype='Role', role_name='Test Approver')).insert(ignore_if_duplicate=True)
frappe.db.commit()
frappe.cache().hdel('roles', frappe.session.user)
workflow = frappe.get_doc({
"doctype": "Workflow",
"workflow_name": "SO Test Workflow",
"document_type": "Sales Order",
"workflow_state_field": "workflow_state",
"is_active": 1,
"send_email_alert": 0,
})
workflow.append('states', dict( state = 'Pending', allow_edit = 'All' ))
workflow.append('states', dict( state = 'Approved', allow_edit = 'Test Approver', doc_status = 1 ))
workflow.append('transitions', dict(
state = 'Pending', action = 'Approve', next_state = 'Approved', allowed = 'Test Junior Approver', allow_self_approval = 1,
condition = 'doc.grand_total < 200'
))
workflow.append('transitions', dict(
state = 'Pending', action = 'Approve', next_state = 'Approved', allowed = 'Test Approver', allow_self_approval = 1,
condition = 'doc.grand_total > 200'
))
workflow.insert(ignore_permissions=True)
return workflow

16
erpnext/selling/page/point_of_sale/pos_payment.js

@ -70,13 +70,23 @@ erpnext.PointOfSale.Payment = class {
this.$invoice_fields.append(
`<div class="invoice_detail_field ${df.fieldname}-field" data-fieldname="${df.fieldname}"></div>`
);
let df_events = {
onchange: function() { frm.set_value(this.df.fieldname, this.value); }
}
if (df.fieldtype == "Button") {
df_events = {
click: function() {
if (frm.script_manager.has_handlers(df.fieldname, frm.doc.doctype)) {
frm.script_manager.trigger(df.fieldname, frm.doc.doctype, frm.doc.docname);
}
}
}
}
this[`${df.fieldname}_field`] = frappe.ui.form.make_control({
df: {
...df,
onchange: function() {
frm.set_value(this.df.fieldname, this.value);
}
...df_events
},
parent: this.$invoice_fields.find(`.${df.fieldname}-field`),
render_input: true,

2
erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py

@ -188,7 +188,7 @@ def get_conditions(filters):
conditions += "AND so.transaction_date <= '%s'" %filters.to_date
if filters.get("item_code"):
conditions += "AND so_item.item_code = '%s'" %frappe.db.escape(filters.item_code)
conditions += "AND so_item.item_code = %s" %frappe.db.escape(filters.item_code)
if filters.get("customer"):
conditions += "AND so.customer = %s" %frappe.db.escape(filters.customer)

2
erpnext/selling/report/sales_analytics/sales_analytics.js

@ -8,7 +8,7 @@ frappe.query_reports["Sales Analytics"] = {
fieldname: "tree_type",
label: __("Tree Type"),
fieldtype: "Select",
options: ["Customer Group","Customer","Item Group","Item","Territory","Order Type"],
options: ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Order Type", "Project"],
default: "Customer",
reqd: 1
},

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

Loading…
Cancel
Save