Merge branch 'develop' into sla_fix

This commit is contained in:
Himanshu 2019-05-28 00:05:04 +05:30 committed by GitHub
commit 574f81e984
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 5736 additions and 6012 deletions

View File

@ -314,13 +314,11 @@ class Subscription(Document):
self.save()
@property
def is_postpaid_to_invoice(self):
return getdate(nowdate()) > getdate(self.current_invoice_end) or \
(getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \
not self.has_outstanding_invoice()
@property
def is_prepaid_to_invoice(self):
if not self.generate_invoice_at_period_start:
return False
@ -340,7 +338,7 @@ class Subscription(Document):
2. Change the `Subscription` status to 'Past Due Date'
3. Change the `Subscription` status to 'Cancelled'
"""
if self.is_postpaid_to_invoice or self.is_prepaid_to_invoice:
if self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice():
self.generate_invoice()
if self.current_invoice_is_past_due():
self.status = 'Past Due Date'

View File

@ -8,7 +8,9 @@ import frappe.defaults
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from frappe.utils import flt, add_days, nowdate, getdate
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_rm_stock_entry as make_subcontract_transfer_entry)
from erpnext.buying.doctype.purchase_order.purchase_order \
import (make_purchase_receipt, make_purchase_invoice as make_pi_from_po, make_rm_stock_entry as make_subcontract_transfer_entry)
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_pi_from_pr
from erpnext.stock.doctype.material_request.test_material_request import make_material_request
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@ -62,7 +64,7 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.db.set_value('Item', '_Test Item', 'tolerance', 50)
pi = make_purchase_invoice(po.name)
pi = make_pi_from_po(po.name)
pi.update_stock = 1
pi.items[0].qty = 12
pi.insert()
@ -89,7 +91,7 @@ class TestPurchaseOrder(unittest.TestCase):
create_pr_against_po(po.name)
make_purchase_invoice(po.name)
make_pi_from_po(po.name)
existing_ordered_qty = get_ordered_qty()
existing_requested_qty = get_requested_qty()
@ -111,29 +113,37 @@ class TestPurchaseOrder(unittest.TestCase):
def test_update_qty(self):
po = create_purchase_order()
make_pr_against_po(po.name, 6)
pr = make_pr_against_po(po.name, 2)
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
self.assertEqual(po.get("items")[0].received_qty, 2)
# Check received_qty after make_purchase_invoice without update_stock checked
pi1 = make_purchase_invoice(po.name)
pi1.get("items")[0].qty = 6
# Check received_qty after making PI from PR without update_stock checked
pi1 = make_pi_from_pr(pr.name)
pi1.get("items")[0].qty = 2
pi1.insert()
pi1.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
self.assertEqual(po.get("items")[0].received_qty, 2)
# Check received_qty after make_purchase_invoice with update_stock checked
pi2 = make_purchase_invoice(po.name)
# Check received_qty after making PI from PO with update_stock checked
pi2 = make_pi_from_po(po.name)
pi2.set("update_stock", 1)
pi2.get("items")[0].qty = 3
pi2.insert()
pi2.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 9)
self.assertEqual(po.get("items")[0].received_qty, 5)
# Check received_qty after making PR from PO
pr = make_pr_against_po(po.name, 1)
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
def test_return_against_purchase_order(self):
po = create_purchase_order()
@ -143,7 +153,7 @@ class TestPurchaseOrder(unittest.TestCase):
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
pi2 = make_purchase_invoice(po.name)
pi2 = make_pi_from_po(po.name)
pi2.set("update_stock", 1)
pi2.get("items")[0].qty = 3
pi2.insert()
@ -175,10 +185,10 @@ class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_invoice(self):
po = create_purchase_order(do_not_submit=True)
self.assertRaises(frappe.ValidationError, make_purchase_invoice, po.name)
self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
po.submit()
pi = make_purchase_invoice(po.name)
pi = make_pi_from_po(po.name)
self.assertEqual(pi.doctype, "Purchase Invoice")
self.assertEqual(len(pi.get("items", [])), 1)
@ -186,7 +196,7 @@ class TestPurchaseOrder(unittest.TestCase):
def test_purchase_order_on_hold(self):
po = create_purchase_order(item_code="_Test Product Bundle Item")
po.db_set('Status', "On Hold")
pi = make_purchase_invoice(po.name)
pi = make_pi_from_po(po.name)
pr = make_purchase_receipt(po.name)
self.assertRaises(frappe.ValidationError, pr.submit)
self.assertRaises(frappe.ValidationError, pi.submit)
@ -195,7 +205,7 @@ class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_invoice_with_terms(self):
po = create_purchase_order(do_not_save=True)
self.assertRaises(frappe.ValidationError, make_purchase_invoice, po.name)
self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
po.update(
{"payment_terms_template": "_Test Payment Term Template"}
@ -208,7 +218,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(getdate(po.payment_schedule[0].due_date), getdate(po.transaction_date))
self.assertEqual(po.payment_schedule[1].payment_amount, 2500.0)
self.assertEqual(getdate(po.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
pi = make_purchase_invoice(po.name)
pi = make_pi_from_po(po.name)
pi.save()
self.assertEqual(pi.doctype, "Purchase Invoice")
@ -346,7 +356,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertTrue(po.get('payment_schedule'))
pi = make_purchase_invoice(po.name)
pi = make_pi_from_po(po.name)
self.assertFalse(pi.get('payment_schedule'))
@ -357,7 +367,7 @@ class TestPurchaseOrder(unittest.TestCase):
po.submit()
self.assertTrue(po.get('payment_schedule'))
pi = make_purchase_invoice(po.name)
pi = make_pi_from_po(po.name)
pi.insert()
self.assertTrue(pi.get('payment_schedule'))
@ -442,7 +452,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Make Purchase Invoice
pi = make_purchase_invoice(po.name)
pi = make_pi_from_po(po.name)
pi.update_stock = 1
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
pi.insert()

View File

@ -80,7 +80,7 @@ class StockController(AccountsController):
"cost_center": item_row.cost_center,
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": flt(sle.stock_value_difference, 2),
"is_opening": item_row.get("is_opening"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"]))
# to target warehouse / expense account
@ -91,7 +91,7 @@ class StockController(AccountsController):
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"credit": flt(sle.stock_value_difference, 2),
"project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening")
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
}))
elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse)

View File

@ -19,6 +19,7 @@ frappe.ui.form.on("Student Group", {
'academic_term': frm.doc.academic_term,
'program': frm.doc.program,
'batch': frm.doc.batch,
'student_category': frm.doc.student_category,
'course': frm.doc.course,
'student_group': frm.doc.name
}
@ -92,6 +93,7 @@ frappe.ui.form.on("Student Group", {
"group_based_on": frm.doc.group_based_on,
"program": frm.doc.program,
"batch" : frm.doc.batch,
"student_category" : frm.doc.student_category,
"course": frm.doc.course
},
callback: function(r) {

View File

@ -1,552 +1,161 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:student_group_name",
"beta": 0,
"creation": "2015-09-07 12:55:52.072792",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"academic_year",
"group_based_on",
"student_group_name",
"max_strength",
"column_break_3",
"academic_term",
"program",
"batch",
"student_category",
"course",
"disabled",
"section_break_6",
"get_students",
"students",
"section_break_12",
"instructors"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "academic_year",
"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": 1,
"label": "Academic Year",
"length": 0,
"no_copy": 0,
"options": "Academic Year",
"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": 1,
"unique": 0
"set_only_once": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "group_based_on",
"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": "Group Based on",
"length": 0,
"no_copy": 0,
"options": "\nBatch\nCourse\nActivity",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "student_group_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Student Group Name",
"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,
"unique": 0
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Set 0 for no limit",
"fieldname": "max_strength",
"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": "Max Strength",
"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
"label": "Max Strength"
},
{
"allow_bulk_edit": 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,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "academic_term",
"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": 1,
"label": "Academic Term",
"length": 0,
"no_copy": 0,
"options": "Academic Term",
"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
"options": "Academic Term"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "program",
"fieldtype": "Link",
"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": "Program",
"length": 0,
"no_copy": 0,
"options": "Program",
"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
"options": "Program"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "batch",
"fieldtype": "Link",
"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": "Batch",
"length": 0,
"no_copy": 0,
"options": "Student Batch Name",
"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
"options": "Student Batch Name"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.group_based_on == 'Course'",
"fieldname": "course",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Course",
"length": 0,
"no_copy": 0,
"options": "Course",
"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
"options": "Course"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "disabled",
"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": "Disabled",
"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
"label": "Disabled"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": "",
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"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": "Students",
"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
"label": "Students"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "get_students",
"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": "Get Students",
"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
"label": "Get Students"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "students",
"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": "Students",
"length": 0,
"no_copy": 0,
"options": "Student Group Student",
"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
"options": "Student Group Student"
},
{
"allow_bulk_edit": 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,
"label": "Instructors",
"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
"label": "Instructors"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "instructors",
"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": "Instructors",
"length": 0,
"no_copy": 0,
"options": "Student Group Instructor",
"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
"options": "Student Group Instructor"
},
{
"fieldname": "student_category",
"fieldtype": "Link",
"label": "Student Category",
"options": "Student Category"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-07-26 04:17:10.836912",
"modified": "2019-04-26 10:52:57.303951",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Group",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Instructor",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
"role": "Instructor"
},
{
"amend": 0,
"apply_user_permissions": 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": "Academics User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education",
"search_fields": "program, batch, course",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "",
"track_changes": 0,
"track_seen": 0
"sort_order": "DESC"
}

View File

@ -30,7 +30,7 @@ class StudentGroup(Document):
frappe.throw(_("""Cannot enroll more than {0} students for this student group.""").format(self.max_strength))
def validate_students(self):
program_enrollment = get_program_enrollment(self.academic_year, self.academic_term, self.program, self.batch, self.course)
program_enrollment = get_program_enrollment(self.academic_year, self.academic_term, self.program, self.batch, self.student_category, self.course)
students = [d.student for d in program_enrollment] if program_enrollment else []
for d in self.students:
if not frappe.db.get_value("Student", d.student, "enabled") and d.active and not self.disabled:
@ -60,8 +60,8 @@ class StudentGroup(Document):
roll_no_list.append(d.group_roll_number)
@frappe.whitelist()
def get_students(academic_year, group_based_on, academic_term=None, program=None, batch=None, course=None):
enrolled_students = get_program_enrollment(academic_year, academic_term, program, batch, course)
def get_students(academic_year, group_based_on, academic_term=None, program=None, batch=None, student_category=None, course=None):
enrolled_students = get_program_enrollment(academic_year, academic_term, program, batch, student_category, course)
if enrolled_students:
student_list = []
@ -76,7 +76,7 @@ def get_students(academic_year, group_based_on, academic_term=None, program=None
frappe.msgprint(_("No students found"))
return []
def get_program_enrollment(academic_year, academic_term=None, program=None, batch=None, course=None):
def get_program_enrollment(academic_year, academic_term=None, program=None, batch=None, student_category=None, course=None):
condition1 = " "
condition2 = " "
@ -86,6 +86,8 @@ def get_program_enrollment(academic_year, academic_term=None, program=None, batc
condition1 += " and pe.program = %(program)s"
if batch:
condition1 += " and pe.student_batch_name = %(batch)s"
if student_category:
condition1 += " and pe.student_category = %(student_category)s"
if course:
condition1 += " and pe.name = pec.parent and pec.course = %(course)s"
condition2 = ", `tabProgram Enrollment Course` pec"
@ -100,14 +102,14 @@ def get_program_enrollment(academic_year, academic_term=None, program=None, batc
order by
pe.student_name asc
'''.format(condition1=condition1, condition2=condition2),
({"academic_year": academic_year, "academic_term":academic_term, "program": program, "batch": batch, "course": course}), as_dict=1)
({"academic_year": academic_year, "academic_term":academic_term, "program": program, "batch": batch, "student_category": student_category, "course": course}), as_dict=1)
@frappe.whitelist()
def fetch_students(doctype, txt, searchfield, start, page_len, filters):
if filters.get("group_based_on") != "Activity":
enrolled_students = get_program_enrollment(filters.get('academic_year'), filters.get('academic_term'),
filters.get('program'), filters.get('batch'))
filters.get('program'), filters.get('batch'), filters.get('student_category'))
student_group_student = frappe.db.sql_list('''select student from `tabStudent Group Student` where parent=%s''',
(filters.get('student_group')))
students = ([d.student for d in enrolled_students if d.student not in student_group_student]

View File

@ -107,10 +107,18 @@ def get_series():
def setup_custom_fields():
custom_fields = {
"Customer": [dict(fieldname='shopify_customer_id', label='Shopify Customer Id',
fieldtype='Data', insert_after='series', read_only=1, print_hide=1)],
"Address": [dict(fieldname='shopify_address_id', label='Shopify Address Id',
fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)],
"Customer": [
dict(fieldname='shopify_customer_id', label='Shopify Customer Id',
fieldtype='Data', insert_after='series', read_only=1, print_hide=1)
],
"Supplier": [
dict(fieldname='shopify_supplier_id', label='Shopify Supplier Id',
fieldtype='Data', insert_after='supplier_name', read_only=1, print_hide=1)
],
"Address": [
dict(fieldname='shopify_address_id', label='Shopify Address Id',
fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)
],
"Item": [
dict(fieldname='shopify_variant_id', label='Shopify Variant Id',
fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
@ -119,16 +127,20 @@ def setup_custom_fields():
dict(fieldname='shopify_description', label='Shopify Description',
fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1)
],
"Sales Order": [dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)],
"Sales Order": [
dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
],
"Delivery Note":[
dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
],
"Sales Invoice": [dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)]
"Sales Invoice": [
dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
]
}
create_custom_fields(custom_fields)

View File

@ -14,6 +14,7 @@ erpnext.hr.AttendanceControlPanel = frappe.ui.form.Controller.extend({
refresh: function() {
this.frm.disable_save();
this.show_upload();
this.setup_import_progress();
},
get_template:function() {
@ -33,46 +34,36 @@ erpnext.hr.AttendanceControlPanel = frappe.ui.form.Controller.extend({
var $wrapper = $(cur_frm.fields_dict.upload_html.wrapper).empty();
new frappe.ui.FileUploader({
wrapper: $wrapper,
method: 'erpnext.hr.doctype.upload_attendance.upload_attendance.upload',
on_success(file_doc, r) {
var $log_wrapper = $(cur_frm.fields_dict.import_log.wrapper).empty();
if(!r.messages) r.messages = [];
// replace links if error has occured
if(r.exc || r.error) {
r.messages = $.map(r.message.messages, function(v) {
var msg = v.replace("Inserted", "Valid")
.replace("Updated", "Valid").split("<");
if (msg.length > 1) {
v = msg[0] + (msg[1].split(">").slice(-1)[0]);
} else {
v = msg[0];
}
return v;
});
r.messages = ["<h4 style='color:red'>"+__("Import Failed!")+"</h4>"]
.concat(r.messages);
} else {
r.messages = ["<h4 style='color:green'>"+__("Import Successful!")+"</h4>"]
.concat(r.message.messages);
}
$.each(r.messages, function(i, v) {
var $p = $('<p>').html(v).appendTo($log_wrapper);
if(v.substr(0,5)=='Error') {
$p.css('color', 'red');
} else if(v.substr(0,8)=='Inserted') {
$p.css('color', 'green');
} else if(v.substr(0,7)=='Updated') {
$p.css('color', 'green');
} else if(v.substr(0,5)=='Valid') {
$p.css('color', '#777');
}
});
}
method: 'erpnext.hr.doctype.upload_attendance.upload_attendance.upload'
});
},
setup_import_progress() {
var $log_wrapper = $(this.frm.fields_dict.import_log.wrapper).empty();
frappe.realtime.on('import_attendance', (data) => {
if (data.progress) {
this.frm.dashboard.show_progress('Import Attendance', data.progress / data.total * 100,
__('Importing {0} of {1}', [data.progress, data.total]));
if (data.progress === data.total) {
this.frm.dashboard.hide_progress('Import Attendance');
}
} else if (data.error) {
this.frm.dashboard.hide();
let messages = [`<th>${__('Error in some rows')}</th>`].concat(data.messages
.filter(message => message.includes('Error'))
.map(message => `<tr><td>${message}</td></tr>`))
.join('');
$log_wrapper.append('<table class="table table-bordered">' + messages);
} else if (data.messages) {
this.frm.dashboard.hide();
let messages = [`<th>${__('Import Successful')}</th>`].concat(data.messages
.map(message => `<tr><td>${message}</td></tr>`))
.join('');
$log_wrapper.append('<table class="table table-bordered">' + messages);
}
});
}
})
cur_frm.cscript = new erpnext.hr.AttendanceControlPanel({frm: cur_frm});

View File

@ -117,22 +117,25 @@ def upload():
raise frappe.PermissionError
from frappe.utils.csvutils import read_csv_content
rows = read_csv_content(frappe.local.uploaded_file)
if not rows:
frappe.throw(_("Please select a csv file"))
frappe.enqueue(import_attendances, rows=rows, now=True if len(rows) < 200 else False)
def import_attendances(rows):
from frappe.modules import scrub
rows = read_csv_content(frappe.local.uploaded_file)
rows = list(filter(lambda x: x and any(x), rows))
if not rows:
msg = [_("Please select a csv file")]
return {"messages": msg, "error": msg}
columns = [scrub(f) for f in rows[4]]
columns[0] = "name"
columns[3] = "attendance_date"
rows = rows[5:]
ret = []
error = False
from frappe.utils.csvutils import check_record, import_doc
for i, row in enumerate(rows[5:]):
for i, row in enumerate(rows):
if not row: continue
row_idx = i + 5
d = frappe._dict(zip(columns, row))
@ -144,6 +147,10 @@ def upload():
try:
check_record(d)
ret.append(import_doc(d, "Attendance", 1, row_idx, submit=True))
frappe.publish_realtime('import_attendance', dict(
progress=i,
total=len(rows)
))
except AttributeError:
pass
except Exception as e:
@ -156,4 +163,8 @@ def upload():
frappe.db.rollback()
else:
frappe.db.commit()
return {"messages": ret, "error": error}
frappe.publish_realtime('import_attendance', dict(
messages=ret,
error=error
))

View File

@ -756,7 +756,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency,
function(exchange_rate) {
if(exchange_rate != me.frm.doc.conversion_rate) {
me.set_margin_amount_based_on_currency(exchange_rate);
me.set_actual_charges_based_on_currency(exchange_rate);
me.frm.set_value("conversion_rate", exchange_rate);
}
});
} else {
this.conversion_rate();
@ -777,7 +781,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
if(this.frm.doc.ignore_pricing_rule) {
this.calculate_taxes_and_totals();
} else if (!this.in_apply_price_list){
this.set_actual_charges_based_on_currency();
this.apply_price_list();
}
@ -804,12 +807,24 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}
},
set_actual_charges_based_on_currency: function() {
set_margin_amount_based_on_currency: function(exchange_rate) {
if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]), this.frm.doc.doctype) {
var me = this;
$.each(this.frm.doc.items || [], function(i, d) {
if(d.margin_type == "Amount") {
frappe.model.set_value(d.doctype, d.name, "margin_rate_or_amount",
flt(d.margin_rate_or_amount) / flt(exchange_rate));
}
});
}
},
set_actual_charges_based_on_currency: function(exchange_rate) {
var me = this;
$.each(this.frm.doc.taxes || [], function(i, d) {
if(d.charge_type == "Actual") {
frappe.model.set_value(d.doctype, d.name, "tax_amount",
flt(d.tax_amount) / flt(me.frm.doc.conversion_rate));
flt(d.tax_amount) / flt(exchange_rate));
}
});
},

View File

@ -3093,7 +3093,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2019-03-26 17:15:50.390548",
"modified": "2019-05-21 17:15:50.390548",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@ -64,7 +64,10 @@ class DeliveryNote(SellingController):
'second_source_dt': 'Sales Invoice Item',
'second_source_field': '-1 * qty',
'second_join_field': 'so_detail',
'extra_cond': """ and exists (select name from `tabDelivery Note` where name=`tabDelivery Note Item`.parent and is_return=1)"""
'extra_cond': """ and exists (select name from `tabDelivery Note`
where name=`tabDelivery Note Item`.parent and is_return=1)""",
'second_source_extra_cond': """ and exists (select name from `tabSales Invoice`
where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)"""
})
def before_print(self):

View File

@ -233,6 +233,8 @@ def update_completed_and_requested_qty(stock_entry, method):
mr_obj.update_requested_qty(mr_item_rows)
def set_missing_values(source, target_doc):
if target_doc.doctype == "Purchase Order" and getdate(target_doc.schedule_date) < getdate(nowdate()):
target_doc.schedule_date = None
target_doc.run_method("set_missing_values")
target_doc.run_method("calculate_taxes_and_totals")
@ -240,6 +242,8 @@ def update_item(obj, target, source_parent):
target.conversion_factor = obj.conversion_factor
target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty))/ target.conversion_factor
target.stock_qty = (target.qty * target.conversion_factor)
if getdate(target.schedule_date) < getdate(nowdate()):
target.schedule_date = None
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
@ -336,7 +340,8 @@ def make_purchase_order_based_on_supplier(source_name, target_doc=None):
def postprocess(source, target_doc):
target_doc.supplier = source_name
target_doc.schedule_date = add_days(nowdate(), 1)
if getdate(target_doc.schedule_date) < getdate(nowdate()):
target_doc.schedule_date = None
target_doc.set("items", [d for d in target_doc.get("items")
if d.get("item_code") in supplier_items and d.get("qty") > 0])

View File

@ -36,7 +36,9 @@ class PurchaseReceipt(BuyingController):
'second_source_field': 'received_qty',
'second_join_field': 'po_detail',
'percent_join_field': 'purchase_order',
'overflow_type': 'receipt'
'overflow_type': 'receipt',
'second_source_extra_cond': """ and exists(select name from `tabPurchase Invoice`
where name=`tabPurchase Invoice Item`.parent and update_stock = 1)"""
},
{
'source_dt': 'Purchase Receipt Item',
@ -55,10 +57,7 @@ class PurchaseReceipt(BuyingController):
'join_field': 'purchase_order_item',
'target_field': 'returned_qty',
'target_parent_dt': 'Purchase Order',
# 'target_parent_field': 'per_received',
# 'target_ref_field': 'qty',
'source_field': '-1 * qty',
# 'overflow_type': 'receipt',
'extra_cond': """ and exists (select name from `tabPurchase Receipt` where name=`tabPurchase Receipt Item`.parent and is_return=1)"""
}]
if cint(self.is_return):
@ -71,7 +70,10 @@ class PurchaseReceipt(BuyingController):
'second_source_dt': 'Purchase Invoice Item',
'second_source_field': '-1 * qty',
'second_join_field': 'po_detail',
'extra_cond': """ and exists (select name from `tabPurchase Receipt` where name=`tabPurchase Receipt Item`.parent and is_return=1)"""
'extra_cond': """ and exists (select name from `tabPurchase Receipt`
where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
})
def validate(self):

