Merge branch 'develop' into navbar_links

This commit is contained in:
Deepesh Garg 2020-08-28 12:48:46 +05:30 committed by GitHub
commit ecc282930a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 802 additions and 397 deletions

View File

@ -9,6 +9,8 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
from frappe.test_runner import make_test_objects from frappe.test_runner import make_test_objects
test_dependencies = ['Item']
def test_create_test_data(): def test_create_test_data():
frappe.set_user("Administrator") frappe.set_user("Administrator")
# create test item # create test item
@ -95,7 +97,6 @@ def test_create_test_data():
}) })
coupon_code.insert() coupon_code.insert()
class TestCouponCode(unittest.TestCase): class TestCouponCode(unittest.TestCase):
def setUp(self): def setUp(self):
test_create_test_data() test_create_test_data()

View File

@ -8,6 +8,8 @@ import unittest
from erpnext.stock.get_item_details import get_pos_profile from erpnext.stock.get_item_details import get_pos_profile
from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes
test_dependencies = ['Item']
class TestPOSProfile(unittest.TestCase): class TestPOSProfile(unittest.TestCase):
def test_pos_profile(self): def test_pos_profile(self):
make_pos_profile() make_pos_profile()
@ -88,7 +90,7 @@ def make_pos_profile(**args):
"write_off_account": args.write_off_account or "_Test Write Off - _TC", "write_off_account": args.write_off_account or "_Test Write Off - _TC",
"write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC" "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
}) })
payments = [{ payments = [{
'mode_of_payment': 'Cash', 'mode_of_payment': 'Cash',
'default': 1 'default': 1

View File

@ -1,5 +1,4 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# For license information, please see license.txt # For license information, please see license.txt
@ -208,7 +207,7 @@ def get_serial_no_for_item(args):
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules,
get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule)
if isinstance(doc, string_types): if isinstance(doc, string_types):
doc = json.loads(doc) doc = json.loads(doc)
@ -237,7 +236,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
update_args_for_pricing_rule(args) update_args_for_pricing_rule(args)
pricing_rules = (get_applied_pricing_rules(args) pricing_rules = (get_applied_pricing_rules(args.get('pricing_rules'))
if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc)) if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc))
if pricing_rules: if pricing_rules:
@ -365,8 +364,9 @@ def set_discount_amount(rate, item_details):
item_details.rate = rate item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
for d in json.loads(pricing_rules): get_pricing_rule_items)
for d in get_applied_pricing_rules(pricing_rules):
if not d or not frappe.db.exists("Pricing Rule", d): continue if not d or not frappe.db.exists("Pricing Rule", d): continue
pricing_rule = frappe.get_cached_doc('Pricing Rule', d) pricing_rule = frappe.get_cached_doc('Pricing Rule', d)

View File

@ -447,9 +447,14 @@ def apply_pricing_rule_on_transaction(doc):
apply_pricing_rule_for_free_items(doc, item_details.free_item_data) apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values() doc.set_missing_values()
def get_applied_pricing_rules(item_row): def get_applied_pricing_rules(pricing_rules):
return (json.loads(item_row.get("pricing_rules")) if pricing_rules:
if item_row.get("pricing_rules") else []) if pricing_rules.startswith('['):
return json.loads(pricing_rules)
else:
return pricing_rules.split(',')
return []
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
free_item = pricing_rule.free_item free_item = pricing_rule.free_item

View File

@ -447,7 +447,7 @@
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"fieldname": "po_no", "fieldname": "po_no",
"fieldtype": "Small Text", "fieldtype": "Data",
"hide_days": 1, "hide_days": 1,
"hide_seconds": 1, "hide_seconds": 1,
"label": "Customer's Purchase Order", "label": "Customer's Purchase Order",
@ -1946,7 +1946,7 @@
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-08-03 23:31:12.675040", "modified": "2020-08-27 01:56:28.532140",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -13,7 +13,8 @@ def get_data():
'Auto Repeat': 'reference_document', 'Auto Repeat': 'reference_document',
}, },
'internal_links': { 'internal_links': {
'Sales Order': ['items', 'sales_order'] 'Sales Order': ['items', 'sales_order'],
'Delivery Note': ['items', 'delivery_note']
}, },
'transactions': [ 'transactions': [
{ {

View File

@ -3,6 +3,22 @@
frappe.ui.form.on('Shipping Rule', { frappe.ui.form.on('Shipping Rule', {
refresh: function(frm) { refresh: function(frm) {
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company
}
}
})
frm.set_query("account", function() {
return {
filters: {
company: frm.doc.company
}
}
})
frm.trigger('toggle_reqd'); frm.trigger('toggle_reqd');
}, },
calculate_based_on: function(frm) { calculate_based_on: function(frm) {
@ -12,4 +28,4 @@ frappe.ui.form.on('Shipping Rule', {
frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === 'Fixed'); frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === 'Fixed');
frm.toggle_reqd("conditions", frm.doc.calculate_based_on !== 'Fixed'); frm.toggle_reqd("conditions", frm.doc.calculate_based_on !== 'Fixed');
} }
}); });

View File

@ -378,7 +378,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
if filters and filters.get('presentation_currency') != d.default_currency: if filters and filters.get('presentation_currency') != d.default_currency:
currency_info['company'] = d.name currency_info['company'] = d.name
currency_info['company_currency'] = d.default_currency currency_info['company_currency'] = d.default_currency
convert_to_presentation_currency(gl_entries, currency_info) convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
for entry in gl_entries: for entry in gl_entries:
key = entry.account_number or entry.account_name key = entry.account_number or entry.account_name

View File

@ -423,7 +423,7 @@ def set_gl_entries_by_account(
distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
if filters and filters.get('presentation_currency'): if filters and filters.get('presentation_currency'):
convert_to_presentation_currency(gl_entries, get_currency(filters)) convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company'))
for entry in gl_entries: for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry) gl_entries_by_account.setdefault(entry.account, []).append(entry)

View File

