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:
payload = response.json()
title = payload.get("title", "").lower().strip()
head_sha = payload.get("head", {}).get("sha")
body = payload.get("body", "").lower()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (title.startswith("feat")
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
} else {
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) {
frm.page.set_primary_action(__("Import"), function () {
frappe.call({
@@ -151,23 +133,25 @@ var create_reset_button = function(frm) {
};
var generate_tree_preview = function(frm) {
let parent = __('All Accounts');
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
if (frm.doc.import_file) {
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
new frappe.ui.Tree({
parent: $(frm.fields_dict['chart_tree'].wrapper),
label: parent,
expandable: true,
method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
args: {
file_name: frm.doc.import_file,
parent: parent,
doctype: 'Chart of Accounts Importer',
file_type: frm.doc.file_type
},
onclick: function(node) {
parent = node.value;
}
});
// generate tree structure based on the csv data
new frappe.ui.Tree({
parent: $(frm.fields_dict['chart_tree'].wrapper),
label: parent,
expandable: true,
method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
args: {
file_name: frm.doc.import_file,
parent: parent,
doctype: 'Chart of Accounts Importer',
file_type: frm.doc.file_type
},
onclick: function(node) {
parent = node.value;
}
});
}
};

View File

@@ -25,8 +25,16 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
class ChartofAccountsImporter(Document):
def validate(self):
validate_accounts(self.import_file)
pass
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()
def validate_company(company):
@@ -131,6 +139,8 @@ def get_coa(doctype, parent, is_root=False, file_name=None):
else:
data = generate_data_from_excel(file_doc, extension)
validate_columns(data)
validate_accounts(data)
forest = build_forest(data)
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):
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 = []
for account in roots:
@@ -364,20 +371,12 @@ def get_mandatory_account_types():
def validate_account_types(accounts):
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))
if 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):
linked = frappe.db.sql('''select fieldname from tabDocField
where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True)

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
frappe.ui.form.on('POS Invoice Merge Log', {
setup: function(frm) {
frm.set_query("pos_invoice", "pos_invoices", doc => {
return{
return {
filters: {
'docstatus': 1,
'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",
"field_order": [
"posting_date",
"customer",
"merge_invoices_based_on",
"column_break_3",
"pos_closing_entry",
"customer",
"customer_group",
"section_break_3",
"pos_invoices",
"references_section",
@@ -88,12 +90,27 @@
"fieldtype": "Link",
"label": "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,
"is_submittable": 1,
"links": [],
"modified": "2020-12-01 11:53:57.267579",
"modified": "2021-09-14 11:17:19.001142",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Merge Log",

View File

@@ -23,6 +23,9 @@ class POSInvoiceMergeLog(Document):
self.validate_pos_invoice_status()
def validate_customer(self):
if self.merge_invoices_based_on == 'Customer Group':
return
for d in self.pos_invoices:
if d.customer != 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
for i in items:
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
i.qty = i.qty + item.qty
@@ -172,6 +175,11 @@ class POSInvoiceMergeLog(Document):
invoice.discount_amount = 0.0
invoice.taxes_and_charges = None
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
@@ -228,7 +236,7 @@ def get_all_unconsolidated_invoices():
return 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 = {}
for invoice in pos_invoices:
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.sales_invoice.sales_invoice import (
check_if_return_invoice_linked_with_payment_entry,
is_overdue,
unlink_inter_company_doc,
update_linked_doc,
validate_inter_company_party,
@@ -1139,10 +1140,7 @@ class PurchaseInvoice(BuyingController):
self.status = 'Draft'
return
precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
if not status:
if self.docstatus == 2:
@@ -1150,9 +1148,11 @@ class PurchaseInvoice(BuyingController):
elif self.docstatus == 1:
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate:
elif is_overdue(self):
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"
#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}):

View File

@@ -2,28 +2,58 @@
// License: GNU General Public License v3. See license.txt
// render
frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
get_indicator: function(doc) {
if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"];
} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
return [__("Temporarily on Hold"), "darkgrey"];
} else if (frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
} else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
}
} else if (cint(doc.is_return)) {
return [__("Return"), "gray", "is_return,=,Yes"];
} 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"];
frappe.listview_settings["Purchase Invoice"] = {
add_fields: [
"supplier",
"supplier_name",
"base_grand_total",
"outstanding_amount",
"due_date",
"company",
"currency",
"is_return",
"release_date",
"on_hold",
"represents_company",
"is_internal_supplier",
],
get_indicator(doc) {
if (doc.status == "Debit Note Issued") {
return [__(doc.status), "darkgrey", "status,=," + doc.status];
}
}
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"
},
{
"depends_on": "exchange_gain_loss",
"fieldname": "exchange_gain_loss",
"fieldtype": "Currency",
"label": "Exchange Gain/Loss",
@@ -104,6 +105,7 @@
"read_only": 1
},
{
"depends_on": "exchange_gain_loss",
"fieldname": "ref_exchange_rate",
"fieldtype": "Float",
"label": "Reference Exchange Rate",
@@ -115,7 +117,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-20 16:26:53.820530",
"modified": "2021-09-26 15:47:28.167371",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",

View File

@@ -1652,7 +1652,7 @@
"label": "Status",
"length": 30,
"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,
"read_only": 1
},
@@ -2032,11 +2032,12 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2021-09-08 15:24:25.486499",
"modified": "2021-09-21 09:27:50.191854",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"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')
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 {}
if not pos_profile:
return
@@ -1472,14 +1472,7 @@ class SalesInvoice(SellingController):
self.status = 'Draft'
return
precision = 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)
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
if not status:
if self.docstatus == 2:
@@ -1487,15 +1480,13 @@ class SalesInvoice(SellingController):
elif self.docstatus == 1:
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discounting_status=='Disbursed':
self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate:
elif is_overdue(self):
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discounting_status=='Disbursed':
self.status = "Unpaid and Discounted"
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"
#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}):
self.status = "Credit Note Issued"
elif self.is_return == 1:
@@ -1504,12 +1495,42 @@ class SalesInvoice(SellingController):
self.status = "Paid"
else:
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:
self.status = "Draft"
if update:
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):
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",
"currency", "is_return"],
get_indicator: function(doc) {
var status_color = {
const status_colors = {
"Draft": "grey",
"Unpaid": "orange",
"Paid": "green",
"Return": "gray",
"Credit Note Issued": "gray",
"Unpaid and Discounted": "orange",
"Partly Paid and Discounted": "yellow",
"Overdue and Discounted": "red",
"Overdue": "red",
"Partly Paid": "yellow",
"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"
};

View File

@@ -131,6 +131,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_payment_entry_unlink_against_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si = frappe.copy_doc(test_records[0])
si.is_pos = 0
si.insert()
@@ -154,6 +155,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_payment_entry_unlink_against_standalone_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si1 = create_sales_invoice(rate=1000)
si2 = create_sales_invoice(rate=300)
si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
@@ -1653,6 +1655,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_credit_note(self):
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)
outstanding_amount = get_outstanding_amount(si.doctype,
@@ -2241,6 +2244,54 @@ class TestSalesInvoice(unittest.TestCase):
party_link.delete()
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():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'

View File

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

View File

@@ -690,13 +690,17 @@ class AccountsController(TransactionBase):
.format(d.reference_name, d.against_order))
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
for d in self.get("advances"):
advance_exchange_rate = d.ref_exchange_rate
if (d.allocated_amount and self.conversion_rate != 1
and self.conversion_rate != advance_exchange_rate):
if (d.allocated_amount and self.conversion_rate != advance_exchange_rate):
base_allocated_amount_in_ref_rate = advance_exchange_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')
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')))
account_currency = get_account_currency(gain_loss_account)
if account_currency != self.company_currency:
@@ -734,7 +738,7 @@ class AccountsController(TransactionBase):
"against": party,
dr_or_cr + "_in_account_currency": 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
}, item=d)
)
@@ -985,42 +989,55 @@ class AccountsController(TransactionBase):
item_allowance = {}
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"):
if item.get(item_ref_dn):
ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
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]
if not item.get(item_ref_dn):
continue
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
self.precision(based_on, item))
ref_amt = flt(frappe.db.get_value(ref_dt + " 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 = \
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
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]
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:
# while making debit note against purchase return entry(purchase receipt) getting overbill error
total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(max_allowed_amt)
allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
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 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 total_billed_amt < 0 and max_allowed_amt < 0:
# while making debit note against purchase return entry(purchase receipt) getting overbill error
total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(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):
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)
def update_invoice_status():
# Daily update the status of the invoices
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""")
"""Updates status as Overdue for applicable invoices. Runs daily."""
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()
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
from frappe import _
from frappe.modules.utils import get_module_app
from frappe.utils import flt, has_common
from frappe.utils.user import is_website_user
@@ -21,8 +22,32 @@ def get_list_context(context=None):
"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
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))
elif suppliers:
filters.append(('supplier', 'in', suppliers))
else:
elif not custom:
return []
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
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,
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
if custom:
return transactions
return post_process(doctype, transactions)
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]
def is_search_module_loaded():
cache = frappe.cache()
out = cache.execute_command('MODULE LIST')
try:
cache = frappe.cache()
out = cache.execute_command('MODULE LIST')
parsed_output = " ".join(
(" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out)
)
return "search" in parsed_output
parsed_output = " ".join(
(" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out)
)
return "search" in parsed_output
except Exception:
return False
def if_redisearch_loaded(function):
"Decorator to check if Redisearch is loaded."

View File

@@ -85,10 +85,8 @@ def add_bank_accounts(response, bank, company):
if not acc_subtype:
add_account_subtype(account["subtype"])
existing_bank_account = frappe.db.exists("Bank Account", {
'account_name': account["name"],
'bank': bank["bank_name"]
})
bank_account_name = "{} - {}".format(account["name"], bank["bank_name"])
existing_bank_account = frappe.db.exists("Bank Account", bank_account_name)
if not existing_bank_account:
try:
@@ -197,6 +195,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
plaid = PlaidConnector(access_token)
transactions = []
try:
transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
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) + " "
frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
return transactions or []
return transactions
def new_bank_transaction(transaction):

View File

@@ -40,7 +40,7 @@ class Patient(Document):
frappe.db.set_value('Patient', self.name, 'status', 'Disabled')
else:
send_registration_sms(self)
self.reload() # self.notify_update()
self.reload()
def on_update(self):
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')
def create_website_user(self):
if self.email and not frappe.db.exists('User', self.email):
user = frappe.get_doc({
'doctype': 'User',
'first_name': self.first_name,
'last_name': self.last_name,
'email': self.email,
'user_type': 'Website User',
'gender': self.sex,
'phone': self.phone,
'mobile_no': self.mobile,
'birth_date': self.dob
})
user.flags.ignore_permissions = True
user.enabled = True
user.send_welcome_email = True
user.add_roles('Patient')
frappe.db.set_value(self.doctype, self.name, 'user_id', user.name)
users = frappe.db.get_all('User', fields=['email', 'mobile_no'], or_filters={'email': self.email, 'mobile_no': self.mobile})
if users and users[0]:
frappe.throw(_("User exists with Email {}, Mobile {}<br>Please check email / mobile or disable 'Invite as User' to skip creating User")
.format(frappe.bold(users[0].email), frappe.bold(users[0].mobile_no)), frappe.DuplicateEntryError)
user = frappe.get_doc({
'doctype': 'User',
'first_name': self.first_name,
'last_name': self.last_name,
'email': self.email,
'user_type': 'Website User',
'gender': self.sex,
'phone': self.phone,
'mobile_no': self.mobile,
'birth_date': self.dob
})
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):
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}
def set_contact(self):
if frappe.db.exists('Dynamic Link', {'parenttype':'Contact', 'link_doctype':'Patient', 'link_name':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)
contact = get_default_contact(self.doctype, self.name)
if contact:
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()
old_doc = self.get_doc_before_save()
if not old_doc:
return
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 old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone:
self.update_contact(contact)
else:
if self.customer:
# customer contact exists, link patient
contact = get_default_contact('Customer', self.customer)
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')
if contact:
self.update_contact(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.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)

View File

@@ -35,3 +35,40 @@ class TestPatient(unittest.TestCase):
settings.collect_registration_fee = 0
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
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)}'}):
patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name'])
return 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.mobile = mobile
patient.email = email
patient.customer = customer
patient.invite_user = create_user
patient.save(ignore_permissions=True)
return patient.name

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import unittest
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.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})
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.start_procedure()
procedure.complete_procedure()

View File

@@ -776,7 +776,7 @@ def update_patient_email_and_phone_numbers(contact, method):
Hook validate Contact
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
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:
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'):
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'):
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'):
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
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"
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"]
@@ -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",
"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",
"Subscription Plan"
"Subscription Plan", "POS Invoice", "POS Invoice Item"
]
regional_overrides = {

View File

@@ -184,7 +184,7 @@ def get_employees_having_an_event_today(event_type):
# --------------------------
def send_work_anniversary_reminders():
"""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:
return

View File

@@ -21,7 +21,7 @@ frappe.ui.form.on('Training Result', {
frm.set_value("employees" ,"");
if (r.message) {
$.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_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, [])
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,
max(`tabJob Card Time Log`.to_time) as to_time
FROM `tabJob Card` , `tabJob Card Time Log`
@@ -687,7 +687,7 @@ def get_job_details(start, end, filters=None):
for d in job_cards:
subject_data = []
for field in ["name", "work_order", "remarks", "employee_name"]:
for field in ["name", "work_order", "remarks"]:
if not d.get(field): continue
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):
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.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.replace_supplier_item_group_with_party_specific_item
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) {
me.frm.set_value("billing_address", r.message);
} 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,
action: function(selections, args) {
let values = selections;
if(values.length === 0){
if (values.length === 0) {
frappe.msgprint(__("Please select {0}", [opts.source_doctype]))
return;
}
opts.source_name = values;
opts.args = args;
if (opts.allow_child_item_selection) {
// args contains filtered child docnames
opts.args = args;
}
d.dialog.hide();
_map();
},

View File

@@ -797,7 +797,7 @@ def set_salary_components(docs):
def set_tax_withholding_category(company):
accounts = []
fiscal_year = None
fiscal_year_details = None
abbr = frappe.get_value("Company", company, "abbr")
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))
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts
else:
return [_("Item"), _("Taxable Amount")] + tax_accounts
return [_("Item"), _("Taxable Amount")] + tax_accounts
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)

View File

@@ -25,7 +25,7 @@
"editable_price_list_rate",
"validate_selling_price",
"editable_bundle_item_rates",
"transaction_settings_section",
"sales_transactions_settings_section",
"so_required",
"dn_required",
"sales_update_frequency",
@@ -143,15 +143,14 @@
{
"default": "Stop",
"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",
"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",
"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",
"fieldtype": "Link",
"label": "Role Allowed to Override Stop Action",
@@ -191,13 +190,15 @@
"label": "Item Price Settings"
},
{
"fieldname": "transaction_settings_section",
"fieldname": "sales_transactions_settings_section",
"fieldtype": "Section Break",
"label": "Transaction Settings"
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
"default": "0",
"fieldname": "editable_bundle_item_rates",
"fieldtype": "Check",
"label": "Calculate Product Bundle Price based on Child Items' Rates"
}
],
"icon": "fa fa-cog",
@@ -205,7 +206,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-09-08 19:38:10.175989",
"modified": "2021-09-14 22:05:06.139820",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",

View File

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

View File

@@ -272,8 +272,9 @@ def update_status(name, status):
material_request.update_status(status)
@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):
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
exists = 0
for d in doc.get("packed_items"):
if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code and\
d.parent_detail_docname == main_item_row.name:
if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code:
if d.parent_detail_docname != main_item_row.name:
d.parent_detail_docname = main_item_row.name
pi, exists = d, 1
break

View File

@@ -11,6 +11,6 @@
{% endfor %}
</ol>
{% else %}
<p>You don't have no upcoming holidays this {{ frequency }}.</p>
<p>You have no upcoming holidays this {{ frequency }}.</p>
{% 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)