Merge branch 'develop' into youtube-analytics

This commit is contained in:
gavin 2020-09-22 15:36:00 +05:30 committed by GitHub
commit d935b2753f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 2213 additions and 3171 deletions

View File

@ -16,7 +16,7 @@
ERPNext as a monolith includes the following areas for managing businesses: ERPNext as a monolith includes the following areas for managing businesses:
1. [Accounting](https://erpnext.com/open-source-accounting) 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. [CRM](https://erpnext.com/open-source-crm)
1. [Sales](https://erpnext.com/open-source-sales-purchase) 1. [Sales](https://erpnext.com/open-source-sales-purchase)
1. [Purchase](https://erpnext.com/open-source-sales-purchase) 1. [Purchase](https://erpnext.com/open-source-sales-purchase)

View File

@ -55,7 +55,7 @@ class BankStatementTransactionEntry(Document):
def populate_payment_entries(self): def populate_payment_entries(self):
if self.bank_statement is None: return 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): if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
frappe.throw(_("Transactions already retreived from the statement")) frappe.throw(_("Transactions already retreived from the statement"))
@ -65,7 +65,7 @@ class BankStatementTransactionEntry(Document):
if self.bank_settings: if self.bank_settings:
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
statement_headers = self.get_statement_headers() 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: for entry in transactions:
date = entry[statement_headers["Date"]].strip() 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"])) #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] = "" transaction[header] = ""
return transaction return transaction
def get_transaction_entries(filename, headers): def get_transaction_entries(file_url, headers):
header_index = {} header_index = {}
rows, transactions = [], [] rows, transactions = [], []
if (filename.lower().endswith("xlsx")): if (file_url.lower().endswith("xlsx")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(file_id=filename) rows = read_xlsx_file_from_attached_file(file_url=file_url)
elif (filename.lower().endswith("csv")): elif (file_url.lower().endswith("csv")):
from frappe.utils.csvutils import read_csv_content 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() filepath = _file.get_full_path()
with open(filepath,'rb') as csvfile: with open(filepath,'rb') as csvfile:
rows = read_csv_content(csvfile.read()) 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) rows = get_rows_from_xls_file(filename)
else: else:
frappe.throw(_("Only .csv and .xlsx files are supported currently")) frappe.throw(_("Only .csv and .xlsx files are supported currently"))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,13 +5,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cint from frappe.utils import cint, get_link_to_form
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.controllers.status_updater import StatusUpdater from erpnext.controllers.status_updater import StatusUpdater
class POSOpeningEntry(StatusUpdater): class POSOpeningEntry(StatusUpdater):
def validate(self): def validate(self):
self.validate_pos_profile_and_cashier() self.validate_pos_profile_and_cashier()
self.validate_payment_method_account()
self.set_status() self.set_status()
def validate_pos_profile_and_cashier(self): def validate_pos_profile_and_cashier(self):
@ -21,5 +22,13 @@ class POSOpeningEntry(StatusUpdater):
if not cint(frappe.db.get_value("User", self.user, "enabled")): if not cint(frappe.db.get_value("User", self.user, "enabled")):
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user))) frappe.throw(_("User {} 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): def on_submit(self):
self.set_status(update=True) self.set_status(update=True)

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import msgprint, _ 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 six import iteritems
from frappe.model.document import Document from frappe.model.document import Document
@ -13,7 +13,7 @@ class POSProfile(Document):
self.validate_default_profile() self.validate_default_profile()
self.validate_all_link_fields() self.validate_all_link_fields()
self.validate_duplicate_groups() self.validate_duplicate_groups()
self.check_default_payment() self.validate_payment_methods()
def validate_default_profile(self): def validate_default_profile(self):
for row in self.applicable_for_users: for row in self.applicable_for_users:
@ -52,14 +52,23 @@ class POSProfile(Document):
if len(customer_groups) != len(set(customer_groups)): if len(customer_groups) != len(set(customer_groups)):
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group") frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
def check_default_payment(self): def validate_payment_methods(self):
if self.payments: 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] default_mode_of_payment = [d.default for d in self.payments if d.default]
if not default_mode_of_payment: if not default_mode_of_payment:
frappe.throw(_("Set default mode of payment")) frappe.throw(_("Please select a default mode of payment"))
if len(default_mode_of_payment) > 1: if len(default_mode_of_payment) > 1:
frappe.throw(_("Multiple default mode of payment is not allowed")) 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): def on_update(self):
self.set_defaults() self.set_defaults()

View File

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

View File

@ -28,7 +28,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
// Trigger supplier event on load if supplier is available // 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 // 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'); this.frm.trigger('supplier');
} }
}, },

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import frappe.defaults 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 frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from frappe.model.mapper import get_mapped_doc 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") {"parent": mode_of_payment, "company": company}, "default_account")
if not account: if not account:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}") 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 { return {
"account": account "account": account
} }
@ -1612,16 +1612,14 @@ def update_multi_mode_option(doc, pos_profile):
payment.type = payment_mode.type payment.type = payment_mode.type
doc.set('payments', []) 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'): for pos_payment_method in pos_profile.get('payments'):
pos_payment_method = pos_payment_method.as_dict() pos_payment_method = pos_payment_method.as_dict()
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
if payment_mode: 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 payment_mode[0].default = pos_payment_method.default
append_payment(payment_mode[0]) append_payment(payment_mode[0])

View File