@ -146,6 +146,12 @@ frappe.query_reports["General Ledger"] = {
return frappe.db.get_link_options('Project', txt); return frappe.db.get_link_options('Project', txt);
} }
}, },
{
"fieldname": "include_dimensions",
"label": __("Consider Accounting Dimensions"),
"fieldtype": "Check",
"default": 0
},
{ {
"fieldname": "show_opening_entries", "fieldname": "show_opening_entries",
"label": __("Show Opening Entries"), "label": __("Show Opening Entries"),

View File

@ -106,15 +106,20 @@ def set_account_currency(filters):
return filters return filters
def get_result(filters, account_details): def get_result(filters, account_details):
gl_entries = get_gl_entries(filters) accounting_dimensions = []
if filters.get("include_dimensions"):
accounting_dimensions = get_accounting_dimensions()
data = get_data_with_opening_closing(filters, account_details, gl_entries) gl_entries = get_gl_entries(filters, accounting_dimensions)
data = get_data_with_opening_closing(filters, account_details,
accounting_dimensions, gl_entries)
result = get_result_as_list(data, filters) result = get_result_as_list(data, filters)
return result return result
def get_gl_entries(filters): def get_gl_entries(filters, accounting_dimensions):
currency_map = get_currency(filters) currency_map = get_currency(filters)
select_fields = """, debit, credit, debit_in_account_currency, select_fields = """, debit, credit, debit_in_account_currency,
credit_in_account_currency """ credit_in_account_currency """
@ -128,6 +133,10 @@ def get_gl_entries(filters):
filters['company_fb'] = frappe.db.get_value("Company", filters['company_fb'] = frappe.db.get_value("Company",
filters.get("company"), 'default_finance_book') filters.get("company"), 'default_finance_book')
dimension_fields = ""
if accounting_dimensions:
dimension_fields = ', '.join(accounting_dimensions) + ','
distributed_cost_center_query = "" distributed_cost_center_query = ""
if filters and filters.get('cost_center'): if filters and filters.get('cost_center'):
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency, select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
@ -141,7 +150,7 @@ def get_gl_entries(filters):
party_type, party_type,
party, party,
voucher_type, voucher_type,
voucher_no, voucher_no, {dimension_fields}
cost_center, project, cost_center, project,
against_voucher_type, against_voucher_type,
against_voucher, against_voucher,
@ -160,13 +169,14 @@ def get_gl_entries(filters):
{conditions} {conditions}
AND posting_date <= %(to_date)s AND posting_date <= %(to_date)s
AND cost_center = DCC_allocation.parent AND cost_center = DCC_allocation.parent
""".format(select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", '')) """.format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
gl_entries = frappe.db.sql( gl_entries = frappe.db.sql(
""" """
select select
name as gl_entry, posting_date, account, party_type, party, name as gl_entry, posting_date, account, party_type, party,
voucher_type, voucher_no, cost_center, project, voucher_type, voucher_no, {dimension_fields}
cost_center, project,
against_voucher_type, against_voucher, account_currency, against_voucher_type, against_voucher, account_currency,
remarks, against, is_opening, creation {select_fields} remarks, against, is_opening, creation {select_fields}
from `tabGL Entry` from `tabGL Entry`
@ -174,13 +184,13 @@ def get_gl_entries(filters):
{distributed_cost_center_query} {distributed_cost_center_query}
{order_by_statement} {order_by_statement}
""".format( """.format(
select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query, dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
order_by_statement=order_by_statement order_by_statement=order_by_statement
), ),
filters, as_dict=1) filters, as_dict=1)
if filters.get('presentation_currency'): if filters.get('presentation_currency'):
return convert_to_presentation_currency(gl_entries, currency_map) return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company'))
else: else:
return gl_entries return gl_entries
@ -247,12 +257,12 @@ def get_conditions(filters):
return "and {}".format(" and ".join(conditions)) if conditions else "" return "and {}".format(" and ".join(conditions)) if conditions else ""
def get_data_with_opening_closing(filters, account_details, gl_entries): def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):
data = [] data = []
gle_map = initialize_gle_map(gl_entries, filters) gle_map = initialize_gle_map(gl_entries, filters)
totals, entries = get_accountwise_gle(filters, gl_entries, gle_map) totals, entries = get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map)
# Opening for filtered account # Opening for filtered account
data.append(totals.opening) data.append(totals.opening)
@ -318,7 +328,7 @@ def initialize_gle_map(gl_entries, filters):
return gle_map return gle_map
def get_accountwise_gle(filters, gl_entries, gle_map): def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
totals = get_totals_dict() totals = get_totals_dict()
entries = [] entries = []
consolidated_gle = OrderedDict() consolidated_gle = OrderedDict()
@ -350,8 +360,11 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
if filters.get("group_by") != _('Group by Voucher (Consolidated)'): if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
gle_map[gle.get(group_by)].entries.append(gle) gle_map[gle.get(group_by)].entries.append(gle)
elif filters.get("group_by") == _('Group by Voucher (Consolidated)'): elif filters.get("group_by") == _('Group by Voucher (Consolidated)'):
key = (gle.get("voucher_type"), gle.get("voucher_no"), keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")]
gle.get("account"), gle.get("cost_center")) for dim in accounting_dimensions:
keylist.append(gle.get(dim))
keylist.append(gle.get("cost_center"))
key = tuple(keylist)
if key not in consolidated_gle: if key not in consolidated_gle:
consolidated_gle.setdefault(key, gle) consolidated_gle.setdefault(key, gle)
else: else:
@ -478,7 +491,19 @@ def get_columns(filters):
"options": "Project", "options": "Project",
"fieldname": "project", "fieldname": "project",
"width": 100 "width": 100
}, }
])
if filters.get("include_dimensions"):
for dim in get_accounting_dimensions(as_list = False):
columns.append({
"label": _(dim.label),
"options": dim.label,
"fieldname": dim.fieldname,
"width": 100
})
columns.extend([
{ {
"label": _("Cost Center"), "label": _("Cost Center"),
"options": "Cost Center", "options": "Cost Center",

View File

@ -6,10 +6,6 @@ from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_dat
from frappe.utils import cint, get_datetime_str, formatdate, flt from frappe.utils import cint, get_datetime_str, formatdate, flt
__exchange_rates = {} __exchange_rates = {}
P_OR_L_ACCOUNTS = list(
sum(frappe.get_list('Account', fields=['name'], or_filters=[{'root_type': 'Income'}, {'root_type': 'Expense'}], as_list=True), ())
)
def get_currency(filters): def get_currency(filters):
""" """
@ -73,18 +69,7 @@ def get_rate_as_at(date, from_currency, to_currency):
return rate return rate
def convert_to_presentation_currency(gl_entries, currency_info, company):
def is_p_or_l_account(account_name):
"""
Check if the given `account name` is an `Account` with `root_type` of either 'Income'
or 'Expense'.
:param account_name:
:return: Boolean
"""
return account_name in P_OR_L_ACCOUNTS
def convert_to_presentation_currency(gl_entries, currency_info):
""" """
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
in `currency_info`. in `currency_info`.
@ -96,6 +81,9 @@ def convert_to_presentation_currency(gl_entries, currency_info):
presentation_currency = currency_info['presentation_currency'] presentation_currency = currency_info['presentation_currency']
company_currency = currency_info['company_currency'] company_currency = currency_info['company_currency']
pl_accounts = [d.name for d in frappe.get_list('Account',
filters={'report_type': 'Profit and Loss', 'company': company})]
for entry in gl_entries: for entry in gl_entries:
account = entry['account'] account = entry['account']
debit = flt(entry['debit']) debit = flt(entry['debit'])
@ -107,7 +95,7 @@ def convert_to_presentation_currency(gl_entries, currency_info):
if account_currency != presentation_currency: if account_currency != presentation_currency:
value = debit or credit value = debit or credit
date = currency_info['report_date'] if not is_p_or_l_account(account) else entry['posting_date'] date = entry['posting_date'] if account in pl_accounts else currency_info['report_date']
converted_value = convert(value, presentation_currency, company_currency, date) converted_value = convert(value, presentation_currency, company_currency, date)
if entry.get('debit'): if entry.get('debit'):

View File

@ -325,7 +325,7 @@ class AccountsController(TransactionBase):
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data')) apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
elif pricing_rule_args.get("validate_applied_rule"): elif pricing_rule_args.get("validate_applied_rule"):
for pricing_rule in get_applied_pricing_rules(item): for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')):
pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule) pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
for field in ['discount_percentage', 'discount_amount', 'rate']: for field in ['discount_percentage', 'discount_amount', 'rate']:
if item.get(field) < pricing_rule_doc.get(field): if item.get(field) < pricing_rule_doc.get(field):

View File

@ -9,6 +9,7 @@ from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
from erpnext.controllers.accounts_controller import validate_conversion_rate, \ from erpnext.controllers.accounts_controller import validate_conversion_rate, \
validate_taxes_and_charges, validate_inclusive_tax validate_taxes_and_charges, validate_inclusive_tax
from erpnext.stock.get_item_details import _get_item_tax_template from erpnext.stock.get_item_details import _get_item_tax_template
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
class calculate_taxes_and_totals(object): class calculate_taxes_and_totals(object):
def __init__(self, doc): def __init__(self, doc):
@ -209,7 +210,7 @@ class calculate_taxes_and_totals(object):
elif tax.charge_type == "On Previous Row Total": elif tax.charge_type == "On Previous Row Total":
current_tax_fraction = (tax_rate / 100.0) * \ current_tax_fraction = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
elif tax.charge_type == "On Item Quantity": elif tax.charge_type == "On Item Quantity":
inclusive_tax_amount_per_qty = flt(tax_rate) inclusive_tax_amount_per_qty = flt(tax_rate)
@ -607,7 +608,7 @@ class calculate_taxes_and_totals(object):
base_rate_with_margin = 0.0 base_rate_with_margin = 0.0
if item.price_list_rate: if item.price_list_rate:
if item.pricing_rules and not self.doc.ignore_pricing_rule: if item.pricing_rules and not self.doc.ignore_pricing_rule:
for d in json.loads(item.pricing_rules): for d in get_applied_pricing_rules(item.pricing_rules):
pricing_rule = frappe.get_cached_doc('Pricing Rule', d) pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\ if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\

View File

@ -30,14 +30,14 @@ frappe.ui.form.on('Social Media Post', {
let color = frm.doc.twitter_post_id ? "green" : "red"; let color = frm.doc.twitter_post_id ? "green" : "red";
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted"; let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
html += `<div class="col-xs-6"> html += `<div class="col-xs-6">
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">Twitter : ${status} </span></span> <span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
</div>` ; </div>` ;
} }
if (frm.doc.linkedin){ if (frm.doc.linkedin){
let color = frm.doc.linkedin_post_id ? "green" : "red"; let color = frm.doc.linkedin_post_id ? "green" : "red";
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted"; let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
html += `<div class="col-xs-6"> html += `<div class="col-xs-6">
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">LinkedIn : ${status} </span></span> <span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
</div>` ; </div>` ;
} }
html = `<div class="row">${html}</div>`; html = `<div class="row">${html}</div>`;

View File

@ -0,0 +1,52 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Lead Details"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -12),
"reqd": 1
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today(),
"reqd": 1
},
{
"fieldname":"status",
"label": __("Status"),
"fieldtype": "Select",
options: [
{ "value": "Lead", "label": __("Lead") },
{ "value": "Open", "label": __("Open") },
{ "value": "Replied", "label": __("Replied") },
{ "value": "Opportunity", "label": __("Opportunity") },
{ "value": "Quotation", "label": __("Quotation") },
{ "value": "Lost Quotation", "label": __("Lost Quotation") },
{ "value": "Interested", "label": __("Interested") },
{ "value": "Converted", "label": __("Converted") },
{ "value": "Do Not Contact", "label": __("Do Not Contact") },
],
},
{
"fieldname":"territory",
"label": __("Territory"),
"fieldtype": "Link",
"options": "Territory",
}
]
};