View File

@ -1963,6 +1963,40 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_opening",
"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": "Is Opening",
"length": 0,
"no_copy": 0,
"options": "No\nYes",
"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_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -2246,7 +2280,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-26 12:24:46.439626",
"modified": "2019-05-23 12:24:46.439626",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",

View File

@ -17,6 +17,7 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, add_additiona
from erpnext.stock.utils import get_bin
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
import json
@ -61,6 +62,7 @@ class StockEntry(StockController):
self.validate_batch()
self.validate_inspection()
self.validate_fg_completed_qty()
self.validate_difference_account()
self.set_job_card_data()
self.set_purpose_for_stock_entry()
@ -226,7 +228,18 @@ class StockEntry(StockController):
production_item = frappe.get_value('Work Order', self.work_order, 'production_item')
for item in self.items:
if item.item_code == production_item and item.qty != self.fg_completed_qty:
frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different").format(item.qty, self.fg_completed_qty))
frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different")
.format(item.qty, self.fg_completed_qty))
def validate_difference_account(self):
if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
return
for d in self.get("items"):
if not d.expense_account:
frappe.throw(_("Please enter Difference Account"))
elif self.is_opening == "Yes" and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"), OpeningEntryAccountError)
def validate_warehouse(self):
"""perform various (sometimes conditional) validations on warehouse"""

