Merge branch 'develop' into general-ledger-filters
This commit is contained in:
commit
e7fabb10e1
@ -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 frappe.test_runner import make_test_objects
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
def test_create_test_data():
|
||||
frappe.set_user("Administrator")
|
||||
# create test item
|
||||
@ -95,7 +97,6 @@ def test_create_test_data():
|
||||
})
|
||||
coupon_code.insert()
|
||||
|
||||
|
||||
class TestCouponCode(unittest.TestCase):
|
||||
def setUp(self):
|
||||
test_create_test_data()
|
||||
|
@ -21,7 +21,7 @@ from six import iteritems
|
||||
class POSInvoice(SalesInvoice):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(POSInvoice, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def validate(self):
|
||||
if not cint(self.is_pos):
|
||||
frappe.throw(_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment")))
|
||||
@ -58,7 +58,7 @@ class POSInvoice(SalesInvoice):
|
||||
if self.redeem_loyalty_points and self.loyalty_points:
|
||||
self.apply_loyalty_points()
|
||||
self.set_status(update=True)
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
# run on cancel method of selling controller
|
||||
super(SalesInvoice, self).on_cancel()
|
||||
@ -68,10 +68,10 @@ class POSInvoice(SalesInvoice):
|
||||
against_psi_doc = frappe.get_doc("POS Invoice", self.return_against)
|
||||
against_psi_doc.delete_loyalty_point_entry()
|
||||
against_psi_doc.make_loyalty_point_entry()
|
||||
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||
|
||||
|
||||
for d in self.get('items'):
|
||||
if d.serial_no:
|
||||
filters = {
|
||||
@ -89,11 +89,11 @@ class POSInvoice(SalesInvoice):
|
||||
for s in serial_nos:
|
||||
if s in reserved_serial_nos:
|
||||
invalid_serial_nos.append(s)
|
||||
|
||||
|
||||
if len(invalid_serial_nos):
|
||||
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
||||
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \
|
||||
Please select valid serial no.".format(d.idx, multiple_nos,
|
||||
Please select valid serial no.".format(d.idx, multiple_nos,
|
||||
frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available"))
|
||||
else:
|
||||
if allow_negative_stock:
|
||||
@ -105,9 +105,9 @@ class POSInvoice(SalesInvoice):
|
||||
.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
|
||||
elif flt(available_stock) < flt(d.qty):
|
||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
|
||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
||||
frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
|
||||
|
||||
|
||||
def validate_serialised_or_batched_item(self):
|
||||
for d in self.get("items"):
|
||||
serialized = d.get("has_serial_no")
|
||||
@ -125,7 +125,7 @@ class POSInvoice(SalesInvoice):
|
||||
if batched and no_batch_selected:
|
||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.'
|
||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
||||
|
||||
|
||||
def validate_return_items(self):
|
||||
if not self.get("is_return"): return
|
||||
|
||||
@ -158,7 +158,7 @@ class POSInvoice(SalesInvoice):
|
||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
||||
if self.is_return and entry.amount > 0:
|
||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
||||
|
||||
|
||||
def validate_pos_return(self):
|
||||
if self.is_pos and self.is_return:
|
||||
total_amount_in_payments = 0
|
||||
@ -167,12 +167,12 @@ class POSInvoice(SalesInvoice):
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
if total_amount_in_payments < invoice_total:
|
||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
|
||||
|
||||
|
||||
def validate_loyalty_transaction(self):
|
||||
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
||||
expense_account, cost_center = frappe.db.get_value('Loyalty Program', self.loyalty_program, ["expense_account", "cost_center"])
|
||||
if not self.loyalty_redemption_account:
|
||||
self.loyalty_redemption_account = expense_account
|
||||
self.loyalty_redemption_account = expense_account
|
||||
if not self.loyalty_redemption_cost_center:
|
||||
self.loyalty_redemption_cost_center = cost_center
|
||||
|
||||
@ -212,7 +212,7 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
if update:
|
||||
self.db_set('status', self.status, update_modified = update_modified)
|
||||
|
||||
|
||||
def set_pos_fields(self, for_validate=False):
|
||||
"""Set retail related fields from POS Profiles"""
|
||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||
@ -315,25 +315,25 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
||||
from `tabStock Ledger Entry`
|
||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and warehouse = %s
|
||||
order by posting_date desc, posting_time desc
|
||||
limit 1""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
|
||||
pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
|
||||
where p.name = p_item.parent
|
||||
and p.consolidated_invoice is NULL
|
||||
where p.name = p_item.parent
|
||||
and p.consolidated_invoice is NULL
|
||||
and p.docstatus = 1
|
||||
and p_item.docstatus = 1
|
||||
and p_item.item_code = %s
|
||||
and p_item.warehouse = %s
|
||||
""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
|
||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||
|
||||
|
||||
if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty:
|
||||
return sle_qty - pos_sales_qty
|
||||
else:
|
||||
@ -360,14 +360,14 @@ def make_merge_log(invoices):
|
||||
merge_log = frappe.new_doc("POS Invoice Merge Log")
|
||||
merge_log.posting_date = getdate(nowdate())
|
||||
for inv in invoices:
|
||||
inv_data = frappe.db.get_values("POS Invoice", inv.get('name'),
|
||||
inv_data = frappe.db.get_values("POS Invoice", inv.get('name'),
|
||||
["customer", "posting_date", "grand_total"], as_dict=1)[0]
|
||||
merge_log.customer = inv_data.customer
|
||||
merge_log.append("pos_invoices", {
|
||||
'pos_invoice': inv.get('name'),
|
||||
'customer': inv_data.customer,
|
||||
'posting_date': inv_data.posting_date,
|
||||
'grand_total': inv_data.grand_total
|
||||
'grand_total': inv_data.grand_total
|
||||
})
|
||||
|
||||
if merge_log.get('pos_invoices'):
|
||||
|
@ -8,6 +8,8 @@ import unittest
|
||||
from erpnext.stock.get_item_details import get_pos_profile
|
||||
from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
class TestPOSProfile(unittest.TestCase):
|
||||
def test_pos_profile(self):
|
||||
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_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
|
||||
})
|
||||
|
||||
|
||||
payments = [{
|
||||
'mode_of_payment': 'Cash',
|
||||
'default': 1
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. 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):
|
||||
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):
|
||||
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)
|
||||
|
||||
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 pricing_rules:
|
||||
@ -365,8 +364,9 @@ def set_discount_amount(rate, item_details):
|
||||
item_details.rate = rate
|
||||
|
||||
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
|
||||
for d in json.loads(pricing_rules):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_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
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
|
||||
|
@ -447,9 +447,14 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||
doc.set_missing_values()
|
||||
|
||||
def get_applied_pricing_rules(item_row):
|
||||
return (json.loads(item_row.get("pricing_rules"))
|
||||
if item_row.get("pricing_rules") else [])
|
||||
def get_applied_pricing_rules(pricing_rules):
|
||||
if pricing_rules:
|
||||
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):
|
||||
free_item = pricing_rule.free_item
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-05-22 12:43:10",
|
||||
"doctype": "DocType",
|
||||
@ -82,6 +81,7 @@
|
||||
"item_tax_rate",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
"purchase_invoice_item",
|
||||
"col_break6",
|
||||
"purchase_order",
|
||||
"po_detail",
|
||||
@ -769,12 +769,21 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "col_break7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "purchase_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Purchase Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-22 10:37:35.103176",
|
||||
"modified": "2020-08-20 11:48:01.398356",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -13,7 +13,8 @@ def get_data():
|
||||
'Auto Repeat': 'reference_document',
|
||||
},
|
||||
'internal_links': {
|
||||
'Sales Order': ['items', 'sales_order']
|
||||
'Sales Order': ['items', 'sales_order'],
|
||||
'Delivery Note': ['items', 'delivery_note']
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-06-04 11:02:19",
|
||||
"doctype": "DocType",
|
||||
@ -87,6 +86,7 @@
|
||||
"edit_references",
|
||||
"sales_order",
|
||||
"so_detail",
|
||||
"sales_invoice_item",
|
||||
"column_break_74",
|
||||
"delivery_note",
|
||||
"dn_detail",
|
||||
@ -790,12 +790,22 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
}
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "sales_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Sales Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-18 12:24:41.749986",
|
||||
"modified": "2020-08-20 11:24:41.749986",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
@ -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:
|
||||
currency_info['company'] = d.name
|
||||
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:
|
||||
key = entry.account_number or entry.account_name
|
||||
|
@ -14,7 +14,7 @@ import frappe, erpnext
|
||||
from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from frappe import _
|
||||
from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr)
|
||||
from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr, cint)
|
||||
|
||||
from six import itervalues
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children
|
||||
@ -46,7 +46,7 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
|
||||
start_date = year_start_date
|
||||
months = get_months(year_start_date, year_end_date)
|
||||
|
||||
for i in range(math.ceil(months / months_to_add)):
|
||||
for i in range(cint(math.ceil(months / months_to_add))):
|
||||
period = frappe._dict({
|
||||
"from_date": start_date
|
||||
})
|
||||
@ -423,7 +423,7 @@ def set_gl_entries_by_account(
|
||||
distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
|
||||
|
||||
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:
|
||||
gl_entries_by_account.setdefault(entry.account, []).append(entry)
|
||||
|
@ -146,6 +146,12 @@ frappe.query_reports["General Ledger"] = {
|
||||
return frappe.db.get_link_options('Project', txt);
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "include_dimensions",
|
||||
"label": __("Consider Accounting Dimensions"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "show_opening_entries",
|
||||
"label": __("Show Opening Entries"),
|
||||
|
@ -109,15 +109,20 @@ def set_account_currency(filters):
|
||||
return filters
|
||||
|
||||
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)
|
||||
|
||||
return result
|
||||
|
||||
def get_gl_entries(filters):
|
||||
def get_gl_entries(filters, accounting_dimensions):
|
||||
currency_map = get_currency(filters)
|
||||
select_fields = """, debit, credit, debit_in_account_currency,
|
||||
credit_in_account_currency """
|
||||
@ -131,6 +136,10 @@ def get_gl_entries(filters):
|
||||
filters['company_fb'] = frappe.db.get_value("Company",
|
||||
filters.get("company"), 'default_finance_book')
|
||||
|
||||
dimension_fields = ""
|
||||
if accounting_dimensions:
|
||||
dimension_fields = ', '.join(accounting_dimensions) + ','
|
||||
|
||||
distributed_cost_center_query = ""
|
||||
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,
|
||||
@ -144,7 +153,7 @@ def get_gl_entries(filters):
|
||||
party_type,
|
||||
party,
|
||||
voucher_type,
|
||||
voucher_no,
|
||||
voucher_no, {dimension_fields}
|
||||
cost_center, project,
|
||||
against_voucher_type,
|
||||
against_voucher,
|
||||
@ -163,13 +172,14 @@ def get_gl_entries(filters):
|
||||
{conditions}
|
||||
AND posting_date <= %(to_date)s
|
||||
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(
|
||||
"""
|
||||
select
|
||||
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,
|
||||
remarks, against, is_opening, creation {select_fields}
|
||||
from `tabGL Entry`
|
||||
@ -177,13 +187,13 @@ def get_gl_entries(filters):
|
||||
{distributed_cost_center_query}
|
||||
{order_by_statement}
|
||||
""".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
|
||||
),
|
||||
filters, as_dict=1)
|
||||
|
||||
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:
|
||||
return gl_entries
|
||||
|
||||
@ -250,12 +260,12 @@ def get_conditions(filters):
|
||||
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 = []
|
||||
|
||||
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
|
||||
data.append(totals.opening)
|
||||
@ -321,7 +331,7 @@ def initialize_gle_map(gl_entries, filters):
|
||||
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()
|
||||
entries = []
|
||||
consolidated_gle = OrderedDict()
|
||||
@ -353,8 +363,11 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
|
||||
gle_map[gle.get(group_by)].entries.append(gle)
|
||||
elif filters.get("group_by") == _('Group by Voucher (Consolidated)'):
|
||||
key = (gle.get("voucher_type"), gle.get("voucher_no"),
|
||||
gle.get("account"), gle.get("cost_center"))
|
||||
keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")]
|
||||
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:
|
||||
consolidated_gle.setdefault(key, gle)
|
||||
else:
|
||||
@ -481,7 +494,19 @@ def get_columns(filters):
|
||||
"options": "Project",
|
||||
"fieldname": "project",
|
||||
"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"),
|
||||
"options": "Cost Center",
|
||||
|
@ -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
|
||||
|
||||
__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):
|
||||
"""
|
||||
@ -73,18 +69,7 @@ def get_rate_as_at(date, from_currency, to_currency):
|
||||
|
||||
return rate
|
||||
|
||||
|
||||
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):
|
||||
def convert_to_presentation_currency(gl_entries, currency_info, company):
|
||||
"""
|
||||
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
|
||||
in `currency_info`.
|
||||
@ -96,6 +81,9 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
presentation_currency = currency_info['presentation_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:
|
||||
account = entry['account']
|
||||
debit = flt(entry['debit'])
|
||||
@ -107,7 +95,7 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
if account_currency != presentation_currency:
|
||||
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)
|
||||
|
||||
if entry.get('debit'):
|
||||
|
@ -325,7 +325,7 @@ class AccountsController(TransactionBase):
|
||||
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
|
||||
|
||||
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)
|
||||
for field in ['discount_percentage', 'discount_amount', 'rate']:
|
||||
if item.get(field) < pricing_rule_doc.get(field):
|
||||
|
@ -559,9 +559,19 @@ class BuyingController(StockController):
|
||||
"serial_no": cstr(d.serial_no).strip()
|
||||
})
|
||||
if self.is_return:
|
||||
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": self.return_against,
|
||||
"item_code": d.item_code}, "incoming_rate")
|
||||
filters = {
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.return_against,
|
||||
"item_code": d.item_code
|
||||
}
|
||||
|
||||
if (self.doctype == "Purchase Invoice" and self.update_stock
|
||||
and d.get("purchase_invoice_item")):
|
||||
filters["voucher_detail_no"] = d.purchase_invoice_item
|
||||
elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"):
|
||||
filters["voucher_detail_no"] = d.purchase_receipt_item
|
||||
|
||||
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate")
|
||||
|
||||
sle.update({
|
||||
"outgoing_rate": original_incoming_rate
|
||||
|
@ -497,24 +497,18 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions, bin_conditions = [], []
|
||||
filter_dict = get_doctype_wise_filters(filters)
|
||||
|
||||
sub_query = """ select round(`tabBin`.actual_qty, 2) from `tabBin`
|
||||
where `tabBin`.warehouse = `tabWarehouse`.name
|
||||
{bin_conditions} """.format(
|
||||
bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),
|
||||
bin_conditions, ignore_permissions=True))
|
||||
|
||||
query = """select `tabWarehouse`.name,
|
||||
CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty
|
||||
from `tabWarehouse`
|
||||
CONCAT_WS(" : ", "Actual Qty", ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty
|
||||
from `tabWarehouse` left join `tabBin`
|
||||
on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions}
|
||||
where
|
||||
`tabWarehouse`.`{key}` like {txt}
|
||||
`tabWarehouse`.`{key}` like {txt}
|
||||
{fcond} {mcond}
|
||||
order by
|
||||
`tabWarehouse`.name desc
|
||||
order by ifnull(`tabBin`.actual_qty, 0) desc
|
||||
limit
|
||||
{start}, {page_len}
|
||||
""".format(
|
||||
sub_query=sub_query,
|
||||
bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),bin_conditions, ignore_permissions=True),
|
||||
key=searchfield,
|
||||
fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
|
||||
mcond=get_match_cond(doctype),
|
||||
|
@ -281,6 +281,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
target_doc.po_detail = source_doc.po_detail
|
||||
target_doc.pr_detail = source_doc.pr_detail
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
|
||||
elif doctype == "Delivery Note":
|
||||
target_doc.against_sales_order = source_doc.against_sales_order
|
||||
target_doc.against_sales_invoice = source_doc.against_sales_invoice
|
||||
@ -296,6 +298,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.so_detail = source_doc.so_detail
|
||||
target_doc.dn_detail = source_doc.dn_detail
|
||||
target_doc.expense_account = source_doc.expense_account
|
||||
target_doc.sales_invoice_item = source_doc.name
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
|
||||
|
@ -217,7 +217,9 @@ class SellingController(StockController):
|
||||
'target_warehouse': p.target_warehouse,
|
||||
'company': self.company,
|
||||
'voucher_type': self.doctype,
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'delivery_note_item': d.get("dn_detail")
|
||||
}))
|
||||
else:
|
||||
il.append(frappe._dict({
|
||||
@ -233,7 +235,9 @@ class SellingController(StockController):
|
||||
'target_warehouse': d.target_warehouse,
|
||||
'company': self.company,
|
||||
'voucher_type': self.doctype,
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'delivery_note_item': d.get("dn_detail")
|
||||
}))
|
||||
return il
|
||||
|
||||
@ -302,7 +306,11 @@ class SellingController(StockController):
|
||||
d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
|
||||
return_rate = 0
|
||||
if cint(self.is_return) and self.return_against and self.docstatus==1:
|
||||
return_rate = self.get_incoming_rate_for_return(d.item_code, self.return_against)
|
||||
against_document_no = (d.get("sales_invoice_item")
|
||||
if self.doctype == "Sales Invoice" else d.get("delivery_note_item"))
|
||||
|
||||
return_rate = self.get_incoming_rate_for_return(d.item_code,
|
||||
self.return_against, against_document_no)
|
||||
|
||||
# On cancellation or if return entry submission, make stock ledger entry for
|
||||
# target warehouse first, to update serial no values properly
|
||||
|
@ -301,14 +301,19 @@ class StockController(AccountsController):
|
||||
|
||||
return serialized_items
|
||||
|
||||
def get_incoming_rate_for_return(self, item_code, against_document):
|
||||
def get_incoming_rate_for_return(self, item_code, against_document, against_document_no=None):
|
||||
incoming_rate = 0.0
|
||||
cond = ''
|
||||
if against_document and item_code:
|
||||
if against_document_no:
|
||||
cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no))
|
||||
|
||||
incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where voucher_type = %s and voucher_no = %s
|
||||
and item_code = %s limit 1""",
|
||||
and item_code = %s {0} limit 1""".format(cond),
|
||||
(self.doctype, against_document, item_code))
|
||||
|
||||
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
|
||||
|
||||
return incoming_rate
|
||||
|
@ -325,7 +325,7 @@ def auto_close_opportunity():
|
||||
doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_opportunity_from_communication(communication, ignore_communication_links=False):
|
||||
def make_opportunity_from_communication(communication, company, ignore_communication_links=False):
|
||||
from erpnext.crm.doctype.lead.lead import make_lead_from_communication
|
||||
doc = frappe.get_doc("Communication", communication)
|
||||
|
||||
@ -337,6 +337,7 @@ def make_opportunity_from_communication(communication, ignore_communication_link
|
||||
|
||||
opportunity = frappe.get_doc({
|
||||
"doctype": "Opportunity",
|
||||
"company": company,
|
||||
"opportunity_from": opportunity_from,
|
||||
"party_name": lead
|
||||
}).insert(ignore_permissions=True)
|
||||
|
@ -30,14 +30,14 @@ frappe.ui.form.on('Social Media Post', {
|
||||
let color = frm.doc.twitter_post_id ? "green" : "red";
|
||||
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
|
||||
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>` ;
|
||||
}
|
||||
if (frm.doc.linkedin){
|
||||
let color = frm.doc.linkedin_post_id ? "green" : "red";
|
||||
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
|
||||
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>` ;
|
||||
}
|
||||
html = `<div class="row">${html}</div>`;
|
||||
|
52
erpnext/crm/report/lead_details/lead_details.js
Normal file
52
erpnext/crm/report/lead_details/lead_details.js
Normal 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",
|
||||
}
|
||||
]
|
||||
};
|
@ -7,16 +7,15 @@
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2020-01-22 16:51:56.591110",
|
||||
"modified": "2020-07-26 23:59:49.897577",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead Details",
|
||||
"owner": "Administrator",
|
||||
"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",
|
||||
"report_name": "Lead Details",
|
||||
"report_type": "Query Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
|
158
erpnext/crm/report/lead_details/lead_details.py
Normal file
158
erpnext/crm/report/lead_details/lead_details.py
Normal 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 ""
|
||||
|
67
erpnext/crm/report/lost_opportunity/lost_opportunity.js
Normal file
67
erpnext/crm/report/lost_opportunity/lost_opportunity.js
Normal 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"
|
||||
},
|
||||
]
|
||||
};
|
@ -1,13 +1,14 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"creation": "2018-12-31 16:30:57.188837",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"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}",
|
||||
"modified": "2019-06-26 16:33:08.083618",
|
||||
"modified": "2020-07-29 15:49:02.848845",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lost Opportunity",
|
||||
@ -15,7 +16,7 @@
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Opportunity",
|
||||
"report_name": "Lost Opportunity",
|
||||
"report_type": "Report Builder",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
|
131
erpnext/crm/report/lost_opportunity/lost_opportunity.py
Normal file
131
erpnext/crm/report/lost_opportunity/lost_opportunity.py
Normal 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
|
@ -7,6 +7,8 @@ import unittest
|
||||
import frappe
|
||||
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_clinical_procedure_template
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
class TestClinicalProcedure(unittest.TestCase):
|
||||
def test_procedure_template_item(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
|
@ -17,10 +17,10 @@
|
||||
"payroll_cost_center",
|
||||
"column_break_9",
|
||||
"leave_block_list",
|
||||
"leave_section",
|
||||
"approvers",
|
||||
"leave_approvers",
|
||||
"expense_section",
|
||||
"expense_approvers",
|
||||
"shift_request_approver",
|
||||
"lft",
|
||||
"rgt",
|
||||
"old_parent"
|
||||
@ -33,14 +33,18 @@
|
||||
"label": "Department",
|
||||
"oldfieldname": "department_name",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parent_department",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Parent Department",
|
||||
"options": "Department"
|
||||
"options": "Department",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
@ -48,7 +52,9 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@ -56,17 +62,23 @@
|
||||
"fieldname": "is_group",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Group"
|
||||
"label": "Is Group",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
"label": "Disabled",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"description": "Days for which Holidays are blocked for this department.",
|
||||
@ -74,31 +86,25 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Leave Block List",
|
||||
"options": "Leave Block List"
|
||||
"options": "Leave Block List",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "leave_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Leave Approvers"
|
||||
},
|
||||
{
|
||||
"description": "The first Leave Approver in the list will be set as the default Leave Approver.",
|
||||
"fieldname": "leave_approvers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Leave Approver",
|
||||
"options": "Department Approver"
|
||||
"options": "Department Approver",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "expense_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Expense Approvers"
|
||||
},
|
||||
{
|
||||
"description": "The first Expense Approver in the list will be set as the default Expense Approver.",
|
||||
"fieldname": "expense_approvers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Expense Approver",
|
||||
"options": "Department Approver"
|
||||
"options": "Department Approver",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "lft",
|
||||
@ -106,7 +112,9 @@
|
||||
"hidden": 1,
|
||||
"label": "lft",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rgt",
|
||||
@ -114,7 +122,9 @@
|
||||
"hidden": 1,
|
||||
"label": "rgt",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "old_parent",
|
||||
@ -122,28 +132,52 @@
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Old Parent",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "payroll_cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payroll Cost Center",
|
||||
"options": "Cost Center"
|
||||
"options": "Cost Center",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"description": "The first Approver in the list will be set as the default Approver.",
|
||||
"fieldname": "approvers",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Approvers",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shift_request_approver",
|
||||
"fieldtype": "Table",
|
||||
"label": "Shift Request Approver",
|
||||
"options": "Department Approver",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-sitemap",
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-05 18:49:28.503931",
|
||||
"modified": "2020-06-23 15:42:00.563272",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Department",
|
||||
|
@ -15,12 +15,12 @@ class DepartmentApprover(Document):
|
||||
def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
if not filters.get("employee"):
|
||||
frappe.throw(_("Please select Employee Record first."))
|
||||
frappe.throw(_("Please select Employee first."))
|
||||
|
||||
approvers = []
|
||||
department_details = {}
|
||||
department_list = []
|
||||
employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver"], as_dict=True)
|
||||
employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
|
||||
|
||||
employee_department = filters.get("department") or employee.department
|
||||
if employee_department:
|
||||
@ -37,13 +37,18 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("doctype") == "Expense Claim" and employee.expense_approver:
|
||||
approvers.append(frappe.db.get_value("User", employee.expense_approver, ['name', 'first_name', 'last_name']))
|
||||
|
||||
if filters.get("doctype") == "Shift Request" and employee.shift_request_approver:
|
||||
approvers.append(frappe.db.get_value("User", employee.shift_request_approver, ['name', 'first_name', 'last_name']))
|
||||
|
||||
if filters.get("doctype") == "Leave Application":
|
||||
parentfield = "leave_approvers"
|
||||
field_name = "Leave Approver"
|
||||
else:
|
||||
elif filters.get("doctype") == "Expense Claim":
|
||||
parentfield = "expense_approvers"
|
||||
field_name = "Expense Approver"
|
||||
elif filters.get("doctype") == "Shift Request":
|
||||
parentfield = "shift_request_approver"
|
||||
field_name = "Shift Request Approver"
|
||||
if department_list:
|
||||
for d in department_list:
|
||||
approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
|
||||
|
@ -51,10 +51,14 @@
|
||||
"column_break_31",
|
||||
"grade",
|
||||
"branch",
|
||||
"approvers_section",
|
||||
"expense_approver",
|
||||
"leave_approver",
|
||||
"column_break_45",
|
||||
"shift_request_approver",
|
||||
"attendance_and_leave_details",
|
||||
"leave_policy",
|
||||
"attendance_device_id",
|
||||
"leave_approver",
|
||||
"column_break_44",
|
||||
"holiday_list",
|
||||
"default_shift",
|
||||
@ -62,7 +66,6 @@
|
||||
"salary_mode",
|
||||
"payroll_cost_center",
|
||||
"column_break_52",
|
||||
"expense_approver",
|
||||
"bank_name",
|
||||
"bank_ac_no",
|
||||
"health_insurance_section",
|
||||
@ -806,14 +809,37 @@
|
||||
"fieldname": "expense_approver",
|
||||
"fieldtype": "Link",
|
||||
"label": "Expense Approver",
|
||||
"options": "User"
|
||||
"options": "User",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "approvers_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Approvers",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_45",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shift_request_approver",
|
||||
"fieldtype": "Link",
|
||||
"label": "Shift Request Approver",
|
||||
"options": "User",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-07-03 21:28:04.109189",
|
||||
"modified": "2020-07-28 01:36:04.109189",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee",
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe import _
|
||||
@ -24,8 +25,7 @@ class JobOffer(Document):
|
||||
check_vacancies = frappe.get_single("HR Settings").check_vacancies
|
||||
if staffing_plan and check_vacancies:
|
||||
job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date)
|
||||
|
||||
if not staffing_plan.get("vacancies") or staffing_plan.vacancies - len(job_offers) <= 0:
|
||||
if not staffing_plan.get("vacancies") or cint(staffing_plan.vacancies) - len(job_offers) <= 0:
|
||||
error_variable = 'for ' + frappe.bold(self.designation)
|
||||
if staffing_plan.get("parent"):
|
||||
error_variable = frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent))
|
||||
@ -65,7 +65,7 @@ def get_staffing_plan_detail(designation, company, offer_date):
|
||||
AND %s between sp.from_date and sp.to_date
|
||||
""", (designation, company, offer_date), as_dict=1)
|
||||
|
||||
return frappe._dict(detail[0]) if detail else None
|
||||
return frappe._dict(detail[0]) if (detail and detail[0].parent) else None
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_employee(source_name, target_doc=None):
|
||||
|
@ -10,9 +10,11 @@
|
||||
"employee",
|
||||
"employee_name",
|
||||
"shift_type",
|
||||
"status",
|
||||
"column_break_3",
|
||||
"company",
|
||||
"date",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"shift_request",
|
||||
"department",
|
||||
"amended_from"
|
||||
@ -59,12 +61,6 @@
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "shift_request",
|
||||
"fieldtype": "Link",
|
||||
@ -80,11 +76,36 @@
|
||||
"options": "Shift Assignment",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "End Date",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "Active",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "Active\nInactive",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2019-12-12 15:49:06.956901",
|
||||
"modified": "2020-06-15 14:27:54.310773",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Shift Assignment",
|
||||
|
@ -11,38 +11,63 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
|
||||
class ShiftAssignment(Document):
|
||||
def validate(self):
|
||||
self.validate_overlapping_dates()
|
||||
|
||||
if self.end_date and self.end_date <= self.start_date:
|
||||
frappe.throw(_("End Date must not be lesser than Start Date"))
|
||||
|
||||
def validate_overlapping_dates(self):
|
||||
if not self.name:
|
||||
self.name = "New Shift Assignment"
|
||||
if not self.name:
|
||||
self.name = "New Shift Assignment"
|
||||
|
||||
d = frappe.db.sql("""
|
||||
select
|
||||
name, shift_type, date
|
||||
from `tabShift Assignment`
|
||||
where employee = %(employee)s and docstatus < 2
|
||||
and date = %(date)s
|
||||
and name != %(name)s""", {
|
||||
"employee": self.employee,
|
||||
"shift_type": self.shift_type,
|
||||
"date": self.date,
|
||||
"name": self.name
|
||||
}, as_dict = 1)
|
||||
condition = """and (
|
||||
end_date is null
|
||||
or
|
||||
%(start_date)s between start_date and end_date
|
||||
"""
|
||||
|
||||
for date_overlap in d:
|
||||
if date_overlap['name']:
|
||||
self.throw_overlap_error(date_overlap)
|
||||
if self.end_date:
|
||||
condition += """ or
|
||||
%(end_date)s between start_date and end_date
|
||||
or
|
||||
start_date between %(start_date)s and %(end_date)s
|
||||
) """
|
||||
else:
|
||||
condition += """ ) """
|
||||
|
||||
def throw_overlap_error(self, d):
|
||||
msg = _("Employee {0} has already applied for {1} on {2} : ").format(self.employee,
|
||||
d['shift_type'], formatdate(d['date'])) \
|
||||
+ """ <b><a href="#Form/Shift Assignment/{0}">{0}</a></b>""".format(d["name"])
|
||||
frappe.throw(msg, OverlapError)
|
||||
assigned_shifts = frappe.db.sql("""
|
||||
select name, shift_type, start_date ,end_date, docstatus, status
|
||||
from `tabShift Assignment`
|
||||
where
|
||||
employee=%(employee)s and docstatus = 1
|
||||
and name != %(name)s
|
||||
and status = "Active"
|
||||
{0}
|
||||
""".format(condition), {
|
||||
"employee": self.employee,
|
||||
"shift_type": self.shift_type,
|
||||
"start_date": self.start_date,
|
||||
"end_date": self.end_date,
|
||||
"name": self.name
|
||||
}, as_dict = 1)
|
||||
|
||||
if len(assigned_shifts):
|
||||
self.throw_overlap_error(assigned_shifts[0])
|
||||
|
||||
def throw_overlap_error(self, shift_details):
|
||||
shift_details = frappe._dict(shift_details)
|
||||
if shift_details.docstatus == 1 and shift_details.status == "Active":
|
||||
msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name))
|
||||
if shift_details.start_date:
|
||||
msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y"))
|
||||
title = "Ongoing Shift"
|
||||
if shift_details.end_date:
|
||||
msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y"))
|
||||
title = "Active Shift"
|
||||
if msg:
|
||||
frappe.throw(msg, title=title)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end, filters=None):
|
||||
@ -62,19 +87,22 @@ def get_events(start, end, filters=None):
|
||||
return events
|
||||
|
||||
def add_assignments(events, start, end, conditions=None):
|
||||
query = """select name, date, employee_name,
|
||||
query = """select name, start_date, end_date, employee_name,
|
||||
employee, docstatus
|
||||
from `tabShift Assignment` where
|
||||
date <= %(date)s
|
||||
and docstatus < 2"""
|
||||
start_date >= %(start_date)s
|
||||
or end_date <= %(end_date)s
|
||||
or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date)
|
||||
and docstatus = 1"""
|
||||
if conditions:
|
||||
query += conditions
|
||||
|
||||
for d in frappe.db.sql(query, {"date":start, "date":end}, as_dict=True):
|
||||
for d in frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True):
|
||||
e = {
|
||||
"name": d.name,
|
||||
"doctype": "Shift Assignment",
|
||||
"date": d.date,
|
||||
"start_date": d.start_date,
|
||||
"end_date": d.end_date if d.end_date else nowdate(),
|
||||
"title": cstr(d.employee_name) + \
|
||||
cstr(d.shift_type),
|
||||
"docstatus": d.docstatus
|
||||
@ -92,7 +120,16 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals
|
||||
:param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date.
|
||||
"""
|
||||
default_shift = frappe.db.get_value('Employee', employee, 'default_shift')
|
||||
shift_type_name = frappe.db.get_value('Shift Assignment', {'employee':employee, 'date': for_date, 'docstatus': '1'}, 'shift_type')
|
||||
shift_type_name = None
|
||||
shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date'])
|
||||
|
||||
if shift_assignment_details:
|
||||
shift_type_name = shift_assignment_details[0]
|
||||
|
||||
# if end_date present means that shift is over after end_date else it is a ongoing shift.
|
||||
if shift_assignment_details[1] and for_date >= shift_assignment_details[1] :
|
||||
shift_type_name = None
|
||||
|
||||
if not shift_type_name and consider_default_shift:
|
||||
shift_type_name = default_shift
|
||||
if shift_type_name:
|
||||
@ -117,16 +154,20 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals
|
||||
direction = '<' if next_shift_direction == 'reverse' else '>'
|
||||
sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc'
|
||||
dates = frappe.db.get_all('Shift Assignment',
|
||||
'date',
|
||||
{'employee':employee, 'date':(direction, for_date), 'docstatus': '1'},
|
||||
['start_date', 'end_date'],
|
||||
{'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"},
|
||||
as_list=True,
|
||||
limit=MAX_DAYS, order_by="date "+sort_order)
|
||||
for date in dates:
|
||||
shift_details = get_employee_shift(employee, date[0], consider_default_shift, None)
|
||||
if shift_details:
|
||||
shift_type_name = shift_details.shift_type.name
|
||||
for_date = date[0]
|
||||
break
|
||||
limit=MAX_DAYS, order_by="start_date "+sort_order)
|
||||
|
||||
if dates:
|
||||
for date in dates:
|
||||
if date[1] and date[1] < for_date:
|
||||
continue
|
||||
shift_details = get_employee_shift(employee, date[0], consider_default_shift, None)
|
||||
if shift_details:
|
||||
shift_type_name = shift_details.shift_type.name
|
||||
for_date = date[0]
|
||||
break
|
||||
|
||||
return get_shift_details(shift_type_name, for_date)
|
||||
|
||||
@ -134,7 +175,7 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals
|
||||
def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_default_shift=False):
|
||||
"""Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee
|
||||
"""
|
||||
# write and verify a test case for midnight shift.
|
||||
# write and verify a test case for midnight shift.
|
||||
prev_shift = curr_shift = next_shift = None
|
||||
curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward')
|
||||
if curr_shift:
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
frappe.views.calendar["Shift Assignment"] = {
|
||||
field_map: {
|
||||
"start": "date",
|
||||
"end": "date",
|
||||
"start": "start_date",
|
||||
"end": "end_date",
|
||||
"id": "name",
|
||||
"docstatus": 1
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import nowdate
|
||||
from frappe.utils import nowdate, add_days
|
||||
|
||||
test_dependencies = ["Shift Type"]
|
||||
|
||||
@ -20,8 +20,61 @@ class TestShiftAssignment(unittest.TestCase):
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"date": nowdate()
|
||||
"start_date": nowdate()
|
||||
}).insert()
|
||||
shift_assignment.submit()
|
||||
|
||||
self.assertEqual(shift_assignment.docstatus, 1)
|
||||
|
||||
def test_overlapping_for_ongoing_shift(self):
|
||||
# shift should be Ongoing if Only start_date is present and status = Active
|
||||
|
||||
shift_assignment_1 = frappe.get_doc({
|
||||
"doctype": "Shift Assignment",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"start_date": nowdate(),
|
||||
"status": 'Active'
|
||||
}).insert()
|
||||
shift_assignment_1.submit()
|
||||
|
||||
self.assertEqual(shift_assignment_1.docstatus, 1)
|
||||
|
||||
shift_assignment = frappe.get_doc({
|
||||
"doctype": "Shift Assignment",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"start_date": add_days(nowdate(), 2)
|
||||
})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, shift_assignment.save)
|
||||
|
||||
def test_overlapping_for_fixed_period_shift(self):
|
||||
# shift should is for Fixed period if Only start_date and end_date both are present and status = Active
|
||||
|
||||
shift_assignment_1 = frappe.get_doc({
|
||||
"doctype": "Shift Assignment",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"start_date": nowdate(),
|
||||
"end_date": add_days(nowdate(), 30),
|
||||
"status": 'Active'
|
||||
}).insert()
|
||||
shift_assignment_1.submit()
|
||||
|
||||
|
||||
# it should not allowed within period of any shift.
|
||||
shift_assignment_3 = frappe.get_doc({
|
||||
"doctype": "Shift Assignment",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"start_date":add_days(nowdate(), 10),
|
||||
"end_date": add_days(nowdate(), 35),
|
||||
"status": 'Active'
|
||||
})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, shift_assignment_3.save)
|
@ -2,7 +2,16 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Shift Request', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
setup: function(frm) {
|
||||
frm.set_query("approver", function() {
|
||||
return {
|
||||
query: "erpnext.hr.doctype.department_approver.department_approver.get_approvers",
|
||||
filters: {
|
||||
employee: frm.doc.employee,
|
||||
doctype: frm.doc.doctype
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query("employee", erpnext.queries.employee);
|
||||
},
|
||||
});
|
||||
|
@ -1,396 +1,155 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "HR-SHR-.YY.-.MM.-.#####",
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 16:32:27.974273",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"autoname": "HR-SHR-.YY.-.MM.-.#####",
|
||||
"creation": "2018-04-13 16:32:27.974273",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"shift_type",
|
||||
"employee",
|
||||
"employee_name",
|
||||
"department",
|
||||
"status",
|
||||
"column_break_4",
|
||||
"company",
|
||||
"approver",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "shift_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Shift Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Shift Type",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "shift_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Shift Type",
|
||||
"options": "Shift Type",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Employee",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Employee",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Employee",
|
||||
"options": "Employee",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "employee.employee_name",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Employee Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fetch_from": "employee.employee_name",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Employee Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "employee.department",
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Department",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Department",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fetch_from": "employee.department",
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
"label": "Department",
|
||||
"options": "Department",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "From Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "To Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Shift Request",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Shift Request",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Draft",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "Draft\nApproved\nRejected",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.shift_request_approver",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "approver",
|
||||
"fieldtype": "Link",
|
||||
"label": "Approver",
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:36.577448",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Shift Request",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-10 17:59:31.550558",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Shift Request",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "employee_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "employee_name",
|
||||
"track_changes": 1
|
||||
}
|
@ -14,19 +14,26 @@ class ShiftRequest(Document):
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_shift_request_overlap_dates()
|
||||
self.validate_approver()
|
||||
self.validate_default_shift()
|
||||
|
||||
def on_submit(self):
|
||||
date_list = self.get_working_days(self.from_date, self.to_date)
|
||||
for date in date_list:
|
||||
if self.status not in ["Approved", "Rejected"]:
|
||||
frappe.throw(_("Only Shift Request with status 'Approved' and 'Rejected' can be submitted"))
|
||||
if self.status == "Approved":
|
||||
assignment_doc = frappe.new_doc("Shift Assignment")
|
||||
assignment_doc.company = self.company
|
||||
assignment_doc.shift_type = self.shift_type
|
||||
assignment_doc.employee = self.employee
|
||||
assignment_doc.date = date
|
||||
assignment_doc.start_date = self.from_date
|
||||
if self.to_date:
|
||||
assignment_doc.end_date = self.to_date
|
||||
assignment_doc.shift_request = self.name
|
||||
assignment_doc.insert()
|
||||
assignment_doc.submit()
|
||||
|
||||
frappe.msgprint(_("Shift Assignment: {0} created for Employee: {1}").format(frappe.bold(assignment_doc.name), frappe.bold(self.employee)))
|
||||
|
||||
def on_cancel(self):
|
||||
shift_assignment_list = frappe.get_list("Shift Assignment", {'employee': self.employee, 'shift_request': self.name})
|
||||
if shift_assignment_list:
|
||||
@ -34,6 +41,19 @@ class ShiftRequest(Document):
|
||||
shift_assignment_doc = frappe.get_doc("Shift Assignment", shift['name'])
|
||||
shift_assignment_doc.cancel()
|
||||
|
||||
def validate_default_shift(self):
|
||||
default_shift = frappe.get_value("Employee", self.employee, "default_shift")
|
||||
if self.shift_type == default_shift:
|
||||
frappe.throw(_("You can not request for your Default Shift: {0}").format(frappe.bold(self.shift_type)))
|
||||
|
||||
def validate_approver(self):
|
||||
department = frappe.get_value("Employee", self.employee, "department")
|
||||
shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver")
|
||||
approvers = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))
|
||||
approvers = [approver[0] for approver in approvers]
|
||||
approvers.append(shift_approver)
|
||||
if self.approver not in approvers:
|
||||
frappe.throw(_("Only Approvers can Approve this Request."))
|
||||
|
||||
def validate_dates(self):
|
||||
if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)):
|
||||
@ -68,28 +88,4 @@ class ShiftRequest(Document):
|
||||
msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
|
||||
d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
|
||||
+ """ <b><a href="#Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
|
||||
frappe.throw(msg, OverlapError)
|
||||
|
||||
def get_working_days(self, start_date, end_date):
|
||||
start_date, end_date = getdate(start_date), getdate(end_date)
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
date_list = []
|
||||
employee_holiday_list = []
|
||||
|
||||
employee_holidays = frappe.db.sql("""select holiday_date from `tabHoliday`
|
||||
where parent in (select holiday_list from `tabEmployee`
|
||||
where name = %s)""",self.employee,as_dict=1)
|
||||
|
||||
for d in employee_holidays:
|
||||
employee_holiday_list.append(d.holiday_date)
|
||||
|
||||
reference_date = start_date
|
||||
|
||||
while reference_date <= end_date:
|
||||
if reference_date not in employee_holiday_list:
|
||||
date_list.append(reference_date)
|
||||
reference_date += timedelta(days=1)
|
||||
|
||||
return date_list
|
||||
frappe.throw(msg, OverlapError)
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import nowdate
|
||||
from frappe.utils import nowdate, add_days
|
||||
|
||||
class TestShiftRequest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -13,14 +13,20 @@ class TestShiftRequest(unittest.TestCase):
|
||||
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
|
||||
|
||||
def test_make_shift_request(self):
|
||||
department = frappe.get_value("Employee", "_T-Employee-00001", 'department')
|
||||
set_shift_approver(department)
|
||||
approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
|
||||
|
||||
shift_request = frappe.get_doc({
|
||||
"doctype": "Shift Request",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"employee_name": "_Test Employee",
|
||||
"start_date": nowdate(),
|
||||
"end_date": nowdate()
|
||||
"from_date": nowdate(),
|
||||
"to_date": add_days(nowdate(), 10),
|
||||
"approver": approver,
|
||||
"status": "Approved"
|
||||
})
|
||||
shift_request.insert()
|
||||
shift_request.submit()
|
||||
@ -34,4 +40,10 @@ class TestShiftRequest(unittest.TestCase):
|
||||
self.assertEqual(shift_request.employee, employee)
|
||||
shift_request.cancel()
|
||||
shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')})
|
||||
self.assertEqual(shift_assignment_doc.docstatus, 2)
|
||||
self.assertEqual(shift_assignment_doc.docstatus, 2)
|
||||
|
||||
def set_shift_approver(department):
|
||||
department_doc = frappe.get_doc("Department", department)
|
||||
department_doc.append('shift_request_approver',{'approver': "test1@example.com"})
|
||||
department_doc.save()
|
||||
department_doc.reload()
|
@ -4,7 +4,7 @@
|
||||
frappe.ui.form.on('Shift Type', {
|
||||
refresh: function(frm) {
|
||||
frm.add_custom_button(
|
||||
'Mark Auto Attendance',
|
||||
'Mark Attendance',
|
||||
() => frm.call({
|
||||
doc: frm.doc,
|
||||
method: 'process_auto_attendance',
|
||||
|
@ -79,9 +79,10 @@ class ShiftType(Document):
|
||||
mark_attendance(employee, date, 'Absent', self.name)
|
||||
|
||||
def get_assigned_employee(self, from_date=None, consider_default_shift=False):
|
||||
filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'}
|
||||
filters = {'start_date':('>', from_date), 'shift_type': self.name, 'docstatus': '1'}
|
||||
if not from_date:
|
||||
del filters['date']
|
||||
del filters["start_date"]
|
||||
|
||||
assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True)
|
||||
assigned_employees = [x[0] for x in assigned_employees]
|
||||
|
||||
|
@ -132,6 +132,9 @@ def get_conditions(filters):
|
||||
if filters.get('employee'):
|
||||
conditions['name'] = filters.get('employee')
|
||||
|
||||
if filters.get('company'):
|
||||
conditions['company'] = filters.get('company')
|
||||
|
||||
return conditions
|
||||
|
||||
def get_department_leave_approver_map(department=None):
|
||||
|
@ -494,7 +494,7 @@ class BOM(WebsiteGenerator):
|
||||
'image' : d.image,
|
||||
'stock_uom' : d.stock_uom,
|
||||
'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
|
||||
}))
|
||||
|
||||
|
@ -721,3 +721,4 @@ erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes
|
||||
erpnext.patches.v13_0.stock_entry_enhancements
|
||||
erpnext.patches.v12_0.update_state_code_for_daman_and_diu
|
||||
erpnext.patches.v12_0.rename_lost_reason_detail
|
||||
erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment
|
||||
|
@ -7,6 +7,7 @@ def execute():
|
||||
frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', 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('setup', 'doctype', 'quotation_lost_reason_detail', force=True)
|
||||
|
||||
company = frappe.get_all('Company', filters = {'country': 'United States'})
|
||||
if not company:
|
||||
|
@ -3,6 +3,7 @@ import frappe
|
||||
|
||||
def execute():
|
||||
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("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` (`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`
|
||||
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`
|
||||
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")
|
@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('hr', 'doctype', 'shift_assignment')
|
||||
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;""")
|
@ -21,34 +21,6 @@ frappe.ui.form.on('Homepage', {
|
||||
});
|
||||
|
||||
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){
|
||||
var child= locals[cdt][cdn]
|
||||
|
@ -1,301 +1,116 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2016-04-22 05:57:06.261401",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2016-04-22 05:57:06.261401",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"col_break1",
|
||||
"item_name",
|
||||
"view",
|
||||
"section_break_5",
|
||||
"description",
|
||||
"column_break_7",
|
||||
"image",
|
||||
"thumbnail",
|
||||
"route"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"length": 0,
|
||||
"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,
|
||||
"bold": 1,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"oldfieldname": "item_code",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item",
|
||||
"print_width": "150px",
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"width": "150px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Name",
|
||||
"length": 0,
|
||||
"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,
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Name",
|
||||
"oldfieldname": "item_name",
|
||||
"oldfieldtype": "Data",
|
||||
"print_hide": 1,
|
||||
"print_width": "150",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"width": "150"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "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
|
||||
},
|
||||
"fieldname": "view",
|
||||
"fieldtype": "Button",
|
||||
"in_list_view": 1,
|
||||
"label": "View"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_5",
|
||||
"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
|
||||
},
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"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,
|
||||
"fetch_from": "item_code.web_long_description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "300px",
|
||||
"width": "300px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach 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
|
||||
},
|
||||
"fetch_from": "item_code.website_image",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "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
|
||||
},
|
||||
"fieldname": "thumbnail",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 1,
|
||||
"label": "Thumbnail"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "route",
|
||||
"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
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "route",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-08-09 06:09:34.731971",
|
||||
"modified_by": "Administrator",
|
||||
"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
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-25 15:27:49.573537",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Portal",
|
||||
"name": "Homepage Featured Product",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -7,7 +7,7 @@ frappe.ui.form.on("Communication", {
|
||||
},
|
||||
|
||||
setup_custom_buttons: (frm) => {
|
||||
let confirm_msg = "Are you sure you want to create {0} from this email";
|
||||
let confirm_msg = "Are you sure you want to create {0} from this email?";
|
||||
if(frm.doc.reference_doctype !== "Issue") {
|
||||
frm.add_custom_button(__("Issue"), () => {
|
||||
frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
|
||||
@ -62,17 +62,36 @@ frappe.ui.form.on("Communication", {
|
||||
},
|
||||
|
||||
make_opportunity_from_communication: (frm) => {
|
||||
return frappe.call({
|
||||
method: "erpnext.crm.doctype.opportunity.opportunity.make_opportunity_from_communication",
|
||||
args: {
|
||||
communication: frm.doc.name
|
||||
},
|
||||
freeze: true,
|
||||
callback: (r) => {
|
||||
if(r.message) {
|
||||
frm.reload_doc()
|
||||
const fields = [{
|
||||
fieldtype: 'Link',
|
||||
label: __('Select a Company'),
|
||||
fieldname: 'company',
|
||||
options: 'Company',
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_user_default("Company")
|
||||
}];
|
||||
|
||||
frappe.prompt(fields, data => {
|
||||
frappe.call({
|
||||
method: "erpnext.crm.doctype.opportunity.opportunity.make_opportunity_from_communication",
|
||||
args: {
|
||||
communication: frm.doc.name,
|
||||
company: data.company
|
||||
},
|
||||
freeze: true,
|
||||
callback: (r) => {
|
||||
if(r.message) {
|
||||
frm.reload_doc();
|
||||
frappe.show_alert({
|
||||
message: __("Opportunity {0} created",
|
||||
['<a href="#Form/Opportunity/'+r.message+'">' + r.message + '</a>']),
|
||||
indicator: 'green'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
'Create an Opportunity',
|
||||
'Create');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -22,6 +22,8 @@
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
margin-left: -5px;
|
||||
padding-left: 5px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, re, json
|
||||
from frappe import _
|
||||
import erpnext
|
||||
from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words
|
||||
from erpnext.regional.india import states, state_numbers
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
|
||||
@ -673,25 +674,34 @@ def update_grand_total_for_rcm(doc, method):
|
||||
if country != 'India':
|
||||
return
|
||||
|
||||
if not doc.total_taxes_and_charges:
|
||||
return
|
||||
|
||||
if doc.reverse_charge == 'Y':
|
||||
gst_accounts = get_gst_accounts(doc.company)
|
||||
gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
|
||||
+ gst_accounts.get('igst_account')
|
||||
|
||||
base_gst_tax = 0
|
||||
gst_tax = 0
|
||||
|
||||
for tax in doc.get('taxes'):
|
||||
if tax.category not in ("Total", "Valuation and Total"):
|
||||
continue
|
||||
|
||||
if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
|
||||
gst_tax += tax.base_tax_amount_after_discount_amount
|
||||
base_gst_tax += tax.base_tax_amount_after_discount_amount
|
||||
gst_tax += tax.tax_amount_after_discount_amount
|
||||
|
||||
doc.taxes_and_charges_added -= gst_tax
|
||||
doc.total_taxes_and_charges -= gst_tax
|
||||
doc.base_taxes_and_charges_added -= base_gst_tax
|
||||
doc.base_total_taxes_and_charges -= base_gst_tax
|
||||
|
||||
update_totals(gst_tax, doc)
|
||||
update_totals(gst_tax, base_gst_tax, doc)
|
||||
|
||||
def update_totals(gst_tax, doc):
|
||||
def update_totals(gst_tax, base_gst_tax, doc):
|
||||
doc.base_grand_total -= base_gst_tax
|
||||
doc.grand_total -= gst_tax
|
||||
|
||||
if doc.meta.get_field("rounded_total"):
|
||||
@ -707,13 +717,14 @@ def update_totals(gst_tax, doc):
|
||||
doc.outstanding_amount = doc.rounded_total or doc.grand_total
|
||||
|
||||
doc.in_words = money_in_words(doc.grand_total, doc.currency)
|
||||
doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company))
|
||||
doc.set_payment_schedule()
|
||||
|
||||
def make_regional_gl_entries(gl_entries, doc):
|
||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||
|
||||
if country != 'India':
|
||||
return
|
||||
return gl_entries
|
||||
|
||||
if doc.reverse_charge == 'Y':
|
||||
gst_accounts = get_gst_accounts(doc.company)
|
||||
@ -724,6 +735,7 @@ def make_regional_gl_entries(gl_entries, doc):
|
||||
if tax.category not in ("Total", "Valuation and Total"):
|
||||
continue
|
||||
|
||||
dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
|
||||
if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
|
||||
@ -733,8 +745,8 @@ def make_regional_gl_entries(gl_entries, doc):
|
||||
"cost_center": tax.cost_center,
|
||||
"posting_date": doc.posting_date,
|
||||
"against": doc.supplier,
|
||||
"credit": tax.base_tax_amount_after_discount_amount,
|
||||
"credits_in_account_currency": tax.base_tax_amount_after_discount_amount \
|
||||
dr_or_cr: tax.base_tax_amount_after_discount_amount,
|
||||
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
|
||||
if account_currency==doc.company_currency \
|
||||
else tax.tax_amount_after_discount_amount
|
||||
}, account_currency, item=tax)
|
||||
|
@ -7,6 +7,8 @@ from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.utils.xlsxutils import handle_html
|
||||
from six import iteritems
|
||||
import json
|
||||
|
||||
def execute(filters=None):
|
||||
return _execute(filters)
|
||||
@ -21,21 +23,24 @@ def _execute(filters=None):
|
||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||
|
||||
data = []
|
||||
added_item = []
|
||||
for d in item_list:
|
||||
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty]
|
||||
total_tax = 0
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
total_tax += flt(item_tax.get("tax_amount"))
|
||||
if (d.parent, d.item_code) not in added_item:
|
||||
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty]
|
||||
total_tax = 0
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
|
||||
total_tax += flt(item_tax.get("tax_amount", 0))
|
||||
|
||||
row += [d.base_net_amount + total_tax]
|
||||
row += [d.base_net_amount]
|
||||
row += [d.base_net_amount + total_tax]
|
||||
row += [d.base_net_amount]
|
||||
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
row += [item_tax.get("tax_amount", 0)]
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
|
||||
row += [item_tax.get("tax_amount", 0)]
|
||||
|
||||
data.append(row)
|
||||
data.append(row)
|
||||
added_item.append((d.parent, d.item_code))
|
||||
if data:
|
||||
data = get_merged_data(columns, data) # merge same hsn code data
|
||||
return columns, data
|
||||
@ -103,7 +108,7 @@ def get_items(filters):
|
||||
match_conditions = " and {0} ".format(match_conditions)
|
||||
|
||||
|
||||
return frappe.db.sql("""
|
||||
items = frappe.db.sql("""
|
||||
select
|
||||
`tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate,
|
||||
`tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty,
|
||||
@ -118,10 +123,9 @@ def get_items(filters):
|
||||
|
||||
""" % (conditions, match_conditions), filters, as_dict=1)
|
||||
|
||||
return items
|
||||
|
||||
def get_tax_accounts(item_list, columns, company_currency,
|
||||
doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
|
||||
import json
|
||||
def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
|
||||
item_row_map = {}
|
||||
tax_columns = []
|
||||
invoice_item_row = {}
|
||||
@ -171,7 +175,7 @@ def get_tax_accounts(item_list, columns, company_currency,
|
||||
for d in item_row_map.get(parent, {}).get(item_code, []):
|
||||
item_tax_amount = tax_amount
|
||||
if item_tax_amount:
|
||||
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
|
||||
itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({
|
||||
"tax_amount": flt(item_tax_amount, tax_amount_precision)
|
||||
})
|
||||
except ValueError:
|
||||
@ -179,42 +183,32 @@ def get_tax_accounts(item_list, columns, company_currency,
|
||||
|
||||
tax_columns.sort()
|
||||
for desc in tax_columns:
|
||||
columns.append(desc + " Amount:Currency/currency:160")
|
||||
columns.append({
|
||||
"label": desc,
|
||||
"fieldname": frappe.scrub(desc),
|
||||
"fieldtype": "Float",
|
||||
"width": 110
|
||||
})
|
||||
|
||||
# columns += ["Total Amount:Currency/currency:110"]
|
||||
return itemised_tax, tax_columns
|
||||
|
||||
def get_merged_data(columns, data):
|
||||
merged_hsn_dict = {} # to group same hsn under one key and perform row addition
|
||||
add_column_index = [] # store index of columns that needs to be added
|
||||
tax_col = len(get_columns())
|
||||
fields_to_merge = ["stock_qty", "total_amount", "taxable_amount"] # columns for which index needs to be found
|
||||
|
||||
for i,d in enumerate(columns):
|
||||
# check if fieldname in to_merge list and ignore tax-columns
|
||||
if i < tax_col and d["fieldname"] in fields_to_merge:
|
||||
add_column_index.append(i)
|
||||
result = []
|
||||
|
||||
for row in data:
|
||||
if row[0] in merged_hsn_dict:
|
||||
to_add_row = merged_hsn_dict.get(row[0])
|
||||
merged_hsn_dict.setdefault(row[0], {})
|
||||
for i, d in enumerate(columns):
|
||||
if d['fieldtype'] not in ('Int', 'Float', 'Currency'):
|
||||
merged_hsn_dict[row[0]][d['fieldname']] = row[i]
|
||||
else:
|
||||
if merged_hsn_dict.get(row[0], {}).get(d['fieldname'], ''):
|
||||
merged_hsn_dict[row[0]][d['fieldname']] += row[i]
|
||||
else:
|
||||
merged_hsn_dict[row[0]][d['fieldname']] = row[i]
|
||||
|
||||
# add columns from the add_column_index table
|
||||
for k in add_column_index:
|
||||
to_add_row[k] += row[k]
|
||||
for key, value in iteritems(merged_hsn_dict):
|
||||
result.append(value)
|
||||
|
||||
# add tax columns
|
||||
for k in range(len(columns)):
|
||||
if tax_col <= k < len(columns):
|
||||
to_add_row[k] += row[k]
|
||||
|
||||
# update hsn dict with the newly added data
|
||||
merged_hsn_dict[row[0]] = to_add_row
|
||||
else:
|
||||
merged_hsn_dict[row[0]] = row
|
||||
|
||||
# extract data rows to be displayed in report
|
||||
data = [merged_hsn_dict[d] for d in merged_hsn_dict]
|
||||
|
||||
return data
|
||||
return result
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"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",
|
||||
@ -14,10 +14,11 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"extends_another_page": 0,
|
||||
"hide_custom": 0,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Retail",
|
||||
"modified": "2020-04-26 22:42:39.346750",
|
||||
"modified": "2020-08-20 18:00:07.515691",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Retail",
|
||||
@ -25,5 +26,27 @@
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
@ -33,7 +33,7 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'label': _('Support'),
|
||||
'items': ['Issue']
|
||||
'items': ['Issue', 'Maintenance Visit']
|
||||
},
|
||||
{
|
||||
'label': _('Projects'),
|
||||
|
@ -10,6 +10,7 @@ def get_data():
|
||||
'Payment Entry': 'reference_name',
|
||||
'Payment Request': 'reference_name',
|
||||
'Auto Repeat': 'reference_document',
|
||||
'Maintenance Visit': 'prevdoc_docname'
|
||||
},
|
||||
'internal_links': {
|
||||
'Quotation': ['items', 'prevdoc_docname']
|
||||
@ -17,7 +18,7 @@ def get_data():
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Fulfillment'),
|
||||
'items': ['Sales Invoice', 'Pick List', 'Delivery Note']
|
||||
'items': ['Sales Invoice', 'Pick List', 'Delivery Note', 'Maintenance Visit']
|
||||
},
|
||||
{
|
||||
'label': _('Purchasing'),
|
||||
|
@ -86,7 +86,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
||||
this.$summary_container.append(
|
||||
`<div class="summary-btns flex summary-btns justify-between w-full f-shrink-0"></div>`
|
||||
)
|
||||
|
||||
|
||||
this.$summary_btns = this.$summary_container.find('.summary-btns');
|
||||
}
|
||||
|
||||
@ -110,7 +110,10 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
||||
{fieldname:'print', fieldtype:'Data', label:'Print Preview'}
|
||||
],
|
||||
primary_action: () => {
|
||||
this.events.get_frm().print_preview.printit(true);
|
||||
const frm = this.events.get_frm();
|
||||
frm.doc = this.doc;
|
||||
frm.print_preview.lang_code = frm.doc.language;
|
||||
frm.print_preview.printit(true);
|
||||
},
|
||||
primary_action_label: __('Print'),
|
||||
});
|
||||
@ -174,7 +177,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
||||
<div class="flex">
|
||||
<div class="text-md-0 text-dark-grey text-bold w-fit">Tax Charges</div>
|
||||
<div class="flex ml-6 text-dark-grey">
|
||||
${
|
||||
${
|
||||
doc.taxes.map((t, i) => {
|
||||
let margin_left = '';
|
||||
if (i !== 0) margin_left = 'ml-2';
|
||||
@ -271,6 +274,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
||||
// this.print_dialog.show();
|
||||
const frm = this.events.get_frm();
|
||||
frm.doc = this.doc;
|
||||
frm.print_preview.lang_code = frm.doc.language;
|
||||
frm.print_preview.printit(true);
|
||||
});
|
||||
}
|
||||
@ -284,9 +288,9 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
||||
this.$summary_container.find('.print-btn').click();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
toggle_component(show) {
|
||||
show ?
|
||||
show ?
|
||||
this.$component.removeClass('d-none') :
|
||||
this.$component.addClass('d-none');
|
||||
}
|
||||
@ -372,9 +376,9 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
||||
}
|
||||
|
||||
get_condition_btn_map(after_submission) {
|
||||
if (after_submission)
|
||||
if (after_submission)
|
||||
return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }];
|
||||
|
||||
|
||||
return [
|
||||
{ condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] },
|
||||
{ condition: !this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt', 'Return']},
|
||||
@ -384,7 +388,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
||||
|
||||
load_summary_of(doc, after_submission=false) {
|
||||
this.$summary_wrapper.removeClass("d-none");
|
||||
|
||||
|
||||
after_submission ?
|
||||
this.switch_to_post_submit_summary() : this.switch_to_recent_invoice_summary();
|
||||
|
||||
|
@ -7,6 +7,7 @@ import frappe
|
||||
from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import DEFAULT_MAPPERS
|
||||
from .default_success_action import get_default_success_action
|
||||
from frappe import _
|
||||
from frappe.utils import cint
|
||||
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules
|
||||
@ -29,8 +30,8 @@ def after_install():
|
||||
|
||||
|
||||
def check_setup_wizard_not_completed():
|
||||
if frappe.db.get_default('desktop:home_page') != 'setup-wizard':
|
||||
message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed.
|
||||
if cint(frappe.db.get_single_value('System Settings', 'setup_complete') or 0):
|
||||
message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed.
|
||||
You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall"""
|
||||
frappe.throw(message)
|
||||
|
||||
|
@ -111,6 +111,7 @@ class Item(WebsiteGenerator):
|
||||
self.synced_with_hub = 0
|
||||
|
||||
self.validate_has_variants()
|
||||
self.validate_attributes_in_variants()
|
||||
self.validate_stock_exists_for_template_item()
|
||||
self.validate_attributes()
|
||||
self.validate_variant_attributes()
|
||||
@ -806,6 +807,77 @@ class Item(WebsiteGenerator):
|
||||
if frappe.db.exists("Item", {"variant_of": self.name}):
|
||||
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):
|
||||
if self.stock_ledger_created() and self._doc_before_save:
|
||||
if (cint(self._doc_before_save.has_variants) != cint(self.has_variants)
|
||||
|
@ -227,6 +227,14 @@ class PurchaseReceipt(BuyingController):
|
||||
if not stock_value_diff:
|
||||
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({
|
||||
"account": warehouse_account[d.warehouse]["account"],
|
||||
"against": stock_rbnb,
|
||||
@ -242,16 +250,16 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
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"))
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": warehouse_account[d.from_warehouse]['account'] \
|
||||
if d.from_warehouse else stock_rbnb,
|
||||
"against": warehouse_account[d.warehouse]["account"],
|
||||
"cost_center": d.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")),
|
||||
"debit_in_account_currency": -1 * credit_amount
|
||||
}, credit_currency, item=d))
|
||||
if credit_amount:
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": warehouse_account[d.from_warehouse]['account'] \
|
||||
if d.from_warehouse else stock_rbnb,
|
||||
"against": warehouse_account[d.warehouse]["account"],
|
||||
"cost_center": d.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")),
|
||||
"debit_in_account_currency": -1 * credit_amount
|
||||
}, credit_currency, item=d))
|
||||
|
||||
negative_expense_to_be_booked += flt(d.item_tax_amount)
|
||||
|
||||
|
@ -18,6 +18,28 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
set_perpetual_inventory(0)
|
||||
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):
|
||||
pr = make_purchase_receipt(do_not_save=True)
|
||||
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")])
|
||||
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):
|
||||
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"),
|
||||
@ -688,7 +726,7 @@ def make_purchase_receipt(**args):
|
||||
"received_qty": received_qty,
|
||||
"rejected_qty": rejected_qty,
|
||||
"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,
|
||||
"serial_no": args.serial_no,
|
||||
"stock_uom": args.stock_uom or "_Test UOM",
|
||||
|
@ -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')
|
||||
|
||||
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'):
|
||||
sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
|
||||
|
@ -209,11 +209,11 @@ function set_time_to_resolve_and_response(frm) {
|
||||
|
||||
frm.dashboard.set_headline_alert(
|
||||
'<div class="row">' +
|
||||
'<div class="col-xs-6">' +
|
||||
'<span class="indicator whitespace-nowrap '+ time_to_respond.indicator +'"><span class="hidden-xs">Time to Respond: '+ time_to_respond.diff_display +'</span></span> ' +
|
||||
'<div class="col-xs-12 col-sm-6">' +
|
||||
'<span class="indicator whitespace-nowrap '+ time_to_respond.indicator +'"><span>Time to Respond: '+ time_to_respond.diff_display +'</span></span> ' +
|
||||
'</div>' +
|
||||
'<div class="col-xs-6">' +
|
||||
'<span class="indicator whitespace-nowrap '+ time_to_resolve.indicator +'"><span class="hidden-xs">Time to Resolve: '+ time_to_resolve.diff_display +'</span></span> ' +
|
||||
'<div class="col-xs-12 col-sm-6">' +
|
||||
'<span class="indicator whitespace-nowrap '+ time_to_resolve.indicator +'"><span>Time to Resolve: '+ time_to_resolve.diff_display +'</span></span> ' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user