Merge branch 'develop' of github.com:frappe/erpnext into feature-pick-list

This commit is contained in:
Suraj Shetty 2019-07-23 13:29:41 +05:30
commit ed4f8cf65d
142 changed files with 16532 additions and 9000 deletions

View File

@ -48,7 +48,10 @@ class BankAccount(Document):
# Encode characters as numbers
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
to_check = int(''.join(encoded))
try:
to_check = int(''.join(encoded))
except ValueError:
frappe.throw(_('IBAN is not valid'))
if to_check % 97 != 1:
frappe.throw(_('IBAN is not valid'))

View File

@ -223,7 +223,10 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
if(in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
// Filter by cost center
if(jvd.cost_center) {
out.filters.push([jvd.reference_type, "cost_center", "in", ["", jvd.cost_center]]);
}
// account filter
frappe.model.validate_missing(jvd, "account");
var party_account_field = jvd.reference_type==="Sales Invoice" ? "debit_to": "credit_to";

View File

@ -1,751 +1,166 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2019-03-24 14:48:59.649168",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"disable",
"column_break_2",
"rule_description",
"section_break_1",
"min_qty",
"max_qty",
"column_break_3",
"min_amount",
"max_amount",
"section_break_6",
"same_item",
"free_item",
"free_qty",
"column_break_9",
"free_item_uom",
"free_item_rate",
"section_break_12",
"warehouse",
"threshold_percentage",
"column_break_15",
"priority",
"apply_multiple_pricing_rules"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "disable",
"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": "Disable",
"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
"fieldtype": "Check",
"label": "Disable"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rule_description",
"fieldtype": "Small Text",
"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": "Rule Description",
"length": 0,
"no_copy": 1,
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_1",
"fieldtype": "Section 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
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "min_qty",
"fieldtype": "Float",
"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": "Min Qty",
"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
"label": "Min Qty"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "max_qty",
"fieldtype": "Float",
"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": "Max Qty",
"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
"label": "Max Qty"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "min_amount",
"fieldtype": "Currency",
"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": "Min Amount",
"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
"label": "Min Amount"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "max_amount",
"fieldtype": "Currency",
"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": "Max Amount",
"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
"label": "Max Amount"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"fieldtype": "Section 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,
"label": "Free Item",
"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
"label": "Free Item"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"depends_on": "eval:!parent.mixed_conditions",
"fieldname": "same_item",
"fieldtype": "Check",
"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": "Same Item",
"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
"label": "Same Item"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.same_item || parent.mixed_conditions",
"fieldname": "free_item",
"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": "Item Code",
"length": 0,
"no_copy": 0,
"options": "Item",
"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
"options": "Item"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "free_qty",
"fieldtype": "Float",
"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": "Qty",
"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
"label": "Qty"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "free_item_uom",
"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": "UOM",
"length": 0,
"no_copy": 0,
"options": "UOM",
"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
"options": "UOM"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "free_item_rate",
"fieldtype": "Currency",
"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": "Rate",
"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
"label": "Rate"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_12",
"fieldtype": "Section 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
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "warehouse",
"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": "Warehouse",
"length": 0,
"no_copy": 0,
"options": "Warehouse",
"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
"options": "Warehouse"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "threshold_percentage",
"fieldtype": "Percent",
"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": "Threshold for Suggestion",
"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
"label": "Threshold for Suggestion"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_15",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "priority",
"fieldtype": "Select",
"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": "Priority",
"length": 0,
"no_copy": 0,
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20",
"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
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "apply_multiple_pricing_rules",
"fieldtype": "Check",
"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": "Apply Multiple Pricing Rules",
"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
"label": "Apply Multiple Pricing Rules"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-03-24 14:48:59.649168",
"modified": "2019-07-21 00:00:56.674284",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme Product Discount",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"sort_order": "DESC"
}

View File

@ -336,6 +336,7 @@ class PurchaseInvoice(BuyingController):
if not self.is_return:
self.update_against_document_in_jv()
self.update_billing_status_for_zero_amount_refdoc("Purchase Receipt")
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr()
@ -774,6 +775,7 @@ class PurchaseInvoice(BuyingController):
self.update_prevdoc_status()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Purchase Receipt")
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr()

View File

@ -451,6 +451,10 @@ def make_customer_and_address(customers):
def add_customer(data):
customer = data.get('full_name') or data.get('customer')
if frappe.db.exists("Customer", customer.strip()):
return customer.strip()
customer_doc = frappe.new_doc('Customer')
customer_doc.customer_name = data.get('full_name') or data.get('customer')
customer_doc.customer_pos_id = data.get('customer_pos_id')

View File

@ -166,6 +166,7 @@ class SalesInvoice(SellingController):
self.make_gl_entries()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.check_credit_limit()
@ -220,6 +221,7 @@ class SalesInvoice(SellingController):
self.update_billing_status_in_dn()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.update_serial_no(in_cancel=True)
@ -395,14 +397,17 @@ class SalesInvoice(SellingController):
if pos.get('account_for_change_amount'):
self.account_for_change_amount = pos.get('account_for_change_amount')
for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name',
'company', 'select_print_heading', 'cash_bank_account', 'company_address',
'write_off_account', 'write_off_cost_center', 'apply_discount_on', 'cost_center'):
for fieldname in ('territory', 'naming_series', 'currency', 'letter_head', 'tc_name',
'company', 'select_print_heading', 'cash_bank_account', 'write_off_account', 'taxes_and_charges',
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname))
customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list')
if pos.get("company_address"):
self.company_address = pos.get("company_address")
if not customer_price_list:
self.set('selling_price_list', pos.get('selling_price_list'))
@ -1255,9 +1260,8 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc
frappe.throw(_("Invalid Company for Inter Company Transaction."))
elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party:
companies = frappe.db.sql("""select company from `tabAllowed To Transact With`
where parenttype = '{0}' and parent = '{1}'""".format(partytype, party), as_list = 1)
companies = [d[0] for d in companies]
companies = frappe.get_all("Allowed To Transact With", fields=["company"], filters={"parenttype": partytype, "parent": party})
companies = [d.company for d in companies]
if not company in companies:
frappe.throw(_("{0} not allowed to transact with {1}. Please change the Company.").format(partytype, company))

View File

@ -12,11 +12,11 @@ def execute(filters=None):
columns = get_columns()
if not filters.get("account"): return columns, []
account_currency = frappe.db.get_value("Account", filters.account, "account_currency")
data = get_entries(filters)
from erpnext.accounts.utils import get_balance_on
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
@ -24,7 +24,7 @@ def execute(filters=None):
for d in data:
total_debit += flt(d.debit)
total_credit += flt(d.credit)
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) \
@ -39,7 +39,7 @@ def execute(filters=None):
"credit": total_credit,
"account_currency": account_currency
},
get_balance_row(_("Cheques and Deposits incorrectly cleared"), amounts_not_reflected_in_system,
get_balance_row(_("Cheques and Deposits incorrectly cleared"), amounts_not_reflected_in_system,
account_currency),
{},
get_balance_row(_("Calculated Bank Statement balance"), bank_bal, account_currency)
@ -55,9 +55,16 @@ def get_columns():
"fieldtype": "Date",
"width": 90
},
{
"fieldname": "payment_document",
"label": _("Payment Document Type"),
"fieldtype": "Link",
"options": "DocType",
"width": 220
},
{
"fieldname": "payment_entry",
"label": _("Payment Entry"),
"label": _("Payment Document"),
"fieldtype": "Dynamic Link",
"options": "payment_document",
"width": 220
@ -100,7 +107,7 @@ def get_columns():
"label": _("Clearance Date"),
"fieldtype": "Date",
"width": 110
},
},
{
"fieldname": "account_currency",
"label": _("Currency"),
@ -112,9 +119,9 @@ def get_columns():
def get_entries(filters):
journal_entries = frappe.db.sql("""
select "Journal Entry" as payment_document, jv.posting_date,
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
jvd.credit_in_account_currency as credit, jvd.against_account,
select "Journal Entry" as payment_document, jv.posting_date,
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
jvd.credit_in_account_currency as credit, jvd.against_account,
jv.cheque_no as reference_no, jv.cheque_date as ref_date, jv.clearance_date, jvd.account_currency
from
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
@ -122,13 +129,13 @@ def get_entries(filters):
and jvd.account = %(account)s and jv.posting_date <= %(report_date)s
and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
and ifnull(jv.is_opening, 'No') = 'No'""", filters, as_dict=1)
payment_entries = frappe.db.sql("""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no, reference_date as ref_date,
if(paid_to=%(account)s, received_amount, 0) as debit,
if(paid_from=%(account)s, paid_amount, 0) as credit,
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no, reference_date as ref_date,
if(paid_to=%(account)s, received_amount, 0) as debit,
if(paid_from=%(account)s, paid_amount, 0) as credit,
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry`
@ -156,25 +163,25 @@ def get_entries(filters):
return sorted(list(payment_entries)+list(journal_entries+list(pos_entries)),
key=lambda k: k['posting_date'] or getdate(nowdate()))
def get_amounts_not_reflected_in_system(filters):
je_amount = frappe.db.sql("""
select sum(jvd.debit_in_account_currency - jvd.credit_in_account_currency)
from `tabJournal Entry Account` jvd, `tabJournal Entry` jv
where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%(account)s
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
and ifnull(jv.is_opening, 'No') = 'No' """, filters)
je_amount = flt(je_amount[0][0]) if je_amount else 0.0
pe_amount = frappe.db.sql("""
select sum(if(paid_from=%(account)s, paid_amount, received_amount))
from `tabPayment Entry`
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date > %(report_date)s and clearance_date <= %(report_date)s""", filters)
pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0
return je_amount + pe_amount
def get_balance_row(label, amount, account_currency):

View File

@ -125,8 +125,9 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
data["total"] = total
return data
def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters):
def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters={}):
cond = ""
filters = frappe._dict(filters)
if filters.finance_book:
cond = " and finance_book = %s" %(frappe.db.escape(filters.finance_book))
@ -187,7 +188,7 @@ def get_chart_data(columns, data):
},
"type": "bar"
}
chart["fieldtype"] = "Currency"
return chart

View File

@ -130,7 +130,7 @@ def get_cash_flow_data(fiscal_year, companies, filters):
section_data.append(net_profit_loss)
for account in cash_flow_account['account_types']:
account_data = get_account_type_based_data(account['account_type'], companies, fiscal_year)
account_data = get_account_type_based_data(account['account_type'], companies, fiscal_year, filters)
account_data.update({
"account_name": account['label'],
"account": account['label'],
@ -148,12 +148,12 @@ def get_cash_flow_data(fiscal_year, companies, filters):
return data
def get_account_type_based_data(account_type, companies, fiscal_year):
def get_account_type_based_data(account_type, companies, fiscal_year, filters):
data = {}
total = 0
for company in companies:
amount = get_account_type_based_gl_data(company,
fiscal_year.year_start_date, fiscal_year.year_end_date, account_type)
fiscal_year.year_start_date, fiscal_year.year_end_date, account_type, filters)
if amount and account_type == "Depreciation":
amount *= -1

View File

@ -121,7 +121,12 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account()
if cost_center and (allow_cost_center_in_entry_of_bs_account or acc.report_type =='Profit and Loss'):
if account:
report_type = acc.report_type
else:
report_type = ""
if cost_center and (allow_cost_center_in_entry_of_bs_account or report_type =='Profit and Loss'):
cc = frappe.get_doc("Cost Center", cost_center)
if cc.is_group:
cond.append(""" exists (
@ -138,7 +143,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
if not frappe.flags.ignore_account_permission:
acc.check_permission("read")
if acc.report_type == 'Profit and Loss':
if report_type == 'Profit and Loss':
# for pl accounts, get balance within a fiscal year
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
% year_start_date)
@ -685,7 +690,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
payment_amount = pe_map.get((d.voucher_type, d.voucher_no), 0)
outstanding_amount = flt(d.invoice_amount - payment_amount, precision)
if outstanding_amount > 0.5 / (10**precision):
if (filters.get("outstanding_amt_greater_than") and
if (filters and filters.get("outstanding_amt_greater_than") and
not (outstanding_amount >= filters.get("outstanding_amt_greater_than") and
outstanding_amount <= filters.get("outstanding_amt_less_than"))):
continue
@ -730,7 +735,6 @@ def get_children(doctype, parent, company, is_root=False):
parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_')
fields = [
'name as value',
'root_type',
'is_group as expandable'
]
filters = [['docstatus', '<', 2]]
@ -738,11 +742,11 @@ def get_children(doctype, parent, company, is_root=False):
filters.append(['ifnull(`{0}`,"")'.format(parent_fieldname), '=', '' if is_root else parent])
if is_root:
fields += ['report_type', 'account_currency'] if doctype == 'Account' else []
fields += ['root_type', 'report_type', 'account_currency'] if doctype == 'Account' else []
filters.append(['company', '=', company])
else:
fields += ['account_currency'] if doctype == 'Account' else []
fields += ['root_type', 'account_currency'] if doctype == 'Account' else []
fields += [parent_fieldname + ' as parent']
acc = frappe.get_list(doctype, fields=fields, filters=filters)

View File

@ -388,7 +388,8 @@ class Asset(AccountsController):
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount,
"credit_in_account_currency": self.purchase_receipt_amount
"credit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center
}))
gl_entries.append(self.get_gl_dict({
@ -397,7 +398,8 @@ class Asset(AccountsController):
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"debit": self.purchase_receipt_amount,
"debit_in_account_currency": self.purchase_receipt_amount
"debit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center
}))
if gl_entries:

View File