@ -72,7 +72,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
check_plaid_status() { check_plaid_status() {
const me = this; const me = this;
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => { frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
if (r && r.enabled == "1") { if (r && r.enabled === "1") {
me.plaid_status = "active" me.plaid_status = "active"
} else { } else {
me.plaid_status = "inactive" me.plaid_status = "inactive"
@ -214,29 +214,33 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync {
init_config() { init_config() {
const me = this; const me = this;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
.then(result => { .then(result => {
me.plaid_env = result.plaid_env; me.plaid_env = result.plaid_env;
me.plaid_public_key = result.plaid_public_key;
me.client_name = result.client_name; me.client_name = result.client_name;
me.sync_transactions() me.link_token = result.link_token;
me.sync_transactions();
}) })
} }
sync_transactions() { sync_transactions() {
const me = this; 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', { frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
bank: v['bank'], bank: r.bank,
bank_account: me.parent.bank_account, bank_account: me.parent.bank_account,
freeze: true freeze: true
}) })
.then((result) => { .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 = ` let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;"> <div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5> <h5 class="text-muted">${result_title}</h5>
</div>` </div>`
this.parent.$main_section.append(result_msg) 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' });
}) })

View File

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

View File

@ -617,9 +617,19 @@ class ReceivablePayableReport(object):
elif party_type_field=="supplier": elif party_type_field=="supplier":
self.add_supplier_filters(conditions, values) self.add_supplier_filters(conditions, values)
if self.filters.cost_center:
self.get_cost_center_conditions(conditions)
self.add_accounting_dimensions_filters(conditions, values) self.add_accounting_dimensions_filters(conditions, values)
return " and ".join(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): def get_order_by_condition(self):
if self.filters.get('group_by_party'): if self.filters.get('group_by_party'):
return "order by party, posting_date" return "order by party, posting_date"

View File

@ -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) { is_existing_asset: function(frm) {
frm.trigger("toggle_reference_doc"); frm.trigger("toggle_reference_doc");
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); // 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; 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");
}
} }
}); });

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext, math, json import frappe, erpnext, math, json
from frappe import _ from frappe import _
from six import string_types 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 frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \ from erpnext.assets.doctype.asset.depreciation \
@ -83,6 +83,11 @@ class Asset(AccountsController):
if not self.available_for_use_date: if not self.available_for_use_date:
frappe.throw(_("Available for use date is required")) 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): def set_missing_values(self):
if not self.asset_category: if not self.asset_category:
self.asset_category = frappe.get_cached_value("Item", self.item_code, "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 row.depreciation_start_date:
if not self.available_for_use_date: if not self.available_for_use_date:
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx)) 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: if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0 self.opening_accumulated_depreciation = 0

View File

@ -371,19 +371,18 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.available_for_use_date = nowdate() asset.available_for_use_date = '2020-01-01'
asset.purchase_date = nowdate() asset.purchase_date = '2020-01-01'
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 10,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 1
"depreciation_start_date": nowdate()
}) })
asset.insert() asset.insert()
asset.submit() asset.submit()
post_depreciation_entries(date=add_months(nowdate(), 10)) post_depreciation_entries(date=add_months('2020-01-01', 4))
scrap_asset(asset.name) scrap_asset(asset.name)
@ -392,9 +391,9 @@ class TestAsset(unittest.TestCase):
self.assertTrue(asset.journal_entry_for_scrap) self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = ( 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 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` 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, "expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 10
"depreciation_start_date": "2020-06-06"
}) })
asset.insert() asset.insert()
accumulated_depreciation_after_full_schedule = \ accumulated_depreciation_after_full_schedule = \

View File

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

View File

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

View File

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

View File

@ -89,7 +89,7 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0) 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) mr = make_material_request(qty=10)
po = make_purchase_order(mr.name) po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier" po.supplier = "_Test Supplier"
@ -119,7 +119,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) 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 = create_purchase_order(do_not_save=1)
po.items[0].qty = 4 po.items[0].qty = 4
po.save() po.save()
@ -145,7 +145,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(po.status, 'To Receive and Bill') 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 = create_purchase_order(do_not_save=1)
po.items[0].qty = 4 po.items[0].qty = 4
po.save() po.save()
@ -185,7 +185,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(len(po.get('items')), 1) self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill') 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) po = create_purchase_order(item_code= "_Test Item", qty=4)
user = 'test@example.com' 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) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
frappe.set_user("Administrator") 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): def test_update_qty(self):
po = create_purchase_order() po = create_purchase_order()

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import json
from frappe import _, throw from frappe import _, throw
from frappe.utils import (today, flt, cint, fmt_money, formatdate, from frappe.utils import (today, flt, cint, fmt_money, formatdate,
getdate, add_days, add_months, get_last_day, nowdate, get_link_to_form) 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.stock.get_item_details import get_conversion_factor, get_item_details
from erpnext.setup.utils import get_exchange_rate from erpnext.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency 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 erpnext.exceptions import InvalidCurrency
from six import text_type from six import text_type
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions 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 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") 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 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): 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 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.item_name = item.item_name
child_item.description = item.description child_item.description = item.description
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date 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 = trans_item.get("uom") or item.stock_uom
child_item.uom = 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) child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse: if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") 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.item_name = item.item_name
child_item.description = item.description child_item.description = item.description
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date 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 = trans_item.get("uom") or item.stock_uom
child_item.uom = 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_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 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 return child_item
def check_and_delete_children(parent, data): def validate_and_delete_children(parent, data):
deleted_children = [] deleted_children = []
updated_item_names = [d.get("docname") for d in data] updated_item_names = [d.get("docname") for d in data]
for item in parent.items: for item in parent.items:
@ -1221,18 +1238,37 @@ def check_and_delete_children(parent, data):
@frappe.whitelist() @frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): 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: try:
doc.check_permission(perm_type) doc.check_permission(perm_type)
except: except frappe.PermissionError:
action = "add" if perm_type == 'create' else "update" actions = { 'create': 'add', 'write': 'update', 'cancel': 'remove' }
frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
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): def get_new_child_item(item_row):
if parent_doctype == "Sales Order": new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults
return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row) return new_child_function(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)
def validate_quantity(child_item, d): def validate_quantity(child_item, d):
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
@ -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'] sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
parent = frappe.get_doc(parent_doctype, parent_doctype_name) 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: for d in data:
new_child_flag = False new_child_flag = False
if not d.get("docname"): if not d.get("docname"):
new_child_flag = True new_child_flag = True
check_permissions(parent, 'create') check_doc_permissions(parent, 'create')
child_item = get_new_child_item(d) child_item = get_new_child_item(d)
else: else:
check_permissions(parent, 'write') check_doc_permissions(parent, 'write')
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) 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_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_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_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': if parent_doctype == 'Sales Order':
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date") 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 rate_unchanged = prev_rate == new_rate
qty_unchanged = prev_qty == new_qty qty_unchanged = prev_qty == new_qty
uom_unchanged = prev_uom == new_uom
conversion_factor_unchanged = prev_con_fac == new_con_fac 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 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 continue
validate_quantity(child_item, d) validate_quantity(child_item, d)
@ -1291,6 +1330,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
else: else:
child_item.conversion_factor = flt(d.get('conversion_factor')) 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': if d.get("delivery_date") and parent_doctype == 'Sales Order':
child_item.delivery_date = d.get('delivery_date') 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() parent.update_receiving_percentage()
if parent.is_subcontracted == "Yes": if parent.is_subcontracted == "Yes":
parent.update_reserved_qty_for_subcontract() parent.update_reserved_qty_for_subcontract()
parent.create_raw_materials_supplied("supplied_items")
parent.save()
else: else:
parent.update_reserved_qty() parent.update_reserved_qty()
parent.update_project() parent.update_project()
parent.update_prevdoc_status('submit') parent.update_prevdoc_status('submit')
parent.update_delivery_status() parent.update_delivery_status()
parent.reload()
validate_workflow_conditions(parent)
parent.update_blanket_order() parent.update_blanket_order()
parent.update_billing_percentage() parent.update_billing_percentage()
parent.set_status() parent.set_status()

