Merge branch 'version-13-hotfix' into backport/version-13-hotfix/27524

This commit is contained in:
Deepesh Garg
2021-09-27 14:55:32 +05:30
committed by GitHub
44 changed files with 905 additions and 733 deletions

View File

@@ -34,9 +34,9 @@ if __name__ == "__main__":
if response.ok: if response.ok:
payload = response.json() payload = response.json()
title = payload.get("title", "").lower().strip() title = (payload.get("title") or "").lower().strip()
head_sha = payload.get("head", {}).get("sha") head_sha = (payload.get("head") or {}).get("sha")
body = payload.get("body", "").lower() body = (payload.get("body") or "").lower()
if (title.startswith("feat") if (title.startswith("feat")
and head_sha and head_sha

View File

@@ -79,7 +79,6 @@ frappe.ui.form.on('Chart of Accounts Importer', {
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file
} else { } else {
generate_tree_preview(frm); generate_tree_preview(frm);
validate_csv_data(frm);
} }
}, },
@@ -104,23 +103,6 @@ frappe.ui.form.on('Chart of Accounts Importer', {
} }
}); });
var validate_csv_data = function(frm) {
frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_accounts",
args: {file_name: frm.doc.import_file},
callback: function(r) {
if(r.message && r.message[0]===true) {
frm.page["show_import_button"] = true;
frm.page["total_accounts"] = r.message[1];
frm.trigger("refresh");
} else {
frm.page.set_indicator(__('Resolve error and upload again.'), 'orange');
frappe.throw(__(r.message));
}
}
});
};
var create_import_button = function(frm) { var create_import_button = function(frm) {
frm.page.set_primary_action(__("Import"), function () { frm.page.set_primary_action(__("Import"), function () {
frappe.call({ frappe.call({
@@ -151,23 +133,25 @@ var create_reset_button = function(frm) {
}; };
var generate_tree_preview = function(frm) { var generate_tree_preview = function(frm) {
let parent = __('All Accounts'); if (frm.doc.import_file) {
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data let parent = __('All Accounts');
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
// generate tree structure based on the csv data // generate tree structure based on the csv data
new frappe.ui.Tree({ new frappe.ui.Tree({
parent: $(frm.fields_dict['chart_tree'].wrapper), parent: $(frm.fields_dict['chart_tree'].wrapper),
label: parent, label: parent,
expandable: true, expandable: true,
method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
args: { args: {
file_name: frm.doc.import_file, file_name: frm.doc.import_file,
parent: parent, parent: parent,
doctype: 'Chart of Accounts Importer', doctype: 'Chart of Accounts Importer',
file_type: frm.doc.file_type file_type: frm.doc.file_type
}, },
onclick: function(node) { onclick: function(node) {
parent = node.value; parent = node.value;
} }
}); });
}
}; };

View File

@@ -25,8 +25,16 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
class ChartofAccountsImporter(Document): class ChartofAccountsImporter(Document):
def validate(self): pass
validate_accounts(self.import_file)
def validate_columns(data):
if not data:
frappe.throw(_('No data found. Seems like you uploaded a blank file'))
no_of_columns = max([len(d) for d in data])
if no_of_columns > 7:
frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template'))
@frappe.whitelist() @frappe.whitelist()
def validate_company(company): def validate_company(company):
@@ -131,6 +139,8 @@ def get_coa(doctype, parent, is_root=False, file_name=None):
else: else:
data = generate_data_from_excel(file_doc, extension) data = generate_data_from_excel(file_doc, extension)
validate_columns(data)
validate_accounts(data)
forest = build_forest(data) forest = build_forest(data)
accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form
@@ -322,9 +332,6 @@ def validate_accounts(file_name):
def validate_root(accounts): def validate_root(accounts):
roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')] roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
if len(roots) < 4:
frappe.throw(_("Number of root accounts cannot be less than 4"))
error_messages = [] error_messages = []
for account in roots: for account in roots:
@@ -364,20 +371,12 @@ def get_mandatory_account_types():
def validate_account_types(accounts): def validate_account_types(accounts):
account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"] account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"]
account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1] account_types = [accounts[d]["account_type"] for d in accounts if not cint(accounts[d]['is_group']) == 1]
missing = list(set(account_types_for_ledger) - set(account_types)) missing = list(set(account_types_for_ledger) - set(account_types))
if missing: if missing:
frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))) frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)))
account_types_for_group = ["Bank", "Cash", "Stock"]
# fix logic bug
account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1]
missing = list(set(account_types_for_group) - set(account_groups))
if missing:
frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing)))
def unset_existing_data(company): def unset_existing_data(company):
linked = frappe.db.sql('''select fieldname from tabDocField linked = frappe.db.sql('''select fieldname from tabDocField
where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True) where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True)

View File