View File

@ -7,16 +7,15 @@
"doctype": "Report", "doctype": "Report",
"idx": 3, "idx": 3,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2020-01-22 16:51:56.591110", "modified": "2020-07-26 23:59:49.897577",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Lead Details", "name": "Lead Details",
"owner": "Administrator", "owner": "Administrator",
"prepared_report": 0, "prepared_report": 0,
"query": "SELECT\n `tabLead`.name as \"Lead Id:Link/Lead:120\",\n `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2)\n\t) as 'Address::180',\n\t`tabAddress`.state as \"State::100\",\n\t`tabAddress`.pincode as \"Pincode::70\",\n\t`tabAddress`.country as \"Country::100\",\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Id::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\",\n\t`tabLead`.notes as \"Notes::360\",\n `tabLead`.owner as \"Owner:Link/User:120\"\nFROM\n\t`tabLead`\n\tleft join `tabDynamic Link` on (\n\t\t`tabDynamic Link`.link_name=`tabLead`.name \n\t\tand `tabDynamic Link`.parenttype = 'Address'\n\t)\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.name=`tabDynamic Link`.parent\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc",
"ref_doctype": "Lead", "ref_doctype": "Lead",
"report_name": "Lead Details", "report_name": "Lead Details",
"report_type": "Query Report", "report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Sales User" "role": "Sales User"

View File