View File

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

View File

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

View File

@ -3,15 +3,15 @@
frappe.ui.form.on('Student', { frappe.ui.form.on('Student', {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("guardian", "guardian_name", "guardian_name"); frm.add_fetch('guardian', 'guardian_name', 'guardian_name');
frm.add_fetch("student", "title", "full_name"); frm.add_fetch('student', 'title', 'full_name');
frm.add_fetch("student", "gender", "gender"); frm.add_fetch('student', 'gender', 'gender');
frm.add_fetch("student", "date_of_birth", "date_of_birth"); 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 { return {
"filters": { 'filters': {
"name": ["!=", doc.name] 'name': ['!=', doc.name]
} }
}; };
}) })
@ -25,6 +25,12 @@ frappe.ui.form.on('Student', {
{party_type:'Student', party:frm.doc.name}); {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);
}
});
} }
}); });

View File

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

View File

@ -7,7 +7,7 @@ frappe.ui.form.on("Student Applicant", {
}, },
refresh: function(frm) { 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.add_custom_button(__("Approve"), function() {
frm.set_value("application_status", "Approved"); frm.set_value("application_status", "Approved");
frm.save_or_update(); frm.save_or_update();
@ -20,10 +20,11 @@ frappe.ui.form.on("Student Applicant", {
}, 'Actions'); }, '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.add_custom_button(__("Enroll"), function() {
frm.events.enroll(frm) frm.events.enroll(frm)
}).addClass("btn-primary"); }).addClass("btn-primary");
frm.add_custom_button(__("Reject"), function() { frm.add_custom_button(__("Reject"), function() {
frm.set_value("application_status", "Rejected"); frm.set_value("application_status", "Rejected");
frm.save_or_update(); frm.save_or_update();
@ -35,7 +36,13 @@ frappe.ui.form.on("Student Applicant", {
frappe.hide_msgprint(true); frappe.hide_msgprint(true);
frappe.show_progress(__("Enrolling student"), data.progress[0],data.progress[1]); 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) { enroll: function(frm) {

View File

@ -2,81 +2,90 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals import plaid
from frappe import _ import requests
from frappe.utils.password import get_decrypted_password from plaid.errors import APIError, ItemError, InvalidRequestError
from plaid import Client
from plaid.errors import APIError, ItemError
import frappe import frappe
import requests from frappe import _
class PlaidConnector(): class PlaidConnector():
def __init__(self, access_token=None): 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.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): def get_access_token(self, public_token):
if public_token is None: if public_token is None:
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error")) frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
response = self.client.Item.public_token.exchange(public_token) response = self.client.Item.public_token.exchange(public_token)
access_token = response['access_token'] access_token = response["access_token"]
return 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): def auth(self):
try: try:
self.client.Auth.get(self.access_token) self.client.Auth.get(self.access_token)
print("Authentication successful.....")
except ItemError as e: except ItemError as e:
if e.code == 'ITEM_LOGIN_REQUIRED': if e.code == "ITEM_LOGIN_REQUIRED":
pass
else:
pass pass
except APIError as e: except APIError as e:
if e.code == 'PLANNED_MAINTENANCE': if e.code == "PLANNED_MAINTENANCE":
pass
else:
pass pass
except requests.Timeout: except requests.Timeout:
pass pass
except Exception as e: except Exception as e:
print(e)
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error")) 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): def get_transactions(self, start_date, end_date, account_id=None):
try:
self.auth() self.auth()
kwargs = dict(
access_token=self.access_token,
start_date=start_date,
end_date=end_date
)
if account_id: if account_id:
account_ids = [account_id] kwargs.update(dict(account_ids=[account_id]))
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids) try:
response = self.client.Transactions.get(**kwargs)
else: transactions = response["transactions"]
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date) while len(transactions) < response["total_transactions"]:
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)) 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 return transactions
except Exception: except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))

View File

@ -7,9 +7,9 @@ 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_client_id', frm.doc.enabled);
frm.toggle_reqd('plaid_secret', 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); frm.toggle_reqd('plaid_env', frm.doc.enabled);
}, },
refresh: function (frm) { refresh: function (frm) {
if (frm.doc.enabled) { if (frm.doc.enabled) {
frm.add_custom_button('Link a new bank account', () => { frm.add_custom_button('Link a new bank account', () => {
@ -22,17 +22,16 @@ frappe.ui.form.on('Plaid Settings', {
erpnext.integrations.plaidLink = class plaidLink { erpnext.integrations.plaidLink = class plaidLink {
constructor(parent) { constructor(parent) {
this.frm = parent; this.frm = parent;
this.product = ["transactions", "auth"];
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'; this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config(); this.init_config();
} }
init_config() { async init_config() {
const me = this; this.product = ["auth", "transactions"];
me.plaid_env = me.frm.doc.plaid_env; this.plaid_env = this.frm.doc.plaid_env;
me.plaid_public_key = me.frm.doc.plaid_public_key; this.client_name = frappe.boot.sitename;
me.client_name = frappe.boot.sitename; this.token = await this.frm.call("get_link_token").then(resp => resp.message);
me.init_plaid(); this.init_plaid();
} }
init_plaid() { init_plaid() {
@ -69,17 +68,17 @@ erpnext.integrations.plaidLink = class plaidLink {
} }
onScriptLoaded(me) { onScriptLoaded(me) {
me.linkHandler = window.Plaid.create({ me.linkHandler = Plaid.create({
clientName: me.client_name, clientName: me.client_name,
product: me.product,
env: me.plaid_env, env: me.plaid_env,
key: me.plaid_public_key, token: me.token,
onSuccess: me.plaid_success, onSuccess: me.plaid_success
product: me.product
}); });
} }
onScriptError(error) { 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); frappe.msgprint(error);
} }
@ -94,12 +93,16 @@ erpnext.integrations.plaidLink = class plaidLink {
reqd: 1 reqd: 1
}, (data) => { }, (data) => {
me.company = data.company; me.company = data.company;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response}) frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {
.then((result) => { token: token,
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response, response: response
bank: result, company: me.company}); }).then((result) => {
}) frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {
.then(() => { response: response,
bank: result,
company: me.company
});
}).then(() => {
frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' }); frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' });
}); });
}, __("Select a company"), __("Continue")); }, __("Select a company"), __("Continue"));