@@ -93,6 +93,7 @@
"options": "Payment Term" "options": "Payment Term"
}, },
{ {
"depends_on": "exchange_gain_loss",
"fieldname": "exchange_gain_loss", "fieldname": "exchange_gain_loss",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Exchange Gain/Loss", "label": "Exchange Gain/Loss",
@@ -103,7 +104,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-04-21 13:30:11.605388", "modified": "2021-09-26 17:06:55.597389",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry Reference", "name": "Payment Entry Reference",

View File

@@ -7,6 +7,7 @@
"field_order": [ "field_order": [
"reference_type", "reference_type",
"reference_name", "reference_name",
"reference_row",
"column_break_3", "column_break_3",
"invoice_type", "invoice_type",
"invoice_number", "invoice_number",
@@ -121,11 +122,17 @@
"label": "Amount", "label": "Amount",
"options": "Currency", "options": "Currency",
"read_only": 1 "read_only": 1
},
{
"fieldname": "reference_row",
"fieldtype": "Data",
"hidden": 1,
"label": "Reference Row"
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-08-30 10:58:42.665107", "modified": "2021-09-20 17:23:09.455803",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reconciliation Allocation", "name": "Payment Reconciliation Allocation",

View File

@@ -4,7 +4,7 @@
frappe.ui.form.on('POS Invoice Merge Log', { frappe.ui.form.on('POS Invoice Merge Log', {
setup: function(frm) { setup: function(frm) {
frm.set_query("pos_invoice", "pos_invoices", doc => { frm.set_query("pos_invoice", "pos_invoices", doc => {
return{ return {
filters: { filters: {
'docstatus': 1, 'docstatus': 1,
'customer': doc.customer, 'customer': doc.customer,
@@ -12,5 +12,10 @@ frappe.ui.form.on('POS Invoice Merge Log', {
} }
} }
}); });
},
merge_invoices_based_on: function(frm) {
frm.set_value('customer', '');
frm.set_value('customer_group', '');
} }
}); });

View File

@@ -6,9 +6,11 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"posting_date", "posting_date",
"customer", "merge_invoices_based_on",
"column_break_3", "column_break_3",
"pos_closing_entry", "pos_closing_entry",
"customer",
"customer_group",
"section_break_3", "section_break_3",
"pos_invoices", "pos_invoices",
"references_section", "references_section",
@@ -88,12 +90,27 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "POS Closing Entry", "label": "POS Closing Entry",
"options": "POS Closing Entry" "options": "POS Closing Entry"
},
{
"fieldname": "merge_invoices_based_on",
"fieldtype": "Select",
"label": "Merge Invoices Based On",
"options": "Customer\nCustomer Group",
"reqd": 1
},
{
"depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
"fieldname": "customer_group",
"fieldtype": "Link",
"label": "Customer Group",
"mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
"options": "Customer Group"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-12-01 11:53:57.267579", "modified": "2021-09-14 11:17:19.001142",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Invoice Merge Log", "name": "POS Invoice Merge Log",

View File

@@ -23,6 +23,9 @@ class POSInvoiceMergeLog(Document):
self.validate_pos_invoice_status() self.validate_pos_invoice_status()
def validate_customer(self): def validate_customer(self):
if self.merge_invoices_based_on == 'Customer Group':
return
for d in self.pos_invoices: for d in self.pos_invoices:
if d.customer != self.customer: if d.customer != self.customer:
frappe.throw(_("Row #{}: POS Invoice {} is not against customer {}").format(d.idx, d.pos_invoice, self.customer)) frappe.throw(_("Row #{}: POS Invoice {} is not against customer {}").format(d.idx, d.pos_invoice, self.customer))
@@ -124,7 +127,7 @@ class POSInvoiceMergeLog(Document):
found = False found = False
for i in items: for i in items:
if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and
i.uom == item.uom and i.net_rate == item.net_rate): i.uom == item.uom and i.net_rate == item.net_rate and i.warehouse == item.warehouse):
found = True found = True
i.qty = i.qty + item.qty i.qty = i.qty + item.qty
@@ -172,6 +175,11 @@ class POSInvoiceMergeLog(Document):
invoice.discount_amount = 0.0 invoice.discount_amount = 0.0
invoice.taxes_and_charges = None invoice.taxes_and_charges = None
invoice.ignore_pricing_rule = 1 invoice.ignore_pricing_rule = 1
invoice.customer = self.customer
if self.merge_invoices_based_on == 'Customer Group':
invoice.flags.ignore_pos_profile = True
invoice.pos_profile = ''
return invoice return invoice
@@ -228,7 +236,7 @@ def get_all_unconsolidated_invoices():
return pos_invoices return pos_invoices
def get_invoice_customer_map(pos_invoices): def get_invoice_customer_map(pos_invoices):
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] } # pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Customer 2' : [{}] }
pos_invoice_customer_map = {} pos_invoice_customer_map = {}
for invoice in pos_invoices: for invoice in pos_invoices:
customer = invoice.get('customer') customer = invoice.get('customer')

View File

@@ -15,6 +15,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
check_if_return_invoice_linked_with_payment_entry, check_if_return_invoice_linked_with_payment_entry,
is_overdue,
unlink_inter_company_doc, unlink_inter_company_doc,
update_linked_doc, update_linked_doc,
validate_inter_company_party, validate_inter_company_party,
@@ -1139,10 +1140,7 @@ class PurchaseInvoice(BuyingController):
self.status = 'Draft' self.status = 'Draft'
return return
precision = self.precision("outstanding_amount") outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
if not status: if not status:
if self.docstatus == 2: if self.docstatus == 2:
@@ -1150,9 +1148,11 @@ class PurchaseInvoice(BuyingController):
elif self.docstatus == 1: elif self.docstatus == 1:
if self.is_internal_transfer(): if self.is_internal_transfer():
self.status = 'Internal Transfer' self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate: elif is_overdue(self):
self.status = "Overdue" self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate: elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")):
self.status = "Partly Paid"
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
self.status = "Unpaid" self.status = "Unpaid"
#Check if outstanding amount is 0 due to debit note issued against invoice #Check if outstanding amount is 0 due to debit note issued against invoice
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):

View File

@@ -2,28 +2,58 @@
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
// render // render
frappe.listview_settings['Purchase Invoice'] = { frappe.listview_settings["Purchase Invoice"] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", add_fields: [
"currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"], "supplier",
get_indicator: function(doc) { "supplier_name",
if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') { "base_grand_total",
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"]; "outstanding_amount",
} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { "due_date",
if(cint(doc.on_hold) && !doc.release_date) { "company",
return [__("On Hold"), "darkgrey"]; "currency",
} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) { "is_return",
return [__("Temporarily on Hold"), "darkgrey"]; "release_date",
} else if (frappe.datetime.get_diff(doc.due_date) < 0) { "on_hold",
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; "represents_company",
} else { "is_internal_supplier",
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"]; ],
} get_indicator(doc) {
} else if (cint(doc.is_return)) { if (doc.status == "Debit Note Issued") {
return [__("Return"), "gray", "is_return,=,Yes"]; return [__(doc.status), "darkgrey", "status,=," + doc.status];
} else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"];
} }
}
if (
flt(doc.outstanding_amount) > 0 &&
doc.docstatus == 1 &&
cint(doc.on_hold)
) {
if (!doc.release_date) {
return [__("On Hold"), "darkgrey"];
} else if (
frappe.datetime.get_diff(
doc.release_date,
frappe.datetime.nowdate()
) > 0
) {
return [__("Temporarily on Hold"), "darkgrey"];
}
}
const status_colors = {
"Unpaid": "orange",
"Paid": "green",
"Return": "gray",
"Overdue": "red",
"Partly Paid": "yellow",
"Internal Transfer": "darkgrey",
};
if (status_colors[doc.status]) {
return [
__(doc.status),
status_colors[doc.status],
"status,=," + doc.status,
];
}
},
}; };

View File

@@ -97,6 +97,7 @@
"width": "100px" "width": "100px"
}, },
{ {
"depends_on": "exchange_gain_loss",
"fieldname": "exchange_gain_loss", "fieldname": "exchange_gain_loss",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Exchange Gain/Loss", "label": "Exchange Gain/Loss",
@@ -104,6 +105,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "exchange_gain_loss",
"fieldname": "ref_exchange_rate", "fieldname": "ref_exchange_rate",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Reference Exchange Rate", "label": "Reference Exchange Rate",
@@ -115,7 +117,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-04-20 16:26:53.820530", "modified": "2021-09-26 15:47:28.167371",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Advance", "name": "Purchase Invoice Advance",

View File

@@ -1652,7 +1652,7 @@
"label": "Status", "label": "Status",
"length": 30, "length": 30,
"no_copy": 1, "no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nUnpaid and Discounted\nPartly Paid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@@ -2032,11 +2032,12 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2021-09-08 15:24:25.486499", "modified": "2021-09-21 09:27:50.191854",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",
"name_case": "Title Case", "name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -501,7 +501,7 @@ class SalesInvoice(SellingController):
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details
if not self.pos_profile: if not self.pos_profile and not self.flags.ignore_pos_profile:
pos_profile = get_pos_profile(self.company) or {} pos_profile = get_pos_profile(self.company) or {}
if not pos_profile: if not pos_profile:
return return
@@ -1472,14 +1472,7 @@ class SalesInvoice(SellingController):
self.status = 'Draft' self.status = 'Draft'
return return
precision = self.precision("outstanding_amount") outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
discounting_status = None
if self.is_discounted:
discounting_status = get_discounting_status(self.name)
if not status: if not status:
if self.docstatus == 2: if self.docstatus == 2:
@@ -1487,15 +1480,13 @@ class SalesInvoice(SellingController):
elif self.docstatus == 1: elif self.docstatus == 1:
if self.is_internal_transfer(): if self.is_internal_transfer():
self.status = 'Internal Transfer' self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discounting_status=='Disbursed': elif is_overdue(self):
self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue" self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discounting_status=='Disbursed': elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")):
self.status = "Unpaid and Discounted" self.status = "Partly Paid"
elif outstanding_amount > 0 and due_date >= nowdate: elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
self.status = "Unpaid" self.status = "Unpaid"
#Check if outstanding amount is 0 due to credit note issued against invoice # Check if outstanding amount is 0 due to credit note issued against invoice
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Credit Note Issued" self.status = "Credit Note Issued"
elif self.is_return == 1: elif self.is_return == 1:
@@ -1504,12 +1495,42 @@ class SalesInvoice(SellingController):
self.status = "Paid" self.status = "Paid"
else: else:
self.status = "Submitted" self.status = "Submitted"
if (
self.status in ("Unpaid", "Partly Paid", "Overdue")
and self.is_discounted
and get_discounting_status(self.name) == "Disbursed"
):
self.status += " and Discounted"
else: else:
self.status = "Draft" self.status = "Draft"
if update: if update:
self.db_set('status', self.status, update_modified = update_modified) self.db_set('status', self.status, update_modified = update_modified)
def is_overdue(doc):
outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))
if outstanding_amount <= 0:
return
grand_total = flt(doc.grand_total, doc.precision("grand_total"))
nowdate = getdate()
if doc.payment_schedule:
# calculate payable amount till date
payable_amount = sum(
payment.payment_amount
for payment in doc.payment_schedule
if getdate(payment.due_date) < nowdate
)
if (grand_total - outstanding_amount) < payable_amount:
return True
elif getdate(doc.due_date) < nowdate:
return True
def get_discounting_status(sales_invoice): def get_discounting_status(sales_invoice):
status = None status = None

View File

@@ -6,18 +6,20 @@ frappe.listview_settings['Sales Invoice'] = {
add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company", add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return"], "currency", "is_return"],
get_indicator: function(doc) { get_indicator: function(doc) {
var status_color = { const status_colors = {
"Draft": "grey", "Draft": "grey",
"Unpaid": "orange", "Unpaid": "orange",
"Paid": "green", "Paid": "green",
"Return": "gray", "Return": "gray",
"Credit Note Issued": "gray", "Credit Note Issued": "gray",
"Unpaid and Discounted": "orange", "Unpaid and Discounted": "orange",
"Partly Paid and Discounted": "yellow",
"Overdue and Discounted": "red", "Overdue and Discounted": "red",
"Overdue": "red", "Overdue": "red",
"Partly Paid": "yellow",
"Internal Transfer": "darkgrey" "Internal Transfer": "darkgrey"
}; };
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; return [__(doc.status), status_colors[doc.status], "status,=,"+doc.status];
}, },
right_column: "grand_total" right_column: "grand_total"
}; };

View File

@@ -131,6 +131,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_payment_entry_unlink_against_invoice(self): def test_payment_entry_unlink_against_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si = frappe.copy_doc(test_records[0]) si = frappe.copy_doc(test_records[0])
si.is_pos = 0 si.is_pos = 0
si.insert() si.insert()
@@ -154,6 +155,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_payment_entry_unlink_against_standalone_credit_note(self): def test_payment_entry_unlink_against_standalone_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si1 = create_sales_invoice(rate=1000) si1 = create_sales_invoice(rate=1000)
si2 = create_sales_invoice(rate=300) si2 = create_sales_invoice(rate=300)
si3 = create_sales_invoice(qty=-1, rate=300, is_return=1) si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
@@ -1653,6 +1655,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_credit_note(self): def test_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si = create_sales_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1) si = create_sales_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
outstanding_amount = get_outstanding_amount(si.doctype, outstanding_amount = get_outstanding_amount(si.doctype,
@@ -2241,6 +2244,54 @@ class TestSalesInvoice(unittest.TestCase):
party_link.delete() party_link.delete()
frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0) frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0)
def test_payment_statuses(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
today = nowdate()
# Test Overdue
si = create_sales_invoice(do_not_submit=True)
si.payment_schedule = []
si.append("payment_schedule", {
"due_date": add_days(today, -5),
"invoice_portion": 50,
"payment_amount": si.grand_total / 2
})
si.append("payment_schedule", {
"due_date": add_days(today, 5),
"invoice_portion": 50,
"payment_amount": si.grand_total / 2
})
si.submit()
self.assertEqual(si.status, "Overdue")
# Test payment less than due amount
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_amount = 1
pe.references[0].allocated_amount = pe.paid_amount
pe.submit()
si.reload()
self.assertEqual(si.status, "Overdue")
# Test Partly Paid
pe = frappe.copy_doc(pe)
pe.paid_amount = si.grand_total / 2
pe.references[0].allocated_amount = pe.paid_amount
pe.submit()
si.reload()
self.assertEqual(si.status, "Partly Paid")
# Test Paid
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_amount = si.outstanding_amount
pe.submit()
si.reload()
self.assertEqual(si.status, "Paid")
def get_sales_invoice_for_e_invoice(): def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill() si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####' si.naming_series = 'INV-2020-.#####'

View File

@@ -98,6 +98,7 @@
"width": "120px" "width": "120px"
}, },
{ {
"depends_on": "exchange_gain_loss",
"fieldname": "exchange_gain_loss", "fieldname": "exchange_gain_loss",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Exchange Gain/Loss", "label": "Exchange Gain/Loss",
@@ -105,6 +106,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "exchange_gain_loss",
"fieldname": "ref_exchange_rate", "fieldname": "ref_exchange_rate",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Reference Exchange Rate", "label": "Reference Exchange Rate",
@@ -116,7 +118,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-06-04 20:25:49.832052", "modified": "2021-09-26 15:47:46.911595",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Advance", "name": "Sales Invoice Advance",

View File

@@ -690,13 +690,17 @@ class AccountsController(TransactionBase):
.format(d.reference_name, d.against_order)) .format(d.reference_name, d.against_order))
def set_advance_gain_or_loss(self): def set_advance_gain_or_loss(self):
if not self.get("advances"): if self.get('conversion_rate') == 1 or not self.get("advances"):
return
is_purchase_invoice = self.doctype == 'Purchase Invoice'
party_account = self.credit_to if is_purchase_invoice else self.debit_to
if get_account_currency(party_account) != self.currency:
return return
for d in self.get("advances"): for d in self.get("advances"):
advance_exchange_rate = d.ref_exchange_rate advance_exchange_rate = d.ref_exchange_rate
if (d.allocated_amount and self.conversion_rate != 1 if (d.allocated_amount and self.conversion_rate != advance_exchange_rate):
and self.conversion_rate != advance_exchange_rate):
base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
@@ -715,7 +719,7 @@ class AccountsController(TransactionBase):
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account') gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
if not gain_loss_account: if not gain_loss_account:
frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}") frappe.throw(_("Please set default Exchange Gain/Loss Account in Company {}")
.format(self.get('company'))) .format(self.get('company')))
account_currency = get_account_currency(gain_loss_account) account_currency = get_account_currency(gain_loss_account)
if account_currency != self.company_currency: if account_currency != self.company_currency:
@@ -734,7 +738,7 @@ class AccountsController(TransactionBase):
"against": party, "against": party,
dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss), dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
dr_or_cr: abs(d.exchange_gain_loss), dr_or_cr: abs(d.exchange_gain_loss),
"cost_center": self.cost_center, "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
"project": self.project "project": self.project
}, item=d) }, item=d)
) )
@@ -985,42 +989,55 @@ class AccountsController(TransactionBase):
item_allowance = {} item_allowance = {}
global_qty_allowance, global_amount_allowance = None, None global_qty_allowance, global_amount_allowance = None, None
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
user_roles = frappe.get_roles()
total_overbilled_amt = 0.0
for item in self.get("items"): for item in self.get("items"):
if item.get(item_ref_dn): if not item.get(item_ref_dn):
ref_amt = flt(frappe.db.get_value(ref_dt + " Item", continue
item.get(item_ref_dn), based_on), self.precision(based_on, item))
if not ref_amt:
frappe.msgprint(
_("Warning: System will not check overbilling since amount for Item {0} in {1} is zero")
.format(item.item_code, ref_dt))
else:
already_billed = frappe.db.sql("""
select sum(%s)
from `tab%s`
where %s=%s and docstatus=1 and parent != %s
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
(item.get(item_ref_dn), self.name))[0][0]
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
self.precision(based_on, item)) item.get(item_ref_dn), based_on), self.precision(based_on, item))
if not ref_amt:
frappe.msgprint(
_("System will not check overbilling since amount for Item {0} in {1} is zero")
.format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
continue
allowance, item_allowance, global_qty_allowance, global_amount_allowance = \ already_billed = frappe.db.sql("""
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount") select sum(%s)
from `tab%s`
where %s=%s and docstatus=1 and parent != %s
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
(item.get(item_ref_dn), self.name))[0][0]
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100) total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
self.precision(based_on, item))
if total_billed_amt < 0 and max_allowed_amt < 0: allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
# while making debit note against purchase return entry(purchase receipt) getting overbill error get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(max_allowed_amt)
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles(): if total_billed_amt < 0 and max_allowed_amt < 0:
if self.doctype != "Purchase Invoice": # while making debit note against purchase return entry(purchase receipt) getting overbill error
self.throw_overbill_exception(item, max_allowed_amt) total_billed_amt = abs(total_billed_amt)
elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): max_allowed_amt = abs(max_allowed_amt)
self.throw_overbill_exception(item, max_allowed_amt)
overbill_amt = total_billed_amt - max_allowed_amt
total_overbilled_amt += overbill_amt
if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles:
if self.doctype != "Purchase Invoice":
self.throw_overbill_exception(item, max_allowed_amt)
elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
self.throw_overbill_exception(item, max_allowed_amt)
if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
.format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange")
def throw_overbill_exception(self, item, max_allowed_amt): def throw_overbill_exception(self, item, max_allowed_amt):
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
@@ -1673,14 +1690,18 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
return list(payment_entries_against_order) + list(unallocated_payment_entries) return list(payment_entries_against_order) + list(unallocated_payment_entries)
def update_invoice_status(): def update_invoice_status():
# Daily update the status of the invoices """Updates status as Overdue for applicable invoices. Runs daily."""
frappe.db.sql(""" update `tabSales Invoice` set status = 'Overdue'
where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue'
where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
for doctype in ("Sales Invoice", "Purchase Invoice"):
frappe.db.sql("""
update `tab{}` as dt set dt.status = 'Overdue'
where dt.docstatus = 1
and dt.status != 'Overdue'
and dt.outstanding_amount > 0
and (dt.grand_total - dt.outstanding_amount) <
(select sum(payment_amount) from `tabPayment Schedule` as ps
where ps.parent = dt.name and ps.due_date < %s)
""".format(doctype), getdate())
@frappe.whitelist() @frappe.whitelist()
def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):

View File

@@ -7,6 +7,7 @@ import json
import frappe import frappe
from frappe import _ from frappe import _
from frappe.modules.utils import get_module_app
from frappe.utils import flt, has_common from frappe.utils import flt, has_common
from frappe.utils.user import is_website_user from frappe.utils.user import is_website_user
@@ -21,8 +22,32 @@ def get_list_context(context=None):
"get_list": get_transaction_list "get_list": get_transaction_list
} }
def get_webform_list_context(module):
if get_module_app(module) != 'erpnext':
return
return {
"get_list": get_webform_transaction_list
}
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"): def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
""" Get List of transactions for custom doctypes """
from frappe.www.list import get_list
if not filters:
filters = []
meta = frappe.get_meta(doctype)
for d in meta.fields:
if d.fieldtype == 'Link' and d.fieldname != 'amended_from':
allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)]
allowed_docs.append('')
filters.append((d.fieldname, 'in', allowed_docs))
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False,
fields=None, order_by="modified")
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False):
user = frappe.session.user user = frappe.session.user
ignore_permissions = False ignore_permissions = False
@@ -46,7 +71,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
filters.append(('customer', 'in', customers)) filters.append(('customer', 'in', customers))
elif suppliers: elif suppliers:
filters.append(('supplier', 'in', suppliers)) filters.append(('supplier', 'in', suppliers))
else: elif not custom:
return [] return []
if doctype == 'Request for Quotation': if doctype == 'Request for Quotation':
@@ -56,9 +81,16 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
# Since customers and supplier do not have direct access to internal doctypes # Since customers and supplier do not have direct access to internal doctypes
ignore_permissions = True ignore_permissions = True
if not customers and not suppliers and custom:
ignore_permissions = False
filters = []
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length, transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc') fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
if custom:
return transactions
return post_process(doctype, transactions) return post_process(doctype, transactions)
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20, def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,

View File

@@ -20,14 +20,16 @@ def get_indexable_web_fields():
return [df.fieldname for df in valid_fields] return [df.fieldname for df in valid_fields]
def is_search_module_loaded(): def is_search_module_loaded():
cache = frappe.cache() try:
out = cache.execute_command('MODULE LIST') cache = frappe.cache()
out = cache.execute_command('MODULE LIST')
parsed_output = " ".join( parsed_output = " ".join(
(" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out) (" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out)
) )
return "search" in parsed_output
return "search" in parsed_output except Exception:
return False
def if_redisearch_loaded(function): def if_redisearch_loaded(function):
"Decorator to check if Redisearch is loaded." "Decorator to check if Redisearch is loaded."

View File

@@ -85,10 +85,8 @@ def add_bank_accounts(response, bank, company):
if not acc_subtype: if not acc_subtype:
add_account_subtype(account["subtype"]) add_account_subtype(account["subtype"])
existing_bank_account = frappe.db.exists("Bank Account", { bank_account_name = "{} - {}".format(account["name"], bank["bank_name"])
'account_name': account["name"], existing_bank_account = frappe.db.exists("Bank Account", bank_account_name)
'bank': bank["bank_name"]
})
if not existing_bank_account: if not existing_bank_account:
try: try:
@@ -197,6 +195,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
plaid = PlaidConnector(access_token) plaid = PlaidConnector(access_token)
transactions = []
try: try:
transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
except ItemError as e: except ItemError as e:
@@ -205,7 +204,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " " msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
frappe.log_error(msg, title=_("Plaid Link Refresh Required")) frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
return transactions or [] return transactions
def new_bank_transaction(transaction): def new_bank_transaction(transaction):

View File

@@ -40,7 +40,7 @@ class Patient(Document):
frappe.db.set_value('Patient', self.name, 'status', 'Disabled') frappe.db.set_value('Patient', self.name, 'status', 'Disabled')
else: else:
send_registration_sms(self) send_registration_sms(self)
self.reload() # self.notify_update() self.reload()
def on_update(self): def on_update(self):
if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'): if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'):
@@ -93,23 +93,27 @@ class Patient(Document):
self.language = frappe.db.get_single_value('System Settings', 'language') self.language = frappe.db.get_single_value('System Settings', 'language')
def create_website_user(self): def create_website_user(self):
if self.email and not frappe.db.exists('User', self.email): users = frappe.db.get_all('User', fields=['email', 'mobile_no'], or_filters={'email': self.email, 'mobile_no': self.mobile})
user = frappe.get_doc({ if users and users[0]:
'doctype': 'User', frappe.throw(_("User exists with Email {}, Mobile {}<br>Please check email / mobile or disable 'Invite as User' to skip creating User")
'first_name': self.first_name, .format(frappe.bold(users[0].email), frappe.bold(users[0].mobile_no)), frappe.DuplicateEntryError)
'last_name': self.last_name,
'email': self.email, user = frappe.get_doc({
'user_type': 'Website User', 'doctype': 'User',
'gender': self.sex, 'first_name': self.first_name,
'phone': self.phone, 'last_name': self.last_name,
'mobile_no': self.mobile, 'email': self.email,
'birth_date': self.dob 'user_type': 'Website User',
}) 'gender': self.sex,
user.flags.ignore_permissions = True 'phone': self.phone,
user.enabled = True 'mobile_no': self.mobile,
user.send_welcome_email = True 'birth_date': self.dob
user.add_roles('Patient') })
frappe.db.set_value(self.doctype, self.name, 'user_id', user.name) user.flags.ignore_permissions = True
user.enabled = True
user.send_welcome_email = True
user.add_roles('Patient')
self.db_set('user_id', user.name)
def autoname(self): def autoname(self):
patient_name_by = frappe.db.get_single_value('Healthcare Settings', 'patient_name_by') patient_name_by = frappe.db.get_single_value('Healthcare Settings', 'patient_name_by')
@@ -159,54 +163,65 @@ class Patient(Document):
return {'invoice': sales_invoice.name} return {'invoice': sales_invoice.name}
def set_contact(self): def set_contact(self):
if frappe.db.exists('Dynamic Link', {'parenttype':'Contact', 'link_doctype':'Patient', 'link_name':self.name}): contact = get_default_contact(self.doctype, self.name)
old_doc = self.get_doc_before_save()
if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone:
self.update_contact()
else:
self.reload()
if self.email or self.mobile or self.phone:
contact = frappe.get_doc({
'doctype': 'Contact',
'first_name': self.first_name,
'middle_name': self.middle_name,
'last_name': self.last_name,
'gender': self.sex,
'is_primary_contact': 1
})
contact.append('links', dict(link_doctype='Patient', link_name=self.name))
if self.customer:
contact.append('links', dict(link_doctype='Customer', link_name=self.customer))
contact.insert(ignore_permissions=True)
self.update_contact(contact) # update email, mobile and phone
def update_contact(self, contact=None):
if not contact:
contact_name = get_default_contact(self.doctype, self.name)
if contact_name:
contact = frappe.get_doc('Contact', contact_name)
if contact: if contact:
if self.email and self.email != contact.email_id: old_doc = self.get_doc_before_save()
for email in contact.email_ids: if not old_doc:
email.is_primary = True if email.email_id == self.email else False return
contact.add_email(self.email, is_primary=True)
contact.set_primary_email()
if self.mobile and self.mobile != contact.mobile_no: if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone:
for mobile in contact.phone_nos: self.update_contact(contact)
mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False else:
contact.add_phone(self.mobile, is_primary_mobile_no=True) if self.customer:
contact.set_primary('mobile_no') # customer contact exists, link patient
contact = get_default_contact('Customer', self.customer)
if self.phone and self.phone != contact.phone: if contact:
for phone in contact.phone_nos: self.update_contact(contact)
phone.is_primary_phone = True if phone.phone == self.phone else False else:
contact.add_phone(self.phone, is_primary_phone=True) self.reload()
contact.set_primary('phone') if self.email or self.mobile or self.phone:
contact = frappe.get_doc({
'doctype': 'Contact',
'first_name': self.first_name,
'middle_name': self.middle_name,
'last_name': self.last_name,
'gender': self.sex,
'is_primary_contact': 1
})
contact.append('links', dict(link_doctype='Patient', link_name=self.name))
if self.customer:
contact.append('links', dict(link_doctype='Customer', link_name=self.customer))
contact.flags.ignore_validate = True # disable hook TODO: safe? contact.insert(ignore_permissions=True)
self.update_contact(contact.name)
def update_contact(self, contact):
contact = frappe.get_doc('Contact', contact)
if not contact.has_link(self.doctype, self.name):
contact.append('links', dict(link_doctype=self.doctype, link_name=self.name))
if self.email and self.email != contact.email_id:
for email in contact.email_ids:
email.is_primary = True if email.email_id == self.email else False
contact.add_email(self.email, is_primary=True)
contact.set_primary_email()
if self.mobile and self.mobile != contact.mobile_no:
for mobile in contact.phone_nos:
mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False
contact.add_phone(self.mobile, is_primary_mobile_no=True)
contact.set_primary('mobile_no')
if self.phone and self.phone != contact.phone:
for phone in contact.phone_nos:
phone.is_primary_phone = True if phone.phone == self.phone else False
contact.add_phone(self.phone, is_primary_phone=True)
contact.set_primary('phone')
contact.flags.skip_patient_update = True
contact.save(ignore_permissions=True) contact.save(ignore_permissions=True)

View File

@@ -35,3 +35,40 @@ class TestPatient(unittest.TestCase):
settings.collect_registration_fee = 0 settings.collect_registration_fee = 0
settings.save() settings.save()
def test_patient_contact(self):
frappe.db.sql("""delete from `tabPatient` where name like '_Test Patient%'""")
frappe.db.sql("""delete from `tabCustomer` where name like '_Test Patient%'""")
frappe.db.sql("""delete from `tabContact` where name like'_Test Patient%'""")
frappe.db.sql("""delete from `tabDynamic Link` where parent like '_Test Patient%'""")
patient = create_patient(patient_name='_Test Patient Contact', email='test-patient@example.com', mobile='+91 0000000001')
customer = frappe.db.get_value('Patient', patient, 'customer')
self.assertTrue(customer)
self.assertTrue(frappe.db.exists('Dynamic Link', {'parenttype': 'Contact', 'link_doctype': 'Patient', 'link_name': patient}))
self.assertTrue(frappe.db.exists('Dynamic Link', {'parenttype': 'Contact', 'link_doctype': 'Customer', 'link_name': customer}))
# a second patient linking with same customer
new_patient = create_patient(email='test-patient@example.com', mobile='+91 0000000009', customer=customer)
self.assertTrue(frappe.db.exists('Dynamic Link', {'parenttype': 'Contact', 'link_doctype': 'Patient', 'link_name': new_patient}))
self.assertTrue(frappe.db.exists('Dynamic Link', {'parenttype': 'Contact', 'link_doctype': 'Customer', 'link_name': customer}))
def test_patient_user(self):
frappe.db.sql("""delete from `tabUser` where email='test-patient-user@example.com'""")
frappe.db.sql("""delete from `tabDynamic Link` where parent like '_Test Patient%'""")
frappe.db.sql("""delete from `tabPatient` where name like '_Test Patient%'""")
patient = create_patient(patient_name='_Test Patient User', email='test-patient-user@example.com', mobile='+91 0000000009', create_user=True)
user = frappe.db.get_value('Patient', patient, 'user_id')
self.assertTrue(frappe.db.exists('User', user))
new_patient = frappe.get_doc({
'doctype': 'Patient',
'first_name': '_Test Patient Duplicate User',
'sex': 'Male',
'email': 'test-patient-user@example.com',
'mobile': '+91 0000000009',
'invite_user': 1
})
self.assertRaises(frappe.exceptions.DuplicateEntryError, new_patient.insert)

View File

@@ -307,14 +307,18 @@ def create_healthcare_docs(id=0):
return patient, practitioner return patient, practitioner
def create_patient(id=0): def create_patient(id=0, patient_name=None, email=None, mobile=None, customer=None, create_user=False):
if frappe.db.exists('Patient', {'firstname':f'_Test Patient {str(id)}'}): if frappe.db.exists('Patient', {'firstname':f'_Test Patient {str(id)}'}):
patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name']) patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name'])
return patient return patient
patient = frappe.new_doc('Patient') patient = frappe.new_doc('Patient')
patient.first_name = f'_Test Patient {str(id)}' patient.first_name = patient_name if patient_name else f'_Test Patient {str(id)}'
patient.sex = 'Female' patient.sex = 'Female'
patient.mobile = mobile
patient.email = email
patient.customer = customer
patient.invite_user = create_user
patient.save(ignore_permissions=True) patient.save(ignore_permissions=True)
return patient.name return patient.name

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from frappe.utils import nowdate from frappe.utils import add_days, nowdate
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import ( from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import (
@@ -38,7 +38,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': vital_signs.name}) medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': vital_signs.name})
self.assertTrue(medical_rec) self.assertTrue(medical_rec)
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1, procedure_template=1) appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1), invoice=1, procedure_template=1)
procedure = create_procedure(appointment) procedure = create_procedure(appointment)
procedure.start_procedure() procedure.start_procedure()
procedure.complete_procedure() procedure.complete_procedure()

View File

@@ -776,7 +776,7 @@ def update_patient_email_and_phone_numbers(contact, method):
Hook validate Contact Hook validate Contact
Update linked Patients' primary mobile and phone numbers Update linked Patients' primary mobile and phone numbers
''' '''
if 'Healthcare' not in frappe.get_active_domains(): if 'Healthcare' not in frappe.get_active_domains() or contact.flags.skip_patient_update:
return return
if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone): if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone):
@@ -784,9 +784,15 @@ def update_patient_email_and_phone_numbers(contact, method):
for link in patient_links: for link in patient_links:
contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1) contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1)
new_contact_details = {}
if contact.email_id and contact.email_id != contact_details.get('email'): if contact.email_id and contact.email_id != contact_details.get('email'):
frappe.db.set_value('Patient', link.get('link_name'), 'email', contact.email_id) new_contact_details.update({'email': contact.email_id})
if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'): if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'):
frappe.db.set_value('Patient', link.get('link_name'), 'mobile', contact.mobile_no) new_contact_details.update({'mobile': contact.mobile_no})
if contact.phone and contact.phone != contact_details.get('phone'): if contact.phone and contact.phone != contact_details.get('phone'):
frappe.db.set_value('Patient', link.get('link_name'), 'phone', contact.phone) new_contact_details.update({'phone': contact.phone})
if new_contact_details:
frappe.db.set_value('Patient', link.get('link_name'), new_contact_details)

View File

@@ -61,6 +61,7 @@ treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Grou
# website # website
update_website_context = ["erpnext.e_commerce.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"] update_website_context = ["erpnext.e_commerce.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"]
my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context" my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context"
webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context"
calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"] calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"]
@@ -436,7 +437,7 @@ accounting_dimension_doctypes = ["GL Entry", "Sales Invoice", "Purchase Invoice"
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan" "Subscription Plan", "POS Invoice", "POS Invoice Item"
] ]
regional_overrides = { regional_overrides = {

View File

@@ -184,7 +184,7 @@ def get_employees_having_an_event_today(event_type):
# -------------------------- # --------------------------
def send_work_anniversary_reminders(): def send_work_anniversary_reminders():
"""Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked""" """Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked"""
to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders") or 1) to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders"))
if not to_send: if not to_send:
return return

View File

@@ -21,7 +21,7 @@ frappe.ui.form.on('Training Result', {
frm.set_value("employees" ,""); frm.set_value("employees" ,"");
if (r.message) { if (r.message) {
$.each(r.message, function(i, d) { $.each(r.message, function(i, d) {
var row = frappe.model.add_child(cur_frm.doc, "Training Result Employee", "employees"); var row = frappe.model.add_child(frm.doc, "Training Result Employee", "employees");
row.employee = d.employee; row.employee = d.employee;
row.employee_name = d.employee_name; row.employee_name = d.employee_name;
}); });

View File

@@ -677,7 +677,7 @@ def get_job_details(start, end, filters=None):
conditions = get_filters_cond("Job Card", filters, []) conditions = get_filters_cond("Job Card", filters, [])
job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order, job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order,
`tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''), `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''),
min(`tabJob Card Time Log`.from_time) as from_time, min(`tabJob Card Time Log`.from_time) as from_time,
max(`tabJob Card Time Log`.to_time) as to_time max(`tabJob Card Time Log`.to_time) as to_time
FROM `tabJob Card` , `tabJob Card Time Log` FROM `tabJob Card` , `tabJob Card Time Log`
@@ -687,7 +687,7 @@ def get_job_details(start, end, filters=None):
for d in job_cards: for d in job_cards:
subject_data = [] subject_data = []
for field in ["name", "work_order", "remarks", "employee_name"]: for field in ["name", "work_order", "remarks"]:
if not d.get(field): continue if not d.get(field): continue
subject_data.append(d.get(field)) subject_data.append(d.get(field))

View File

@@ -457,7 +457,8 @@ class ProductionPlan(Document):
def prepare_args_for_sub_assembly_items(self, row, args): def prepare_args_for_sub_assembly_items(self, row, args):
for field in ["production_item", "item_name", "qty", "fg_warehouse", for field in ["production_item", "item_name", "qty", "fg_warehouse",
"description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]: "description", "bom_no", "stock_uom", "bom_level",
"production_plan_item", "schedule_date"]:
args[field] = row.get(field) args[field] = row.get(field)
args.update({ args.update({

View File

@@ -315,3 +315,5 @@ erpnext.patches.v13_0.make_homepage_products_website_items
erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v13_0.update_dates_in_tax_withholding_category
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.gst_fields_for_pos_invoice
erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes
erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries

View File

@@ -0,0 +1,42 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
frappe.reload_doc('accounts', 'doctype', 'accounting_dimension')
accounting_dimensions = frappe.db.sql("""select fieldname, label, document_type, disabled from
`tabAccounting Dimension`""", as_dict=1)
if not accounting_dimensions:
return
count = 1
for d in accounting_dimensions:
if count % 2 == 0:
insert_after_field = 'dimension_col_break'
else:
insert_after_field = 'accounting_dimensions_section'
for doctype in ["POS Invoice", "POS Invoice Item"]:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
meta = frappe.get_meta(doctype, cached=False)
fieldnames = [d.fieldname for d in meta.get("fields")]
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": insert_after_field
}
if df['fieldname'] not in fieldnames:
create_custom_field(doctype, df)
frappe.clear_cache(doctype=doctype)
count += 1

View File

@@ -0,0 +1,49 @@
from __future__ import unicode_literals
import json
import frappe
def execute():
frappe.reload_doc('accounts', 'doctype', 'purchase_invoice_advance')
frappe.reload_doc('accounts', 'doctype', 'sales_invoice_advance')
purchase_invoices = frappe.db.sql("""
select
parenttype as type, parent as name
from
`tabPurchase Invoice Advance`
where
ref_exchange_rate = 1
and docstatus = 1
and ifnull(exchange_gain_loss, '') != ''
group by
parent
""", as_dict=1)
sales_invoices = frappe.db.sql("""
select
parenttype as type, parent as name
from
`tabSales Invoice Advance`
where
ref_exchange_rate = 1
and docstatus = 1
and ifnull(exchange_gain_loss, '') != ''
group by
parent
""", as_dict=1)
if purchase_invoices + sales_invoices:
frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log")
for invoice in purchase_invoices + sales_invoices:
doc = frappe.get_doc(invoice.type, invoice.name)
doc.docstatus = 2
doc.make_gl_entries()
for advance in doc.advances:
if advance.ref_exchange_rate == 1:
advance.db_set('exchange_gain_loss', 0, False)
doc.docstatus = 1
doc.make_gl_entries()

View File

@@ -864,7 +864,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
if (r.message) { if (r.message) {
me.frm.set_value("billing_address", r.message); me.frm.set_value("billing_address", r.message);
} else { } else {
me.frm.set_value("company_address", ""); if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) {
me.frm.set_value("company_address", "");
}
} }
} }
}); });

View File

@@ -714,12 +714,15 @@ erpnext.utils.map_current_doc = function(opts) {
child_columns: opts.child_columns, child_columns: opts.child_columns,
action: function(selections, args) { action: function(selections, args) {
let values = selections; let values = selections;
if(values.length === 0){ if (values.length === 0) {
frappe.msgprint(__("Please select {0}", [opts.source_doctype])) frappe.msgprint(__("Please select {0}", [opts.source_doctype]))
return; return;
} }
opts.source_name = values; opts.source_name = values;
opts.args = args; if (opts.allow_child_item_selection) {
// args contains filtered child docnames
opts.args = args;
}
d.dialog.hide(); d.dialog.hide();
_map(); _map();
}, },

View File

@@ -797,7 +797,7 @@ def set_salary_components(docs):
def set_tax_withholding_category(company): def set_tax_withholding_category(company):
accounts = [] accounts = []
fiscal_year = None fiscal_year_details = None
abbr = frappe.get_value("Company", company, "abbr") abbr = frappe.get_value("Company", company, "abbr")
tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name') tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name')

View File

@@ -112,10 +112,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'):
frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label))
def get_itemised_tax_breakup_header(item_doctype, tax_accounts): def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): return [_("Item"), _("Taxable Amount")] + tax_accounts
return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts
else:
return [_("Item"), _("Taxable Amount")] + tax_accounts
def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False):
itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise)

View File

@@ -25,7 +25,7 @@
"editable_price_list_rate", "editable_price_list_rate",
"validate_selling_price", "validate_selling_price",
"editable_bundle_item_rates", "editable_bundle_item_rates",
"transaction_settings_section", "sales_transactions_settings_section",
"so_required", "so_required",
"dn_required", "dn_required",
"sales_update_frequency", "sales_update_frequency",
@@ -143,15 +143,14 @@
{ {
"default": "Stop", "default": "Stop",
"depends_on": "maintain_same_sales_rate", "depends_on": "maintain_same_sales_rate",
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
"fieldname": "maintain_same_rate_action", "fieldname": "maintain_same_rate_action",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Action If Same Rate is Not Maintained", "label": "Action if Same Rate is Not Maintained Throughout Sales Cycle",
"mandatory_depends_on": "maintain_same_sales_rate", "mandatory_depends_on": "maintain_same_sales_rate",
"options": "Stop\nWarn" "options": "Stop\nWarn"
}, },
{ {
"depends_on": "eval: doc.maintain_same_rate_action == 'Stop'", "depends_on": "eval: doc.maintain_same_sales_rate && doc.maintain_same_rate_action == 'Stop'",
"fieldname": "role_to_override_stop_action", "fieldname": "role_to_override_stop_action",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Role Allowed to Override Stop Action", "label": "Role Allowed to Override Stop Action",
@@ -191,13 +190,15 @@
"label": "Item Price Settings" "label": "Item Price Settings"
}, },
{ {
"fieldname": "transaction_settings_section", "fieldname": "sales_transactions_settings_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Transaction Settings" "label": "Transaction Settings"
}, },
{ {
"fieldname": "column_break_5", "default": "0",
"fieldtype": "Column Break" "fieldname": "editable_bundle_item_rates",
"fieldtype": "Check",
"label": "Calculate Product Bundle Price based on Child Items' Rates"
} }
], ],
"icon": "fa fa-cog", "icon": "fa fa-cog",
@@ -205,7 +206,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-09-08 19:38:10.175989", "modified": "2021-09-14 22:05:06.139820",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Selling Settings", "name": "Selling Settings",

View File

@@ -2116,9 +2116,9 @@
}, },
"Saudi Arabia": { "Saudi Arabia": {
"KSA VAT 5%": { "KSA VAT 15%": {
"account_name": "VAT 5%", "account_name": "VAT 15%",
"tax_rate": 5.00 "tax_rate": 15.00
}, },
"KSA VAT Zero": { "KSA VAT Zero": {
"account_name": "VAT Zero", "account_name": "VAT Zero",

View File

@@ -272,8 +272,9 @@ def update_status(name, status):
material_request.update_status(status) material_request.update_status(status)
@frappe.whitelist() @frappe.whitelist()
def make_purchase_order(source_name, target_doc=None, args={}): def make_purchase_order(source_name, target_doc=None, args=None):
if args is None:
args = {}
if isinstance(args, string_types): if isinstance(args, string_types):
args = json.loads(args) args = json.loads(args)

View File

@@ -44,8 +44,10 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip
# check if exists # check if exists
exists = 0 exists = 0
for d in doc.get("packed_items"): for d in doc.get("packed_items"):
if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code and\ if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code:
d.parent_detail_docname == main_item_row.name: if d.parent_detail_docname != main_item_row.name:
d.parent_detail_docname = main_item_row.name
pi, exists = d, 1 pi, exists = d, 1
break break

View File

@@ -11,6 +11,6 @@
{% endfor %} {% endfor %}
</ol> </ol>
{% else %} {% else %}
<p>You don't have no upcoming holidays this {{ frequency }}.</p> <p>You have no upcoming holidays this {{ frequency }}.</p>
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@@ -0,0 +1,138 @@
import unittest
import frappe
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
class TestWebsite(unittest.TestCase):
def test_permission_for_custom_doctype(self):
create_user('Supplier 1', 'supplier1@gmail.com')
create_user('Supplier 2', 'supplier2@gmail.com')
create_supplier_with_contact('Supplier1', 'All Supplier Groups', 'Supplier 1', 'supplier1@gmail.com')
create_supplier_with_contact('Supplier2', 'All Supplier Groups', 'Supplier 2', 'supplier2@gmail.com')
po1 = create_purchase_order(supplier='Supplier1')
po2 = create_purchase_order(supplier='Supplier2')
create_custom_doctype()
create_webform()
create_order_assignment(supplier='Supplier1', po = po1.name)
create_order_assignment(supplier='Supplier2', po = po2.name)
frappe.set_user("Administrator")
# checking if data consist of all order assignment of Supplier1 and Supplier2
self.assertTrue('Supplier1' and 'Supplier2' in [data.supplier for data in get_data()])
frappe.set_user("supplier1@gmail.com")
# checking if data only consist of order assignment of Supplier1
self.assertTrue('Supplier1' in [data.supplier for data in get_data()])
self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier1'])
frappe.set_user("supplier2@gmail.com")
# checking if data only consist of order assignment of Supplier2
self.assertTrue('Supplier2' in [data.supplier for data in get_data()])
self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier2'])
frappe.set_user("Administrator")
def get_data():
webform_list_contexts = frappe.get_hooks('webform_list_context')
if webform_list_contexts:
context = frappe._dict(frappe.get_attr(webform_list_contexts[0])('Buying') or {})
kwargs = dict(doctype='Order Assignment', order_by = 'modified desc')
return context.get_list(**kwargs)
def create_user(name, email):
frappe.get_doc({
'doctype': 'User',
'send_welcome_email': 0,
'user_type': 'Website User',
'first_name': name,
'email': email,
'roles': [{"doctype": "Has Role", "role": "Supplier"}]
}).insert(ignore_if_duplicate = True)
def create_supplier_with_contact(name, group, contact_name, contact_email):
supplier = frappe.get_doc({
'doctype': 'Supplier',
'supplier_name': name,
'supplier_group': group
}).insert(ignore_if_duplicate = True)
if not frappe.db.exists('Contact', contact_name+'-1-'+name):
new_contact = frappe.new_doc("Contact")
new_contact.first_name = contact_name
new_contact.is_primary_contact = True,
new_contact.append('links', {
"link_doctype": "Supplier",
"link_name": supplier.name
})
new_contact.append('email_ids', {
"email_id": contact_email,
"is_primary": 1
})
new_contact.insert(ignore_mandatory=True)
def create_custom_doctype():
frappe.get_doc({
'doctype': 'DocType',
'name': 'Order Assignment',
'module': 'Buying',
'custom': 1,
'autoname': 'field:po',
'fields': [
{'label': 'PO', 'fieldname': 'po', 'fieldtype': 'Link', 'options': 'Purchase Order'},
{'label': 'Supplier', 'fieldname': 'supplier', 'fieldtype': 'Data', "fetch_from": "po.supplier"}
],
'permissions': [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Supplier"
}
]
}).insert(ignore_if_duplicate = True)
def create_webform():
frappe.get_doc({
'doctype': 'Web Form',
'module': 'Buying',
'title': 'SO Schedule',
'route': 'so-schedule',
'doc_type': 'Order Assignment',
'web_form_fields': [
{
'doctype': 'Web Form Field',
'fieldname': 'po',
'fieldtype': 'Link',
'options': 'Purchase Order',
'label': 'PO'
},
{
'doctype': 'Web Form Field',
'fieldname': 'supplier',
'fieldtype': 'Data',
'label': 'Supplier'
}
]
}).insert(ignore_if_duplicate = True)
def create_order_assignment(supplier, po):
frappe.get_doc({
'doctype': 'Order Assignment',
'po': po,
'supplier': supplier,
}).insert(ignore_if_duplicate = True)