@ -7,7 +7,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-03-29 17:18:06.678728",
"modified": "2019-07-21 23:24:21.094269",
"modified_by": "Administrator",
"module": "Buying",
"name": "Procurement Tracker",
@ -16,5 +16,12 @@
"ref_doctype": "Purchase Order",
"report_name": "Procurement Tracker",
"report_type": "Script Report",
"roles": []
"roles": [
{
"role": "Purchase Manager"
},
{
"role": "Purchase User"
}
]
}

View File

@ -150,10 +150,10 @@ def get_conditions(filters):
"""% (filters.get('cost_center'), filters.get('project'))
if filters.get("from_date"):
conditions += "AND transaction_date>=%s"% filters.get('from_date')
conditions += " AND transaction_date>=%s"% filters.get('from_date')
if filters.get("to_date"):
conditions += "AND transaction_date<=%s"% filters.get('to_date')
conditions += " AND transaction_date<=%s"% filters.get('to_date')
return conditions
def get_data(filters):

View File

@ -157,6 +157,13 @@ def get_data():
"reference_doctype": "Purchase Order",
"onboard": 1,
},
{
"type": "report",
"is_query_report": True,
"name": "Procurement Tracker",
"reference_doctype": "Purchase Order",
"onboard": 1,
},
{
"type": "report",
"is_query_report": True,
@ -228,5 +235,5 @@ def get_data():
}
]
},
]

View File

@ -141,6 +141,11 @@ def get_data():
"name": "Campaign",
"description": _("Sales campaigns."),
},
{
"type": "doctype",
"name": "Email Campaign",
"description": _("Sends Mails to lead or contact based on a Campaign schedule"),
},
{
"type": "doctype",
"name": "SMS Center",

View File

@ -219,6 +219,11 @@ def get_data():
"name": "Employee Onboarding",
"dependencies": ["Job Applicant"],
},
{
"type": "doctype",
"name": "Employee Skill Map",
"dependencies": ["Employee"],
},
{
"type": "doctype",
"name": "Employee Promotion",

View File

@ -861,7 +861,7 @@ class AccountsController(TransactionBase):
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount)
if total != grand_total:
if total != flt(grand_total, self.precision("grand_total")):
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
def is_rounded_total_disabled(self):

View File

@ -636,7 +636,8 @@ class BuyingController(StockController):
asset.set_missing_values()
asset.insert()
frappe.msgprint(_("Asset {0} created").format(asset.name))
asset_link = frappe.utils.get_link_to_form('Asset', asset.name)
frappe.msgprint(_("Asset {0} created").format(asset_link))
return asset.name
def make_asset_movement(self, row):

View File

@ -207,10 +207,10 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
idx desc, name
limit %(start)s, %(page_len)s """.format(
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype),
key=frappe.db.escape(searchfield)),
mcond=get_match_cond(doctype).replace('%', '%%'),
key=searchfield),
{
'txt': "%"+frappe.db.escape(txt)+"%",
'txt': '%' + txt + '%',
'_txt': txt.replace("%", ""),
'start': start or 0,
'page_len': page_len or 20

View File

@ -40,7 +40,6 @@ status_map = {
["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.order_type == 'Maintenance' and self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["On Hold", "eval:self.status=='On Hold'"],
@ -90,7 +89,8 @@ status_map = {
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"]
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
],
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],

View File

@ -323,7 +323,7 @@ class calculate_taxes_and_totals(object):
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \
if self.doc.total_taxes_and_charges else self.doc.base_net_total
else:
self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0

View File

@ -0,0 +1,38 @@
{
"creation": "2019-06-30 15:56:20.306901",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"email_template",
"send_after_days"
],
"fields": [
{
"fieldname": "send_after_days",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Send After (days)",
"reqd": 1
},
{
"fieldname": "email_template",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Email Template",
"options": "Email Template",
"reqd": 1
}
],
"istable": 1,
"modified": "2019-07-12 11:46:43.184123",
"modified_by": "Administrator",
"module": "CRM",
"name": "Campaign Email Schedule",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, 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 CampaignEmailSchedule(Document):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Email Campaign', {
email_campaign_for: function(frm) {
frm.set_value('recipient', '');
}
});

View File

@ -0,0 +1,95 @@
{
"autoname": "format:MAIL-CAMP-{YYYY}-{#####}",
"creation": "2019-06-30 16:05:30.015615",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"campaign_name",
"email_campaign_for",
"recipient",
"sender",
"column_break_4",
"start_date",
"end_date",
"status"
],
"fields": [
{
"fieldname": "campaign_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Campaign",
"options": "Campaign",
"reqd": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed",
"read_only": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Start Date",
"reqd": 1
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"label": "End Date",
"read_only": 1
},
{
"default": "Lead",
"fieldname": "email_campaign_for",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Email Campaign For ",
"options": "\nLead\nContact"
},
{
"fieldname": "recipient",
"fieldtype": "Dynamic Link",
"label": "Recipient",
"options": "email_campaign_for",
"reqd": 1
},
{
"default": "__user",
"fieldname": "sender",
"fieldtype": "Link",
"label": "Sender",
"options": "User"
}
],
"modified": "2019-07-12 13:47:37.261213",
"modified_by": "Administrator",
"module": "CRM",
"name": "Email Campaign",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import getdate, add_days, today, nowdate, cstr
from frappe.model.document import Document
from frappe.core.doctype.communication.email import make
class EmailCampaign(Document):
def validate(self):
self.set_date()
#checking if email is set for lead. Not checking for contact as email is a mandatory field for contact.
if self.email_campaign_for == "Lead":
self.validate_lead()
self.validate_email_campaign_already_exists()
self.update_status()
def set_date(self):
if getdate(self.start_date) < getdate(today()):
frappe.throw(_("Start Date cannot be before the current date"))
#set the end date as start date + max(send after days) in campaign schedule
send_after_days = []
campaign = frappe.get_doc("Campaign", self.campaign_name)
for entry in campaign.get("campaign_schedules"):
send_after_days.append(entry.send_after_days)
try:
end_date = add_days(getdate(self.start_date), max(send_after_days))
except ValueError:
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))
def validate_lead(self):
lead_email_id = frappe.db.get_value("Lead", self.recipient, 'email_id')
if not lead_email_id:
lead_name = frappe.db.get_value("Lead", self.recipient, 'lead_name')
frappe.throw(_("Please set an email id for the Lead {0}").format(lead_name))
def validate_email_campaign_already_exists(self):
email_campaign_exists = frappe.db.exists("Email Campaign", {
"campaign_name": self.campaign_name,
"recipient": self.recipient,
"status": ("in", ["In Progress", "Scheduled"])
})
if email_campaign_exists:
frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
def update_status(self):
start_date = getdate(self.start_date)
end_date = getdate(self.end_date)
today_date = getdate(today())
if start_date > today_date:
self.status = "Scheduled"
elif end_date >= today_date:
self.status = "In Progress"
elif end_date < today_date:
self.status = "Completed"
#called through hooks to send campaign mails to leads
def send_email_to_leads_or_contacts():
email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('not in', ['Unsubscribed', 'Completed', 'Scheduled']) })
for camp in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", camp.name)
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
for entry in campaign.get("campaign_schedules"):
scheduled_date = add_days(email_campaign.get('start_date'), entry.get('send_after_days'))
if scheduled_date == getdate(today()):
send_mail(entry, email_campaign)
def send_mail(entry, email_campaign):
recipient = frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), 'email_id')
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email')
# send mail and link communication to document
comm = make(
doctype = "Email Campaign",
name = email_campaign.name,
subject = email_template.get("subject"),
content = email_template.get("response"),
sender = sender,
recipients = recipient,
communication_medium = "Email",
sent_or_received = "Sent",
send_email = True,
email_template = email_template.name
)
return comm
#called from hooks on doc_event Email Unsubscribe
def unsubscribe_recipient(unsubscribe, method):
if unsubscribe.reference_doctype == 'Email Campaign':
frappe.db.set_value("Email Campaign", unsubscribe.reference_name, "status", "Unsubscribed")
#called through hooks to update email campaign status daily
def set_email_campaign_status():
email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('!=', 'Unsubscribed')})
for entry in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", entry.name)
email_campaign.update_status()

View File

@ -0,0 +1,11 @@
frappe.listview_settings['Email Campaign'] = {
get_indicator: function(doc) {
var colors = {
"Unsubscribed": "red",
"Scheduled": "blue",
"In Progress": "orange",
"Completed": "green"
};
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
}
};

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestEmailCampaign(unittest.TestCase):
pass

View File