View File

@ -89,10 +89,11 @@ def make_stock_entry(**args):
s.purchase_receipt_no = args.purchase_receipt_no
s.delivery_note_no = args.delivery_note_no
s.sales_invoice_no = args.sales_invoice_no
s.is_opening = args.is_opening or "No"
if not args.cost_center:
args.cost_center = frappe.get_value('Company', s.company, 'cost_center')
if not args.expense_account:
if not args.expense_account and s.is_opening == "No":
args.expense_account = frappe.get_value('Company', s.company, 'stock_adjustment_account')
# We can find out the serial number using the batch source document

View File

@ -6,8 +6,7 @@ import frappe, unittest
import frappe.defaults
from frappe.utils import flt, nowdate, nowtime
from erpnext.stock.doctype.serial_no.serial_no import *
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
import set_perpetual_inventory
from erpnext import set_perpetual_inventory
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
from erpnext.stock.stock_ledger import get_previous_sle
from frappe.permissions import add_user_permission, remove_user_permission
@ -16,6 +15,7 @@ from erpnext.stock.doctype.item.test_item import set_item_variant_settings, make
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse, make_stock_in_entry
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
from six import iteritems
@ -772,6 +772,22 @@ class TestStockEntry(unittest.TestCase):
doc = frappe.get_doc('Stock Entry', outward_entry.name)
self.assertEqual(doc.per_transferred, 100)
def test_gle_for_opening_stock_entry(self):
set_perpetual_inventory(1)
mr = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
qty=50, basic_rate=100, expense_account="Stock Adjustment - _TC", is_opening="Yes", do_not_save=True)
self.assertRaises(OpeningEntryAccountError, mr.save)
mr.items[0].expense_account = "Temporary Opening - _TC"
mr.save()
mr.submit()
is_opening = frappe.db.get_value("GL Entry",
filters={"voucher_type": "Stock Entry", "voucher_no": mr.name}, fieldname="is_opening")
self.assertEqual(is_opening, "Yes")
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
se = frappe.copy_doc(test_records[0])
se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"

View File

@ -238,8 +238,8 @@ class StockReconciliation(StockController):
return
if not self.expense_account:
msgprint(_("Please enter Expense Account"), raise_exception=1)
elif not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""):
frappe.throw(_("Please enter Expense Account"))
elif self.purpose == "Opening Stock" or not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""):
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError)