Merge branch 'develop' into fix-margin-for-filter
This commit is contained in:
commit
25ba271562
@ -27,4 +27,4 @@ def get_vouchar_detials(column_list, doctype, docname):
|
||||
for col in column_list:
|
||||
sanitize_searchfield(col)
|
||||
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
|
||||
.format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0]
|
||||
.format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]
|
||||
|
@ -135,7 +135,7 @@ var create_import_button = function(frm) {
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
clearInterval(frm.page["interval"]);
|
||||
frm.page.set_indicator(__('Import Successfull'), 'blue');
|
||||
frm.page.set_indicator(__('Import Successful'), 'blue');
|
||||
create_reset_button(frm);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'label': _('References'),
|
||||
'items': ['Period Closing Voucher', 'Request for Quotation', 'Tax Withholding Category']
|
||||
'items': ['Period Closing Voucher', 'Tax Withholding Category']
|
||||
},
|
||||
{
|
||||
'label': _('Target Details'),
|
||||
|
@ -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'):
|
||||
|
@ -302,10 +302,10 @@
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Warehouse",
|
||||
"mandatory_depends_on": "update_stock",
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"reqd": 1
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -350,4 +350,4 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
@ -1619,22 +1619,23 @@ def update_multi_mode_option(doc, pos_profile):
|
||||
|
||||
for pos_payment_method in pos_profile.get('payments'):
|
||||
pos_payment_method = pos_payment_method.as_dict()
|
||||
|
||||
|
||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||
payment_mode[0].default = pos_payment_method.default
|
||||
append_payment(payment_mode[0])
|
||||
if payment_mode:
|
||||
payment_mode[0].default = pos_payment_method.default
|
||||
append_payment(payment_mode[0])
|
||||
|
||||
def get_all_mode_of_payments(doc):
|
||||
return frappe.db.sql("""
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
|
||||
{'company': doc.company}, as_dict=1)
|
||||
|
||||
def get_mode_of_payment_info(mode_of_payment, company):
|
||||
return frappe.db.sql("""
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
|
||||
(company, mode_of_payment), as_dict=1)
|
||||
|
||||
|
@ -206,10 +206,19 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"rate": 14,
|
||||
'included_in_print_rate': 1
|
||||
})
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Item Quantity",
|
||||
"account_head": "_Test Account Education Cess - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "CESS",
|
||||
"rate": 5,
|
||||
'included_in_print_rate': 1
|
||||
})
|
||||
si.insert()
|
||||
|
||||
# with inclusive tax
|
||||
self.assertEqual(si.net_total, 4385.96)
|
||||
self.assertEqual(si.items[0].net_amount, 3947.368421052631)
|
||||
self.assertEqual(si.net_total, 3947.37)
|
||||
self.assertEqual(si.grand_total, 5000)
|
||||
|
||||
si.reload()
|
||||
@ -222,8 +231,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.save()
|
||||
|
||||
# with inclusive tax and additional discount
|
||||
self.assertEqual(si.net_total, 4285.96)
|
||||
self.assertEqual(si.grand_total, 4885.99)
|
||||
self.assertEqual(si.net_total, 3847.37)
|
||||
self.assertEqual(si.grand_total, 4886)
|
||||
|
||||
si.reload()
|
||||
|
||||
@ -235,7 +244,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.save()
|
||||
|
||||
# with inclusive tax and additional discount
|
||||
self.assertEqual(si.net_total, 4298.25)
|
||||
self.assertEqual(si.net_total, 3859.65)
|
||||
self.assertEqual(si.grand_total, 4900.00)
|
||||
|
||||
def test_sales_invoice_discount_amount(self):
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -11,7 +11,7 @@ from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate
|
||||
|
||||
class AssetMaintenanceLog(Document):
|
||||
def validate(self):
|
||||
if getdate(self.due_date) < getdate(nowdate()):
|
||||
if getdate(self.due_date) < getdate(nowdate()) and self.maintenance_status not in ["Completed", "Cancelled"]:
|
||||
self.maintenance_status = "Overdue"
|
||||
|
||||
if self.maintenance_status == "Completed" and not self.completion_date:
|
||||
|
@ -1,14 +1,15 @@
|
||||
frappe.listview_settings['Asset Maintenance Log'] = {
|
||||
add_fields: ["maintenance_status"],
|
||||
has_indicator_for_draft: 1,
|
||||
get_indicator: function(doc) {
|
||||
if(doc.maintenance_status=="Pending") {
|
||||
return [__("Pending"), "orange"];
|
||||
} else if(doc.maintenance_status=="Completed") {
|
||||
return [__("Completed"), "green"];
|
||||
} else if(doc.maintenance_status=="Cancelled") {
|
||||
return [__("Cancelled"), "red"];
|
||||
} else if(doc.maintenance_status=="Overdue") {
|
||||
return [__("Overdue"), "red"];
|
||||
if (doc.maintenance_status=="Planned") {
|
||||
return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status=="Completed") {
|
||||
return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status=="Cancelled") {
|
||||
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status=="Overdue") {
|
||||
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -94,7 +94,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
|
||||
this.frm.add_custom_button(__('Update Items'), () => {
|
||||
erpnext.utils.update_child_items({
|
||||
frm: frm,
|
||||
frm: this.frm,
|
||||
child_docname: "items",
|
||||
child_doctype: "Purchase Order Detail",
|
||||
cannot_add_row: false,
|
||||
|
@ -985,7 +985,7 @@ def validate_inclusive_tax(tax, doc):
|
||||
# all rows about the reffered tax should be inclusive
|
||||
_on_previous_row_error("1 - %d" % (tax.row_id,))
|
||||
elif tax.get("category") == "Valuation":
|
||||
frappe.throw(_("Valuation type charges can not marked as Inclusive"))
|
||||
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
|
||||
|
||||
|
||||
def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None):
|
||||
|
@ -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),
|
||||
|
@ -161,8 +161,9 @@ class calculate_taxes_and_totals(object):
|
||||
for item in self.doc.get("items"):
|
||||
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
|
||||
cumulated_tax_fraction = 0
|
||||
total_inclusive_tax_amount_per_qty = 0
|
||||
for i, tax in enumerate(self.doc.get("taxes")):
|
||||
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map)
|
||||
tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map)
|
||||
|
||||
if i==0:
|
||||
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
|
||||
@ -172,9 +173,12 @@ class calculate_taxes_and_totals(object):
|
||||
+ tax.tax_fraction_for_current_item
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
|
||||
|
||||
if cumulated_tax_fraction and not self.discount_amount_applied and item.qty:
|
||||
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction))
|
||||
if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
|
||||
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
|
||||
|
||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
|
||||
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
||||
item.discount_percentage = flt(item.discount_percentage,
|
||||
item.precision("discount_percentage"))
|
||||
@ -190,6 +194,7 @@ class calculate_taxes_and_totals(object):
|
||||
from tax inclusive amount
|
||||
"""
|
||||
current_tax_fraction = 0
|
||||
inclusive_tax_amount_per_qty = 0
|
||||
|
||||
if cint(tax.included_in_print_rate):
|
||||
tax_rate = self._get_tax_rate(tax, item_tax_map)
|
||||
@ -204,10 +209,15 @@ class calculate_taxes_and_totals(object):
|
||||
elif tax.charge_type == "On Previous Row Total":
|
||||
current_tax_fraction = (tax_rate / 100.0) * \
|
||||
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
|
||||
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
inclusive_tax_amount_per_qty = flt(tax_rate)
|
||||
|
||||
if getattr(tax, "add_deduct_tax", None):
|
||||
current_tax_fraction *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
|
||||
return current_tax_fraction
|
||||
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
|
||||
current_tax_fraction *= -1.0
|
||||
inclusive_tax_amount_per_qty *= -1.0
|
||||
|
||||
return current_tax_fraction, inclusive_tax_amount_per_qty
|
||||
|
||||
def _get_tax_rate(self, tax, item_tax_map):
|
||||
if tax.account_head in item_tax_map:
|
||||
@ -321,7 +331,7 @@ class calculate_taxes_and_totals(object):
|
||||
current_tax_amount = (tax_rate / 100.0) * \
|
||||
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
current_tax_amount = tax_rate * item.stock_qty
|
||||
current_tax_amount = tax_rate * item.qty
|
||||
|
||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||
|
||||
@ -472,7 +482,7 @@ class calculate_taxes_and_totals(object):
|
||||
actual_taxes_dict = {}
|
||||
|
||||
for tax in self.doc.get("taxes"):
|
||||
if tax.charge_type == "Actual":
|
||||
if tax.charge_type in ["Actual", "On Item Quantity"]:
|
||||
tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
|
||||
actual_taxes_dict.setdefault(tax.idx, tax_amount)
|
||||
elif tax.row_id in actual_taxes_dict:
|
||||
|
@ -411,7 +411,7 @@
|
||||
"fieldname": "lost_reasons",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Lost Reasons",
|
||||
"options": "Lost Reason Detail",
|
||||
"options": "Opportunity Lost Reason Detail",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-07-16 16:11:39.830389",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"lost_reason"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "lost_reason",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Opportunity Lost Reason",
|
||||
"options": "Opportunity Lost Reason"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-26 17:58:26.313242",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity Lost Reason Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class OpportunityLostReasonDetail(Document):
|
||||
pass
|
@ -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",
|
||||
|
@ -1,5 +1,5 @@
|
||||
frappe.listview_settings['Expense Claim'] = {
|
||||
add_fields: ["total_claimed_amount", "docstatus"],
|
||||
add_fields: ["total_claimed_amount", "docstatus", "company"],
|
||||
get_indicator: function(doc) {
|
||||
if(doc.status == "Paid") {
|
||||
return [__("Paid"), "green", "status,=,Paid"];
|
||||
|
@ -24,8 +24,13 @@ 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 staffing_plan.vacancies - len(job_offers) <= 0:
|
||||
frappe.throw(_("There are no vacancies under staffing plan {0}").format(frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent))))
|
||||
|
||||
if not staffing_plan.get("vacancies") or 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))
|
||||
|
||||
frappe.throw(_("There are no vacancies under staffing plan {0}").format(error_variable))
|
||||
|
||||
def on_change(self):
|
||||
update_job_applicant(self.status, self.job_applicant)
|
||||
|
@ -5,20 +5,23 @@ cur_frm.add_fetch('employee','employee_name','employee_name');
|
||||
|
||||
frappe.ui.form.on("Leave Allocation", {
|
||||
onload: function(frm) {
|
||||
// Ignore cancellation of doctype on cancel all.
|
||||
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
|
||||
|
||||
if(!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today());
|
||||
|
||||
frm.set_query("employee", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query("leave_type", function() {
|
||||
return {
|
||||
filters: {
|
||||
is_lwp: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -19,6 +19,10 @@ frappe.ui.form.on("Leave Application", {
|
||||
frm.set_query("employee", erpnext.queries.employee);
|
||||
},
|
||||
onload: function(frm) {
|
||||
|
||||
// Ignore cancellation of doctype on cancel all.
|
||||
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
|
||||
|
||||
if (!frm.doc.posting_date) {
|
||||
frm.set_value("posting_date", frappe.datetime.get_today());
|
||||
}
|
||||
|
@ -2,6 +2,10 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Leave Encashment', {
|
||||
onload: function(frm) {
|
||||
// Ignore cancellation of doctype on cancel all.
|
||||
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
|
||||
},
|
||||
setup: function(frm) {
|
||||
frm.set_query("leave_type", function() {
|
||||
return {
|
||||
@ -33,7 +37,7 @@ frappe.ui.form.on('Leave Encashment', {
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
||||
|
@ -16,14 +16,16 @@ from six import string_types
|
||||
|
||||
class LoanApplication(Document):
|
||||
def validate(self):
|
||||
|
||||
validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount,
|
||||
self.repayment_periods, self.is_term_loan)
|
||||
|
||||
self.validate_loan_type()
|
||||
self.set_pledge_amount()
|
||||
self.set_loan_amount()
|
||||
self.validate_loan_amount()
|
||||
|
||||
if self.is_term_loan:
|
||||
validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount,
|
||||
self.repayment_periods, self.is_term_loan)
|
||||
|
||||
self.validate_loan_type()
|
||||
|
||||
self.get_repayment_details()
|
||||
self.check_sanctioned_amount_limit()
|
||||
|
||||
@ -106,7 +108,7 @@ class LoanApplication(Document):
|
||||
if self.is_secured_loan and self.proposed_pledges:
|
||||
self.maximum_loan_amount = 0
|
||||
for security in self.proposed_pledges:
|
||||
self.maximum_loan_amount += security.post_haircut_amount
|
||||
self.maximum_loan_amount += flt(security.post_haircut_amount)
|
||||
|
||||
if not self.loan_amount and self.is_secured_loan and self.proposed_pledges:
|
||||
self.loan_amount = self.maximum_loan_amount
|
||||
@ -133,10 +135,7 @@ def create_loan(source_name, target_doc=None, submit=0):
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
},
|
||||
"postprocess": update_accounts,
|
||||
"field_no_map": [
|
||||
"is_secured_loan"
|
||||
]
|
||||
"postprocess": update_accounts
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
|
@ -10,22 +10,20 @@ from frappe.utils import nowdate, getdate, add_days, flt
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans
|
||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||
from frappe.utils import get_datetime
|
||||
|
||||
class LoanDisbursement(AccountsController):
|
||||
|
||||
def validate(self):
|
||||
self.set_missing_values()
|
||||
|
||||
def before_submit(self):
|
||||
self.set_status_and_amounts()
|
||||
|
||||
def before_cancel(self):
|
||||
self.set_status_and_amounts(cancel=1)
|
||||
|
||||
def on_submit(self):
|
||||
self.set_status_and_amounts()
|
||||
self.make_gl_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
self.set_status_and_amounts(cancel=1)
|
||||
self.make_gl_entries(cancel=1)
|
||||
self.ignore_linked_doctypes = ['GL Entry']
|
||||
|
||||
@ -45,29 +43,69 @@ class LoanDisbursement(AccountsController):
|
||||
def set_status_and_amounts(self, cancel=0):
|
||||
|
||||
loan_details = frappe.get_all("Loan",
|
||||
fields = ["loan_amount", "disbursed_amount", "total_principal_paid", "status", "is_term_loan"],
|
||||
filters= { "name": self.against_loan }
|
||||
)[0]
|
||||
|
||||
if loan_details.status == "Disbursed" and not loan_details.is_term_loan:
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
|
||||
loan=self.against_loan)
|
||||
fields = ["loan_amount", "disbursed_amount", "total_payment", "total_principal_paid", "total_interest_payable",
|
||||
"status", "is_term_loan", "is_secured_loan"], filters= { "name": self.against_loan })[0]
|
||||
|
||||
if cancel:
|
||||
disbursed_amount = loan_details.disbursed_amount - self.disbursed_amount
|
||||
total_payment = loan_details.total_payment
|
||||
|
||||
if loan_details.disbursed_amount > loan_details.loan_amount:
|
||||
topup_amount = loan_details.disbursed_amount - loan_details.loan_amount
|
||||
if topup_amount > self.disbursed_amount:
|
||||
topup_amount = self.disbursed_amount
|
||||
|
||||
total_payment = total_payment - topup_amount
|
||||
|
||||
if disbursed_amount == 0:
|
||||
status = "Sanctioned"
|
||||
elif disbursed_amount >= loan_details.disbursed_amount:
|
||||
elif disbursed_amount >= loan_details.loan_amount:
|
||||
status = "Disbursed"
|
||||
else:
|
||||
status = "Partially Disbursed"
|
||||
else:
|
||||
disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount
|
||||
total_payment = loan_details.total_payment
|
||||
|
||||
if flt(disbursed_amount) - flt(loan_details.total_principal_paid) > flt(loan_details.loan_amount):
|
||||
if disbursed_amount > loan_details.loan_amount and loan_details.is_term_loan:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than loan amount"))
|
||||
|
||||
if flt(disbursed_amount) >= loan_details.disbursed_amount:
|
||||
if loan_details.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
||||
- flt(loan_details.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = loan_details.disbursed_amount
|
||||
|
||||
security_value = 0.0
|
||||
if loan_details.is_secured_loan:
|
||||
security_value = get_total_pledged_security_value(self.against_loan)
|
||||
|
||||
if not security_value:
|
||||
security_value = loan_details.loan_amount
|
||||
|
||||
if pending_principal_amount + self.disbursed_amount > flt(security_value):
|
||||
allowed_amount = security_value - pending_principal_amount
|
||||
if allowed_amount < 0:
|
||||
allowed_amount = 0
|
||||
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(allowed_amount))
|
||||
|
||||
if loan_details.status == "Disbursed" and not loan_details.is_term_loan:
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
|
||||
loan=self.against_loan)
|
||||
|
||||
if disbursed_amount > loan_details.loan_amount:
|
||||
topup_amount = disbursed_amount - loan_details.loan_amount
|
||||
|
||||
if topup_amount < 0:
|
||||
topup_amount = 0
|
||||
|
||||
if topup_amount > self.disbursed_amount:
|
||||
topup_amount = self.disbursed_amount
|
||||
|
||||
total_payment = total_payment + topup_amount
|
||||
|
||||
if flt(disbursed_amount) >= loan_details.loan_amount:
|
||||
status = "Disbursed"
|
||||
else:
|
||||
status = "Partially Disbursed"
|
||||
@ -75,7 +113,8 @@ class LoanDisbursement(AccountsController):
|
||||
frappe.db.set_value("Loan", self.against_loan, {
|
||||
"disbursement_date": self.disbursement_date,
|
||||
"disbursed_amount": disbursed_amount,
|
||||
"status": status
|
||||
"status": status,
|
||||
"total_payment": total_payment
|
||||
})
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
@ -116,3 +155,24 @@ class LoanDisbursement(AccountsController):
|
||||
|
||||
if gle_map:
|
||||
make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj)
|
||||
|
||||
def get_total_pledged_security_value(loan):
|
||||
update_time = get_datetime()
|
||||
|
||||
loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price",
|
||||
fields=["loan_security", "loan_security_price"],
|
||||
filters = {
|
||||
"valid_from": ("<=", update_time),
|
||||
"valid_upto": (">=", update_time)
|
||||
}, as_list=1))
|
||||
|
||||
hair_cut_map = frappe._dict(frappe.get_all('Loan Security',
|
||||
fields=["name", "haircut"], as_list=1))
|
||||
|
||||
security_value = 0.0
|
||||
pledged_securities = get_pledged_security_qty(loan)
|
||||
|
||||
for security, qty in pledged_securities.items():
|
||||
security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100
|
||||
|
||||
return security_value
|
||||
|
@ -85,8 +85,8 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
if no_of_days <= 0:
|
||||
return
|
||||
|
||||
pending_principal_amount = loan.total_payment - loan.total_interest_payable \
|
||||
- loan.total_amount_paid
|
||||
pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
|
||||
interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)
|
||||
payable_interest = interest_per_day * no_of_days
|
||||
|
@ -718,3 +718,7 @@ erpnext.patches.v13_0.delete_report_requested_items_to_order
|
||||
erpnext.patches.v12_0.update_item_tax_template_company
|
||||
erpnext.patches.v13_0.move_branch_code_to_bank_account
|
||||
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
|
||||
|
17
erpnext/patches/v12_0/rename_lost_reason_detail.py
Normal file
17
erpnext/patches/v12_0/rename_lost_reason_detail.py
Normal file
@ -0,0 +1,17 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
if frappe.db.exists("DocType", "Lost Reason Detail"):
|
||||
frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail")
|
||||
frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail")
|
||||
|
||||
frappe.db.sql("""INSERT INTO `tabOpportunity Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Opportunity'""")
|
||||
|
||||
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`
|
||||
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")
|
@ -19,7 +19,7 @@ def create_stock_entry_types():
|
||||
|
||||
for purpose in ["Material Issue", "Material Receipt", "Material Transfer",
|
||||
"Material Transfer for Manufacture", "Material Consumption for Manufacture", "Manufacture",
|
||||
"Repack", "Send to Subcontractor", "Send to Warehouse", "Receive at Warehouse"]:
|
||||
"Repack", "Send to Subcontractor"]:
|
||||
|
||||
ste_type = frappe.get_doc({
|
||||
'doctype': 'Stock Entry Type',
|
||||
|
22
erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py
Normal file
22
erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py
Normal file
@ -0,0 +1,22 @@
|
||||
import frappe
|
||||
from erpnext.regional.india import states
|
||||
|
||||
def execute():
|
||||
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
# Update options in gst_state custom field
|
||||
gst_state = frappe.get_doc('Custom Field', 'Address-gst_state')
|
||||
gst_state.options = '\n'.join(states)
|
||||
gst_state.save()
|
||||
|
||||
# Update gst_state and state code in existing address
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabAddress`
|
||||
SET
|
||||
gst_state = 'Dadra and Nagar Haveli and Daman and Diu',
|
||||
gst_state_number = 26
|
||||
WHERE gst_state = 'Daman and Diu'
|
||||
""")
|
27
erpnext/patches/v13_0/stock_entry_enhancements.py
Normal file
27
erpnext/patches/v13_0/stock_entry_enhancements.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright(c) 2020, Frappe Technologies Pvt.Ltd.and Contributors
|
||||
# License: GNU General Public License v3.See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("stock", "doctype", "stock_entry")
|
||||
if frappe.db.has_column("Stock Entry", "add_to_transit"):
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabStock Entry` SET
|
||||
stock_entry_type = 'Material Transfer',
|
||||
purpose = 'Material Transfer',
|
||||
add_to_transit = 1 WHERE stock_entry_type = 'Send to Warehouse'
|
||||
""")
|
||||
|
||||
frappe.db.sql("""UPDATE `tabStock Entry` SET
|
||||
stock_entry_type = 'Material Transfer',
|
||||
purpose = 'Material Transfer'
|
||||
WHERE stock_entry_type = 'Receive at Warehouse'
|
||||
""")
|
||||
|
||||
frappe.reload_doc("stock", "doctype", "warehouse_type")
|
||||
if not frappe.db.exists('Warehouse Type', 'Transit'):
|
||||
doc = frappe.new_doc('Warehouse Type')
|
||||
doc.name = 'Transit'
|
||||
doc.insert()
|
@ -0,0 +1,10 @@
|
||||
# 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')
|
||||
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;")
|
@ -7,27 +7,30 @@
|
||||
"field_order": [
|
||||
"salary_component",
|
||||
"abbr",
|
||||
"statistical_component",
|
||||
"column_break_3",
|
||||
"deduct_full_tax_on_selected_payroll_date",
|
||||
"amount",
|
||||
"section_break_5",
|
||||
"additional_salary",
|
||||
"statistical_component",
|
||||
"depends_on_payment_days",
|
||||
"is_tax_applicable",
|
||||
"exempted_from_income_tax",
|
||||
"is_tax_applicable",
|
||||
"column_break_11",
|
||||
"is_flexible_benefit",
|
||||
"variable_based_on_taxable_salary",
|
||||
"do_not_include_in_total",
|
||||
"deduct_full_tax_on_selected_payroll_date",
|
||||
"section_break_2",
|
||||
"condition",
|
||||
"column_break_18",
|
||||
"amount_based_on_formula",
|
||||
"formula",
|
||||
"amount",
|
||||
"do_not_include_in_total",
|
||||
"section_break_19",
|
||||
"default_amount",
|
||||
"additional_amount",
|
||||
"column_break_24",
|
||||
"tax_on_flexible_benefit",
|
||||
"tax_on_additional_salary",
|
||||
"section_break_11",
|
||||
"additional_salary",
|
||||
"condition_and_formula_help"
|
||||
"tax_on_additional_salary"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -110,9 +113,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "eval:doc.is_flexible_benefit != 1",
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Condtion and formula"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -181,23 +186,12 @@
|
||||
"label": "Tax on additional salary",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.parenttype=='Salary Structure'",
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.parenttype=='Salary Structure'",
|
||||
"fieldname": "condition_and_formula_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Condition and Formula Help",
|
||||
"options": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base < 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS > 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_salary",
|
||||
"fieldtype": "Link",
|
||||
"label": "Additional Salary ",
|
||||
"options": "Additional Salary"
|
||||
"options": "Additional Salary",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -207,11 +201,43 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Exempted from Income Tax",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Component properties and references ",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_19",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_24",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 23:21:26.300951",
|
||||
"modified": "2020-07-01 12:13:41.956495",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Detail",
|
||||
|
@ -123,13 +123,13 @@ frappe.ui.form.on("Salary Slip", {
|
||||
doc: frm.doc,
|
||||
callback: function(r, rt) {
|
||||
frm.refresh();
|
||||
if (frm.doc.absent_days){
|
||||
if (r.message){
|
||||
frm.fields_dict.absent_days.set_description("Unmarked Days is treated as "+ r.message +". You can can change this in " + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Salary Slip Timesheet', {
|
||||
time_sheet: function(frm, dt, dn) {
|
||||
|
@ -20,15 +20,17 @@
|
||||
"company",
|
||||
"letter_head",
|
||||
"section_break_10",
|
||||
"salary_slip_based_on_timesheet",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"salary_structure",
|
||||
"column_break_18",
|
||||
"salary_slip_based_on_timesheet",
|
||||
"payroll_frequency",
|
||||
"column_break_15",
|
||||
"section_break_20",
|
||||
"total_working_days",
|
||||
"unmarked_days",
|
||||
"leave_without_pay",
|
||||
"column_break_24",
|
||||
"absent_days",
|
||||
"payment_days",
|
||||
"hourly_wages",
|
||||
@ -200,10 +202,6 @@
|
||||
"fieldtype": "Date",
|
||||
"label": "End Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "salary_structure",
|
||||
"fieldtype": "Link",
|
||||
@ -490,13 +488,25 @@
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Unmarked days"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_20",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_24",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 9,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-22 12:41:03.659422",
|
||||
"modified": "2020-08-11 17:37:54.274384",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Slip",
|
||||
|
@ -94,13 +94,6 @@ frappe.ui.form.on("Timesheet", {
|
||||
}
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
frappe.db.get_value('Company', { 'company_name' : frm.doc.company }, 'standard_working_hours')
|
||||
.then(({ message }) => {
|
||||
(frappe.working_hours = message.standard_working_hours || 0);
|
||||
});
|
||||
},
|
||||
|
||||
make_invoice: function(frm) {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Item (optional)"),
|
||||
|
@ -163,9 +163,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
$.each(me.frm.doc["items"] || [], function(n, item) {
|
||||
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
|
||||
var cumulated_tax_fraction = 0.0;
|
||||
|
||||
var total_inclusive_tax_amount_per_qty = 0;
|
||||
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
||||
tax.tax_fraction_for_current_item = me.get_current_tax_fraction(tax, item_tax_map);
|
||||
var current_tax_fraction = me.get_current_tax_fraction(tax, item_tax_map);
|
||||
tax.tax_fraction_for_current_item = current_tax_fraction[0];
|
||||
var inclusive_tax_amount_per_qty = current_tax_fraction[1];
|
||||
|
||||
if(i==0) {
|
||||
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
|
||||
@ -176,10 +178,12 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
}
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item;
|
||||
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty);
|
||||
});
|
||||
|
||||
if(cumulated_tax_fraction && !me.discount_amount_applied) {
|
||||
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction));
|
||||
if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) {
|
||||
var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty;
|
||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction));
|
||||
item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
|
||||
|
||||
me.set_in_company_currency(item, ["net_rate", "net_amount"]);
|
||||
@ -191,6 +195,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
// Get tax fraction for calculating tax exclusive amount
|
||||
// from tax inclusive amount
|
||||
var current_tax_fraction = 0.0;
|
||||
var inclusive_tax_amount_per_qty = 0;
|
||||
|
||||
if(cint(tax.included_in_print_rate)) {
|
||||
var tax_rate = this._get_tax_rate(tax, item_tax_map);
|
||||
@ -205,13 +210,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
} else if(tax.charge_type == "On Previous Row Total") {
|
||||
current_tax_fraction = (tax_rate / 100.0) *
|
||||
this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
|
||||
} else if (tax.charge_type == "On Item Quantity") {
|
||||
inclusive_tax_amount_per_qty = flt(tax_rate);
|
||||
}
|
||||
}
|
||||
|
||||
if(tax.add_deduct_tax) {
|
||||
current_tax_fraction *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
|
||||
if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
|
||||
current_tax_fraction *= -1;
|
||||
inclusive_tax_amount_per_qty *= -1;
|
||||
}
|
||||
return current_tax_fraction;
|
||||
return [current_tax_fraction, inclusive_tax_amount_per_qty];
|
||||
},
|
||||
|
||||
_get_tax_rate: function(tax, item_tax_map) {
|
||||
@ -360,8 +368,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
} else if(tax.charge_type == "On Previous Row Total") {
|
||||
current_tax_amount = (tax_rate / 100.0) *
|
||||
this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item;
|
||||
} else if (tax.charge_type == "On Item Quantity") {
|
||||
current_tax_amount = tax_rate * item.qty;
|
||||
}
|
||||
|
||||
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
|
||||
|
||||
return current_tax_amount;
|
||||
@ -573,7 +582,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
var actual_taxes_dict = {};
|
||||
|
||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||
if (tax.charge_type == "Actual") {
|
||||
if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) {
|
||||
var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount;
|
||||
tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
|
||||
actual_taxes_dict[tax.idx] = tax_amount;
|
||||
@ -586,7 +595,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
$.each(actual_taxes_dict, function(key, value) {
|
||||
if (value) total_actual_tax += value;
|
||||
});
|
||||
|
||||
|
||||
return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total"));
|
||||
}
|
||||
},
|
||||
|
@ -10,8 +10,7 @@ states = [
|
||||
'Bihar',
|
||||
'Chandigarh',
|
||||
'Chhattisgarh',
|
||||
'Dadra and Nagar Haveli',
|
||||
'Daman and Diu',
|
||||
'Dadra and Nagar Haveli and Daman and Diu',
|
||||
'Delhi',
|
||||
'Goa',
|
||||
'Gujarat',
|
||||
@ -50,8 +49,7 @@ state_numbers = {
|
||||
"Bihar": "10",
|
||||
"Chandigarh": "04",
|
||||
"Chhattisgarh": "22",
|
||||
"Dadra and Nagar Haveli": "26",
|
||||
"Daman and Diu": "25",
|
||||
"Dadra and Nagar Haveli and Daman and Diu": "26",
|
||||
"Delhi": "07",
|
||||
"Goa": "30",
|
||||
"Gujarat": "24",
|
||||
|
@ -134,15 +134,10 @@
|
||||
"state_code": "DL",
|
||||
"state_name": "Delhi"
|
||||
},
|
||||
{
|
||||
"state_number": "25",
|
||||
"state_code": "DD",
|
||||
"state_name": "Daman and Diu"
|
||||
},
|
||||
{
|
||||
"state_number": "26",
|
||||
"state_code": "DN",
|
||||
"state_name": "Dadra and Nagar Haveli"
|
||||
"state_name": "Dadra and Nagar Haveli and Daman and Diu"
|
||||
},
|
||||
{
|
||||
"state_number": "22",
|
||||
|
@ -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,17 @@ 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 not doc.total_taxes_and_charges:
|
||||
return gl_entries
|
||||
|
||||
if doc.reverse_charge == 'Y':
|
||||
gst_accounts = get_gst_accounts(doc.company)
|
||||
|
@ -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
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Key Reports",
|
||||
"links": "[\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Analytics\",\n \"name\": \"Sales Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Analysis\",\n \"name\": \"Sales Order Analysis\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Trends\",\n \"name\": \"Sales Order Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quotation\"\n ],\n \"doctype\": \"Quotation\",\n \"is_query_report\": true,\n \"label\": \"Quotation Trends\",\n \"name\": \"Quotation Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"icon\": \"fa fa-bar-chart\",\n \"is_query_report\": true,\n \"label\": \"Customer Acquisition and Loyalty\",\n \"name\": \"Customer Acquisition and Loyalty\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Person-wise Transaction Summary\",\n \"name\": \"Sales Person-wise Transaction Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Sales History\",\n \"name\": \"Item-wise Sales History\",\n \"type\": \"report\"\n }\n]"
|
||||
"links": "[\n {\n \n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Analytics\",\n \"name\": \"Sales Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Analysis\",\n \"name\": \"Sales Order Analysis\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Trends\",\n \"name\": \"Sales Order Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quotation\"\n ],\n \"doctype\": \"Quotation\",\n \"is_query_report\": true,\n \"label\": \"Quotation Trends\",\n \"name\": \"Quotation Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"icon\": \"fa fa-bar-chart\",\n \"is_query_report\": true,\n \"label\": \"Customer Acquisition and Loyalty\",\n \"name\": \"Customer Acquisition and Loyalty\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Person-wise Transaction Summary\",\n \"name\": \"Sales Person-wise Transaction Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Sales History\",\n \"name\": \"Item-wise Sales History\",\n \"type\": \"report\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
@ -44,7 +44,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Selling",
|
||||
"modified": "2020-06-29 19:26:35.139097",
|
||||
"modified": "2020-08-15 10:12:53.131621",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling",
|
||||
|
@ -184,6 +184,14 @@ class Customer(TransactionBase):
|
||||
def validate_credit_limit_on_change(self):
|
||||
if self.get("__islocal") or not self.credit_limits:
|
||||
return
|
||||
|
||||
past_credit_limits = [d.credit_limit
|
||||
for d in frappe.db.get_all("Customer Credit Limit", filters={'parent': self.name}, fields=["credit_limit"], order_by="company")]
|
||||
|
||||
current_credit_limits = [d.credit_limit for d in sorted(self.credit_limits, key=lambda k: k.company)]
|
||||
|
||||
if past_credit_limits == current_credit_limits:
|
||||
return
|
||||
|
||||
company_record = []
|
||||
for limit in self.credit_limits:
|
||||
|
@ -923,7 +923,7 @@
|
||||
"fieldname": "lost_reasons",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Lost Reasons",
|
||||
"options": "Lost Reason Detail",
|
||||
"options": "Quotation Lost Reason Detail",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
@ -932,7 +932,7 @@
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"max_attachments": 1,
|
||||
"modified": "2020-07-18 04:59:09.960118",
|
||||
"modified": "2020-07-26 17:46:19.951223",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation",
|
||||
|
@ -68,7 +68,7 @@ class Quotation(SellingController):
|
||||
|
||||
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
|
||||
if not self.has_sales_order():
|
||||
get_lost_reasons = frappe.get_list('Opportunity Lost Reason',
|
||||
get_lost_reasons = frappe.get_list('Quotation Lost Reason',
|
||||
fields = ["name"])
|
||||
lost_reasons_lst = [reason.get('name') for reason in get_lost_reasons]
|
||||
frappe.db.set(self, 'status', 'Lost')
|
||||
|
@ -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();
|
||||
|
||||
|
@ -494,13 +494,18 @@ frappe.ui.form.on(cur_frm.doctype, {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Set as Lost"),
|
||||
fields: [
|
||||
{"fieldtype": "Table MultiSelect",
|
||||
"label": __("Lost Reasons"),
|
||||
"fieldname": "lost_reason",
|
||||
"options": "Lost Reason Detail",
|
||||
"reqd": 1},
|
||||
|
||||
{"fieldtype": "Text", "label": __("Detailed Reason"), "fieldname": "detailed_reason"},
|
||||
{
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": __("Lost Reasons"),
|
||||
"fieldname": "lost_reason",
|
||||
"options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail',
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldtype": "Text",
|
||||
"label": __("Detailed Reason"),
|
||||
"fieldname": "detailed_reason"
|
||||
},
|
||||
],
|
||||
primary_action: function() {
|
||||
var values = dialog.get_values();
|
||||
|
@ -34,6 +34,16 @@ frappe.ui.form.on("Company", {
|
||||
frm.set_query("default_buying_terms", function() {
|
||||
return { filters: { buying: 1 } };
|
||||
});
|
||||
|
||||
frm.set_query("default_in_transit_warehouse", function() {
|
||||
return {
|
||||
filters:{
|
||||
'warehouse_type' : 'Transit',
|
||||
'is_group': 0,
|
||||
'company': frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
company_name: function(frm) {
|
||||
|
@ -25,6 +25,7 @@
|
||||
"default_selling_terms",
|
||||
"default_buying_terms",
|
||||
"default_warehouse_for_sales_return",
|
||||
"default_in_transit_warehouse",
|
||||
"column_break_10",
|
||||
"country",
|
||||
"create_chart_of_accounts_based_on",
|
||||
@ -733,6 +734,12 @@
|
||||
"fieldname": "enable_perpetual_inventory_for_non_stock_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Perpetual Inventory For Non Stock Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "default_in_transit_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default In Transit Warehouse",
|
||||
"options": "Warehouse"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-building",
|
||||
@ -740,7 +747,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-24 12:45:31.462195",
|
||||
"modified": "2020-08-06 00:38:08.311216",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
@ -140,7 +140,8 @@ class Company(NestedSet):
|
||||
{"warehouse_name": _("All Warehouses"), "is_group": 1},
|
||||
{"warehouse_name": _("Stores"), "is_group": 0},
|
||||
{"warehouse_name": _("Work In Progress"), "is_group": 0},
|
||||
{"warehouse_name": _("Finished Goods"), "is_group": 0}]:
|
||||
{"warehouse_name": _("Finished Goods"), "is_group": 0},
|
||||
{"warehouse_name": _("Goods In Transit"), "is_group": 0, "warehouse_type": "Transit"}]:
|
||||
|
||||
if not frappe.db.exists("Warehouse", "{0} - {1}".format(wh_detail["warehouse_name"], self.abbr)):
|
||||
warehouse = frappe.get_doc({
|
||||
@ -149,7 +150,8 @@ class Company(NestedSet):
|
||||
"is_group": wh_detail["is_group"],
|
||||
"company": self.name,
|
||||
"parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr) \
|
||||
if not wh_detail["is_group"] else ""
|
||||
if not wh_detail["is_group"] else "",
|
||||
"warehouse_type" : wh_detail["warehouse_type"] if "warehouse_type" in wh_detail else None
|
||||
})
|
||||
warehouse.flags.ignore_permissions = True
|
||||
warehouse.flags.ignore_mandatory = True
|
||||
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-07-14 09:21:44.057724",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"lost_reason"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "lost_reason",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Quotation Lost Reason",
|
||||
"options": "Quotation Lost Reason"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-26 17:58:56.373775",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Quotation Lost Reason Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class QuotationLostReasonDetail(Document):
|
||||
pass
|
@ -95,8 +95,6 @@ def install(country=None):
|
||||
{'doctype': 'Stock Entry Type', 'name': 'Send to Subcontractor', 'purpose': 'Send to Subcontractor'},
|
||||
{'doctype': 'Stock Entry Type', 'name': 'Material Transfer for Manufacture', 'purpose': 'Material Transfer for Manufacture'},
|
||||
{'doctype': 'Stock Entry Type', 'name': 'Material Consumption for Manufacture', 'purpose': 'Material Consumption for Manufacture'},
|
||||
{'doctype': 'Stock Entry Type', 'name': 'Send to Warehouse', 'purpose': 'Send to Warehouse'},
|
||||
{'doctype': 'Stock Entry Type', 'name': 'Receive at Warehouse', 'purpose': 'Receive at Warehouse'},
|
||||
|
||||
# Designation
|
||||
{'doctype': 'Designation', 'designation_name': _('CEO')},
|
||||
@ -244,7 +242,10 @@ def install(country=None):
|
||||
{"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")}
|
||||
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")},
|
||||
|
||||
# Warehouse Type
|
||||
{'doctype': 'Warehouse Type', 'name': 'Transit'},
|
||||
]
|
||||
|
||||
from erpnext.setup.setup_wizard.data.industry_type import get_industry_types
|
||||
|
@ -123,6 +123,7 @@
|
||||
"weightage",
|
||||
"slideshow",
|
||||
"website_image",
|
||||
"website_image_alt",
|
||||
"thumbnail",
|
||||
"cb72",
|
||||
"website_warehouse",
|
||||
@ -1054,15 +1055,21 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Default Manufacturer Part No",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "website_image_alt",
|
||||
"fieldtype": "Data",
|
||||
"label": "Image Description"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"icon": "fa fa-tag",
|
||||
"idx": 2,
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"max_attachments": 1,
|
||||
"modified": "2020-07-31 21:21:10.956453",
|
||||
"modified": "2020-08-07 14:24:58.384992",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
@ -1124,4 +1131,4 @@
|
||||
"sort_order": "DESC",
|
||||
"title_field": "item_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ class Item(WebsiteGenerator):
|
||||
if variant:
|
||||
context.variant = frappe.get_doc("Item", variant)
|
||||
|
||||
for fieldname in ("website_image", "web_long_description", "description",
|
||||
for fieldname in ("website_image", "website_image_alt", "web_long_description", "description",
|
||||
"website_specifications"):
|
||||
if context.variant.get(fieldname):
|
||||
value = context.variant.get(fieldname)
|
||||
|
@ -11,6 +11,7 @@
|
||||
"naming_series",
|
||||
"title",
|
||||
"material_request_type",
|
||||
"transfer_status",
|
||||
"customer",
|
||||
"column_break_2",
|
||||
"schedule_date",
|
||||
@ -303,13 +304,22 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Set From Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "eval:doc.add_to_transit == 1",
|
||||
"fieldname": "transfer_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Transfer Status",
|
||||
"options": "\nNot Started\nIn Transit\nCompleted",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-ticket",
|
||||
"idx": 70,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-01 20:21:09.990867",
|
||||
"modified": "2020-08-10 13:27:54.891058",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request",
|
||||
|
@ -1,8 +1,16 @@
|
||||
frappe.listview_settings['Material Request'] = {
|
||||
add_fields: ["material_request_type", "status", "per_ordered", "per_received"],
|
||||
add_fields: ["material_request_type", "status", "per_ordered", "per_received", "transfer_status"],
|
||||
get_indicator: function(doc) {
|
||||
if(doc.status=="Stopped") {
|
||||
return [__("Stopped"), "red", "status,=,Stopped"];
|
||||
} else if(doc.transfer_status && doc.docstatus != 2) {
|
||||
if (doc.transfer_status == "Not Started") {
|
||||
return [__("Not Started"), "orange"];
|
||||
} else if (doc.transfer_status == "In Transit") {
|
||||
return [__("In Transit"), "yellow"];
|
||||
} else if (doc.transfer_status == "Completed") {
|
||||
return [__("Completed"), "green"];
|
||||
}
|
||||
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 0) {
|
||||
return [__("Pending"), "orange", "per_ordered,=,0"];
|
||||
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) {
|
||||
|
@ -19,7 +19,6 @@ frappe.ui.form.on('Stock Entry', {
|
||||
filters: [
|
||||
['Stock Entry', 'docstatus', '=', 1],
|
||||
['Stock Entry', 'per_transferred', '<','100'],
|
||||
['Stock Entry', 'purpose', '=', 'Send to Warehouse']
|
||||
]
|
||||
}
|
||||
});
|
||||
@ -171,9 +170,9 @@ frappe.ui.form.on('Stock Entry', {
|
||||
}
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus === 1 && frm.doc.purpose == 'Send to Warehouse') {
|
||||
if (frm.doc.per_transferred < 100) {
|
||||
frm.add_custom_button(__('Receive at Warehouse Entry'), function() {
|
||||
if (frm.doc.docstatus === 1) {
|
||||
if (frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer' && frm.doc.per_transferred < 100) {
|
||||
frm.add_custom_button('End Transit', function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry",
|
||||
frm: frm
|
||||
@ -266,6 +265,7 @@ frappe.ui.form.on('Stock Entry', {
|
||||
stock_entry_type: function(frm){
|
||||
frm.remove_custom_button('Bill of Materials', "Get items from");
|
||||
frm.events.show_bom_custom_button(frm);
|
||||
frm.trigger('add_to_transit');
|
||||
},
|
||||
|
||||
purpose: function(frm) {
|
||||
@ -532,6 +532,26 @@ frappe.ui.form.on('Stock Entry', {
|
||||
|
||||
target_warehouse_address: function(frm) {
|
||||
erpnext.utils.get_address_display(frm, 'target_warehouse_address', 'target_address_display', false);
|
||||
},
|
||||
|
||||
add_to_transit: function(frm) {
|
||||
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
|
||||
frm.set_value('stock_entry_type', 'Material Transfer');
|
||||
frm.fields_dict.to_warehouse.get_query = function() {
|
||||
return {
|
||||
filters:{
|
||||
'warehouse_type' : 'Transit',
|
||||
'is_group': 0,
|
||||
'company': frm.doc.company
|
||||
}
|
||||
};
|
||||
};
|
||||
frappe.db.get_value('Company', frm.doc.company, 'default_in_transit_warehouse', (r) => {
|
||||
if (r.default_in_transit_warehouse) {
|
||||
frm.set_value('to_warehouse', r.default_in_transit_warehouse);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -754,6 +774,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
}
|
||||
erpnext.hide_company();
|
||||
erpnext.utils.add_item(this.frm);
|
||||
this.frm.trigger('add_to_transit');
|
||||
},
|
||||
|
||||
scan_barcode: function() {
|
||||
@ -919,8 +940,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
doc.purpose!='Material Issue');
|
||||
|
||||
this.frm.fields_dict["items"].grid.set_column_disp("additional_cost", doc.purpose!='Material Issue');
|
||||
this.frm.toggle_reqd("outgoing_stock_entry",
|
||||
doc.purpose == 'Receive at Warehouse' ? 1: 0);
|
||||
},
|
||||
|
||||
supplier: function(doc) {
|
||||
|
@ -13,6 +13,7 @@
|
||||
"stock_entry_type",
|
||||
"outgoing_stock_entry",
|
||||
"purpose",
|
||||
"add_to_transit",
|
||||
"work_order",
|
||||
"purchase_order",
|
||||
"delivery_note_no",
|
||||
@ -116,11 +117,12 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.purpose == 'Receive at Warehouse'",
|
||||
"depends_on": "eval:doc.purpose == 'Material Transfer'",
|
||||
"fieldname": "outgoing_stock_entry",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock Entry (Outward GIT)",
|
||||
"options": "Stock Entry"
|
||||
"options": "Stock Entry",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@ -132,7 +134,7 @@
|
||||
"label": "Purpose",
|
||||
"oldfieldname": "purpose",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nSend to Warehouse\nReceive at Warehouse",
|
||||
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -630,13 +632,21 @@
|
||||
{
|
||||
"fieldname": "print_settings_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry",
|
||||
"fieldname": "add_to_transit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Add to Transit",
|
||||
"no_copy": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-23 12:56:52.881752",
|
||||
"modified": "2020-08-11 19:10:07.954981",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry",
|
||||
|
@ -96,6 +96,11 @@ class StockEntry(StockController):
|
||||
self.update_quality_inspection()
|
||||
if self.work_order and self.purpose == "Manufacture":
|
||||
self.update_so_in_serial_number()
|
||||
|
||||
if self.purpose == 'Material Transfer' and self.add_to_transit:
|
||||
self.set_material_request_transfer_status('In Transit')
|
||||
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
|
||||
self.set_material_request_transfer_status('Completed')
|
||||
|
||||
def on_cancel(self):
|
||||
|
||||
@ -116,6 +121,11 @@ class StockEntry(StockController):
|
||||
self.update_quality_inspection()
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
if self.purpose == 'Material Transfer' and self.add_to_transit:
|
||||
self.set_material_request_transfer_status('Not Started')
|
||||
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
|
||||
self.set_material_request_transfer_status('In Transit')
|
||||
|
||||
def set_job_card_data(self):
|
||||
if self.job_card and not self.work_order:
|
||||
data = frappe.db.get_value('Job Card',
|
||||
@ -133,7 +143,7 @@ class StockEntry(StockController):
|
||||
def validate_purpose(self):
|
||||
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
|
||||
"Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor",
|
||||
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
|
||||
"Material Consumption for Manufacture"]
|
||||
|
||||
if self.purpose not in valid_purposes:
|
||||
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
|
||||
@ -199,7 +209,8 @@ class StockEntry(StockController):
|
||||
item.set(f, item_details.get(f))
|
||||
|
||||
if not item.transfer_qty and item.qty:
|
||||
item.transfer_qty = item.qty * item.conversion_factor
|
||||
item.transfer_qty = flt(flt(item.qty) * flt(item.conversion_factor),
|
||||
self.precision("transfer_qty", item))
|
||||
|
||||
if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture")
|
||||
and not item.serial_no
|
||||
@ -258,10 +269,10 @@ class StockEntry(StockController):
|
||||
"""perform various (sometimes conditional) validations on warehouse"""
|
||||
|
||||
source_mandatory = ["Material Issue", "Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture",
|
||||
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
|
||||
"Material Consumption for Manufacture"]
|
||||
|
||||
target_mandatory = ["Material Receipt", "Material Transfer", "Send to Subcontractor",
|
||||
"Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
|
||||
"Material Transfer for Manufacture"]
|
||||
|
||||
validate_for_manufacture = any([d.bom_no for d in self.get("items")])
|
||||
|
||||
@ -809,7 +820,7 @@ class StockEntry(StockController):
|
||||
def set_items_for_stock_in(self):
|
||||
self.items = []
|
||||
|
||||
if self.outgoing_stock_entry and self.purpose == 'Receive at Warehouse':
|
||||
if self.outgoing_stock_entry and self.purpose == 'Material Transfer':
|
||||
doc = frappe.get_doc('Stock Entry', self.outgoing_stock_entry)
|
||||
|
||||
if doc.per_transferred == 100:
|
||||
@ -1210,13 +1221,25 @@ class StockEntry(StockController):
|
||||
|
||||
def validate_with_material_request(self):
|
||||
for item in self.get("items"):
|
||||
if item.material_request:
|
||||
material_request = item.material_request or None
|
||||
material_request_item = item.material_request_item or None
|
||||
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
|
||||
parent_se = frappe.get_value("Stock Entry Detail", item.ste_detail, ['material_request','material_request_item'],as_dict=True)
|
||||
if parent_se:
|
||||
material_request = parent_se.material_request
|
||||
material_request_item = parent_se.material_request_item
|
||||
|
||||
if material_request:
|
||||
mreq_item = frappe.db.get_value("Material Request Item",
|
||||
{"name": item.material_request_item, "parent": item.material_request},
|
||||
{"name": material_request_item, "parent": material_request},
|
||||
["item_code", "warehouse", "idx"], as_dict=True)
|
||||
if mreq_item.item_code != item.item_code or \
|
||||
mreq_item.warehouse != (item.s_warehouse if self.purpose== "Material Issue" else item.t_warehouse):
|
||||
frappe.throw(_("Item or Warehouse for row {0} does not match Material Request").format(item.idx),
|
||||
if mreq_item.item_code != item.item_code:
|
||||
frappe.throw(_("Item for row {0} does not match Material Request").format(item.idx),
|
||||
frappe.MappingMismatchError)
|
||||
elif self.purpose == "Material Transfer" and self.add_to_transit:
|
||||
continue
|
||||
elif mreq_item.warehouse != (item.s_warehouse if self.purpose == "Material Issue" else item.t_warehouse):
|
||||
frappe.throw(_("Warehouse for row {0} does not match Material Request").format(item.idx),
|
||||
frappe.MappingMismatchError)
|
||||
|
||||
def validate_batch(self):
|
||||
@ -1284,7 +1307,7 @@ class StockEntry(StockController):
|
||||
to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order))
|
||||
|
||||
def update_transferred_qty(self):
|
||||
if self.purpose == 'Receive at Warehouse':
|
||||
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
|
||||
stock_entries = {}
|
||||
stock_entries_child_list = []
|
||||
for d in self.items:
|
||||
@ -1342,6 +1365,20 @@ class StockEntry(StockController):
|
||||
'reference_type': reference_type,
|
||||
'reference_name': reference_name
|
||||
})
|
||||
def set_material_request_transfer_status(self, status):
|
||||
material_requests = []
|
||||
if self.outgoing_stock_entry:
|
||||
parent_se = frappe.get_value("Stock Entry", self.outgoing_stock_entry, 'add_to_transit')
|
||||
|
||||
for item in self.items:
|
||||
material_request = item.material_request or None
|
||||
if self.purpose == "Material Transfer" and material_request not in material_requests:
|
||||
if self.outgoing_stock_entry and parent_se:
|
||||
material_request = frappe.get_value("Stock Entry Detail", item.ste_detail, 'material_request')
|
||||
|
||||
if material_request and material_request not in material_requests:
|
||||
material_requests.append(material_request)
|
||||
frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
|
||||
|
||||
@frappe.whitelist()
|
||||
def move_sample_to_retention_warehouse(company, items):
|
||||
@ -1381,12 +1418,19 @@ def move_sample_to_retention_warehouse(company, items):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_stock_in_entry(source_name, target_doc=None):
|
||||
|
||||
def set_missing_values(source, target):
|
||||
target.purpose = 'Receive at Warehouse'
|
||||
target.set_stock_entry_type()
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.t_warehouse = ''
|
||||
|
||||
if source_doc.material_request_item and source_doc.material_request :
|
||||
add_to_transit = frappe.db.get_value('Stock Entry', source_name, 'add_to_transit')
|
||||
if add_to_transit:
|
||||
warehouse = frappe.get_value('Material Request Item', source_doc.material_request_item, 'warehouse')
|
||||
target_doc.t_warehouse = warehouse
|
||||
|
||||
target_doc.s_warehouse = source_doc.t_warehouse
|
||||
target_doc.qty = source_doc.qty - source_doc.transferred_qty
|
||||
|
||||
|
@ -737,34 +737,6 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
|
||||
self.assertEqual(se.get("items")[0].amount, 0)
|
||||
|
||||
def test_goods_in_transit(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
warehouse = "_Test Warehouse FG 1 - _TC"
|
||||
|
||||
if not frappe.db.exists('Warehouse', warehouse):
|
||||
create_warehouse("_Test Warehouse FG 1")
|
||||
|
||||
outward_entry = make_stock_entry(item_code="_Test Item",
|
||||
purpose="Send to Warehouse",
|
||||
source="_Test Warehouse - _TC",
|
||||
target="_Test Warehouse 1 - _TC", qty=50, basic_rate=100)
|
||||
|
||||
inward_entry1 = make_stock_in_entry(outward_entry.name)
|
||||
inward_entry1.items[0].t_warehouse = warehouse
|
||||
inward_entry1.items[0].qty = 25
|
||||
inward_entry1.submit()
|
||||
|
||||
doc = frappe.get_doc('Stock Entry', outward_entry.name)
|
||||
self.assertEqual(doc.per_transferred, 50)
|
||||
|
||||
inward_entry2 = make_stock_in_entry(outward_entry.name)
|
||||
inward_entry2.items[0].t_warehouse = warehouse
|
||||
inward_entry2.items[0].qty = 25
|
||||
inward_entry2.submit()
|
||||
|
||||
doc = frappe.get_doc('Stock Entry', outward_entry.name)
|
||||
self.assertEqual(doc.per_transferred, 100)
|
||||
|
||||
def test_gle_for_opening_stock_entry(self):
|
||||
mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True)
|
||||
|
||||
|
@ -1,156 +1,83 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "Prompt",
|
||||
"beta": 0,
|
||||
"creation": "2019-03-13 16:23:46.636769",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"purpose"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Material Issue",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "purpose",
|
||||
"fieldtype": "Select",
|
||||
"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": "Purpose",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nSend to Warehouse\nReceive at Warehouse",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-26 12:02:42.144377",
|
||||
"links": [],
|
||||
"modified": "2020-08-10 23:24:37.160817",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Type",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
})
|
||||
</script>
|
||||
{% else %}
|
||||
{{ product_image(website_image or image or 'no-image.jpg') }}
|
||||
{{ product_image(website_image or image or 'no-image.jpg', alt=website_image_alt or item_name) }}
|
||||
{% endif %}
|
||||
|
||||
<!-- Simple image preview -->
|
||||
|
@ -7,9 +7,9 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro product_image(website_image, css_class="") %}
|
||||
{% macro product_image(website_image, css_class="", alt="") %}
|
||||
<div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;">
|
||||
<img itemprop="image" class="website-image h-100 w-100" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
|
||||
<img itemprop="image" class="website-image h-100 w-100" alt="{{ alt }}" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
@ -9,6 +9,33 @@
|
||||
<p class="hero-subtitle">{{ greeting_subtitle }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="search-container">
|
||||
<div class="website-search" id="search-container">
|
||||
<div class="dropdown">
|
||||
<div class="search-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-search">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<input type="search" class="form-control" placeholder="Search the docs (Press ? to focus)" />
|
||||
<div class="overflow-hidden shadow dropdown-menu w-100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="navbar-toggler" type="button"
|
||||
data-toggle="collapse"
|
||||
data-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -54,5 +81,21 @@
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
{%- block script -%}
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
frappe.setup_search('#search-container', 'kb');
|
||||
});
|
||||
</script>
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block style -%}
|
||||
<style>
|
||||
.search-container {
|
||||
margin-top: 1.2rem;
|
||||
max-width: 500px;
|
||||
}
|
||||
</style>
|
||||
{%- endblock -%}
|
||||
|
Loading…
x
Reference in New Issue
Block a user