@ -7,14 +7,8 @@ cur_frm.email_field = "email_id";
erpnext.LeadController = frappe.ui.form.Controller.extend({
setup: function () {
this.frm.make_methods = {
'Quotation': () => erpnext.utils.create_new_doc('Quotation', {
'quotation_to': this.frm.doc.doctype,
'party_name': this.frm.doc.name
}),
'Opportunity': () => erpnext.utils.create_new_doc('Opportunity', {
'opportunity_from': this.frm.doc.doctype,
'party_name': this.frm.doc.name
})
'Quotation': this.make_quotation,
'Opportunity': this.create_opportunity
}
this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) {

View File

@ -296,7 +296,9 @@ class TallyMigration(Document):
else:
function = voucher_to_journal_entry
try:
vouchers.append(function(voucher))
processed_voucher = function(voucher)
if processed_voucher:
vouchers.append(processed_voucher)
except:
self.log(voucher)
return vouchers
@ -342,6 +344,10 @@ class TallyMigration(Document):
account_field = "credit_to"
account_name = encode_company_abbr(self.tally_creditors_account, self.erpnext_company)
price_list_field = "buying_price_list"
else:
# Do not handle vouchers other than "Purchase", "Debit Note", "Sales" and "Credit Note"
# Do not handle Custom Vouchers either
return
invoice = {
"doctype": doctype,

View File

@ -233,6 +233,9 @@ doc_events = {
},
"Contact":{
"on_trash": "erpnext.support.doctype.issue.issue.update_issue"
},
"Email Unsubscribe": {
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
}
}
@ -272,6 +275,8 @@ scheduler_events = {
"erpnext.projects.doctype.project.project.send_project_status_email_to_users",
"erpnext.quality_management.doctype.quality_review.quality_review.review",
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
],
"daily_long": [
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"

View File

@ -4,4 +4,17 @@ from __future__ import unicode_literals
import frappe
test_records = frappe.get_test_records('Designation')
# test_records = frappe.get_test_records('Designation')
def create_designation(**args):
args = frappe._dict(args)
if frappe.db.exists("Designation", args.designation_name or "_Test designation"):
return frappe.get_doc("Designation", args.designation_name or "_Test designation")
designation = frappe.get_doc({
"doctype": "Designation",
"designation_name": args.designation_name or "_Test designation",
"description": args.description or "_Test description"
})
designation.save()
return designation

View File

@ -1,516 +1,124 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
"beta": 0,
"creation": "2017-10-17 08:21:50.489773",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"full_name",
"status",
"transporter",
"column_break_2",
"employee",
"cell_number",
"address",
"license_details",
"license_number",
"column_break_8",
"issuing_date",
"column_break_10",
"expiry_date",
"driving_license_categories",
"driving_license_category"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series",
"fieldtype": "Select",
"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": "Series",
"length": 0,
"no_copy": 0,
"options": "HR-DRI-.YYYY.-",
"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
"options": "HR-DRI-.YYYY.-"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "full_name",
"fieldtype": "Data",
"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": "Full Name",
"length": 0,
"no_copy": 0,
"options": "",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "status",
"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": "Status",
"length": 0,
"no_copy": 0,
"options": "Active\nSuspended\nLeft",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Applicable for external driver",
"fieldname": "transporter",
"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": "Transporter",
"length": 0,
"no_copy": 0,
"options": "Supplier",
"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
"options": "Supplier"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"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
"fieldtype": "Column Break"
},
{
"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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Employee"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cell_number",
"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": "Cellphone Number",
"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
"label": "Cellphone Number"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "license_details",
"fieldtype": "Section 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,
"label": "License Details",
"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
"label": "License Details"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "license_number",
"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": "License Number",
"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
"label": "License Number"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_8",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "issuing_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": "Issuing 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
"label": "Issuing Date"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "expiry_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": "Expiry 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
"label": "Expiry Date"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "driving_license_categories",
"fieldtype": "Section 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,
"label": "Driving License Categories",
"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
"label": "Driving License Categories"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "driving_license_category",
"fieldtype": "Table",
"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": "Driving License Category",
"length": 0,
"no_copy": 0,
"options": "Driving License Category",
"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
"options": "Driving License Category"
},
{
"fieldname": "address",
"fieldtype": "Link",
"label": "Address",
"options": "Address"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-user",
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-03 19:53:50.924391",
"modified": "2019-07-18 16:29:14.151380",
"modified_by": "Administrator",
"module": "HR",
"name": "Driver",
@ -518,72 +126,44 @@
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Fleet Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 0
"share": 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": "HR User",
"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": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "full_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "full_name",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View File

@ -12,6 +12,7 @@ from frappe.permissions import add_user_permission, remove_user_permission, \
from frappe.model.document import Document
from erpnext.utilities.transaction_base import delete_events
from frappe.utils.nestedset import NestedSet
from erpnext.hr.doctype.job_offer.job_offer import get_staffing_plan_detail
class EmployeeUserDisabledError(frappe.ValidationError): pass
class EmployeeLeftValidationError(frappe.ValidationError): pass

View File

@ -3,6 +3,7 @@
"doctype": "DocType",
"document_type": "Other",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"employee_settings",
"retirement_age",
@ -22,7 +23,9 @@
"leave_status_notification_template",
"column_break_18",
"leave_approver_mandatory_in_leave_application",
"show_leaves_of_all_department_members_in_calendar"
"show_leaves_of_all_department_members_in_calendar",
"hiring_settings",
"check_vacancies"
],
"fields": [
{
@ -44,18 +47,6 @@
"label": "Employee Records to be created by",
"options": "Naming Series\nEmployee Number\nFull Name"
},
{
"fieldname": "leave_approval_notification_template",
"fieldtype": "Link",
"label": "Leave Approval Notification Template",
"options": "Email Template"
},
{
"fieldname": "leave_status_notification_template",
"fieldtype": "Link",
"label": "Leave Status Notification Template",
"options": "Email Template"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
@ -67,12 +58,6 @@
"fieldtype": "Check",
"label": "Stop Birthday Reminders"
},
{
"default": "1",
"fieldname": "leave_approver_mandatory_in_leave_application",
"fieldtype": "Check",
"label": "Leave Approver Mandatory In Leave Application"
},
{
"default": "1",
"fieldname": "expense_approver_mandatory_in_expense_claim",
@ -91,6 +76,15 @@
"fieldtype": "Check",
"label": "Include holidays in Total no. of Working Days"
},
{
"fieldname": "max_working_hours_against_timesheet",
"fieldtype": "Float",
"label": "Max working hours against Timesheet"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"default": "1",
"description": "Emails salary slip to employee based on preferred email selected in Employee",
@ -115,15 +109,33 @@
"label": "Password Policy"
},
{
"fieldname": "max_working_hours_against_timesheet",
"fieldtype": "Float",
"label": "Max working hours against Timesheet"
},
{
"collapsible": 1,
"fieldname": "leave_settings",
"fieldtype": "Section Break",
"label": "Leave Settings"
},
{
"fieldname": "leave_approval_notification_template",
"fieldtype": "Link",
"label": "Leave Approval Notification Template",
"options": "Email Template"
},
{
"fieldname": "leave_status_notification_template",
"fieldtype": "Link",
"label": "Leave Status Notification Template",
"options": "Email Template"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
},
{
"default": "1",
"fieldname": "leave_approver_mandatory_in_leave_application",
"fieldtype": "Check",
"label": "Leave Approver Mandatory In Leave Application"
},
{
"default": "0",
"fieldname": "show_leaves_of_all_department_members_in_calendar",
@ -131,18 +143,22 @@
"label": "Show Leaves Of All Department Members In Calendar"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
"collapsible": 1,
"fieldname": "hiring_settings",
"fieldtype": "Section Break",
"label": "Hiring Settings"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
"default": "0",
"fieldname": "check_vacancies",
"fieldtype": "Check",
"label": "Check Vacancies On Job Offer Creation"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"modified": "2019-05-31 16:18:50.245872",
"modified": "2019-07-01 18:59:55.256878",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
@ -158,5 +174,6 @@
"write": 1
}
],
"sort_field": "modified",
"sort_order": "ASC"
}

View File

@ -39,7 +39,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@ -71,7 +71,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@ -96,7 +96,7 @@
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "Open\nReplied\nRejected\nHold",
"options": "Open\nReplied\nRejected\nHold\nAccepted",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@ -346,7 +346,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:43.552049",
"modified": "2019-06-21 16:15:43.552049",
"modified_by": "Administrator",
"module": "HR",
"name": "Job Applicant",

View File

@ -10,3 +10,14 @@ import unittest
class TestJobApplicant(unittest.TestCase):
pass
def create_job_applicant(**args):
args = frappe._dict(args)
job_applicant = frappe.get_doc({
"doctype": "Job Applicant",
"applicant_name": args.applicant_name or "_Test Applicant",
"email_id": args.email_id or "test_applicant@example.com",
"status": args.status or "Open"
})
job_applicant.save()
return job_applicant

View File

@ -5,12 +5,56 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe import _
from frappe.utils.data import get_link_to_form
class JobOffer(Document):
def onload(self):
employee = frappe.db.get_value("Employee", {"job_applicant": self.job_applicant}, "name") or ""
self.set_onload("employee", employee)
def validate(self):
self.validate_vacancies()
def validate_vacancies(self):
staffing_plan = get_staffing_plan_detail(self.designation, self.company, self.offer_date)
check_vacancies = frappe.get_single("HR Settings").check_vacancies
if staffing_plan and check_vacancies:
vacancies = frappe.db.get_value("Staffing Plan Detail", filters={"name": staffing_plan.name}, fieldname=['vacancies'])
job_offers = len(self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date))
if vacancies - job_offers <= 0:
frappe.throw(_("There are no vacancies under staffing plan {0}").format(get_link_to_form("Staffing Plan", staffing_plan.parent)))
def on_change(self):
update_job_applicant(self.status, self.job_applicant)
def get_job_offer(self, from_date, to_date):
''' Returns job offer created during a time period '''
return frappe.get_all("Job Offer", filters={
"offer_date": ['between', (from_date, to_date)],
"designation": self.designation,
"company": self.company
}, fields=['name'])
def update_job_applicant(status, job_applicant):
if status in ("Accepted", "Rejected"):
frappe.set_value("Job Applicant", job_applicant, "status", status)
def get_staffing_plan_detail(designation, company, offer_date):
detail = frappe.db.sql("""
SELECT spd.name as name,
sp.from_date as from_date,
sp.to_date as to_date,
sp.name as parent
FROM `tabStaffing Plan Detail` spd, `tabStaffing Plan` sp
WHERE
sp.docstatus=1
AND spd.designation=%s
AND sp.company=%s
AND %s between sp.from_date and sp.to_date
""", (designation, company, offer_date), as_dict=1)
return detail[0] if detail else None
@frappe.whitelist()
def make_employee(source_name, target_doc=None):
def set_missing_values(source, target):
@ -23,4 +67,3 @@ def make_employee(source_name, target_doc=None):
}}
}, target_doc, set_missing_values)
return doc

View File

@ -4,8 +4,78 @@ from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import nowdate, add_days
from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant
from erpnext.hr.doctype.designation.test_designation import create_designation
from erpnext.hr.doctype.staffing_plan.test_staffing_plan import make_company
# test_records = frappe.get_test_records('Job Offer')
class TestJobOffer(unittest.TestCase):
pass
def test_job_offer_creation_against_vacancies(self):
create_staffing_plan(staffing_details=[{
"designation": "Designer",
"vacancies": 0,
"estimated_cost_per_position": 5000
}])
frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
job_offer = create_job_offer(job_applicant=job_applicant.name, designation="Researcher")
self.assertRaises(frappe.ValidationError, job_offer.submit)
# test creation of job offer when vacancies are not present
frappe.db.set_value("HR Settings", None, "check_vacancies", 0)
job_offer.submit()
self.assertTrue(frappe.db.exists("Job Offer", job_offer.name))
def test_job_applicant_update(self):
create_staffing_plan()
job_applicant = create_job_applicant(email_id="test_job_applicants@example.com")
job_offer = create_job_offer(job_applicant=job_applicant.name)
job_offer.submit()
job_applicant.reload()
self.assertEquals(job_applicant.status, "Accepted")
# status update after rejection
job_offer.status = "Rejected"
job_offer.submit()
job_applicant.reload()
self.assertEquals(job_applicant.status, "Rejected")
def create_job_offer(**args):
args = frappe._dict(args)
if not args.job_applicant:
job_applicant = create_job_applicant()
if not frappe.db.exists("Designation", args.designation):
designation = create_designation(designation_name=args.designation)
job_offer = frappe.get_doc({
"doctype": "Job Offer",
"job_applicant": args.job_applicant or job_applicant.name,
"offer_date": args.offer_date or nowdate(),
"designation": args.designation or "Researcher",
"status": args.status or "Accepted"
})
return job_offer
def create_staffing_plan(**args):
args = frappe._dict(args)
make_company()
frappe.db.set_value("Company", "_Test Company", "is_group", 1)
if frappe.db.exists("Staffing Plan", args.name or "Test"):
return
staffing_plan = frappe.get_doc({
"doctype": "Staffing Plan",
"name": args.name or "Test",
"from_date": args.from_date or nowdate(),
"to_date": args.to_date or add_days(nowdate(), 10),
"staffing_details": args.staffing_details or [{
"designation": "Researcher",
"vacancies": 1,
"estimated_cost_per_position": 50000
}]
})
staffing_plan.insert()
staffing_plan.submit()
return staffing_plan

View File

@ -30,11 +30,11 @@ class LoanApplication(Document):
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
if monthly_interest_rate:
min_repayment_amount = self.loan_amount*monthly_interest_rate
if self.repayment_amount - min_repayment_amount < 0:
if self.repayment_amount - min_repayment_amount <= 0:
frappe.throw(_("Repayment Amount must be greater than " \
+ str(flt(min_repayment_amount, 2))))
self.repayment_periods = math.ceil(math.log(self.repayment_amount) -
math.log(self.repayment_amount - min_repayment_amount) /(math.log(1 + monthly_interest_rate)))
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
math.log(self.repayment_amount - min_repayment_amount)) /(math.log(1 + monthly_interest_rate)))
else:
self.repayment_periods = self.loan_amount / self.repayment_amount

View File