View File

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

View File

@ -2,37 +2,44 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json 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.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector 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.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): class PlaidSettings(Document):
pass @staticmethod
def get_link_token():
plaid = PlaidConnector()
return plaid.get_link_token()
@frappe.whitelist() @frappe.whitelist()
def plaid_configuration(): def get_plaid_configuration():
if frappe.db.get_single_value("Plaid Settings", "enabled"): if frappe.db.get_single_value("Plaid Settings", "enabled"):
plaid_settings = frappe.get_single("Plaid Settings") plaid_settings = frappe.get_single("Plaid Settings")
return { return {
"plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": plaid_settings.plaid_env, "plaid_env": plaid_settings.plaid_env,
"link_token": plaid_settings.get_link_token(),
"client_name": frappe.local.site "client_name": frappe.local.site
} }
else:
return "disabled" return "disabled"
@frappe.whitelist() @frappe.whitelist()
def add_institution(token, response): def add_institution(token, response):
response = json.loads(response) response = json.loads(response)
plaid = PlaidConnector() plaid = PlaidConnector()
access_token = plaid.get_access_token(token) access_token = plaid.get_access_token(token)
bank = None
if not frappe.db.exists("Bank", response["institution"]["name"]): if not frappe.db.exists("Bank", response["institution"]["name"]):
try: try:
@ -44,7 +51,6 @@ def add_institution(token, response):
bank.insert() bank.insert()
except Exception: except Exception:
frappe.throw(frappe.get_traceback()) frappe.throw(frappe.get_traceback())
else: else:
bank = frappe.get_doc("Bank", response["institution"]["name"]) bank = frappe.get_doc("Bank", response["institution"]["name"])
bank.plaid_access_token = access_token bank.plaid_access_token = access_token
@ -52,6 +58,7 @@ def add_institution(token, response):
return bank return bank
@frappe.whitelist() @frappe.whitelist()
def add_bank_accounts(response, bank, company): def add_bank_accounts(response, bank, company):
try: try:
@ -92,9 +99,8 @@ def add_bank_accounts(response, bank, company):
new_account.insert() new_account.insert()
result.append(new_account.name) result.append(new_account.name)
except frappe.UniqueValidationError: 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: except Exception:
frappe.throw(frappe.get_traceback()) frappe.throw(frappe.get_traceback())
@ -103,6 +109,7 @@ def add_bank_accounts(response, bank, company):
return result return result
def add_account_type(account_type): def add_account_type(account_type):
try: try:
frappe.get_doc({ frappe.get_doc({
@ -122,10 +129,11 @@ def add_account_subtype(account_subtype):
except Exception: except Exception:
frappe.throw(frappe.get_traceback()) frappe.throw(frappe.get_traceback())
@frappe.whitelist() @frappe.whitelist()
def sync_transactions(bank, bank_account): def sync_transactions(bank, bank_account):
'''Sync transactions based on the last integration date as the start date, after the sync is completed """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''' 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") last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
if last_transaction_date: if last_transaction_date:
start_date = formatdate(last_transaction_date, "YYYY-MM-dd") 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)) len(result), bank_account, start_date, end_date))
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date) frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
except Exception: except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
def get_transactions(bank, bank_account=None, start_date=None, end_date=None): def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
access_token = None access_token = None
@ -168,6 +176,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
return transactions return transactions
def new_bank_transaction(transaction): def new_bank_transaction(transaction):
result = [] result = []
@ -182,8 +191,8 @@ def new_bank_transaction(transaction):
status = "Pending" if transaction["pending"] == "True" else "Settled" status = "Pending" if transaction["pending"] == "True" else "Settled"
try:
tags = [] tags = []
try:
tags += transaction["category"] tags += transaction["category"]
tags += ["Plaid Cat. {}".format(transaction["category_id"])] tags += ["Plaid Cat. {}".format(transaction["category_id"])]
except KeyError: except KeyError:
@ -216,6 +225,7 @@ def new_bank_transaction(transaction):
return result return result
def automatic_synchronization(): def automatic_synchronization():
settings = frappe.get_doc("Plaid Settings", "Plaid Settings") 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"]) plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
for plaid_account in plaid_accounts: for plaid_account in plaid_accounts:
frappe.enqueue("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
)

View File

@ -1,14 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals
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 import json
from frappe.utils.response import json_handler import unittest
import frappe
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account 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): class TestPlaidSettings(unittest.TestCase):
def setUp(self): def setUp(self):
@ -31,7 +34,7 @@ class TestPlaidSettings(unittest.TestCase):
def test_plaid_disabled(self): def test_plaid_disabled(self):
frappe.db.set_value("Plaid Settings", None, "enabled", 0) 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): def test_add_account_type(self):
add_account_type("brokerage") add_account_type("brokerage")

View File

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

View File

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

View File