@ -0,0 +1,158 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe import _
import frappe
def execute(filters=None):
columns, data = get_columns(), get_data(filters)
return columns, data
def get_columns():
columns = [
{
"label": _("Lead"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Lead",
"width": 150,
},
{
"label": _("Lead Name"),
"fieldname": "lead_name",
"fieldtype": "Data",
"width": 120
},
{
"fieldname":"status",
"label": _("Status"),
"fieldtype": "Data",
"width": 100
},
{
"fieldname":"lead_owner",
"label": _("Lead Owner"),
"fieldtype": "Link",
"options": "User",
"width": 100
},
{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
"width": 100
},
{
"label": _("Source"),
"fieldname": "source",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Email"),
"fieldname": "email_id",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Mobile"),
"fieldname": "mobile_no",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Phone"),
"fieldname": "phone",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Owner"),
"fieldname": "owner",
"fieldtype": "Link",
"options": "user",
"width": 120
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120
},
{
"fieldname":"address",
"label": _("Address"),
"fieldtype": "Data",
"width": 130
},
{
"fieldname":"state",
"label": _("State"),
"fieldtype": "Data",
"width": 100
},
{
"fieldname":"pincode",
"label": _("Postal Code"),
"fieldtype": "Data",
"width": 90
},
{
"fieldname":"country",
"label": _("Country"),
"fieldtype": "Link",
"options": "Country",
"width": 100
},
]
return columns
def get_data(filters):
return frappe.db.sql("""
SELECT
`tabLead`.name,
`tabLead`.lead_name,
`tabLead`.status,
`tabLead`.lead_owner,
`tabLead`.territory,
`tabLead`.source,
`tabLead`.email_id,
`tabLead`.mobile_no,
`tabLead`.phone,
`tabLead`.owner,
`tabLead`.company,
concat_ws(', ',
trim(',' from `tabAddress`.address_line1),
trim(',' from tabAddress.address_line2)
) AS address,
`tabAddress`.state,
`tabAddress`.pincode,
`tabAddress`.country
FROM
`tabLead` left join `tabDynamic Link` on (
`tabLead`.name = `tabDynamic Link`.link_name and
`tabDynamic Link`.parenttype = 'Address')
left join `tabAddress` on (
`tabAddress`.name=`tabDynamic Link`.parent)
WHERE
company = %(company)s
AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s
{conditions}
ORDER BY
`tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1)
def get_conditions(filters) :
conditions = []
if filters.get("territory"):
conditions.append(" and `tabLead`.territory=%(territory)s")
if filters.get("status"):
conditions.append(" and `tabLead`.status=%(status)s")
return " ".join(conditions) if conditions else ""

View File

@ -0,0 +1,67 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Lost Opportunity"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -12),
"reqd": 1
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today(),
"reqd": 1
},
{
"fieldname":"lost_reason",
"label": __("Lost Reason"),
"fieldtype": "Link",
"options": "Opportunity Lost Reason"
},
{
"fieldname":"territory",
"label": __("Territory"),
"fieldtype": "Link",
"options": "Territory"
},
{
"fieldname":"opportunity_from",
"label": __("Opportunity From"),
"fieldtype": "Link",
"options": "DocType",
"get_query": function() {
return {
"filters": {
"name": ["in", ["Customer", "Lead"]],
}
}
}
},
{
"fieldname":"party_name",
"label": __("Party"),
"fieldtype": "Dynamic Link",
"options": "opportunity_from"
},
{
"fieldname":"contact_by",
"label": __("Next Contact By"),
"fieldtype": "Link",
"options": "User"
},
]
};

View File

@ -1,13 +1,14 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"creation": "2018-12-31 16:30:57.188837", "creation": "2018-12-31 16:30:57.188837",
"disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"json": "{\"order_by\": \"`tabOpportunity`.`modified` desc\", \"filters\": [[\"Opportunity\", \"status\", \"=\", \"Lost\"]], \"fields\": [[\"name\", \"Opportunity\"], [\"opportunity_from\", \"Opportunity\"], [\"party_name\", \"Opportunity\"], [\"customer_name\", \"Opportunity\"], [\"opportunity_type\", \"Opportunity\"], [\"status\", \"Opportunity\"], [\"contact_by\", \"Opportunity\"], [\"docstatus\", \"Opportunity\"], [\"lost_reason\", \"Lost Reason Detail\"]], \"add_totals_row\": 0, \"add_total_row\": 0, \"page_length\": 20}", "json": "{\"order_by\": \"`tabOpportunity`.`modified` desc\", \"filters\": [[\"Opportunity\", \"status\", \"=\", \"Lost\"]], \"fields\": [[\"name\", \"Opportunity\"], [\"opportunity_from\", \"Opportunity\"], [\"party_name\", \"Opportunity\"], [\"customer_name\", \"Opportunity\"], [\"opportunity_type\", \"Opportunity\"], [\"status\", \"Opportunity\"], [\"contact_by\", \"Opportunity\"], [\"docstatus\", \"Opportunity\"], [\"lost_reason\", \"Lost Reason Detail\"]], \"add_totals_row\": 0, \"add_total_row\": 0, \"page_length\": 20}",
"modified": "2019-06-26 16:33:08.083618", "modified": "2020-07-29 15:49:02.848845",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Lost Opportunity", "name": "Lost Opportunity",
@ -15,7 +16,7 @@
"prepared_report": 0, "prepared_report": 0,
"ref_doctype": "Opportunity", "ref_doctype": "Opportunity",
"report_name": "Lost Opportunity", "report_name": "Lost Opportunity",
"report_type": "Report Builder", "report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Sales User" "role": "Sales User"

View File

@ -0,0 +1,131 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe import _
import frappe
def execute(filters=None):
columns, data = get_columns(), get_data(filters)
return columns, data
def get_columns():
columns = [
{
"label": _("Opportunity"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Opportunity",
"width": 170,
},
{
"label": _("Opportunity From"),
"fieldname": "opportunity_from",
"fieldtype": "Link",
"options": "DocType",
"width": 130
},
{
"label": _("Party"),
"fieldname":"party_name",
"fieldtype": "Dynamic Link",
"options": "opportunity_from",
"width": 160
},
{
"label": _("Customer/Lead Name"),
"fieldname":"customer_name",
"fieldtype": "Data",
"width": 150
},
{
"label": _("Opportunity Type"),
"fieldname": "opportunity_type",
"fieldtype": "Data",
"width": 130
},
{
"label": _("Lost Reasons"),
"fieldname": "lost_reason",
"fieldtype": "Data",
"width": 220
},
{
"label": _("Sales Stage"),
"fieldname": "sales_stage",
"fieldtype": "Link",
"options": "Sales Stage",
"width": 150
},
{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
"width": 150
},
{
"label": _("Next Contact By"),
"fieldname": "contact_by",
"fieldtype": "Link",
"options": "User",
"width": 150
}
]
return columns
def get_data(filters):
return frappe.db.sql("""
SELECT
`tabOpportunity`.name,
`tabOpportunity`.opportunity_from,
`tabOpportunity`.party_name,
`tabOpportunity`.customer_name,
`tabOpportunity`.opportunity_type,
`tabOpportunity`.contact_by,
GROUP_CONCAT(`tabOpportunity Lost Reason Detail`.lost_reason separator ', ') lost_reason,
`tabOpportunity`.sales_stage,
`tabOpportunity`.territory
FROM
`tabOpportunity`
{join}
WHERE
`tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s
AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s
{conditions}
GROUP BY
`tabOpportunity`.name
ORDER BY
`tabOpportunity`.creation asc """.format(conditions=get_conditions(filters), join=get_join(filters)), filters, as_dict=1)
def get_conditions(filters):
conditions = []
if filters.get("territory"):
conditions.append(" and `tabOpportunity`.territory=%(territory)s")
if filters.get("opportunity_from"):
conditions.append(" and `tabOpportunity`.opportunity_from=%(opportunity_from)s")
if filters.get("party_name"):
conditions.append(" and `tabOpportunity`.party_name=%(party_name)s")
if filters.get("contact_by"):
conditions.append(" and `tabOpportunity`.contact_by=%(contact_by)s")
return " ".join(conditions) if conditions else ""
def get_join(filters):
join = """LEFT JOIN `tabOpportunity Lost Reason Detail`
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name"""
if filters.get("lost_reason"):
join = """JOIN `tabOpportunity Lost Reason Detail`
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and
`tabOpportunity Lost Reason Detail`.lost_reason = '{0}'
""".format(filters.get("lost_reason"))
return join

View File

@ -7,6 +7,8 @@ import unittest
import frappe import frappe
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_clinical_procedure_template from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_clinical_procedure_template
test_dependencies = ['Item']
class TestClinicalProcedure(unittest.TestCase): class TestClinicalProcedure(unittest.TestCase):
def test_procedure_template_item(self): def test_procedure_template_item(self):
patient, medical_department, practitioner = create_healthcare_docs() patient, medical_department, practitioner = create_healthcare_docs()

View File

@ -226,7 +226,9 @@ let check_and_set_availability = function(frm) {
primary_action_label: __('Book'), primary_action_label: __('Book'),
primary_action: function() { primary_action: function() {
frm.set_value('appointment_time', selected_slot); frm.set_value('appointment_time', selected_slot);
frm.set_value('duration', duration); if (!frm.doc.duration) {
frm.set_value('duration', duration);
}
frm.set_value('practitioner', d.get_value('practitioner')); frm.set_value('practitioner', d.get_value('practitioner'));
frm.set_value('department', d.get_value('department')); frm.set_value('department', d.get_value('department'));
frm.set_value('appointment_date', d.get_value('appointment_date')); frm.set_value('appointment_date', d.get_value('appointment_date'));

View File

@ -132,6 +132,9 @@ def get_conditions(filters):
if filters.get('employee'): if filters.get('employee'):
conditions['name'] = filters.get('employee') conditions['name'] = filters.get('employee')
if filters.get('company'):
conditions['company'] = filters.get('company')
return conditions return conditions
def get_department_leave_approver_map(department=None): def get_department_leave_approver_map(department=None):

View File

@ -494,7 +494,7 @@ class BOM(WebsiteGenerator):
'image' : d.image, 'image' : d.image,
'stock_uom' : d.stock_uom, 'stock_uom' : d.stock_uom,
'stock_qty' : flt(d.stock_qty), 'stock_qty' : flt(d.stock_qty),
'rate' : flt(d.base_rate) / flt(d.conversion_factor), 'rate' : flt(d.base_rate) / (flt(d.conversion_factor) or 1.0),
'include_item_in_manufacturing': d.include_item_in_manufacturing 'include_item_in_manufacturing': d.include_item_in_manufacturing
})) }))

View File

@ -90,6 +90,7 @@ def update_latest_price_in_all_boms():
update_cost() update_cost()
def replace_bom(args): def replace_bom(args):
frappe.db.auto_commit_on_many_writes = 1
args = frappe._dict(args) args = frappe._dict(args)
doc = frappe.get_doc("BOM Update Tool") doc = frappe.get_doc("BOM Update Tool")
@ -97,6 +98,8 @@ def replace_bom(args):
doc.new_bom = args.new_bom doc.new_bom = args.new_bom
doc.replace_bom() doc.replace_bom()
frappe.db.auto_commit_on_many_writes = 0
def update_cost(): def update_cost():
frappe.db.auto_commit_on_many_writes = 1 frappe.db.auto_commit_on_many_writes = 1
bom_list = get_boms_in_bottom_up_order() bom_list = get_boms_in_bottom_up_order()

View File

@ -7,6 +7,7 @@ def execute():
frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True) frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True)
frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True) frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True)
frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True) frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True)
frappe.reload_doc('setup', 'doctype', 'quotation_lost_reason_detail', force=True)
company = frappe.get_all('Company', filters = {'country': 'United States'}) company = frappe.get_all('Company', filters = {'country': 'United States'})
if not company: if not company:

View File

@ -3,6 +3,7 @@ import frappe
def execute(): def execute():
if frappe.db.exists("DocType", "Lost Reason Detail"): if frappe.db.exists("DocType", "Lost Reason Detail"):
frappe.reload_doc("crm", "doctype", "opportunity_lost_reason")
frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail") frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail")
frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail") frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail")
@ -10,8 +11,8 @@ def execute():
frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Quotation'""") frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Quotation'""")
frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason` (`name`, `creation`, `modified`, `modified_by`, `owner`, `docstatus`, `parent`, `parentfield`, `parenttype`, `idx`, `_comments`, `_assign`, `_user_tags`, `_liked_by`, `order_lost_reason`) frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason` (`name`, `creation`, `modified`, `modified_by`, `owner`, `docstatus`, `parent`, `parentfield`, `parenttype`, `idx`, `_comments`, `_assign`, `_user_tags`, `_liked_by`, `order_lost_reason`)
SELECT o.`name`, o.`creation`, o.`modified`, o.`modified_by`, o.`owner`, o.`docstatus`, o.`parent`, o.`parentfield`, o.`parenttype`, o.`idx`, o.`_comments`, o.`_assign`, o.`_user_tags`, o.`_liked_by`, o.`lost_reason` SELECT o.`name`, o.`creation`, o.`modified`, o.`modified_by`, o.`owner`, o.`docstatus`, o.`parent`, o.`parentfield`, o.`parenttype`, o.`idx`, o.`_comments`, o.`_assign`, o.`_user_tags`, o.`_liked_by`, o.`lost_reason`
FROM `tabOpportunity Lost Reason` o LEFT JOIN `tabQuotation Lost Reason` q ON q.name = o.name WHERE q.name IS NULL""") FROM `tabOpportunity Lost Reason` o LEFT JOIN `tabQuotation Lost Reason` q ON q.name = o.name WHERE q.name IS NULL""")
frappe.delete_doc("DocType", "Lost Reason Detail") frappe.delete_doc("DocType", "Lost Reason Detail")

View File

@ -7,4 +7,7 @@ import frappe
def execute(): def execute():
frappe.reload_doc('hr', 'doctype', 'shift_assignment') frappe.reload_doc('hr', 'doctype', 'shift_assignment')
frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date where date IS NOT NULL and start_date IS NULL and end_date IS NULL;") if frappe.db.has_column('Shift Assignment', 'date'):
frappe.db.sql("""update `tabShift Assignment`
set end_date=date, start_date=date
where date IS NOT NULL and start_date IS NULL and end_date IS NULL;""")

View File

@ -21,34 +21,6 @@ frappe.ui.form.on('Homepage', {
}); });
frappe.ui.form.on('Homepage Featured Product', { frappe.ui.form.on('Homepage Featured Product', {
item_code: function(frm, cdt, cdn) {
var featured_product = frappe.model.get_doc(cdt, cdn);
if (featured_product.item_code) {
frappe.call({
method: 'frappe.client.get_value',
args: {
'doctype': 'Item',
'filters': {'name': featured_product.item_code},
'fieldname': [
'item_name',
'web_long_description',
'description',
'image',
'thumbnail'
]
},
callback: function(r) {
if (!r.exc) {
$.extend(featured_product, r.message);
if (r.message.web_long_description) {
featured_product.description = r.message.web_long_description;
}
frm.refresh_field('products');
}
}
});
}
},
view: function(frm, cdt, cdn){ view: function(frm, cdt, cdn){
var child= locals[cdt][cdn] var child= locals[cdt][cdn]

View File

@ -1,301 +1,116 @@
{ {
"allow_copy": 0, "actions": [],
"allow_import": 0, "autoname": "hash",
"allow_rename": 0, "creation": "2016-04-22 05:57:06.261401",
"autoname": "hash", "doctype": "DocType",
"beta": 0, "document_type": "Document",
"creation": "2016-04-22 05:57:06.261401", "editable_grid": 1,
"custom": 0, "engine": "InnoDB",
"docstatus": 0, "field_order": [
"doctype": "DocType", "item_code",
"document_type": "Document", "col_break1",
"editable_grid": 1, "item_name",
"view",
"section_break_5",
"description",
"column_break_7",
"image",
"thumbnail",
"route"
],
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "bold": 1,
"bold": 1, "fieldname": "item_code",
"collapsible": 0, "fieldtype": "Link",
"fieldname": "item_code", "in_filter": 1,
"fieldtype": "Link", "in_list_view": 1,
"hidden": 0, "label": "Item Code",
"ignore_user_permissions": 0, "oldfieldname": "item_code",
"ignore_xss_filter": 0, "oldfieldtype": "Link",
"in_filter": 1, "options": "Item",
"in_list_view": 1, "print_width": "150px",
"label": "Item Code", "reqd": 1,
"length": 0, "search_index": 1,
"no_copy": 0,
"oldfieldname": "item_code",
"oldfieldtype": "Link",
"options": "Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "150px",
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"unique": 0,
"width": "150px" "width": "150px"
}, },
{ {
"allow_on_submit": 0, "fieldname": "col_break1",
"bold": 0, "fieldtype": "Column Break"
"collapsible": 0, },
"fieldname": "col_break1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fetch_from": "item_code.item_name",
"bold": 0, "fetch_if_empty": 1,
"collapsible": 0, "fieldname": "item_name",
"fieldname": "item_name", "fieldtype": "Data",
"fieldtype": "Data", "in_list_view": 1,
"hidden": 0, "label": "Item Name",
"ignore_user_permissions": 0, "oldfieldname": "item_name",
"ignore_xss_filter": 0, "oldfieldtype": "Data",
"in_filter": 0, "print_hide": 1,
"in_list_view": 1, "print_width": "150",
"label": "Item Name", "read_only": 1,
"length": 0, "reqd": 1,
"no_copy": 0,
"oldfieldname": "item_name",
"oldfieldtype": "Data",
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": "150",
"read_only": 1,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150" "width": "150"
}, },
{ {
"allow_on_submit": 0, "fieldname": "view",
"bold": 0, "fieldtype": "Button",
"collapsible": 0, "in_list_view": 1,
"fieldname": "view", "label": "View"
"fieldtype": "Button", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "View",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "collapsible": 1,
"bold": 0, "fieldname": "section_break_5",
"collapsible": 1, "fieldtype": "Section Break",
"fieldname": "section_break_5", "label": "Description"
"fieldtype": "Section Break", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fetch_from": "item_code.web_long_description",
"bold": 0, "fieldname": "description",
"collapsible": 0, "fieldtype": "Text Editor",
"fieldname": "description", "in_filter": 1,
"fieldtype": "Text Editor", "in_list_view": 1,
"hidden": 0, "label": "Description",
"ignore_user_permissions": 0, "oldfieldname": "description",
"ignore_xss_filter": 0, "oldfieldtype": "Small Text",
"in_filter": 1, "print_width": "300px",
"in_list_view": 1,
"label": "Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Small Text",
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "300px",
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "300px" "width": "300px"
}, },
{ {
"allow_on_submit": 0, "fieldname": "column_break_7",
"bold": 0, "fieldtype": "Column Break"
"collapsible": 0, },
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fetch_from": "item_code.website_image",
"bold": 0, "fetch_if_empty": 1,
"collapsible": 0, "fieldname": "image",
"fieldname": "image", "fieldtype": "Attach Image",
"fieldtype": "Attach Image", "label": "Image"
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fieldname": "thumbnail",
"bold": 0, "fieldtype": "Attach Image",
"collapsible": 0, "hidden": 1,
"fieldname": "thumbnail", "label": "Thumbnail"
"fieldtype": "Attach Image", },
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Thumbnail",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fieldname": "route",
"bold": 0, "fieldtype": "Small Text",
"collapsible": 0, "label": "route",
"fieldname": "route", "read_only": 1
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "route",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0, "index_web_pages_for_search": 1,
"hide_toolbar": 0, "istable": 1,
"idx": 0, "links": [],
"image_view": 0, "modified": "2020-08-25 15:27:49.573537",
"in_create": 0, "modified_by": "Administrator",
"module": "Portal",
"is_submittable": 0, "name": "Homepage Featured Product",
"issingle": 0, "owner": "Administrator",
"istable": 1, "permissions": [],
"max_attachments": 0, "quick_entry": 1,
"modified": "2016-08-09 06:09:34.731971", "sort_field": "modified",
"modified_by": "Administrator", "sort_order": "DESC"
"module": "Portal",
"name": "Homepage Featured Product",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
} }

View File

@ -3,7 +3,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Retail Operations", "label": "Retail Operations",
"links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point-of-Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"POS Profile\"\n ],\n \"description\": \"Point of Sale\",\n \"label\": \"POS\",\n \"name\": \"pos\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"description\": \"Cashier Closing\",\n \"label\": \"Cashier Closing\",\n \"name\": \"Cashier Closing\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup mode of POS (Online / Offline)\",\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point of Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"POS Profile\"\n ],\n \"description\": \"Point of Sale\",\n \"label\": \"Point of Sale\",\n \"name\": \"point-of-sale\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"description\": \"Setup mode of POS (Online / Offline)\",\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Cashier Closing\",\n \"label\": \"Cashier Closing\",\n \"name\": \"Cashier Closing\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]"
} }
], ],
"category": "Domains", "category": "Domains",
@ -14,10 +14,11 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Desk Page", "doctype": "Desk Page",
"extends_another_page": 0, "extends_another_page": 0,
"hide_custom": 0,
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Retail", "label": "Retail",
"modified": "2020-04-26 22:42:39.346750", "modified": "2020-08-20 18:00:07.515691",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Retail", "name": "Retail",
@ -25,5 +26,27 @@
"pin_to_bottom": 0, "pin_to_bottom": 0,
"pin_to_top": 0, "pin_to_top": 0,
"restrict_to_domain": "Retail", "restrict_to_domain": "Retail",
"shortcuts": [] "shortcuts": [
{
"color": "#9deca2",
"doc_view": "",
"format": "{} Active",
"label": "Point of Sale Profile",
"link_to": "POS Profile",
"stats_filter": "{\n \"disabled\": 0\n}",
"type": "DocType"
},
{
"doc_view": "",
"label": "Point of Sale",
"link_to": "point-of-sale",
"type": "Page"
},
{
"doc_view": "",
"label": "POS Settings",
"link_to": "POS Settings",
"type": "DocType"
}
]
} }

View File

@ -33,7 +33,7 @@ def get_data():
}, },
{ {
'label': _('Support'), 'label': _('Support'),
'items': ['Issue'] 'items': ['Issue', 'Maintenance Visit', 'Installation Note', 'Warranty Claim']
}, },
{ {
'label': _('Projects'), 'label': _('Projects'),

View File

@ -10,6 +10,7 @@ def get_data():
'Payment Entry': 'reference_name', 'Payment Entry': 'reference_name',
'Payment Request': 'reference_name', 'Payment Request': 'reference_name',
'Auto Repeat': 'reference_document', 'Auto Repeat': 'reference_document',
'Maintenance Visit': 'prevdoc_docname'
}, },
'internal_links': { 'internal_links': {
'Quotation': ['items', 'prevdoc_docname'] 'Quotation': ['items', 'prevdoc_docname']
@ -17,7 +18,7 @@ def get_data():
'transactions': [ 'transactions': [
{ {
'label': _('Fulfillment'), 'label': _('Fulfillment'),
'items': ['Sales Invoice', 'Pick List', 'Delivery Note'] 'items': ['Sales Invoice', 'Pick List', 'Delivery Note', 'Maintenance Visit']
}, },
{ {
'label': _('Purchasing'), 'label': _('Purchasing'),

View File

@ -26,7 +26,8 @@ def delete_company_transactions(company_name):
tabDocField where fieldtype='Link' and options='Company'"""): tabDocField where fieldtype='Link' and options='Company'"""):
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
"Party Account", "Employee", "Sales Taxes and Charges Template", "Party Account", "Employee", "Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template", "POS Profile", 'BOM'): "Purchase Taxes and Charges Template", "POS Profile", "BOM",
"Company", "Bank Account"):
delete_for_doctype(doctype, company_name) delete_for_doctype(doctype, company_name)
# reset company values # reset company values

View File

@ -111,6 +111,7 @@ class Item(WebsiteGenerator):
self.synced_with_hub = 0 self.synced_with_hub = 0
self.validate_has_variants() self.validate_has_variants()
self.validate_attributes_in_variants()
self.validate_stock_exists_for_template_item() self.validate_stock_exists_for_template_item()
self.validate_attributes() self.validate_attributes()
self.validate_variant_attributes() self.validate_variant_attributes()
@ -806,6 +807,77 @@ class Item(WebsiteGenerator):
if frappe.db.exists("Item", {"variant_of": self.name}): if frappe.db.exists("Item", {"variant_of": self.name}):
frappe.throw(_("Item has variants.")) frappe.throw(_("Item has variants."))
def validate_attributes_in_variants(self):
if not self.has_variants or self.get("__islocal"):
return
old_doc = self.get_doc_before_save()
old_doc_attributes = set([attr.attribute for attr in old_doc.attributes])
own_attributes = [attr.attribute for attr in self.attributes]
# Check if old attributes were removed from the list
# Is old_attrs is a subset of new ones
# that means we need not check any changes
if old_doc_attributes.issubset(set(own_attributes)):
return
from collections import defaultdict
# get all item variants
items = [item["name"] for item in frappe.get_all("Item", {"variant_of": self.name})]
# get all deleted attributes
deleted_attribute = list(old_doc_attributes.difference(set(own_attributes)))
# fetch all attributes of these items
item_attributes = frappe.get_all(
"Item Variant Attribute",
filters={
"parent": ["in", items],
"attribute": ["in", deleted_attribute]
},
fields=["attribute", "parent"]
)
not_included = defaultdict(list)
for attr in item_attributes:
if attr["attribute"] not in own_attributes:
not_included[attr["parent"]].append(attr["attribute"])
if not len(not_included):
return
def body(docnames):
docnames.sort()
return "<br>".join(docnames)
def table_row(title, body):
return """<tr>
<td>{0}</td>
<td>{1}</td>
</tr>""".format(title, body)
rows = ''
for docname, attr_list in not_included.items():
link = "<a href='#Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
rows += table_row(link, body(attr_list))
error_description = _('The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template.')
message = """
<div>{0}</div><br>
<table class="table">
<thead>
<td>{1}</td>
<td>{2}</td>
</thead>
{3}
</table>
""".format(error_description, _('Variant Items'), _('Attributes'), rows)
frappe.throw(message, title=_("Variant Attribute Error"), is_minimizable=True, wide=True)
def validate_stock_exists_for_template_item(self): def validate_stock_exists_for_template_item(self):
if self.stock_ledger_created() and self._doc_before_save: if self.stock_ledger_created() and self._doc_before_save:
if (cint(self._doc_before_save.has_variants) != cint(self.has_variants) if (cint(self._doc_before_save.has_variants) != cint(self.has_variants)

View File

@ -227,6 +227,14 @@ class PurchaseReceipt(BuyingController):
if not stock_value_diff: if not stock_value_diff:
continue continue
# If PR is sub-contracted and fg item rate is zero
# in that case if account for shource and target warehouse are same,
# then GL entries should not be posted
if flt(stock_value_diff) == flt(d.rm_supp_cost) \
and warehouse_account.get(self.supplier_warehouse) \
and warehouse_account[d.warehouse]["account"] == warehouse_account[self.supplier_warehouse]["account"]:
continue
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": warehouse_account[d.warehouse]["account"], "account": warehouse_account[d.warehouse]["account"],
"against": stock_rbnb, "against": stock_rbnb,
@ -242,16 +250,16 @@ class PurchaseReceipt(BuyingController):
credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \ credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \
if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount")) if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount"))
if credit_amount:
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": warehouse_account[d.from_warehouse]['account'] \ "account": warehouse_account[d.from_warehouse]['account'] \
if d.from_warehouse else stock_rbnb, if d.from_warehouse else stock_rbnb,
"against": warehouse_account[d.warehouse]["account"], "against": warehouse_account[d.warehouse]["account"],
"cost_center": d.cost_center, "cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")), "debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")),
"debit_in_account_currency": -1 * credit_amount "debit_in_account_currency": -1 * credit_amount
}, credit_currency, item=d)) }, credit_currency, item=d))
negative_expense_to_be_booked += flt(d.item_tax_amount) negative_expense_to_be_booked += flt(d.item_tax_amount)

View File

@ -18,6 +18,28 @@ class TestPurchaseReceipt(unittest.TestCase):
set_perpetual_inventory(0) set_perpetual_inventory(0)
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
def test_reverse_purchase_receipt_sle(self):
frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 0)
pr = make_purchase_receipt(qty=0.5)
sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
"voucher_no": pr.name}, ['actual_qty'])
self.assertEqual(len(sl_entry), 1)
self.assertEqual(sl_entry[0].actual_qty, 0.5)
pr.cancel()
sl_entry_cancelled = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
"voucher_no": pr.name}, ['actual_qty'], order_by='creation')
self.assertEqual(len(sl_entry_cancelled), 2)
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1)
def test_make_purchase_invoice(self): def test_make_purchase_invoice(self):
pr = make_purchase_receipt(do_not_save=True) pr = make_purchase_receipt(do_not_save=True)
self.assertRaises(frappe.ValidationError, make_purchase_invoice, pr.name) self.assertRaises(frappe.ValidationError, make_purchase_invoice, pr.name)
@ -121,6 +143,22 @@ class TestPurchaseReceipt(unittest.TestCase):
rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")]) rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")])
self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
def test_subcontracting_gle_fg_item_rate_zero(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
set_perpetual_inventory()
frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1", qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1",
qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=0, is_subcontracted="Yes",
company="_Test Company with perpetual inventory", warehouse='Stores - TCP1', supplier_warehouse='Work In Progress - TCP1')
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertFalse(gl_entries)
set_perpetual_inventory(0)
def test_serial_no_supplier(self): def test_serial_no_supplier(self):
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"), self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"),
@ -688,7 +726,7 @@ def make_purchase_receipt(**args):
"received_qty": received_qty, "received_qty": received_qty,
"rejected_qty": rejected_qty, "rejected_qty": rejected_qty,
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "", "rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
"rate": args.rate or 50, "rate": args.rate if args.rate != None else 50,
"conversion_factor": args.conversion_factor or 1.0, "conversion_factor": args.conversion_factor or 1.0,
"serial_no": args.serial_no, "serial_no": args.serial_no,
"stock_uom": args.stock_uom or "_Test UOM", "stock_uom": args.stock_uom or "_Test UOM",

View File

@ -31,7 +31,7 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
sle['posting_time'] = now_datetime().strftime('%H:%M:%S.%f') sle['posting_time'] = now_datetime().strftime('%H:%M:%S.%f')
if cancel: if cancel:
sle['actual_qty'] = -flt(sle.get('actual_qty'), 0) sle['actual_qty'] = -flt(sle.get('actual_qty'))
if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'): if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'):
sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code, sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,

View File

@ -209,11 +209,11 @@ function set_time_to_resolve_and_response(frm) {
frm.dashboard.set_headline_alert( frm.dashboard.set_headline_alert(
'<div class="row">' + '<div class="row">' +
'<div class="col-xs-6">' + '<div class="col-xs-12 col-sm-6">' +
'<span class="indicator whitespace-nowrap '+ time_to_respond.indicator +'"><span class="hidden-xs">Time to Respond: '+ time_to_respond.diff_display +'</span></span> ' + '<span class="indicator whitespace-nowrap '+ time_to_respond.indicator +'"><span>Time to Respond: '+ time_to_respond.diff_display +'</span></span> ' +
'</div>' + '</div>' +
'<div class="col-xs-6">' + '<div class="col-xs-12 col-sm-6">' +
'<span class="indicator whitespace-nowrap '+ time_to_resolve.indicator +'"><span class="hidden-xs">Time to Resolve: '+ time_to_resolve.diff_display +'</span></span> ' + '<span class="indicator whitespace-nowrap '+ time_to_resolve.indicator +'"><span>Time to Resolve: '+ time_to_resolve.diff_display +'</span></span> ' +
'</div>' + '</div>' +
'</div>' '</div>'
); );

View File

@ -20,6 +20,13 @@ frappe.ready(() => {
options: 'Email', options: 'Email',
reqd: 1 reqd: 1
}, },
{
fieldtype: 'Data',
label: __('Phone Number'),
fieldname: 'phone',
options: 'Phone',
reqd: 1
},
{ {
fieldtype: 'Data', fieldtype: 'Data',
label: __('Subject'), label: __('Subject'),