@ -119,6 +119,8 @@ class PayrollEntry(Document):
frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args)
else:
create_salary_slips_for_employees(emp_list, args, publish_progress=False)
# since this method is called via frm.call this doc needs to be updated manually
self.reload()
def get_sal_slip_list(self, ss_status, as_dict=False):
"""

View File

@ -5,7 +5,7 @@ frappe.ui.form.on('Staffing Plan', {
setup: function(frm) {
frm.set_query("designation", "staffing_details", function() {
let designations = [];
$.each(frm.doc.staffing_details, function(index, staff_detail) {
(frm.doc.staffing_details || []).forEach(function(staff_detail) {
if(staff_detail.designation){
designations.push(staff_detail.designation)
}
@ -25,69 +25,63 @@ frappe.ui.form.on('Staffing Plan', {
}
};
});
}
},
});
frappe.ui.form.on('Staffing Plan Detail', {
designation: function(frm, cdt, cdn) {
let child = locals[cdt][cdn]
if(frm.doc.company && child.designation){
frappe.call({
"method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_designation_counts",
args: {
designation: child.designation,
company: frm.doc.company
},
callback: function (data) {
if(data.message){
frappe.model.set_value(cdt, cdn, 'current_count', data.message.employee_count);
frappe.model.set_value(cdt, cdn, 'current_openings', data.message.job_openings);
if (child.number_of_positions < (data.message.employee_count + data.message.job_openings)){
frappe.model.set_value(cdt, cdn, 'number_of_positions', data.message.employee_count + data.message.job_openings);
}
}
else{ // No employees for this designation
frappe.model.set_value(cdt, cdn, 'current_count', 0);
frappe.model.set_value(cdt, cdn, 'current_openings', 0);
}
}
});
let child = locals[cdt][cdn];
if(frm.doc.company && child.designation) {
set_number_of_positions(frm, cdt, cdn);
}
},
number_of_positions: function(frm, cdt, cdn) {
set_vacancies(frm, cdt, cdn);
vacancies: function(frm, cdt, cdn) {
let child = locals[cdt][cdn];
if(child.vacancies < child.current_openings) {
frappe.throw(__("Vacancies cannot be lower than the current openings"));
}
set_number_of_positions(frm, cdt, cdn);
},
current_count: function(frm, cdt, cdn) {
set_vacancies(frm, cdt, cdn);
set_number_of_positions(frm, cdt, cdn);
},
estimated_cost_per_position: function(frm, cdt, cdn) {
let child = locals[cdt][cdn];
set_total_estimated_cost(frm, cdt, cdn);
}
});
var set_vacancies = function(frm, cdt, cdn) {
let child = locals[cdt][cdn]
if (child.number_of_positions < (child.current_count + child.current_openings)){
frappe.throw(__("Number of positions cannot be less then current count of employees"))
}
if(child.number_of_positions > 0) {
frappe.model.set_value(cdt, cdn, 'vacancies', child.number_of_positions - (child.current_count + child.current_openings));
}
else{
frappe.model.set_value(cdt, cdn, 'vacancies', 0);
}
var set_number_of_positions = function(frm, cdt, cdn) {
let child = locals[cdt][cdn];
if (!child.designation) frappe.throw(__("Please enter the designation"));
frappe.call({
"method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_designation_counts",
args: {
designation: child.designation,
company: frm.doc.company
},
callback: function (data) {
if(data.message){
frappe.model.set_value(cdt, cdn, 'current_count', data.message.employee_count);
frappe.model.set_value(cdt, cdn, 'current_openings', data.message.job_openings);
let total_positions = cint(data.message.employee_count) + cint(child.vacancies);
if (cint(child.number_of_positions) < total_positions){
frappe.model.set_value(cdt, cdn, 'number_of_positions', total_positions);
}
}
else{ // No employees for this designation
frappe.model.set_value(cdt, cdn, 'current_count', 0);
frappe.model.set_value(cdt, cdn, 'current_openings', 0);
}
}
});
refresh_field("staffing_details");
set_total_estimated_cost(frm, cdt, cdn);
}
// Note: Estimated Cost is calculated on number of Vacancies
// Validate for > 0 ?
var set_total_estimated_cost = function(frm, cdt, cdn) {
let child = locals[cdt][cdn]
if(child.vacancies > 0 && child.estimated_cost_per_position) {
@ -102,11 +96,11 @@ var set_total_estimated_cost = function(frm, cdt, cdn) {
var set_total_estimated_budget = function(frm) {
let estimated_budget = 0.0
if(frm.doc.staffing_details) {
$.each(frm.doc.staffing_details, function(index, staff_detail) {
(frm.doc.staffing_details || []).forEach(function(staff_detail) {
if(staff_detail.total_estimated_cost){
estimated_budget += staff_detail.total_estimated_cost
}
})
frm.set_value('total_estimated_budget', estimated_budget);
}
}
}

View File

@ -13,41 +13,39 @@ class ParentCompanyError(frappe.ValidationError): pass
class StaffingPlan(Document):
def validate(self):
self.validate_period()
self.validate_details()
self.set_total_estimated_budget()
def validate_period(self):
# Validate Dates
if self.from_date and self.to_date and self.from_date > self.to_date:
frappe.throw(_("From Date cannot be greater than To Date"))
self.total_estimated_budget = 0
def validate_details(self):
for detail in self.get("staffing_details"):
self.set_vacancies(detail)
self.validate_overlap(detail)
self.validate_with_subsidiary_plans(detail)
self.validate_with_parent_plan(detail)
def set_total_estimated_budget(self):
self.total_estimated_budget = 0
for detail in self.get("staffing_details"):
#Set readonly fields
self.set_number_of_positions(detail)
designation_counts = get_designation_counts(detail.designation, self.company)
detail.current_count = designation_counts['employee_count']
detail.current_openings = designation_counts['job_openings']
if detail.number_of_positions < (detail.current_count + detail.current_openings):
frappe.throw(_("Number of positions cannot be less then current count of employees"))
elif detail.number_of_positions > 0:
detail.vacancies = detail.number_of_positions - (detail.current_count + detail.current_openings)
if detail.number_of_positions > 0:
if detail.vacancies > 0 and detail.estimated_cost_per_position:
detail.total_estimated_cost = detail.vacancies * detail.estimated_cost_per_position
else: detail.total_estimated_cost = 0
else: detail.vacancies = detail.number_of_positions = detail.total_estimated_cost = 0
detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position)
self.total_estimated_budget += detail.total_estimated_cost
def set_vacancies(self, row):
if not row.vacancies:
current_openings = 0
for field in ['current_count', 'current_openings']:
if row.get(field):
current_openings += row.get(field)
row.vacancies = row.number_of_positions - current_openings
def set_number_of_positions(self, detail):
detail.number_of_positions = cint(detail.vacancies) + cint(detail.current_count)
def validate_overlap(self, staffing_plan_detail):
# Validate if any submitted Staffing Plan exist for any Designations in this plan
@ -132,19 +130,24 @@ def get_designation_counts(designation, company):
if not designation:
return False
employee_counts_dict = {}
lft, rgt = frappe.get_cached_value('Company', company, ["lft", "rgt"])
employee_counts_dict["employee_count"] = frappe.db.sql("""select count(*) from `tabEmployee`
where designation = %s and status='Active'
and company in (select name from tabCompany where lft>=%s and rgt<=%s)
""", (designation, lft, rgt))[0][0]
employee_counts = {}
company_set = get_company_set(company)
employee_counts_dict['job_openings'] = frappe.db.sql("""select count(*) from `tabJob Opening` \
where designation=%s and status='Open'
and company in (select name from tabCompany where lft>=%s and rgt<=%s)
""", (designation, lft, rgt))[0][0]
employee_counts["employee_count"] = frappe.db.get_value("Employee",
filters={
'designation': designation,
'status': 'Active',
'company': ('in', company_set)
}, fieldname=['count(name)'])
return employee_counts_dict
employee_counts['job_openings'] = frappe.db.get_value("Job Opening",
filters={
'designation': designation,
'status': 'Open',
'company': ('in', company_set)
}, fieldname=['count(name)'])
return employee_counts
@frappe.whitelist()
def get_active_staffing_plan_details(company, designation, from_date=getdate(nowdate()), to_date=getdate(nowdate())):
@ -165,3 +168,13 @@ def get_active_staffing_plan_details(company, designation, from_date=getdate(now
# Only a single staffing plan can be active for a designation on given date
return staffing_plan if staffing_plan else None
def get_company_set(company):
return frappe.db.sql_list("""
SELECT
name
FROM `tabCompany`
WHERE
parent_company=%(company)s
OR name=%(company)s
""", (dict(company=company)))

View File

@ -24,7 +24,7 @@ class TestStaffingPlan(unittest.TestCase):
staffing_plan.to_date = add_days(nowdate(), 10)
staffing_plan.append("staffing_details", {
"designation": "Designer",
"number_of_positions": 6,
"vacancies": 6,
"estimated_cost_per_position": 50000
})
staffing_plan.insert()
@ -42,7 +42,7 @@ class TestStaffingPlan(unittest.TestCase):
staffing_plan.to_date = add_days(nowdate(), 10)
staffing_plan.append("staffing_details", {
"designation": "Designer",
"number_of_positions": 3,
"vacancies": 3,
"estimated_cost_per_position": 45000
})
self.assertRaises(SubsidiaryCompanyError, staffing_plan.insert)
@ -58,7 +58,7 @@ class TestStaffingPlan(unittest.TestCase):
staffing_plan.to_date = add_days(nowdate(), 10)
staffing_plan.append("staffing_details", {
"designation": "Designer",
"number_of_positions": 7,
"vacancies": 7,
"estimated_cost_per_position": 50000
})
staffing_plan.insert()
@ -73,7 +73,7 @@ class TestStaffingPlan(unittest.TestCase):
staffing_plan.to_date = add_days(nowdate(), 10)
staffing_plan.append("staffing_details", {
"designation": "Designer",
"number_of_positions": 7,
"vacancies": 7,
"estimated_cost_per_position": 60000
})
staffing_plan.insert()
@ -93,4 +93,4 @@ def make_company():
company.parent_company = "_Test Company"
company.default_currency = "INR"
company.country = "India"
company.insert()
company.insert()

View File

@ -1,297 +1,79 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-04-13 18:04:20.978931",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"designation",
"vacancies",
"estimated_cost_per_position",
"total_estimated_cost",
"column_break_5",
"current_count",
"current_openings",
"number_of_positions"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "designation",
"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": "Designation",
"length": 0,
"no_copy": 0,
"options": "Designation",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "number_of_positions",
"fieldtype": "Int",
"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": "Number Of Positions",
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "estimated_cost_per_position",
"fieldtype": "Currency",
"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": "Estimated Cost Per Position",
"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
"label": "Estimated Cost Per Position"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "current_count",
"fieldtype": "Int",
"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": "Current Count",
"length": 0,
"no_copy": 0,
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "current_openings",
"fieldtype": "Int",
"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": "Current Openings",
"length": 0,
"no_copy": 0,
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "vacancies",
"fieldtype": "Int",
"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": "Vacancies",
"length": 0,
"no_copy": 0,
"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
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_estimated_cost",
"fieldtype": "Currency",
"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": "Total Estimated Cost",
"length": 0,
"no_copy": 0,
"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
}
"label": "Vacancies"
},
{
"fieldname": "total_estimated_cost",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total Estimated Cost",
"read_only": 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": 1,
"max_attachments": 0,
"modified": "2018-06-01 17:03:38.020993",
"modified": "2019-06-24 18:40:37.140178",
"modified_by": "Administrator",
"module": "HR",
"name": "Staffing Plan Detail",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
"track_changes": 1
}

View File

@ -73,7 +73,7 @@ def create_columns():
def get_record():
data = []
loans = frappe.get_all("Loan",
filters=[("status", "=", "Fully Disbursed")],
filters=[("status", "=", "Disbursed")],
fields=["applicant", "applicant_name", "name", "loan_amount", "rate_of_interest",
"total_payment", "monthly_repayment_amount", "total_amount_paid"]
)

View File

@ -578,6 +578,8 @@ class BOM(WebsiteGenerator):
for d in self.operations:
if not d.description:
d.description = frappe.db.get_value('Operation', d.operation, 'description')
if not d.batch_size > 0:
d.batch_size = 1
def get_list_context(context):
context.title = _("Bill of Materials")

View File

@ -1,361 +1,119 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:49",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2013-02-22 01:27:49",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"operation",
"workstation",
"description",
"col_break1",
"hour_rate",
"time_in_mins",
"batch_size",
"operating_cost",
"base_hour_rate",
"base_operating_cost",
"image"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "operation",
"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": "Operation",
"length": 0,
"no_copy": 0,
"oldfieldname": "operation_no",
"oldfieldtype": "Data",
"options": "Operation",
"permlevel": 0,
"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": "operation",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Operation",
"oldfieldname": "operation_no",
"oldfieldtype": "Data",
"options": "Operation",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "workstation",
"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": "Workstation",
"length": 0,
"no_copy": 0,
"oldfieldname": "workstation",
"oldfieldtype": "Link",
"options": "Workstation",
"permlevel": 0,
"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": "workstation",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Workstation",
"oldfieldname": "workstation",
"oldfieldtype": "Link",
"options": "Workstation"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"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": "Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "opn_description",
"oldfieldtype": "Text",
"permlevel": 0,
"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": "description",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Description",
"oldfieldname": "opn_description",
"oldfieldtype": "Text"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "col_break1",
"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,
"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": "col_break1",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "hour_rate",
"fieldtype": "Currency",
"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": "Hour Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "hour_rate",
"oldfieldtype": "Currency",
"options": "currency",
"permlevel": 0,
"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": "hour_rate",
"fieldtype": "Currency",
"label": "Hour Rate",
"oldfieldname": "hour_rate",
"oldfieldtype": "Currency",
"options": "currency"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "In minutes",
"fieldname": "time_in_mins",
"fieldtype": "Float",
"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": "Operation Time ",
"length": 0,
"no_copy": 0,
"oldfieldname": "time_in_mins",
"oldfieldtype": "Currency",
"options": "",
"permlevel": 0,
"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
},
"description": "In minutes",
"fieldname": "time_in_mins",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Operation Time ",
"oldfieldname": "time_in_mins",
"oldfieldtype": "Currency",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "operating_cost",
"fieldtype": "Currency",
"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": "Operating Cost",
"length": 0,
"no_copy": 0,
"oldfieldname": "operating_cost",
"oldfieldtype": "Currency",
"options": "currency",
"permlevel": 0,
"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
},
"fieldname": "operating_cost",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Operating Cost",
"oldfieldname": "operating_cost",
"oldfieldtype": "Currency",
"options": "currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "base_hour_rate",
"fieldtype": "Currency",
"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": "Base Hour Rate(Company Currency)",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"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": "base_hour_rate",
"fieldtype": "Currency",
"label": "Base Hour Rate(Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "5",
"fieldname": "base_operating_cost",
"fieldtype": "Currency",
"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": "Operating Cost(Company Currency)",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"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
},
"default": "5",
"fieldname": "base_operating_cost",
"fieldtype": "Currency",
"label": "Operating Cost(Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach",
"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": "Image",
"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": "image",
"fieldtype": "Attach",
"label": "Image"
},
{
"default": "1",
"fieldname": "batch_size",
"fieldtype": "Int",
"label": "Batch Size"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-03-26 09:55:28.107451",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0
}
],
"idx": 1,
"istable": 1,
"modified": "2019-07-16 22:35:55.374037",
"modified_by": "govindsmenokee@gmail.com",
"module": "Manufacturing",
"name": "BOM Operation",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -505,7 +505,7 @@ def get_material_request_items(row, sales_order,
total_qty = row['qty']
required_qty = 0
if ignore_existing_ordered_qty or bin_dict.get("projected_qty") < 0:
if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0:
required_qty = total_qty
elif total_qty > bin_dict.get("projected_qty"):
required_qty = total_qty - bin_dict.get("projected_qty")

View File

@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
import json
import math
from frappe import _
from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
from frappe.model.document import Document
@ -323,7 +324,7 @@ class WorkOrder(Document):
select
operation, description, workstation, idx,
base_hour_rate as hour_rate, time_in_mins,
"Pending" as status, parent as bom
"Pending" as status, parent as bom, batch_size
from
`tabBOM Operation`
where
@ -348,7 +349,7 @@ class WorkOrder(Document):
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
for d in self.get("operations"):
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * flt(self.qty)
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * math.ceil(flt(self.qty) / flt(d.batch_size))
self.calculate_operating_cost()

View File

@ -1,690 +1,200 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2014-10-16 14:35:41.950175",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2014-10-16 14:35:41.950175",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"details",
"operation",
"bom",
"description",
"col_break1",
"completed_qty",
"status",
"workstation",
"estimated_time_and_cost",
"planned_start_time",
"planned_end_time",
"column_break_10",
"time_in_mins",
"hour_rate",
"batch_size",
"planned_operating_cost",
"section_break_9",
"actual_start_time",
"actual_end_time",
"column_break_11",
"actual_operation_time",
"actual_operating_cost"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "details",
"fieldtype": "Section 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,
"label": "",
"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,
"unique": 0
},
"fieldname": "details",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "operation",
"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": "Operation",
"length": 0,
"no_copy": 0,
"oldfieldname": "operation_no",
"oldfieldtype": "Data",
"options": "Operation",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "operation",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Operation",
"oldfieldname": "operation_no",
"oldfieldtype": "Data",
"options": "Operation",
"read_only": 1,
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bom",
"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": "BOM",
"length": 0,
"no_copy": 1,
"options": "BOM",
"permlevel": 0,
"precision": "",
"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,
"unique": 0
},
"fieldname": "bom",
"fieldtype": "Link",
"label": "BOM",
"no_copy": 1,
"options": "BOM",
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"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": "Operation Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "opn_description",
"oldfieldtype": "Text",
"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,
"unique": 0
},
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Operation Description",
"oldfieldname": "opn_description",
"oldfieldtype": "Text",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "col_break1",
"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,
"unique": 0
},
"fieldname": "col_break1",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Operation completed for how many finished goods?",
"fieldname": "completed_qty",
"fieldtype": "Float",
"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": "Completed Qty",
"length": 0,
"no_copy": 1,
"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,
"unique": 0
},
"description": "Operation completed for how many finished goods?",
"fieldname": "completed_qty",
"fieldtype": "Float",
"label": "Completed Qty",
"no_copy": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Pending",
"fieldname": "status",
"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": "Status",
"length": 0,
"no_copy": 1,
"options": "Pending\nWork in Progress\nCompleted",
"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,
"unique": 0
},
"default": "Pending",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"no_copy": 1,
"options": "Pending\nWork in Progress\nCompleted",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "workstation",
"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": "Workstation",
"length": 0,
"no_copy": 0,
"oldfieldname": "workstation",
"oldfieldtype": "Link",
"options": "Workstation",
"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,
"unique": 0
},
"fieldname": "workstation",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Workstation",
"oldfieldname": "workstation",
"oldfieldtype": "Link",
"options": "Workstation"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "estimated_time_and_cost",
"fieldtype": "Section 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,
"label": "Estimated Time and Cost",
"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,
"unique": 0
},
"fieldname": "estimated_time_and_cost",
"fieldtype": "Section Break",
"label": "Estimated Time and Cost"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "planned_start_time",
"fieldtype": "Datetime",
"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": "Planned Start Time",
"length": 0,
"no_copy": 1,
"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,
"unique": 0
},
"fieldname": "planned_start_time",
"fieldtype": "Datetime",
"label": "Planned Start Time",
"no_copy": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "planned_end_time",
"fieldtype": "Datetime",
"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": "Planned End Time",
"length": 0,
"no_copy": 1,
"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,
"unique": 0
},
"fieldname": "planned_end_time",
"fieldtype": "Datetime",
"label": "Planned End Time",
"no_copy": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"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,
"unique": 0
},
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "in Minutes",
"fieldname": "time_in_mins",
"fieldtype": "Float",
"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": "Operation Time",
"length": 0,
"no_copy": 0,
"oldfieldname": "time_in_mins",
"oldfieldtype": "Currency",
"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,
"unique": 0
},
"description": "in Minutes",
"fieldname": "time_in_mins",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Operation Time",
"oldfieldname": "time_in_mins",
"oldfieldtype": "Currency",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "hour_rate",
"fieldtype": "Float",
"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": "Hour Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "hour_rate",
"oldfieldtype": "Currency",
"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,
"unique": 0
},
"fieldname": "hour_rate",
"fieldtype": "Float",
"label": "Hour Rate",
"oldfieldname": "hour_rate",
"oldfieldtype": "Currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "planned_operating_cost",
"fieldtype": "Currency",
"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": "Planned Operating Cost",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"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,
"unique": 0
},
"fieldname": "planned_operating_cost",
"fieldtype": "Currency",
"label": "Planned Operating Cost",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_9",
"fieldtype": "Section 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,
"label": "Actual Time and Cost",
"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,
"unique": 0
},
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"label": "Actual Time and Cost"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "actual_start_time",
"fieldtype": "Datetime",
"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": "Actual Start Time",
"length": 0,
"no_copy": 1,
"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,
"unique": 0
},
"fieldname": "actual_start_time",
"fieldtype": "Datetime",
"label": "Actual Start Time",
"no_copy": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Updated via 'Time Log'",
"fieldname": "actual_end_time",
"fieldtype": "Datetime",
"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": "Actual End Time",
"length": 0,
"no_copy": 1,
"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,
"unique": 0
},
"description": "Updated via 'Time Log'",
"fieldname": "actual_end_time",
"fieldtype": "Datetime",
"label": "Actual End Time",
"no_copy": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_11",
"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,
"unique": 0
},
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "in Minutes\nUpdated via 'Time Log'",
"fieldname": "actual_operation_time",
"fieldtype": "Float",
"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": "Actual Operation Time",
"length": 0,
"no_copy": 1,
"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,
"unique": 0
},
"description": "in Minutes\nUpdated via 'Time Log'",
"fieldname": "actual_operation_time",
"fieldtype": "Float",
"label": "Actual Operation Time",
"no_copy": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "(Hour Rate / 60) * Actual Operation Time",
"fieldname": "actual_operating_cost",
"fieldtype": "Currency",
"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": "Actual Operating Cost",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"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,
"unique": 0
"description": "(Hour Rate / 60) * Actual Operation Time",
"fieldname": "actual_operating_cost",
"fieldtype": "Currency",
"label": "Actual Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "batch_size",
"fieldtype": "Int",
"label": "Batch Size",
"read_only": 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": 1,
"max_attachments": 0,
"modified": "2018-02-13 02:58:11.328693",
"modified_by": "Administrator",
"module": "Manufacturing",
],
"istable": 1,
"modified": "2019-07-16 23:01:07.720337",
"modified_by": "govindsmenokee@gmail.com",
"module": "Manufacturing",
"name": "Work Order Operation",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -618,8 +618,10 @@ erpnext.patches.v11_1.set_missing_opportunity_from
erpnext.patches.v12_0.set_quotation_status
erpnext.patches.v12_0.set_priority_for_support
erpnext.patches.v12_0.delete_priority_property_setter
erpnext.patches.v12_0.set_default_batch_size
execute:frappe.delete_doc("DocType", "Project Task")
erpnext.patches.v11_1.update_default_supplier_in_item_defaults
erpnext.patches.v12_0.update_due_date_in_gle
erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture

View File

@ -0,0 +1,9 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.sql("""
update `tabMaterial Request`
set status='Manufactured'
where docstatus=1 and material_request_type='Manufacture' and per_ordered=100 and status != 'Stopped'
""")

View File

@ -0,0 +1,19 @@
import frappe
def execute():
frappe.reload_doc("manufacturing", "doctype", "bom_operation")
frappe.reload_doc("manufacturing", "doctype", "work_order_operation")
frappe.db.sql("""
UPDATE
`tabBOM Operation` bo
SET
bo.batch_size = 1
""")
frappe.db.sql("""
UPDATE
`tabWork Order Operation` wop
SET
wop.batch_size = 1
""")

View File

@ -1,6 +1,23 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Project", {
setup(frm) {
frm.make_methods = {
'Timesheet': () => {
let doctype = 'Timesheet';
frappe.model.with_doctype(doctype, () => {
let new_doc = frappe.model.get_new_doc(doctype);
// add a new row and set the project
let time_log = frappe.model.get_new_doc('Timesheet Detail');
time_log.project = frm.doc.name;
new_doc.time_logs = [time_log];
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
},
}
},
onload: function (frm) {
var so = frappe.meta.get_docfield("Project", "sales_order");
so.get_route_options_for_new_doc = function (field) {

View File

@ -6,18 +6,13 @@
"description": "Keep Track of Sales Campaigns. Keep track of Leads, Quotations, Sales Order etc from Campaigns to gauge Return on Investment. ",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"campaign",
"campaign_name",
"naming_series",
"from_date",
"column_break1",
"status",
"to_date",
"budget_section",
"currency",
"column_break2",
"budget",
"campaign_schedules_section",
"campaign_schedules",
"description_section",
"description"
],
@ -52,57 +47,25 @@
"oldfieldtype": "Text",
"width": "300px"
},
{
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "\nPlanned\nIn Progress\nCompleted\nCancelled",
"reqd": 1,
"default": "Planned"
},
{
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date"
},
{
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date"
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break"
},
{
"fieldname": "budget",
"fieldtype": "Currency",
"label": "Budget"
},
{
"fieldname": "description_section",
"fieldtype": "Section Break"
},
{
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
"fieldname": "campaign_schedules",
"fieldtype": "Table",
"label": "Campaign Schedules",
"options": "Campaign Email Schedule"
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break"
},
{
"fieldname": "budget_section",
"fieldname": "campaign_schedules_section",
"fieldtype": "Section Break",
"label": "BUDGET"
"label": "Campaign Schedules"
}
],
"icon": "fa fa-bullhorn",
"idx": 1,
"modified": "2019-04-29 22:09:39.251884",
"modified": "2019-07-22 12:03:39.832342",
"modified_by": "Administrator",
"module": "Selling",
"name": "Campaign",
@ -140,5 +103,7 @@
"write": 1
}
],
"quick_entry": 1
}
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -0,0 +1,13 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'campaign_name',
'transactions': [
{
'label': _('Email Campaigns'),
'items': ['Email Campaign']
}
],
}

View File

@ -509,10 +509,12 @@ def make_material_request(source_name, target_doc=None):
doc.material_request_type = "Purchase"
def update_item(source, target, source_parent):
# qty is for packed items, because packed items don't have stock_qty field
qty = source.get("stock_qty") or source.get("qty")
target.project = source_parent.project
target.qty = source.stock_qty - requested_item_qty.get(source.name, 0)
target.qty = qty - requested_item_qty.get(source.name, 0)
target.conversion_factor = 1
target.stock_qty = source.stock_qty - requested_item_qty.get(source.name, 0)
target.stock_qty = qty - requested_item_qty.get(source.name, 0)
doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {

View File

@ -69,8 +69,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
"items": get_product_list_for_group(product_group = self.name, start=start,
limit=context.page_length + 1, search=frappe.form_dict.get("search")),
"parents": get_parent_item_groups(self.parent_item_group),
"title": self.name,
"products_as_list": cint(frappe.db.get_single_value('Products Settings', 'products_as_list'))
"title": self.name
})
if self.slideshow:

View File

@ -16,18 +16,47 @@ erpnext.stock.ItemDashboard = Class.extend({
this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent);
this.result = this.content.find('.result');
// move
this.content.on('click', '.btn-move', function() {
erpnext.stock.move_item(unescape($(this).attr('data-item')), $(this).attr('data-warehouse'),
null, $(this).attr('data-actual_qty'), null, function() { me.refresh(); });
handle_move_add($(this), "Move")
});
this.content.on('click', '.btn-add', function() {
erpnext.stock.move_item(unescape($(this).attr('data-item')), null, $(this).attr('data-warehouse'),
$(this).attr('data-actual_qty'), $(this).attr('data-rate'),
function() { me.refresh(); });
handle_move_add($(this), "Add")
});
function handle_move_add(element, action) {
let item = unescape(element.attr('data-item'));
let warehouse = unescape(element.attr('data-warehouse'));
let actual_qty = unescape(element.attr('data-actual_qty'));
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
let entry_type = action === "Move" ? "Material Transfer": null;
if (disable_quick_entry) {
open_stock_entry(item, warehouse, entry_type);
} else {
if (action === "Add") {
let rate = unescape($(this).attr('data-rate'));
erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); });
}
else {
erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); });
}
}
}
function open_stock_entry(item, warehouse, entry_type) {
frappe.model.with_doctype('Stock Entry', function() {
var doc = frappe.model.get_new_doc('Stock Entry');
if (entry_type) doc.stock_entry_type = entry_type;
var row = frappe.model.add_child(doc, 'items');
row.item_code = item;
row.s_warehouse = warehouse;
frappe.set_route('Form', doc.doctype, doc.name);
})
}
// more
this.content.find('.btn-more').on('click', function() {
me.start += 20;
@ -196,4 +225,4 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
frappe.set_route('Form', doc.doctype, doc.name);
})
});
}
}

View File

@ -44,7 +44,9 @@ def get_data(item_code=None, warehouse=None, item_group=None,
for item in items:
item.update({
'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name')
'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no')
or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'),
})
return items

View File

@ -43,11 +43,13 @@
<div class="col-sm-2 text-right" style="margin-top: 8px;">
{% if d.actual_qty %}
<button class="btn btn-default btn-xs btn-move"
data-disable_quick_entry="{{ d.disable_quick_entry }}"
data-warehouse="{{ d.warehouse }}"
data-actual_qty="{{ d.actual_qty }}"
data-item="{{ escape(d.item_code) }}">{{ __("Move") }}</a>
{% endif %}
<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add"
data-disable_quick_entry="{{ d.disable_quick_entry }}"
data-warehouse="{{ d.warehouse }}"
data-actual_qty="{{ d.actual_qty }}"
data-item="{{ escape(d.item_code) }}"

View File

@ -122,8 +122,8 @@ class Batch(Document):
self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
if has_expiry_date and not self.expiry_date:
frappe.throw(_('Expiry date is mandatory for selected item'))
frappe.msgprint(_('Set items shelf life in days, to set expiry based on manufacturing_date plus self life'))
frappe.msgprint(_('Expiry date is mandatory for selected item.'))
frappe.throw(_("Set item's shelf life in days, to set expiry based on manufacturing date plus shelf-life."))
def get_name_from_naming_series(self):
"""