@ -34,10 +34,10 @@ frappe.ui.form.on('Lab Test', {
if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') { if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') {
frm.add_custom_button(__('Approve'), function () { frm.add_custom_button(__('Approve'), function () {
status_update(1, frm); status_update(1, frm);
}); }, __('Actions'));
frm.add_custom_button(__('Reject'), function () { frm.add_custom_button(__('Reject'), function () {
status_update(0, frm); status_update(0, frm);
}); }, __('Actions'));
} }
} }
@ -179,23 +179,6 @@ var show_lab_tests = function (frm, lab_test_list) {
d.show(); 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 make_dialog = function (frm, emailed, printed) {
var number = frm.doc.mobile; var number = frm.doc.mobile;
@ -203,7 +186,7 @@ var make_dialog = function (frm, emailed, printed) {
title: 'Send SMS', title: 'Send SMS',
width: 400, width: 400,
fields: [ 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: 'number', fieldtype: 'Data', label: 'Mobile Number', reqd: 1 },
{ fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 } { fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 }
], ],
@ -217,22 +200,22 @@ var make_dialog = function (frm, emailed, printed) {
dialog.hide(); dialog.hide();
} }
}); });
if (frm.doc.report_preference == 'Print') { if (frm.doc.report_preference === 'Print') {
dialog.set_values({ dialog.set_values({
'sms_type': 'Printed', 'result_format': 'Printed',
'number': number, 'number': number,
'message': printed 'message': printed
}); });
} else { } else {
dialog.set_values({ dialog.set_values({
'sms_type': 'Emailed', 'result_format': 'Emailed',
'number': number, 'number': number,
'message': emailed 'message': emailed
}); });
} }
var fd = dialog.fields_dict; var fd = dialog.fields_dict;
$(fd.sms_type.input).change(function () { $(fd.result_format.input).change(function () {
if (dialog.get_value('sms_type') == 'Emailed') { if (dialog.get_value('result_format') === 'Emailed') {
dialog.set_values({ dialog.set_values({
'number': number, 'number': number,
'message': emailed 'message': emailed

View File

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

View File

@ -6,23 +6,24 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import getdate, cstr from frappe.utils import getdate, cstr, get_link_to_form
class LabTest(Document): class LabTest(Document):
def validate(self):
if not self.is_new():
self.set_secondary_uom_result()
def on_submit(self): def on_submit(self):
self.validate_result_values()
self.db_set('submitted_date', getdate()) self.db_set('submitted_date', getdate())
self.db_set('status', 'Completed') self.db_set('status', 'Completed')
insert_lab_test_to_medical_record(self) insert_lab_test_to_medical_record(self)
def on_cancel(self): def on_cancel(self):
delete_lab_test_from_medical_record(self)
self.db_set('status', 'Cancelled') self.db_set('status', 'Cancelled')
delete_lab_test_from_medical_record(self)
self.reload() self.reload()
def validate(self):
if not self.is_new():
self.set_secondary_uom_result()
def on_update(self): def on_update(self):
if self.sensitivity_test_items: if self.sensitivity_test_items:
sensitivity = sorted(self.sensitivity_test_items, key=lambda x: x.antibiotic_sensitivity) 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) item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor)
except: except:
item.secondary_uom_result = '' 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): 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) lab_test_created = create_lab_test_from_encounter(docname)
if lab_test_created: 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: else:
frappe.msgprint(_('No Lab Tests created')) frappe.msgprint(_('No Lab Tests created'))
@ -211,8 +225,9 @@ def create_sample_doc(template, patient, invoice, company = None):
'docstatus': 0, 'docstatus': 0,
'sample': template.sample 'sample': template.sample
}) })
if sample_exists: 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]) sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0])
quantity = int(sample_collection.sample_qty) + int(template.sample_qty) quantity = int(sample_collection.sample_qty) + int(template.sample_qty)
if template.sample_details: if template.sample_details:
@ -238,7 +253,7 @@ def create_sample_doc(template, patient, invoice, company = None):
sample_collection.company = company sample_collection.company = company
if template.sample_details: 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) sample_collection.save(ignore_permissions=True)
return sample_collection 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) sample_collection = create_sample_doc(template, patient, invoice, lab_test.company)
if sample_collection: if sample_collection:
lab_test.sample = sample_collection.name 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 return lab_test
def load_result_format(lab_test, template, prescription, invoice): def load_result_format(lab_test, template, prescription, invoice):
if template.lab_test_template_type == 'Single': if template.lab_test_template_type == 'Single':
create_normals(template, lab_test) create_normals(template, lab_test)
elif template.lab_test_template_type == 'Compound': elif template.lab_test_template_type == 'Compound':
create_compounds(template, lab_test, False) create_compounds(template, lab_test, False)
elif template.lab_test_template_type == 'Descriptive': elif template.lab_test_template_type == 'Descriptive':
create_descriptives(template, lab_test) create_descriptives(template, lab_test)
elif template.lab_test_template_type == 'Grouped': elif template.lab_test_template_type == 'Grouped':
# Iterate for each template in the group and create one result for all. # Iterate for each template in the group and create one result for all.
for lab_test_group in template.lab_test_groups: for lab_test_group in template.lab_test_groups:
# Template_in_group = None # Template_in_group = None
if lab_test_group.lab_test_template: if lab_test_group.lab_test_template:
template_in_group = frappe.get_doc('Lab Test Template', template_in_group = frappe.get_doc('Lab Test Template', lab_test_group.lab_test_template)
lab_test_group.lab_test_template)
if template_in_group: if template_in_group:
if template_in_group.lab_test_template_type == 'Single': if template_in_group.lab_test_template_type == 'Single':
create_normals(template_in_group, lab_test) create_normals(template_in_group, lab_test)
elif template_in_group.lab_test_template_type == 'Compound': elif template_in_group.lab_test_template_type == 'Compound':
normal_heading = lab_test.append('normal_test_items') normal_heading = lab_test.append('normal_test_items')
normal_heading.lab_test_name = template_in_group.lab_test_name 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.allow_blank = 1
normal_heading.template = template_in_group.name normal_heading.template = template_in_group.name
create_compounds(template_in_group, lab_test, True) create_compounds(template_in_group, lab_test, True)
elif template_in_group.lab_test_template_type == 'Descriptive': elif template_in_group.lab_test_template_type == 'Descriptive':
descriptive_heading = lab_test.append('descriptive_test_items') descriptive_heading = lab_test.append('descriptive_test_items')
descriptive_heading.lab_test_name = template_in_group.lab_test_name 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.allow_blank = 1
descriptive_heading.template = template_in_group.name descriptive_heading.template = template_in_group.name
create_descriptives(template_in_group, lab_test) create_descriptives(template_in_group, lab_test)
else: # Lab Test Group - Add New Line else: # Lab Test Group - Add New Line
normal = lab_test.append('normal_test_items') normal = lab_test.append('normal_test_items')
normal.lab_test_name = lab_test_group.group_event 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.allow_blank = lab_test_group.allow_blank
normal.require_result_value = 1 normal.require_result_value = 1
normal.template = template.name normal.template = template.name
if template.lab_test_template_type != 'No Result': if template.lab_test_template_type != 'No Result':
if prescription: if prescription:
lab_test.prescription = prescription lab_test.prescription = prescription
@ -302,9 +325,10 @@ def load_result_format(lab_test, template, prescription, invoice):
@frappe.whitelist() @frappe.whitelist()
def get_employee_by_user_id(user_id): def get_employee_by_user_id(user_id):
emp_id = frappe.db.get_value('Employee', { 'user_id': user_id }) emp_id = frappe.db.exists('Employee', { 'user_id': user_id })
employee = frappe.get_doc('Employee', emp_id) if emp_id:
return employee return frappe.get_doc('Employee', emp_id)
return None
def insert_lab_test_to_medical_record(doc): def insert_lab_test_to_medical_record(doc):
table_row = False table_row = False