View File

@ -67,43 +67,34 @@ frappe.ui.form.on('Delivery Trip', {
},
calculate_arrival_time: function (frm) {
frappe.db.get_value("Google Maps Settings", { name: "Google Maps Settings" }, "enabled", (r) => {
if (r.enabled == 0) {
frappe.throw(__("Please enable Google Maps Settings to estimate and optimize routes"));
} else {
frappe.call({
method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.get_arrival_times',
freeze: true,
freeze_message: __("Updating estimated arrival times."),
args: {
delivery_trip: frm.doc.name,
},
callback: function (r) {
frm.reload_doc();
}
});
}
})
if (!frm.doc.driver_address) {
frappe.throw(__("Cannot Calculate Arrival Time as Driver Address is Missing."));
}
frappe.show_alert({
message: "Calculating Arrival Times",
indicator: 'orange'
});
frm.call("process_route", {
optimize: false,
}, () => {
frm.reload_doc();
});
},
optimize_route: function (frm) {
frappe.db.get_value("Google Maps Settings", {name: "Google Maps Settings"}, "enabled", (r) => {
if (r.enabled == 0) {
frappe.throw(__("Please enable Google Maps Settings to estimate and optimize routes"));
} else {
frappe.call({
method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.optimize_route',
freeze: true,
freeze_message: __("Optimizing routes."),
args: {
delivery_trip: frm.doc.name,
},
callback: function (r) {
frm.reload_doc();
}
});
}
})
if (!frm.doc.driver_address) {
frappe.throw(__("Cannot Optimize Route as Driver Address is Missing."));
}
frappe.show_alert({
message: "Optimizing Route",
indicator: 'orange'
});
frm.call("process_route", {
optimize: true,
}, () => {
frm.reload_doc();
});
},
notify_customers: function (frm) {

View File

@ -1,735 +1,214 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "naming_series:",
"beta": 0,
"creation": "2017-10-16 16:45:48.293335",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"autoname": "naming_series:",
"creation": "2017-10-16 16:45:48.293335",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"company",
"column_break_2",
"email_notification_sent",
"section_break_3",
"driver",
"driver_name",
"driver_address",
"total_distance",
"uom",
"column_break_4",
"vehicle",
"departure_time",
"delivery_service_stops",
"delivery_stops",
"calculate_arrival_time",
"optimize_route",
"section_break_15",
"status",
"cb_more_info",
"amended_from"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series",
"fieldtype": "Select",
"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": "Series",
"length": 0,
"no_copy": 0,
"options": "MAT-DT-.YYYY.-",
"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": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "MAT-DT-.YYYY.-"
},
{
"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": 0,
"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",
"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": "column_break_2",
"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_2",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_notification_sent",
"fieldtype": "Check",
"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": "Initial Email Notification Sent",
"length": 0,
"no_copy": 0,
"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
},
"allow_on_submit": 1,
"default": "0",
"fieldname": "email_notification_sent",
"fieldtype": "Check",
"label": "Initial Email Notification Sent",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_3",
"fieldtype": "Section 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,
"label": "Delivery Details",
"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": "section_break_3",
"fieldtype": "Section Break",
"label": "Delivery Details"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "driver",
"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": "Driver",
"length": 0,
"no_copy": 0,
"options": "Driver",
"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": "driver",
"fieldtype": "Link",
"label": "Driver",
"options": "Driver",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "driver.full_name",
"fieldname": "driver_name",
"fieldtype": "Data",
"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": "Driver 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": "driver.full_name",
"fieldname": "driver_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Driver Name",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_distance",
"fieldtype": "Float",
"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": "Total Estimated Distance",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "2",
"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
},
"fieldname": "total_distance",
"fieldtype": "Float",
"label": "Total Estimated Distance",
"precision": "2",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"depends_on": "eval:doc.total_distance",
"fieldname": "uom",
"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": "Distance UOM",
"length": 0,
"no_copy": 0,
"options": "UOM",
"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
},
"depends_on": "eval:doc.total_distance",
"fieldname": "uom",
"fieldtype": "Link",
"label": "Distance UOM",
"options": "UOM",
"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": "vehicle",
"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": "Vehicle",
"length": 0,
"no_copy": 0,
"options": "Vehicle",
"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": "vehicle",
"fieldtype": "Link",
"label": "Vehicle",
"options": "Vehicle",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "departure_time",
"fieldtype": "Datetime",
"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": "Departure Time",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "departure_time",
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Departure Time",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "delivery_service_stops",
"fieldtype": "Section 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,
"label": "Delivery Stops",
"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": "delivery_service_stops",
"fieldtype": "Section Break",
"label": "Delivery Stops"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "delivery_stops",
"fieldtype": "Table",
"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": "Delivery Stop",
"length": 0,
"no_copy": 0,
"options": "Delivery Stop",
"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": "delivery_stops",
"fieldtype": "Table",
"label": "Delivery Stop",
"options": "Delivery Stop",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fieldname": "calculate_arrival_time",
"fieldtype": "Button",
"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": "Calculate Estimated Arrival Times",
"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
},
"depends_on": "eval:!doc.__islocal",
"description": "Use Google Maps Direction API to calculate estimated arrival times",
"fieldname": "calculate_arrival_time",
"fieldtype": "Button",
"label": "Calculate Estimated Arrival Times"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fieldname": "optimize_route",
"fieldtype": "Button",
"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": "Optimize Route",
"length": 0,
"no_copy": 0,
"options": "",
"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
},
"depends_on": "eval:!doc.__islocal",
"description": "Use Google Maps Direction API to optimize route",
"fieldname": "optimize_route",
"fieldtype": "Button",
"label": "Optimize Route"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_15",
"fieldtype": "Section 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
},
"allow_on_submit": 1,
"fieldname": "section_break_15",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Status",
"length": 0,
"no_copy": 1,
"options": "Draft\nScheduled\nIn Transit\nCompleted\nCancelled",
"permlevel": 0,
"precision": "",
"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": "status",
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "Draft\nScheduled\nIn Transit\nCompleted\nCancelled",
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_more_info",
"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": "cb_more_info",
"fieldtype": "Column Break"
},
{
"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": "Delivery Trip",
"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": "Delivery Trip",
"print_hide": 1,
"read_only": 1
},
{
"fetch_from": "driver.address",
"fieldname": "driver_address",
"fieldtype": "Link",
"label": "Driver Address",
"options": "Address",
"read_only": 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-10-22 08:25:42.323147",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Trip",
"name_case": "",
"owner": "Administrator",
],
"is_submittable": 1,
"modified": "2019-07-18 16:38:44.112651",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Trip",
"owner": "Administrator",
"permissions": [
{
"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": "Fulfillment User",
"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": "Fulfillment User",
"share": 1,
"submit": 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": "Stock User",
"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": "Stock 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": "driver_name",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
],
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "driver_name"
}

View File

@ -100,16 +100,13 @@ class DeliveryTrip(Document):
optimize (bool): True if route needs to be optimized, else False
"""
if not frappe.db.get_single_value("Google Maps Settings", "enabled"):
frappe.throw(_("Cannot process route, since Google Maps Settings is disabled."))
departure_datetime = get_datetime(self.departure_time)
route_list = self.form_route_list(optimize)
# For locks, maintain idx count while looping through route list
idx = 0
for route in route_list:
directions = get_directions(route, optimize)
directions = self.get_directions(route, optimize)
if directions:
if optimize and len(directions.get("waypoint_order")) > 1:
@ -157,9 +154,10 @@ class DeliveryTrip(Document):
Returns:
(list of list of str): List of address routes split at locks, if optimize is `True`
"""
if not self.driver_address:
frappe.throw(_("Cannot Calculate Arrival Time as Driver Address is Missing."))
settings = frappe.get_single("Google Maps Settings")
home_address = get_address_display(frappe.get_doc("Address", settings.home_address).as_dict())
home_address = get_address_display(frappe.get_doc("Address", self.driver_address).as_dict())
route_list = []
# Initialize first leg with origin as the home address
@ -204,6 +202,47 @@ class DeliveryTrip(Document):
self.delivery_stops[start:start + len(stops_order)] = stops_order
def get_directions(self, route, optimize):
"""
Retrieve map directions for a given route and departure time.
If optimize is `True`, Google Maps will return an optimized
order for the intermediate waypoints.
NOTE: Google's API does take an additional `departure_time` key,
but it only works for routes without any waypoints.
Args:
route (list of str): Route addresses (origin -> waypoint(s), if any -> destination)
optimize (bool): `True` if route needs to be optimized, else `False`
Returns:
(dict): Route legs and, if `optimize` is `True`, optimized waypoint order
"""
if not frappe.db.get_single_value("Google Settings", "api_key"):
frappe.throw(_("Enter API key in Google Settings."))
import googlemaps
try:
maps_client = googlemaps.Client(key=frappe.db.get_single_value("Google Settings", "api_key"))
except Exception as e:
frappe.throw(e)
directions_data = {
"origin": route[0],
"destination": route[-1],
"waypoints": route[1: -1],
"optimize_waypoints": optimize
}
try:
directions = maps_client.directions(**directions_data)
except Exception as e:
frappe.throw(_(e.message))
return directions[0] if directions else False
@frappe.whitelist()
def get_contact_and_address(name):
@ -278,18 +317,6 @@ def get_contact_display(contact):
return contact_info.html
@frappe.whitelist()
def optimize_route(delivery_trip):
delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
delivery_trip.process_route(optimize=True)
@frappe.whitelist()
def get_arrival_times(delivery_trip):
delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
delivery_trip.process_route(optimize=False)
def sanitize_address(address):
"""
Remove HTML breaks in a given address
@ -310,41 +337,6 @@ def sanitize_address(address):
return ', '.join(address[:3])
def get_directions(route, optimize):
"""
Retrieve map directions for a given route and departure time.
If optimize is `True`, Google Maps will return an optimized
order for the intermediate waypoints.
NOTE: Google's API does take an additional `departure_time` key,
but it only works for routes without any waypoints.
Args:
route (list of str): Route addresses (origin -> waypoint(s), if any -> destination)
optimize (bool): `True` if route needs to be optimized, else `False`
Returns:
(dict): Route legs and, if `optimize` is `True`, optimized waypoint order
"""
settings = frappe.get_single("Google Maps Settings")
maps_client = settings.get_client()
directions_data = {
"origin": route[0],
"destination": route[-1],
"waypoints": route[1: -1],
"optimize_waypoints": optimize
}
try:
directions = maps_client.directions(**directions_data)
except Exception as e:
frappe.throw(_(e.message))
return directions[0] if directions else False
@frappe.whitelist()
def notify_customers(delivery_trip):
delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)

View File

@ -14,16 +14,13 @@ from frappe.utils import add_days, flt, now_datetime, nowdate
class TestDeliveryTrip(unittest.TestCase):
def setUp(self):
create_driver()
driver = create_driver()
create_vehicle()
create_delivery_notification()
create_test_contact_and_address()
address = create_address(driver)
settings = frappe.get_single("Google Maps Settings")
settings.home_address = frappe.get_last_doc("Address").name
settings.save()
self.delivery_trip = create_delivery_trip()
self.delivery_trip = create_delivery_trip(driver, address)
def tearDown(self):
frappe.db.sql("delete from `tabDriver`")
@ -99,17 +96,42 @@ class TestDeliveryTrip(unittest.TestCase):
self.delivery_trip.save()
self.assertEqual(self.delivery_trip.status, "Completed")
def create_address(driver):
if not frappe.db.exists("Address", {"address_title": "_Test Address for Driver"}):
address = frappe.get_doc({
"doctype": "Address",
"address_title": "_Test Address for Driver",
"address_type": "Office",
"address_line1": "Station Road",
"city": "_Test City",
"state": "Test State",
"country": "India",
"links":[
{
"link_doctype": "Driver",
"link_name": driver.name
}
]
}).insert(ignore_permissions=True)
frappe.db.set_value("Driver", driver.name, "address", address.name)
return address
return frappe.get_doc("Address", {"address_title": "_Test Address for Driver"})
def create_driver():
if not frappe.db.exists("Driver", "Newton Scmander"):
if not frappe.db.exists("Driver", {"full_name": "Newton Scmander"}):
driver = frappe.get_doc({
"doctype": "Driver",
"full_name": "Newton Scmander",
"cell_number": "98343424242",
"license_number": "B809"
})
driver.insert()
"license_number": "B809",
}).insert(ignore_permissions=True)
return driver
return frappe.get_doc("Driver", {"full_name": "Newton Scmander"})
def create_delivery_notification():
if not frappe.db.exists("Email Template", "Delivery Notification"):
@ -144,16 +166,16 @@ def create_vehicle():
vehicle.insert()
def create_delivery_trip(contact=None):
def create_delivery_trip(driver, address, contact=None):
if not contact:
contact = get_contact_and_address("_Test Customer")
delivery_trip = frappe.new_doc("Delivery Trip")
delivery_trip.update({
delivery_trip = frappe.get_doc({
"doctype": "Delivery Trip",
"company": erpnext.get_default_company(),
"departure_time": add_days(now_datetime(), 5),
"driver": frappe.db.get_value('Driver', {"full_name": "Newton Scmander"}),
"driver": driver.name,
"driver_address": address.name,
"vehicle": "JB 007",
"delivery_stops": [{
"customer": "_Test Customer",
@ -165,7 +187,6 @@ def create_delivery_trip(contact=None):
"address": contact.shipping_address.parent,
"contact": contact.contact_person.parent
}]
})
delivery_trip.insert()
}).insert(ignore_permissions=True)
return delivery_trip

View File

@ -197,6 +197,9 @@ class Item(WebsiteGenerator):
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
def validate_website_image(self):
if frappe.flags.in_import:
return
"""Validate if the website image is a public file"""
auto_set_website_image = False
if not self.website_image and self.image:
@ -216,8 +219,7 @@ class Item(WebsiteGenerator):
if not file_doc:
if not auto_set_website_image:
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
.format(self.website_image, self.name))
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name))
self.website_image = None
@ -228,6 +230,9 @@ class Item(WebsiteGenerator):
self.website_image = None
def make_thumbnail(self):
if frappe.flags.in_import:
return
"""Make a thumbnail of `website_image`"""
import requests.exceptions

View File

@ -20,6 +20,8 @@ frappe.listview_settings['Material Request'] = {
return [__("Issued"), "green", "per_ordered,=,100"];
} else if (doc.material_request_type == "Customer Provided") {
return [__("Received"), "green", "per_ordered,=,100"];
} else if (doc.material_request_type == "Manufacture") {
return [__("Manufactured"), "green", "per_ordered,=,100"];
}
}
}

View File

@ -264,8 +264,12 @@ frappe.ui.form.on('Purchase Receipt Item', {
var d = locals[cdt][cdn];
frappe.db.get_value('Item', {name: d.item_code}, 'sample_quantity', (r) => {
frappe.model.set_value(cdt, cdn, "sample_quantity", r.sample_quantity);
validate_sample_quantity(frm, cdt, cdn);
});
},
qty: function(frm, cdt, cdn) {
validate_sample_quantity(frm, cdt, cdn);
},
sample_quantity: function(frm, cdt, cdn) {
validate_sample_quantity(frm, cdt, cdn);
},
@ -283,7 +287,7 @@ cur_frm.cscript['Make Stock Entry'] = function() {
var validate_sample_quantity = function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.sample_quantity) {
if (d.sample_quantity && d.qty) {
frappe.call({
method: 'erpnext.stock.doctype.stock_entry.stock_entry.validate_sample_quantity',
args: {

View File

@ -222,7 +222,7 @@ def validate_serial_no(sle, item_det):
frappe.throw(_("Serial No {0} has already been received").format(serial_no),
SerialNoDuplicateError)
if (sr.delivery_document_no and sle.voucher_type != 'Stock Entry'
if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
and sle.voucher_type == sr.delivery_document_type):
return_against = frappe.db.get_value(sle.voucher_type, sle.voucher_no, 'return_against')
if return_against and return_against != sr.delivery_document_no:
@ -299,7 +299,7 @@ def validate_so_serial_no(sr, sales_order,):
be delivered""").format(sales_order, sr.item_code, sr.name))
def has_duplicate_serial_no(sn, sle):
if sn.warehouse:
if sn.warehouse and sle.voucher_type != 'Stock Reconciliation':
return True
if sn.company != sle.company:
@ -415,16 +415,20 @@ def update_serial_nos_after_submit(controller, parentfield):
if not stock_ledger_entries: return
for d in controller.get(parentfield):
if d.serial_no:
continue
update_rejected_serial_nos = True if (controller.doctype in ("Purchase Receipt", "Purchase Invoice")
and d.rejected_qty) else False
accepted_serial_nos_updated = False
if controller.doctype == "Stock Entry":
warehouse = d.t_warehouse
qty = d.transfer_qty
else:
warehouse = d.warehouse
qty = d.stock_qty
qty = (d.qty if controller.doctype == "Stock Reconciliation"
else d.stock_qty)
for sle in stock_ledger_entries:
if sle.voucher_detail_no==d.name:
if not accepted_serial_nos_updated and qty and abs(sle.actual_qty)==qty \