View File

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

View File

@ -3,8 +3,204 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe
# test_records = frappe.get_test_records('Lab Test') 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): 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

View File

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

View File

@ -34,14 +34,15 @@
"descriptive_test_templates", "descriptive_test_templates",
"section_break_group", "section_break_group",
"lab_test_groups", "lab_test_groups",
"medical_coding_section",
"medical_code_standard",
"medical_code",
"sb_sample_collection", "sb_sample_collection",
"sample", "sample",
"sample_uom", "sample_uom",
"sample_qty", "sample_qty",
"column_break_33",
"sample_details", "sample_details",
"medical_coding_section",
"medical_code",
"medical_code_standard",
"worksheet_section", "worksheet_section",
"worksheet_instructions", "worksheet_instructions",
"result_legend_section", "result_legend_section",
@ -112,7 +113,7 @@
{ {
"default": "1", "default": "1",
"depends_on": "eval:doc.lab_test_template_type != 'Grouped'", "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", "fieldname": "is_billable",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Billable", "label": "Is Billable",
@ -128,6 +129,7 @@
"mandatory_depends_on": "eval:doc.is_billable == 1" "mandatory_depends_on": "eval:doc.is_billable == 1"
}, },
{ {
"collapsible": 1,
"fieldname": "medical_coding_section", "fieldname": "medical_coding_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Medical Coding" "label": "Medical Coding"
@ -184,7 +186,7 @@
"depends_on": "eval:doc.lab_test_template_type == 'Descriptive'", "depends_on": "eval:doc.lab_test_template_type == 'Descriptive'",
"fieldname": "section_break_special", "fieldname": "section_break_special",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Descriptive" "label": "Descriptive Test"
}, },
{ {
"default": "0", "default": "0",
@ -196,7 +198,7 @@
"depends_on": "eval:doc.lab_test_template_type == 'Grouped'", "depends_on": "eval:doc.lab_test_template_type == 'Grouped'",
"fieldname": "section_break_group", "fieldname": "section_break_group",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Group" "label": "Group Tests"
}, },
{ {
"fieldname": "lab_test_groups", "fieldname": "lab_test_groups",
@ -217,7 +219,6 @@
"no_copy": 1 "no_copy": 1
}, },
{ {
"collapsible": 1,
"fieldname": "sb_sample_collection", "fieldname": "sb_sample_collection",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Sample Collection" "label": "Sample Collection"
@ -311,10 +312,14 @@
"fieldname": "descriptive_test_templates", "fieldname": "descriptive_test_templates",
"fieldtype": "Table", "fieldtype": "Table",
"options": "Descriptive Test Template" "options": "Descriptive Test Template"
},
{
"fieldname": "column_break_33",
"fieldtype": "Column Break"
} }
], ],
"links": [], "links": [],
"modified": "2020-07-13 12:57:09.925436", "modified": "2020-07-30 14:32:40.449818",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Lab Test Template", "name": "Lab Test Template",

View File

@ -15,7 +15,8 @@ class LabTestTemplate(Document):
def validate(self): def validate(self):
if self.is_billable and (not self.lab_test_rate or self.lab_test_rate <= 0.0): 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.validate_conversion_factor()
self.enable_disable_item() self.enable_disable_item()
@ -42,7 +43,9 @@ class LabTestTemplate(Document):
# Remove template reference from item and disable item # Remove template reference from item and disable item
if self.item: if self.item:
try: try:
frappe.delete_doc('Item', self.item) item = self.item
self.db_set('item', '')
frappe.delete_doc('Item', item)
except Exception: except Exception:
frappe.throw(_('Not permitted. Please disable the Lab Test Template')) frappe.throw(_('Not permitted. Please disable the Lab Test Template'))
@ -63,26 +66,26 @@ class LabTestTemplate(Document):
'standard_rate': self.lab_test_rate, 'standard_rate': self.lab_test_rate,
'description': self.lab_test_description 'description': self.lab_test_description
}) })
item.save() item.flags.ignore_mandatory = True
item.save(ignore_permissions=True)
def item_price_exists(self): def item_price_exists(self):
item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code}) item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code})
if item_price: if item_price:
return item_price[0][0] return item_price[0][0]
else:
return False return False
def validate_conversion_factor(self): def validate_conversion_factor(self):
if self.lab_test_template_type == "Single" and self.secondary_uom and not self.conversion_factor: if self.lab_test_template_type == 'Single' and self.secondary_uom and not self.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory")) frappe.throw(_('Conversion Factor is mandatory'))
if self.lab_test_template_type == "Compound": if self.lab_test_template_type == 'Compound':
for item in self.normal_test_templates: for item in self.normal_test_templates:
if item.secondary_uom and not item.conversion_factor: if item.secondary_uom and not item.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory")) frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(item.idx))
if self.lab_test_template_type == "Grouped": if self.lab_test_template_type == 'Grouped':
for group in self.lab_test_groups: 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: if group.template_or_new_line == 'Add New Line' and group.secondary_uom and not group.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory")) frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(group.idx))
def create_item_from_template(doc): def create_item_from_template(doc):

View File

@ -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']
}
]
}

View File

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

View File

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

View File

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

View File