View File

@ -359,7 +359,7 @@ class StockEntry(StockController):
d.basic_rate = 0.0
elif d.t_warehouse and not d.basic_rate:
d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
self.doctype, d.name, d.allow_zero_valuation_rate,
self.doctype, self.name, d.allow_zero_valuation_rate,
currency=erpnext.get_company_currency(self.company))
def set_actual_qty(self):

View File

@ -38,7 +38,7 @@ class StockLedgerEntry(Document):
self.check_stock_frozen_date()
self.actual_amt_check()
if not self.get("via_landed_cost_voucher") and self.voucher_type != 'Stock Reconciliation':
if not self.get("via_landed_cost_voucher"):
from erpnext.stock.doctype.serial_no.serial_no import process_serial_no
process_serial_no(self)

View File

@ -12,8 +12,7 @@ frappe.ui.form.on("Stock Reconciliation", {
return {
query: "erpnext.controllers.queries.item_query",
filters:{
"is_stock_item": 1,
"has_serial_no": 0
"is_stock_item": 1
}
}
});
@ -77,6 +76,7 @@ frappe.ui.form.on("Stock Reconciliation", {
set_valuation_rate_and_qty: function(frm, cdt, cdn) {
var d = frappe.model.get_doc(cdt, cdn);
if(d.item_code && d.warehouse) {
frappe.call({
method: "erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_stock_balance_for",
@ -84,7 +84,8 @@ frappe.ui.form.on("Stock Reconciliation", {
item_code: d.item_code,
warehouse: d.warehouse,
posting_date: frm.doc.posting_date,
posting_time: frm.doc.posting_time
posting_time: frm.doc.posting_time,
batch_no: d.batch_no
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, "qty", r.message.qty);
@ -93,7 +94,7 @@ frappe.ui.form.on("Stock Reconciliation", {
frappe.model.set_value(cdt, cdn, "current_valuation_rate", r.message.rate);
frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty);
frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
}
});
}
@ -152,17 +153,44 @@ frappe.ui.form.on("Stock Reconciliation Item", {
barcode: function(frm, cdt, cdn) {
frm.events.set_item_code(frm, cdt, cdn);
},
warehouse: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
if (child.batch_no) {
frappe.model.set_value(child.cdt, child.cdn, "batch_no", "");
}
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
},
item_code: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
if (child.batch_no) {
frappe.model.set_value(cdt, cdn, "batch_no", "");
}
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
},
batch_no: function(frm, cdt, cdn) {
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
},
qty: function(frm, cdt, cdn) {
frm.events.set_amount_quantity(frm, cdt, cdn);
},
valuation_rate: function(frm, cdt, cdn) {
frm.events.set_amount_quantity(frm, cdt, cdn);
},
serial_no: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
if (child.serial_no) {
const serial_nos = child.serial_no.trim().split('\n');
frappe.model.set_value(cdt, cdn, "qty", serial_nos.length);
}
}
});

View File

@ -9,7 +9,9 @@ from frappe.utils import cstr, flt, cint
from erpnext.stock.stock_ledger import update_entries_after
from erpnext.controllers.stock_controller import StockController
from erpnext.accounts.utils import get_company_default
from erpnext.stock.utils import get_stock_balance
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos
from erpnext.stock.doctype.batch.batch import get_batch_qty
class OpeningEntryAccountError(frappe.ValidationError): pass
class EmptyStockReconciliationItemsError(frappe.ValidationError): pass
@ -30,10 +32,16 @@ class StockReconciliation(StockController):
self.validate_expense_account()
self.set_total_qty_and_amount()
if self._action=="submit":
self.make_batches('warehouse')
def on_submit(self):
self.update_stock_ledger()
self.make_gl_entries()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
update_serial_nos_after_submit(self, "items")
def on_cancel(self):
self.delete_and_repost_sle()
self.make_gl_entries_on_cancel()
@ -42,23 +50,28 @@ class StockReconciliation(StockController):
"""Remove items if qty or rate is not changed"""
self.difference_amount = 0.0
def _changed(item):
qty, rate = get_stock_balance(item.item_code, item.warehouse,
self.posting_date, self.posting_time, with_valuation_rate=True)
if (item.qty==None or item.qty==qty) and (item.valuation_rate==None or item.valuation_rate==rate):
item_dict = get_stock_balance_for(item.item_code, item.warehouse,
self.posting_date, self.posting_time, batch_no=item.batch_no)
if (((item.qty is None or item.qty==item_dict.get("qty")) and
(item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and not item.serial_no)
or (item.serial_no and item.serial_no == item_dict.get("serial_nos"))):
return False
else:
# set default as current rates
if item.qty==None:
item.qty = qty
if item.qty is None:
item.qty = item_dict.get("qty")
if item.valuation_rate==None:
item.valuation_rate = rate
if item.valuation_rate is None:
item.valuation_rate = item_dict.get("rate")
item.current_qty = qty
item.current_valuation_rate = rate
if item_dict.get("serial_nos"):
item.current_serial_no = item_dict.get("serial_nos")
item.current_qty = item_dict.get("qty")
item.current_valuation_rate = item_dict.get("rate")
self.difference_amount += (flt(item.qty, item.precision("qty")) * \
flt(item.valuation_rate or rate, item.precision("valuation_rate")) \
- flt(qty, item.precision("qty")) * flt(rate, item.precision("valuation_rate")))
flt(item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")) \
- flt(item_dict.get("qty"), item.precision("qty")) * flt(item_dict.get("rate"), item.precision("valuation_rate")))
return True
items = list(filter(lambda d: _changed(d), self.items))
@ -84,12 +97,17 @@ class StockReconciliation(StockController):
for row_num, row in enumerate(self.items):
# find duplicates
if [row.item_code, row.warehouse] in item_warehouse_combinations:
key = [row.item_code, row.warehouse]
for field in ['serial_no', 'batch_no']:
if row.get(field):
key.append(row.get(field))
if key in item_warehouse_combinations:
self.validation_messages.append(_get_msg(row_num, _("Duplicate entry")))
else:
item_warehouse_combinations.append([row.item_code, row.warehouse])
item_warehouse_combinations.append(key)
self.validate_item(row.item_code, row_num+1)
self.validate_item(row.item_code, row)
# validate warehouse
if not frappe.db.get_value("Warehouse", row.warehouse):
@ -131,7 +149,7 @@ class StockReconciliation(StockController):
raise frappe.ValidationError(self.validation_messages)
def validate_item(self, item_code, row_num):
def validate_item(self, item_code, row):
from erpnext.stock.doctype.item.item import validate_end_of_life, \
validate_is_stock_item, validate_cancelled_item
@ -145,51 +163,130 @@ class StockReconciliation(StockController):
validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
# item should not be serialized
if item.has_serial_no == 1:
raise frappe.ValidationError(_("Serialized Item {0} cannot be updated using Stock Reconciliation, please use Stock Entry").format(item_code))
if item.has_serial_no and not row.serial_no and not item.serial_no_series:
raise frappe.ValidationError(_("Serial no(s) required for serialized item {0}").format(item_code))
# item managed batch-wise not allowed
if item.has_batch_no == 1:
raise frappe.ValidationError(_("Batched Item {0} cannot be updated using Stock Reconciliation, instead use Stock Entry").format(item_code))
if item.has_batch_no and not row.batch_no and not item.create_new_batch:
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
# docstatus should be < 2
validate_cancelled_item(item_code, item.docstatus, verbose=0)
except Exception as e:
self.validation_messages.append(_("Row # ") + ("%d: " % (row_num)) + cstr(e))
self.validation_messages.append(_("Row # ") + ("%d: " % (row.idx)) + cstr(e))
def update_stock_ledger(self):
""" find difference between current and expected entries
and create stock ledger entries based on the difference"""
from erpnext.stock.stock_ledger import get_previous_sle
sl_entries = []
for row in self.items:
item = frappe.get_doc("Item", row.item_code)
if item.has_serial_no or item.has_batch_no:
self.get_sle_for_serialized_items(row, sl_entries)
else:
previous_sle = get_previous_sle({
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time
})
if previous_sle:
if row.qty in ("", None):
row.qty = previous_sle.get("qty_after_transaction", 0)
if row.valuation_rate in ("", None):
row.valuation_rate = previous_sle.get("valuation_rate", 0)
if row.qty and not row.valuation_rate:
frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
or (not previous_sle and not row.qty)):
continue
sl_entries.append(self.get_sle_for_items(row))
if sl_entries:
self.make_sl_entries(sl_entries)
def get_sle_for_serialized_items(self, row, sl_entries):
from erpnext.stock.stock_ledger import get_previous_sle
serial_nos = get_serial_nos(row.serial_no)
# To issue existing serial nos
if row.current_qty and (row.current_serial_no or row.batch_no):
args = self.get_sle_for_items(row)
args.update({
'actual_qty': -1 * row.current_qty,
'serial_no': row.current_serial_no,
'batch_no': row.batch_no,
'valuation_rate': row.current_valuation_rate
})
if row.current_serial_no:
args.update({
'qty_after_transaction': 0,
})
sl_entries.append(args)
for serial_no in serial_nos:
args = self.get_sle_for_items(row, [serial_no])
previous_sle = get_previous_sle({
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time
"posting_time": self.posting_time,
"serial_no": serial_no
})
if previous_sle:
if row.qty in ("", None):
row.qty = previous_sle.get("qty_after_transaction", 0)
if row.valuation_rate in ("", None):
row.valuation_rate = previous_sle.get("valuation_rate", 0)
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
# If serial no exists in different warehouse
if row.qty and not row.valuation_rate:
frappe.throw(_("Valuation Rate required for Item in row {0}").format(row.idx))
new_args = args.copy()
new_args.update({
'actual_qty': -1,
'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1,
'warehouse': previous_sle.get("warehouse", '') or row.warehouse,
'valuation_rate': previous_sle.get("valuation_rate")
})
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
or (not previous_sle and not row.qty)):
continue
sl_entries.append(new_args)
self.insert_entries(row)
if row.qty:
args = self.get_sle_for_items(row)
def insert_entries(self, row):
args.update({
'actual_qty': row.qty,
'incoming_rate': row.valuation_rate,
'valuation_rate': row.valuation_rate
})
sl_entries.append(args)
if serial_nos == get_serial_nos(row.current_serial_no):
# update valuation rate
self.update_valuation_rate_for_serial_nos(row, serial_nos)
def update_valuation_rate_for_serial_nos(self, row, serial_nos):
valuation_rate = row.valuation_rate if self.docstatus == 1 else row.current_valuation_rate
for d in serial_nos:
frappe.db.set_value("Serial No", d, 'purchase_rate', valuation_rate)
def get_sle_for_items(self, row, serial_nos=None):
"""Insert Stock Ledger Entries"""
args = frappe._dict({
if not serial_nos and row.serial_no:
serial_nos = get_serial_nos(row.serial_no)
data = frappe._dict({
"doctype": "Stock Ledger Entry",
"item_code": row.item_code,
"warehouse": row.warehouse,
@ -197,13 +294,19 @@ class StockReconciliation(StockController):
"posting_time": self.posting_time,
"voucher_type": self.doctype,
"voucher_no": self.name,
"voucher_detail_no": row.name,
"company": self.company,
"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
"is_cancelled": "No",
"qty_after_transaction": flt(row.qty, row.precision("qty")),
"is_cancelled": "No" if self.docstatus != 2 else "Yes",
"serial_no": '\n'.join(serial_nos) if serial_nos else '',
"batch_no": row.batch_no,
"valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate"))
})
self.make_sl_entries([args])
if not row.batch_no:
data.qty_after_transaction = flt(row.qty, row.precision("qty"))
return data
def delete_and_repost_sle(self):
""" Delete Stock Ledger Entries related to this voucher
@ -217,6 +320,15 @@ class StockReconciliation(StockController):
frappe.db.sql("""delete from `tabStock Ledger Entry`
where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name))
sl_entries = []
for row in self.items:
if row.serial_no or row.batch_no or row.current_serial_no:
self.get_sle_for_serialized_items(row, sl_entries)
if sl_entries:
sl_entries.reverse()
self.make_sl_entries(sl_entries)
# repost future entries for selected item_code, warehouse
for entries in existing_entries:
update_entries_after({
@ -310,17 +422,52 @@ def get_items(warehouse, posting_date, posting_time, company):
return res
@frappe.whitelist()
def get_stock_balance_for(item_code, warehouse, posting_date, posting_time):
def get_stock_balance_for(item_code, warehouse,
posting_date, posting_time, batch_no=None, with_valuation_rate= True):
frappe.has_permission("Stock Reconciliation", "write", throw = True)
qty, rate = get_stock_balance(item_code, warehouse,
posting_date, posting_time, with_valuation_rate=True)
item_dict = frappe.db.get_value("Item", item_code,
["has_serial_no", "has_batch_no"], as_dict=1)
serial_nos = ""
if item_dict.get("has_serial_no"):
qty, rate, serial_nos = get_qty_rate_for_serial_nos(item_code,
warehouse, posting_date, posting_time, item_dict)
else:
qty, rate = get_stock_balance(item_code, warehouse,
posting_date, posting_time, with_valuation_rate=with_valuation_rate)
if item_dict.get("has_batch_no"):
qty = get_batch_qty(batch_no, warehouse) or 0
return {
'qty': qty,
'rate': rate
'rate': rate,
'serial_nos': serial_nos
}
def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time, item_dict):
args = {
"item_code": item_code,
"warehouse": warehouse,
"posting_date": posting_date,
"posting_time": posting_time,
}
serial_nos_list = [serial_no.get("name")
for serial_no in get_available_serial_nos(item_code, warehouse)]
qty = len(serial_nos_list)
serial_nos = '\n'.join(serial_nos_list)
args.update({
'qty': qty,
"serial_nos": serial_nos
})
rate = get_incoming_rate(args, raise_error_if_no_rate=False) or 0
return qty, rate, serial_nos
@frappe.whitelist()
def get_difference_account(purpose, company):
if purpose == 'Stock Reconciliation':

View File

@ -13,9 +13,12 @@ from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos, get_stock_value_on
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class TestStockReconciliation(unittest.TestCase):
def setUp(self):
create_batch_or_serial_no_items()
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
self.insert_existing_sle()
@ -106,6 +109,135 @@ class TestStockReconciliation(unittest.TestCase):
make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=15, basic_rate=1200)
def test_stock_reco_for_serialized_item(self):
set_perpetual_inventory()
to_delete_records = []
to_delete_serial_nos = []
# Add new serial nos
serial_item_code = "Stock-Reco-Serial-Item-1"
serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC"
sr = create_stock_reconciliation(item_code=serial_item_code,
warehouse = serial_warehouse, qty=5, rate=200)
# print(sr.name)
serial_nos = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos), 5)
args = {
"item_code": serial_item_code,
"warehouse": serial_warehouse,
"posting_date": nowdate(),
"posting_time": nowtime(),
"serial_no": sr.items[0].serial_no
}
valuation_rate = get_incoming_rate(args)
self.assertEqual(valuation_rate, 200)
to_delete_records.append(sr.name)
sr = create_stock_reconciliation(item_code=serial_item_code,
warehouse = serial_warehouse, qty=5, rate=300, serial_no = '\n'.join(serial_nos))
# print(sr.name)
serial_nos1 = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos1), 5)
args = {
"item_code": serial_item_code,
"warehouse": serial_warehouse,
"posting_date": nowdate(),
"posting_time": nowtime(),
"serial_no": sr.items[0].serial_no
}
valuation_rate = get_incoming_rate(args)
self.assertEqual(valuation_rate, 300)
to_delete_records.append(sr.name)
to_delete_records.reverse()
for d in to_delete_records:
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
frappe.delete_doc("Stock Reconciliation", stock_doc.name)
for d in serial_nos + serial_nos1:
if frappe.db.exists("Serial No", d):
frappe.delete_doc("Serial No", d)
def test_stock_reco_for_batch_item(self):
set_perpetual_inventory()
to_delete_records = []
to_delete_serial_nos = []
# Add new serial nos
item_code = "Stock-Reco-batch-Item-1"
warehouse = "_Test Warehouse for Stock Reco2 - _TC"
sr = create_stock_reconciliation(item_code=item_code,
warehouse = warehouse, qty=5, rate=200, do_not_submit=1)
sr.save(ignore_permissions=True)
sr.submit()
self.assertTrue(sr.items[0].batch_no)
to_delete_records.append(sr.name)
sr1 = create_stock_reconciliation(item_code=item_code,
warehouse = warehouse, qty=6, rate=300, batch_no=sr.items[0].batch_no)
args = {
"item_code": item_code,
"warehouse": warehouse,
"posting_date": nowdate(),
"posting_time": nowtime(),
}
valuation_rate = get_incoming_rate(args)
self.assertEqual(valuation_rate, 300)
to_delete_records.append(sr1.name)
sr2 = create_stock_reconciliation(item_code=item_code,
warehouse = warehouse, qty=0, rate=0, batch_no=sr.items[0].batch_no)
stock_value = get_stock_value_on(warehouse, nowdate(), item_code)
self.assertEqual(stock_value, 0)
to_delete_records.append(sr2.name)
to_delete_records.reverse()
for d in to_delete_records:
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
frappe.delete_doc("Batch", sr.items[0].batch_no)
for d in to_delete_records:
frappe.delete_doc("Stock Reconciliation", d)
def create_batch_or_serial_no_items():
create_warehouse("_Test Warehouse for Stock Reco1",
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
create_warehouse("_Test Warehouse for Stock Reco2",
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
serial_item_doc = create_item("Stock-Reco-Serial-Item-1", is_stock_item=1)
if not serial_item_doc.has_serial_no:
serial_item_doc.has_serial_no = 1
serial_item_doc.serial_no_series = "SRSI.####"
serial_item_doc.save(ignore_permissions=True)
batch_item_doc = create_item("Stock-Reco-batch-Item-1", is_stock_item=1)
if not batch_item_doc.has_batch_no:
batch_item_doc.has_batch_no = 1
batch_item_doc.create_new_batch = 1
serial_item_doc.batch_number_series = "BASR.#####"
batch_item_doc.save(ignore_permissions=True)
def create_stock_reconciliation(**args):
args = frappe._dict(args)
sr = frappe.new_doc("Stock Reconciliation")
@ -120,11 +252,14 @@ def create_stock_reconciliation(**args):
"item_code": args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty,
"valuation_rate": args.rate
"valuation_rate": args.rate,
"serial_no": args.serial_no,
"batch_no": args.batch_no
})
try:
sr.submit()
if not args.do_not_submit:
sr.submit()
except EmptyStockReconciliationItemsError:
pass
return sr
@ -140,3 +275,4 @@ def set_valuation_method(item_code, valuation_method):
}, allow_negative_stock=1)
test_dependencies = ["Item", "Warehouse"]

View File

@ -1,560 +1,182 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2015-02-17 01:06:05.072764",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Other",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2015-02-17 01:06:05.072764",
"doctype": "DocType",
"document_type": "Other",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"barcode",
"item_code",
"item_name",
"warehouse",
"column_break_6",
"qty",
"valuation_rate",
"amount",
"serial_no_and_batch_section",
"serial_no",
"column_break_11",
"batch_no",
"section_break_3",
"current_qty",
"current_serial_no",
"column_break_9",
"current_valuation_rate",
"current_amount",
"section_break_14",
"quantity_difference",
"column_break_16",
"amount_difference"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "barcode",
"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": "Barcode",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"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,
"unique": 0
},
"fieldname": "barcode",
"fieldtype": "Data",
"label": "Barcode",
"print_hide": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 3,
"fieldname": "item_code",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code",
"length": 0,
"no_copy": 0,
"options": "Item",
"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,
"unique": 0
},
"columns": 3,
"fieldname": "item_code",
"fieldtype": "Link",
"in_global_search": 1,
"in_list_view": 1,
"label": "Item Code",
"options": "Item",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Item Name",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"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,
"unique": 0
},
"fieldname": "item_name",
"fieldtype": "Data",
"in_global_search": 1,
"label": "Item Name",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 3,
"fieldname": "warehouse",
"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": "Warehouse",
"length": 0,
"no_copy": 0,
"options": "Warehouse",
"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,
"unique": 0
},
"columns": 3,
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Warehouse",
"options": "Warehouse",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"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,
"unique": 0
},
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"description": "",
"fieldname": "qty",
"fieldtype": "Float",
"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": "Quantity",
"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,
"unique": 0
},
"columns": 2,
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Quantity"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"description": "",
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"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": "Valuation Rate",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"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,
"unique": 0
},
"columns": 2,
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Valuation Rate",
"options": "Company:company:default_currency"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"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": "Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"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,
"unique": 0
},
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_3",
"fieldtype": "Section 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,
"label": "Before reconciliation",
"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,
"unique": 0
},
"fieldname": "serial_no_and_batch_section",
"fieldtype": "Section Break",
"label": "Serial No and Batch"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "current_qty",
"fieldtype": "Float",
"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": "Current Qty",
"length": 0,
"no_copy": 0,
"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,
"unique": 0
},
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"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,
"unique": 0
},
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "current_valuation_rate",
"fieldtype": "Currency",
"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": "Current Valuation Rate",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"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,
"unique": 0
},
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"label": "Before reconciliation"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "current_amount",
"fieldtype": "Currency",
"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": "Current Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"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,
"unique": 0
},
"default": "0",
"fieldname": "current_qty",
"fieldtype": "Float",
"label": "Current Qty",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_14",
"fieldtype": "Section 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,
"unique": 0
},
"fieldname": "current_serial_no",
"fieldtype": "Small Text",
"label": "Current Serial No",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "quantity_difference",
"fieldtype": "Read Only",
"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": "Quantity Difference",
"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,
"unique": 0
},
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_16",
"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,
"unique": 0
},
"fieldname": "current_valuation_rate",
"fieldtype": "Currency",
"label": "Current Valuation Rate",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount_difference",
"fieldtype": "Currency",
"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": "Amount Difference",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"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,
"unique": 0
"fieldname": "current_amount",
"fieldtype": "Currency",
"label": "Current Amount",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "section_break_14",
"fieldtype": "Section Break"
},
{
"fieldname": "quantity_difference",
"fieldtype": "Read Only",
"label": "Quantity Difference"
},
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
"fieldname": "amount_difference",
"fieldtype": "Currency",
"label": "Amount Difference",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-08-03 00:03:40.412071",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
],
"istable": 1,
"modified": "2019-06-14 17:10:53.188305",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -55,10 +55,12 @@ def get_conditions(filters):
#get all details
def get_stock_ledger_entries(filters):
conditions = get_conditions(filters)
return frappe.db.sql("""select item_code, batch_no, warehouse,
posting_date, actual_qty
return frappe.db.sql("""
select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty
from `tabStock Ledger Entry`
where docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
where docstatus < 2 and ifnull(batch_no, '') != '' %s
group by voucher_no, batch_no, item_code, warehouse
order by item_code, warehouse""" %
conditions, as_dict=1)
def get_item_warehouse_batch_map(filters, float_precision):

View File

@ -157,9 +157,12 @@ class update_entries_after(object):
if sle.serial_no:
self.get_serialized_values(sle)
self.qty_after_transaction += flt(sle.actual_qty)
if sle.voucher_type == "Stock Reconciliation":
self.qty_after_transaction = sle.qty_after_transaction
self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
else:
if sle.voucher_type=="Stock Reconciliation":
if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no:
# assert
self.valuation_rate = sle.valuation_rate
self.qty_after_transaction = sle.qty_after_transaction
@ -371,7 +374,7 @@ class update_entries_after(object):
"""get Stock Ledger Entries after a particular datetime, for reposting"""
return get_stock_ledger_entries(self.previous_sle or frappe._dict({
"item_code": self.args.get("item_code"), "warehouse": self.args.get("warehouse") }),
">", "asc", for_update=True)
">", "asc", for_update=True, check_serial_no=False)
def raise_exceptions(self):
deficiency = min(e["diff"] for e in self.exceptions)
@ -412,7 +415,8 @@ def get_previous_sle(args, for_update=False):
sle = get_stock_ledger_entries(args, "<=", "desc", "limit 1", for_update=for_update)
return sle and sle[0] or {}
def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=None, for_update=False, debug=False):
def get_stock_ledger_entries(previous_sle, operator=None,
order="desc", limit=None, for_update=False, debug=False, check_serial_no=True):
"""get stock ledger entries filtered by specific posting datetime conditions"""
conditions = " and timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format(operator)
if previous_sle.get("warehouse"):
@ -420,6 +424,9 @@ def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=No
elif previous_sle.get("warehouse_condition"):
conditions += " and " + previous_sle.get("warehouse_condition")
if check_serial_no and previous_sle.get("serial_no"):
conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
if not previous_sle.get("posting_date"):
previous_sle["posting_date"] = "1900-01-01"
if not previous_sle.get("posting_time"):
@ -479,6 +486,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
and cint(erpnext.is_perpetual_inventory_enabled(company)):
frappe.local.message_log = []
frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting/cancelling this entry").format(item_code, voucher_type, voucher_no))
frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting / cancelling this entry.")
.format(item_code, voucher_type, voucher_no))
return valuation_rate

View File

@ -173,7 +173,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
in_rate = get_valuation_rate(args.get('item_code'), args.get('warehouse'),
args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'),
currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'),
raise_error_if_no_rate=True)
raise_error_if_no_rate=raise_error_if_no_rate)
return in_rate
@ -277,3 +277,7 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
new_row.append(None)
result[row_idx] = new_row
def get_available_serial_nos(item_code, warehouse):
return frappe.get_all("Serial No", filters = {'item_code': item_code,
'warehouse': warehouse, 'delivery_document_no': ''}) or []

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More