@ -3,19 +3,19 @@
frappe.ui.form.on('Sample Collection', { frappe.ui.form.on('Sample Collection', {
refresh: function(frm) { refresh: function(frm) {
if(frappe.defaults.get_default("create_sample_collection_for_lab_test")){ if (frappe.defaults.get_default('create_sample_collection_for_lab_test')) {
frm.add_custom_button(__("View Lab Tests"), function() { frm.add_custom_button(__('View Lab Tests'), function() {
frappe.route_options = {"sample": frm.doc.name}; frappe.route_options = {'sample': frm.doc.name};
frappe.set_route("List", "Lab Test"); 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){ if(frm.doc.patient){
frappe.call({ frappe.call({
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail", 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
args: { args: {
patient: frm.doc.patient patient: frm.doc.patient
}, },
@ -24,8 +24,8 @@ frappe.ui.form.on("Sample Collection", "patient", function(frm) {
if (data.message.dob){ if (data.message.dob){
age = calculate_age(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_age', age);
frappe.model.set_value(frm.doctype,frm.docname, "patient_sex", data.message.sex); 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(); var age = new Date();
age.setTime(ageMS); age.setTime(ageMS);
var years = age.getFullYear() - 1970; 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)';
}; };

View File

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

View File

@ -3,7 +3,12 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import flt
from frappe import _
class SampleCollection(Document): 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'))

View File

@ -7,14 +7,28 @@ frappe.query_reports["Lab Test Report"] = {
"fieldname": "from_date", "fieldname": "from_date",
"label": __("From Date"), "label": __("From Date"),
"fieldtype": "Date", "fieldtype": "Date",
"default": frappe.datetime.now_date(), "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
"width": "80" "reqd": 1
}, },
{ {
"fieldname": "to_date", "fieldname": "to_date",
"label": __("To Date"), "label": __("To Date"),
"fieldtype": "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",
@ -27,6 +41,17 @@ frappe.query_reports["Lab Test Report"] = {
"label": __("Medical Department"), "label": __("Medical Department"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Medical Department" "options": "Medical Department"
},
{
"fieldname": "status",
"label": __("Status"),
"fieldtype": "Select",
"options": "\nCompleted\nApproved\nRejected"
},
{
"fieldname": "invoiced",
"label": __("Invoiced"),
"fieldtype": "Check"
} }
] ]
}; };

View File

@ -1,12 +1,13 @@
{ {
"add_total_row": 1, "add_total_row": 0,
"creation": "2013-04-23 18:15:29", "creation": "2013-04-23 18:15:29",
"disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 1, "idx": 1,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2018-08-06 11:41:50.218737", "modified": "2020-07-30 18:53:20.102873",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Lab Test Report", "name": "Lab Test Report",

View File

@ -8,51 +8,204 @@ from frappe import msgprint, _
def execute(filters=None): def execute(filters=None):
if not filters: filters = {} if not filters: filters = {}
lab_test_list = get_lab_test(filters) data, columns = [], []
columns = get_columns() columns = get_columns()
lab_test_list = get_lab_tests(filters)
if not lab_test_list: if not lab_test_list:
msgprint(_("No record found")) msgprint(_('No records found'))
return columns, lab_test_list return columns, lab_test_list
data = [] data = []
for lab_test in lab_test_list: 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) 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(): def get_columns():
columns = [ return [
_("Test") + ":Data:120", {
_("Patient") + ":Link/Patient:180", 'fieldname': 'test',
_("Healthcare Practitioner") + ":Link/Healthcare Practitioner:120", 'label': _('Lab Test'),
_("Invoiced") + ":Check:100", 'fieldtype': 'Link',
_("Status") + ":Data:120", 'options': 'Lab Test',
_("Result Date") + ":Date:120", 'width': '120'
_("Department") + ":Data: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): def get_conditions(filters):
conditions = "" conditions = {
'docstatus': ('=', 1)
}
if filters.get("patient"): if filters.get('from_date') and filters.get('to_date'):
conditions += "and patient = %(patient)s" conditions['result_date'] = ('between', (filters.get('from_date'), filters.get('to_date')))
if filters.get("from_date"): filters.pop('from_date')
conditions += "and result_date >= %(from_date)s" filters.pop('to_date')
if filters.get("to_date"):
conditions += " and result_date <= %(to_date)s" for key, value in filters.items():
if filters.get("department"): if filters.get(key):
conditions += " and department = %(department)s" conditions[key] = value
return conditions return conditions
def get_lab_test(filters): def get_chart_data(data):
conditions = get_conditions(filters) if not data:
return frappe.db.sql("""select name, patient, lab_test_name, patient_name, status, result_date, practitioner, invoiced, department return None
from `tabLab Test`
where docstatus<2 %s order by submitted_date desc, name desc""" % labels = ['Completed', 'Approved', 'Rejected']
conditions, filters, as_dict=1)
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',
}
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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", var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges",
me.frm.doc.name); 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; return;
} }

View File

@ -466,6 +466,33 @@ erpnext.utils.update_child_items = function(opts) {
read_only: 0, read_only: 0,
disabled: 0, disabled: 0,
label: __('Item Code') 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', fieldtype:'Float',
fieldname:"qty", fieldname:"qty",
@ -546,6 +573,7 @@ erpnext.utils.update_child_items = function(opts) {
"conversion_factor": d.conversion_factor, "conversion_factor": d.conversion_factor,
"qty": d.qty, "qty": d.qty,
"rate": d.rate, "rate": d.rate,
"uom": d.uom
}); });
this.data = dialog.fields_dict.trans_items.df.data; this.data = dialog.fields_dict.trans_items.df.data;
dialog.fields_dict.trans_items.grid.refresh(); dialog.fields_dict.trans_items.grid.refresh();

View File

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

View File

@ -460,80 +460,8 @@ ACCOUNT_NAME_COLUMNS = [
"Sprach-ID" "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(): class DataCategory():
"""Field of the CSV Header.""" """Field of the CSV Header."""
DEBTORS_CREDITORS = "16" DEBTORS_CREDITORS = "16"
@ -542,6 +470,7 @@ class DataCategory():
POSTING_TEXT_CONSTANTS = "67" POSTING_TEXT_CONSTANTS = "67"
class FormatName(): class FormatName():
"""Field of the CSV Header, corresponds to DataCategory.""" """Field of the CSV Header, corresponds to DataCategory."""
DEBTORS_CREDITORS = "Debitoren/Kreditoren" DEBTORS_CREDITORS = "Debitoren/Kreditoren"

View File

@ -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'

View File

@ -9,31 +9,91 @@ Provide a report and downloadable CSV according to the German DATEV format.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime
import json import json
import zipfile
import six
import frappe import frappe
import pandas as pd
from frappe import _ from frappe import _
from csv import QUOTE_NONNUMERIC
from six import BytesIO
from six import string_types from six import string_types
from .datev_constants import DataCategory from erpnext.regional.germany.utils.datev.datev_csv import download_csv_files_as_zip, get_datev_csv
from .datev_constants import Transactions from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames
from .datev_constants import DebtorsCreditors
from .datev_constants import AccountNames COLUMNS = [
from .datev_constants import 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
}
]
def execute(filters=None): def execute(filters=None):
"""Entry point for frappe.""" """Entry point for frappe."""
validate(filters) validate(filters)
result = get_transactions(filters, as_dict=0) return COLUMNS, get_transactions(filters, as_dict=0)
columns = QUERY_REPORT_COLUMNS
return columns, result
def validate(filters): def validate(filters):
@ -240,146 +300,8 @@ def get_account_names(filters):
""", filters, as_dict=1) """, 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() @frappe.whitelist()
def download_datev_csv(filters=None): def download_datev_csv(filters):
""" """
Provide accounting entries for download in DATEV format. 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') 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 '') 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 = 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 = 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 = 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 = 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. download_csv_files_as_zip([
datev_zip.close() {
'file_name': 'EXTF_Buchungsstapel.csv',
frappe.response['filecontent'] = zip_buffer.getvalue() 'csv_data': get_datev_csv(transactions, filters, csv_class=Transactions)
frappe.response['filename'] = 'DATEV.zip' },
frappe.response['type'] = 'binary' {
'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)
},
])

View File

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

View File

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

View File

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

View File

@ -318,7 +318,7 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20) 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) so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4) create_dn_against_so(so.name, 4)
make_sales_invoice(so.name) make_sales_invoice(so.name)
@ -338,7 +338,7 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(so.get("items")[-1].amount, 1400) self.assertEqual(so.get("items")[-1].amount, 1400)
self.assertEqual(so.status, 'To Deliver and Bill') 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(**{ so = make_sales_order(**{
"item_list": [{ "item_list": [{
"item_code": '_Test Item', "item_code": '_Test Item',
@ -381,7 +381,7 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(so.status, 'To Deliver and Bill') 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) so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4) create_dn_against_so(so.name, 4)
make_sales_invoice(so.name) 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}]) 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) 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) so = make_sales_order(item_code= "_Test Item", qty=4)
user = 'test@example.com' user = 'test@example.com'
@ -417,9 +417,44 @@ class TestSalesOrder(unittest.TestCase):
# add new item # add new item
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) 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) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
test_user.remove_roles("Accounts User")
frappe.set_user("Administrator") frappe.set_user("Administrator")
def test_update_child_qty_rate_product_bundle(self): def test_update_child_qty_rate_with_workflow(self):
from frappe.model.workflow import apply_workflow
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 # test Update Items with product bundle
if not frappe.db.exists("Item", "_Product Bundle Item"): if not frappe.db.exists("Item", "_Product Bundle Item"):
bundle_item = make_item("_Product Bundle Item", {"is_stock_item": 0}) bundle_item = make_item("_Product Bundle Item", {"is_stock_item": 0})
@ -439,6 +474,20 @@ class TestSalesOrder(unittest.TestCase):
so.reload() so.reload()
self.assertEqual(so.packed_items[0].qty, 4) 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): 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 1 - _TC", "test@example.com")
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@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")) "reserved_qty"))
test_dependencies = ["Currency Exchange"] 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

View File

@ -70,13 +70,23 @@ erpnext.PointOfSale.Payment = class {
this.$invoice_fields.append( this.$invoice_fields.append(
`<div class="invoice_detail_field ${df.fieldname}-field" data-fieldname="${df.fieldname}"></div>` `<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({ this[`${df.fieldname}_field`] = frappe.ui.form.make_control({
df: { df: {
...df, ...df,
onchange: function() { ...df_events
frm.set_value(this.df.fieldname, this.value);
}
}, },
parent: this.$invoice_fields.find(`.${df.fieldname}-field`), parent: this.$invoice_fields.find(`.${df.fieldname}-field`),
render_input: true, render_input: true,

View File

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

View File

@ -8,7 +8,7 @@ frappe.query_reports["Sales Analytics"] = {
fieldname: "tree_type", fieldname: "tree_type",
label: __("Tree Type"), label: __("Tree Type"),
fieldtype: "Select", 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", default: "Customer",
reqd: 1 reqd: 1
}, },

View File

@ -34,7 +34,7 @@ class Analytics(object):
def get_columns(self): def get_columns(self):
self.columns = [{ self.columns = [{
"label": _(self.filters.tree_type + " ID"), "label": _(self.filters.tree_type),
"options": self.filters.tree_type if self.filters.tree_type != "Order Type" else "", "options": self.filters.tree_type if self.filters.tree_type != "Order Type" else "",
"fieldname": "entity", "fieldname": "entity",
"fieldtype": "Link" if self.filters.tree_type != "Order Type" else "Data", "fieldtype": "Link" if self.filters.tree_type != "Order Type" else "Data",
@ -97,6 +97,10 @@ class Analytics(object):
self.get_sales_transactions_based_on_order_type() self.get_sales_transactions_based_on_order_type()
self.get_rows_by_group() self.get_rows_by_group()
elif self.filters.tree_type == "Project":
self.get_sales_transactions_based_on_project()
self.get_rows()
def get_sales_transactions_based_on_order_type(self): def get_sales_transactions_based_on_order_type(self):
if self.filters["value_quantity"] == 'Value': if self.filters["value_quantity"] == 'Value':
value_field = "base_net_total" value_field = "base_net_total"
@ -198,6 +202,24 @@ class Analytics(object):
self.get_groups() self.get_groups()
def get_sales_transactions_based_on_project(self):
if self.filters["value_quantity"] == 'Value':
value_field = "base_net_total as value_field"
else:
value_field = "total_qty as value_field"
entity = "project as entity"
self.entries = frappe.get_all(self.filters.doc_type,
fields=[entity, value_field, self.date_field],
filters={
"docstatus": 1,
"company": self.filters.company,
"project": ["!=", ""],
self.date_field: ('between', [self.filters.from_date, self.filters.to_date])
}
)
def get_rows(self): def get_rows(self):
self.data = [] self.data = []
self.get_periodic_data() self.get_periodic_data()
@ -205,7 +227,7 @@ class Analytics(object):
for entity, period_data in iteritems(self.entity_periodic_data): for entity, period_data in iteritems(self.entity_periodic_data):
row = { row = {
"entity": entity, "entity": entity,
"entity_name": self.entity_names.get(entity) "entity_name": self.entity_names.get(entity) if hasattr(self, 'entity_names') else None
} }
total = 0 total = 0
for end_date in self.periodic_daterange: for end_date in self.periodic_daterange:

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