diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index f4876bd46f..1b88bdc5b9 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '10.0.14'
+__version__ = '10.0.17'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 48062a3dc7..60c974f262 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -82,8 +82,8 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
$.each(this.frm.doc.accounts || [], function(i, jvd) {
frappe.model.set_default_values(jvd);
});
-
- if(!this.frm.doc.amended_from) this.frm.doc.posting_date = this.frm.posting_date || frappe.datetime.get_today();
+ var posting_date = this.frm.posting_date;
+ if(!this.frm.doc.amended_from) this.frm.set_value('posting_date', posting_date || frappe.datetime.get_today());
}
},
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 3f9bca7cf1..21b71ff37c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -96,7 +96,7 @@ class PurchaseInvoice(BuyingController):
if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
if not self.due_date:
- self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier)
+ self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company)
super(PurchaseInvoice, self).set_missing_values(for_validate)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index bf6ad49d0e..266e3e13fb 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -237,7 +237,7 @@ class SalesInvoice(SellingController):
if not self.debit_to:
self.debit_to = get_party_account("Customer", self.customer, self.company)
if not self.due_date and self.customer:
- self.due_date = get_due_date(self.posting_date, "Customer", self.customer)
+ self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
super(SalesInvoice, self).set_missing_values(for_validate)
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 1103b701dc..480abd42ee 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -232,6 +232,8 @@ def get_next_date(dt, mcount, day=None):
def send_notification(new_rv, subscription_doc, print_format='Standard'):
"""Notify concerned persons about recurring document generation"""
print_format = print_format
+ subject = subscription_doc.subject or ''
+ message = subscription_doc.message or ''
if not subscription_doc.subject:
subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name)
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index c575d59ae7..03a06cc5f2 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -191,8 +191,9 @@ def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
for entry in gl_entries:
validate_frozen_account(entry["account"], adv_adj)
validate_balance_type(entry["account"], adv_adj)
- validate_expense_against_budget(entry)
+ if not adv_adj:
+ validate_expense_against_budget(entry)
- if entry.get("against_voucher") and update_outstanding == 'Yes':
+ if entry.get("against_voucher") and update_outstanding == 'Yes' and not adv_adj:
update_outstanding_amt(entry["account"], entry.get("party_type"), entry.get("party"), entry.get("against_voucher_type"),
entry.get("against_voucher"), on_cancel=True)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 7bccfe89f3..5237a71949 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -51,7 +51,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
set_other_values(out, party, party_type)
set_price_list(out, party, party_type, price_list)
out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_type)
- out["payment_terms_template"] = get_pyt_term_template(party.name, party_type)
+ out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
if not out.get("currency"):
out["currency"] = currency
@@ -164,7 +164,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
out = {
party_type.lower(): party,
account_fieldname : account,
- "due_date": get_due_date(posting_date, party_type, party)
+ "due_date": get_due_date(posting_date, party_type, party, company)
}
return out
@@ -267,12 +267,12 @@ def validate_party_accounts(doc):
@frappe.whitelist()
-def get_due_date(posting_date, party_type, party):
+def get_due_date(posting_date, party_type, party, company=None):
"""Get due date from `Payment Terms Template`"""
due_date = None
if posting_date and party:
due_date = posting_date
- template_name = get_pyt_term_template(party, party_type)
+ template_name = get_pyt_term_template(party, party_type, company)
if template_name:
due_date = get_due_date_from_template(template_name, posting_date).strftime("%Y-%m-%d")
else:
@@ -305,12 +305,11 @@ def get_due_date_from_template(template_name, posting_date):
return due_date
-
-def validate_due_date(posting_date, due_date, party_type, party):
+def validate_due_date(posting_date, due_date, party_type, party, company=None):
if getdate(due_date) < getdate(posting_date):
frappe.throw(_("Due Date cannot be before Posting Date"))
else:
- default_due_date = get_due_date(posting_date, party_type, party)
+ default_due_date = get_due_date(posting_date, party_type, party, company)
if not default_due_date:
return
@@ -360,14 +359,32 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup
@frappe.whitelist()
-def get_pyt_term_template(party_name, party_type):
+def get_pyt_term_template(party_name, party_type, company=None):
+ if party_type not in ("Customer", "Supplier"):
+ return
+
template = None
- if party_type in ('Customer', 'Supplier'):
- template = frappe.db.get_value(party_type, party_name, fieldname='payment_terms')
+ if party_type == 'Customer':
+ customer = frappe.db.get_value("Customer", party_name,
+ fieldname=['payment_terms', "customer_group"], as_dict=1)
+ template = customer.payment_terms
+
+ if not template and customer.customer_group:
+ template = frappe.db.get_value("Customer Group",
+ customer.customer_group, fieldname='payment_terms')
+ else:
+ supplier = frappe.db.get_value("Supplier", party_name,
+ fieldname=['payment_terms', "supplier_type"], as_dict=1)
+ template = supplier.payment_terms
+
+ if not template and supplier.supplier_type:
+ template = frappe.db.get_value("Supplier Type", supplier.supplier_type, fieldname='payment_terms')
+
+ if not template and company:
+ template = frappe.db.get_value("Company", company, fieldname='payment_terms')
return template
-
def validate_party_frozen_disabled(party_type, party_name):
if party_type and party_name:
if party_type in ("Customer", "Supplier"):
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index fd5241b1ff..ac641c29cc 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -28,6 +28,24 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldtype": "Link",
"options": "Payment Terms Template"
},
+ {
+ "fieldname":"territory",
+ "label": __("Territory"),
+ "fieldtype": "Link",
+ "options": "Territory"
+ },
+ {
+ "fieldname":"sales_partner",
+ "label": __("Sales Partner"),
+ "fieldtype": "Link",
+ "options": "Sales Partner"
+ },
+ {
+ "fieldname":"sales_person",
+ "label": __("Sales Person"),
+ "fieldtype": "Link",
+ "options": "Sales Person"
+ },
{
"fieldtype": "Break",
},
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 0732b7dc09..14fdbbd6ce 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -282,11 +282,27 @@ class ReceivablePayableReport(object):
conditions.append("""party in (select name from tabCustomer
where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1}
and name=tabCustomer.customer_group))""".format(lft, rgt))
+
+ if self.filters.get("territory"):
+ lft, rgt = frappe.db.get_value("Territory",
+ self.filters.get("territory"), ["lft", "rgt"])
+
+ conditions.append("""party in (select name from tabCustomer
+ where exists(select name from `tabTerritory` where lft >= {0} and rgt <= {1}
+ and name=tabCustomer.territory))""".format(lft, rgt))
if self.filters.get("payment_terms_template"):
conditions.append("party in (select name from tabCustomer where payment_terms=%s)")
values.append(self.filters.get("payment_terms_template"))
+ if self.filters.get("sales_partner"):
+ conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
+ values.append(self.filters.get("sales_partner"))
+
+ if self.filters.get("sales_person"):
+ conditions.append("""party in (select parent
+ from `tabSales Team` where sales_person=%s and parenttype = 'Customer')""")
+ values.append(self.filters.get("sales_person"))
return " and ".join(conditions), values
def get_gl_entries_for(self, party, party_type, against_voucher_type, against_voucher):
diff --git a/erpnext/buying/doctype/supplier/test_records.json b/erpnext/buying/doctype/supplier/test_records.json
index 7479f55650..370ce8dad1 100644
--- a/erpnext/buying/doctype/supplier/test_records.json
+++ b/erpnext/buying/doctype/supplier/test_records.json
@@ -9,7 +9,6 @@
"doctype": "Supplier",
"supplier_name": "_Test Supplier P",
"supplier_type": "_Test Supplier Type",
- "credit_days_based_on": "Fixed Days"
},
{
"doctype": "Supplier",
diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py
index ffe99967ac..18bb36b01d 100644
--- a/erpnext/config/desktop.py
+++ b/erpnext/config/desktop.py
@@ -283,7 +283,7 @@ def get_data():
"label": _("Education"),
"hidden": 1
},
- {
+ {
"module_name": "Healthcare",
"color": "#FF888B",
"icon": "fa fa-heartbeat",
@@ -291,30 +291,15 @@ def get_data():
"label": _("Healthcare"),
"hidden": 1
},
- {
- "module_name": "Lab Test",
- "color": "#7578f6",
- "icon": "octicon octicon-beaker",
- "doctype": "Lab Test",
- "type": "list",
- "link": "List/Lab Test",
- "label": _("Lab Test")
- },
- {
- "module_name": "Consultation",
- "color": "#2ecc71",
- "icon": "fa fa-stethoscope",
- "doctype": "Consultation",
- "type": "link",
- "label": _("Consultationt")
- },
{
"module_name": "Patient",
"color": "#6BE273",
"icon": "fa fa-user",
"doctype": "Patient",
"type": "link",
- "label": _("Patient")
+ "link": "List/Patient",
+ "label": _("Patient"),
+ "hidden": 1
},
{
"module_name": "Patient Appointment",
@@ -322,7 +307,29 @@ def get_data():
"icon": "fa fa-calendar-plus-o",
"doctype": "Patient Appointment",
"type": "link",
- "label": _("Patient Appointment")
+ "link": "List/Patient Appointment",
+ "label": _("Patient Appointment"),
+ "hidden": 1
+ },
+ {
+ "module_name": "Consultation",
+ "color": "#2ecc71",
+ "icon": "fa fa-stethoscope",
+ "doctype": "Consultation",
+ "type": "link",
+ "link": "List/Consultation",
+ "label": _("Consultation"),
+ "hidden": 1
+ },
+ {
+ "module_name": "Lab Test",
+ "color": "#7578f6",
+ "icon": "octicon octicon-beaker",
+ "doctype": "Lab Test",
+ "type": "list",
+ "link": "List/Lab Test",
+ "label": _("Lab Test"),
+ "hidden": 1
},
{
"module_name": "Hub",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index c7b520608e..b3672cbde3 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -135,9 +135,9 @@ class AccountsController(TransactionBase):
if not self.due_date:
frappe.throw(_("Due Date is mandatory"))
- validate_due_date(self.posting_date, self.due_date, "Customer", self.customer)
+ validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company)
elif self.doctype == "Purchase Invoice":
- validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier)
+ validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company)
def set_price_list_currency(self, buying_or_selling):
if self.meta.get_field("posting_date"):
diff --git a/erpnext/docs/user/manual/en/regional/france/fichier_des_écritures_comptables.md b/erpnext/docs/user/manual/en/regional/france/fichier_des_ecritures_comptables.md
similarity index 100%
rename from erpnext/docs/user/manual/en/regional/france/fichier_des_écritures_comptables.md
rename to erpnext/docs/user/manual/en/regional/france/fichier_des_ecritures_comptables.md
diff --git a/erpnext/docs/user/manual/en/regional/france/index.txt b/erpnext/docs/user/manual/en/regional/france/index.txt
new file mode 100644
index 0000000000..2edf323e63
--- /dev/null
+++ b/erpnext/docs/user/manual/en/regional/france/index.txt
@@ -0,0 +1 @@
+fichier_des_ecritures_comptables
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index ea4da69504..99fb36e52c 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -269,7 +269,10 @@ def get_grade(grading_scale, percentage):
:param Percentage: Score Percentage Percentage
"""
grading_scale_intervals = {}
- for d in frappe.get_all("Grading Scale Interval", fields=["grade_code", "threshold"], filters={"parent": grading_scale}):
+ if not hasattr(frappe.local, 'grading_scale'):
+ grading_scale = frappe.get_all("Grading Scale Interval", fields=["grade_code", "threshold"], filters={"parent": grading_scale})
+ frappe.local.grading_scale = grading_scale
+ for d in frappe.local.grading_scale:
grading_scale_intervals.update({d.threshold:d.grade_code})
intervals = sorted(grading_scale_intervals.keys(), key=float, reverse=True)
for interval in intervals:
diff --git a/erpnext/education/doctype/assessment_criteria/assessment_criteria.py b/erpnext/education/doctype/assessment_criteria/assessment_criteria.py
index e666a74959..eadc0de95b 100644
--- a/erpnext/education/doctype/assessment_criteria/assessment_criteria.py
+++ b/erpnext/education/doctype/assessment_criteria/assessment_criteria.py
@@ -6,5 +6,9 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+STD_CRITERIA = ["total", "total score", "total grade", "maximum score", "score", "grade"]
+
class AssessmentCriteria(Document):
- pass
+ def validate(self):
+ if self.assessment_criteria.lower() in STD_CRITERIA:
+ frappe.throw("Can't create standard criteria. Please rename the criteria")
\ No newline at end of file
diff --git a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.html b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.html
index 60300b0528..e46a5e764a 100644
--- a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.html
+++ b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.html
@@ -6,9 +6,15 @@
{% } %}
{%= __("Assessment Report") %}
+{%= __("Academic Year: ") %} {%= filters.academic_year %}
+{% if (filters.academic_term){ %}
+{%= __("Academic Term: ") %} {%= filters.academic_term %}
+{% } %}
{%= __("Course Code: ") %} {%= filters.course %}
{%= __("Assessment Group: ") %} {%= filters.assessment_group %}
-{%= __("Assessment Plan: ") %} {%= data_to_be_printed[0]["assessment_plan"] %}
+{% if (filters.student_group){ %}
+{%= __("Student Group: ") %} {%= filters.student_group %}
+{% } %}
diff --git a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.js b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.js
index 42b19ebf06..8c42d48641 100644
--- a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.js
+++ b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.js
@@ -4,18 +4,17 @@
frappe.query_reports["Course wise Assessment Report"] = {
"filters": [
{
- "fieldname":"assessment_group",
- "label": __("Assessment Group"),
+ "fieldname":"academic_year",
+ "label": __("Academic Year"),
"fieldtype": "Link",
- "options": "Assessment Group",
- "reqd": 1,
- "get_query": function() {
- return{
- filters: {
- 'is_group': 0
- }
- };
- }
+ "options": "Academic Year",
+ "reqd": 1
+ },
+ {
+ "fieldname":"academic_term",
+ "label": __("Academic Term"),
+ "fieldtype": "Link",
+ "options": "Academic Term"
},
{
"fieldname":"course",
@@ -29,6 +28,13 @@ frappe.query_reports["Course wise Assessment Report"] = {
"label": __("Student Group"),
"fieldtype": "Link",
"options": "Student Group"
+ },
+ {
+ "fieldname":"assessment_group",
+ "label": __("Assessment Group"),
+ "fieldtype": "Link",
+ "options": "Assessment Group",
+ "reqd": 1
}
]
};
diff --git a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py
index ff172381b3..a50ad7b91e 100644
--- a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py
+++ b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py
@@ -5,129 +5,189 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
-from collections import defaultdict
+from collections import defaultdict, OrderedDict
from erpnext.education.api import get_grade
def execute(filters=None):
- data = []
-
+ data, chart, grades = [], [], []
args = frappe._dict()
+ grade_wise_analysis = defaultdict(dict)
+
+ args["academic_year"] = filters.get("academic_year")
+ args["course"] = filters.get("course")
args["assessment_group"] = filters.get("assessment_group")
+
+ args["academic_term"] = filters.get("academic_term")
+ args["student_group"] = filters.get("student_group")
+
if args["assessment_group"] == "All Assessment Groups":
frappe.throw(_("Please select the assessment group other than 'All Assessment Groups'"))
- args["course"] = filters.get("course")
- args["student_group"] = filters.get("student_group")
+ returned_values = get_formatted_result(args, get_assessment_criteria=True)
+ student_dict = returned_values["student_details"]
+ result_dict = returned_values["assessment_result"]
+ assessment_criteria_dict = returned_values["assessment_criteria"]
+
+ for student in result_dict:
+ student_row = {}
+ student_row["student"] = student
+ student_row["student_name"] = student_dict[student]
+ for criteria in assessment_criteria_dict:
+ scrub_criteria = frappe.scrub(criteria)
+ if criteria in result_dict[student][args.course][args.assessment_group]:
+ student_row[scrub_criteria] = result_dict[student][args.course][args.assessment_group][criteria]["grade"]
+ student_row[scrub_criteria + "_score"] = result_dict[student][args.course][args.assessment_group][criteria]["score"]
+
+ # create the list of possible grades
+ if student_row[scrub_criteria] not in grades:
+ grades.append(student_row[scrub_criteria])
+
+ # create the dict of for gradewise analysis
+ if student_row[scrub_criteria] not in grade_wise_analysis[criteria]:
+ grade_wise_analysis[criteria][student_row[scrub_criteria]] = 1
+ else:
+ grade_wise_analysis[criteria][student_row[scrub_criteria]] += 1
+ else:
+ student_row[frappe.scrub(criteria)] = ""
+ student_row[frappe.scrub(criteria)+ "_score"] = ""
+ data.append(student_row)
+
+ assessment_criteria_list = [d for d in assessment_criteria_dict]
+ columns = get_column(assessment_criteria_dict)
+ chart = get_chart_data(grades, assessment_criteria_list, grade_wise_analysis)
+
+ return columns, data, None, chart
- # find all assessment plan and related details linked with the given filters
- def get_assessment_details():
- if args["student_group"]:
- cond = "and ap.student_group=%(student_group)s"
+def get_formatted_result(args, get_assessment_criteria=False, get_course=False):
+ cond, cond1, cond2, cond3, cond4 = " ", " ", " ", " ", " "
+ args_list = [args.academic_year]
+
+ if args.course:
+ cond = " and ar.course=%s"
+ args_list.append(args.course)
+
+ if args.academic_term:
+ cond1 = " and ar.academic_term=%s"
+ args_list.append(args.academic_term)
+
+ if args.student_group:
+ cond2 = " and ar.student_group=%s"
+ args_list.append(args.student_group)
+
+ create_total_dict = False
+ group_type = frappe.get_value("Assessment Group", args.assessment_group, "is_group")
+ if group_type:
+ from frappe.desk.treeview import get_children
+ assessment_groups = [d.get("value") for d in get_children("Assessment Group",
+ args.assessment_group) if d.get("value") and not d.get("expandable")]
+ cond3 = " and ar.assessment_group in (%s)"%(', '.join(['%s']*len(assessment_groups)))
+ else:
+ assessment_groups = [args.assessment_group]
+ cond3 = " and ar.assessment_group=%s"
+ args_list += assessment_groups
+
+ if args.students:
+ cond4 = " and ar.student in (%s)"%(', '.join(['%s']*len(args.students)))
+ args_list += args.students
+
+ assessment_result = frappe.db.sql('''
+ SELECT
+ ar.student, ar.student_name, ar.academic_year, ar.academic_term, ar.program, ar.course,
+ ar.assessment_plan, ar.grading_scale, ar.assessment_group, ar.student_group,
+ ard.assessment_criteria, ard.maximum_score, ard.grade, ard.score
+ FROM
+ `tabAssessment Result` ar, `tabAssessment Result Detail` ard
+ WHERE
+ ar.name=ard.parent and ar.docstatus=1 and ar.academic_year=%s {0} {1} {2} {3} {4}
+ ORDER BY
+ ard.assessment_criteria'''.format(cond, cond1, cond2, cond3, cond4),
+ tuple(args_list), as_dict=1)
+
+ # create the nested dictionary structure as given below:
+ # .....
+ # "Total Score" -> assessment criteria used for totaling and args.assessment_group -> for totaling all the assesments
+
+ student_details = {}
+ formatted_assessment_result = defaultdict(dict)
+ assessment_criteria_dict = OrderedDict()
+ course_dict = OrderedDict()
+ total_maximum_score = None
+ if not (len(assessment_groups) == 1 and assessment_groups[0] == args.assessment_group):
+ create_total_dict = True
+
+ # add the score for a given score and recalculate the grades
+ def add_score_and_recalculate_grade(result, assessment_group, assessment_criteria):
+ formatted_assessment_result[result.student][result.course][assessment_group]\
+ [assessment_criteria]["maximum_score"] += result.maximum_score
+ formatted_assessment_result[result.student][result.course][assessment_group]\
+ [assessment_criteria]["score"] += result.score
+ tmp_grade = get_grade(result.grading_scale, ((formatted_assessment_result[result.student][result.course]
+ [assessment_group][assessment_criteria]["score"])/(formatted_assessment_result[result.student]
+ [result.course][assessment_group][assessment_criteria]["maximum_score"]))*100)
+ formatted_assessment_result[result.student][result.course][assessment_group]\
+ [assessment_criteria]["grade"] = tmp_grade
+
+ # create the assessment criteria "Total Score" with the sum of all the scores of the assessment criteria in a given assessment group
+ def add_total_score(result, assessment_group):
+ if "Total Score" not in formatted_assessment_result[result.student][result.course][assessment_group]:
+ formatted_assessment_result[result.student][result.course][assessment_group]["Total Score"] = frappe._dict({
+ "assessment_criteria": "Total Score", "maximum_score": result.maximum_score, "score": result.score, "grade": result.grade})
else:
- cond = ''
+ add_score_and_recalculate_grade(result, assessment_group, "Total Score")
- assessment_plan = frappe.db.sql('''
- select
- ap.name, ap.student_group, ap.grading_scale, apc.assessment_criteria, apc.maximum_score as max_score
- from
- `tabAssessment Plan` ap, `tabAssessment Plan Criteria` apc
- where
- ap.assessment_group=%(assessment_group)s and ap.course=%(course)s and
- ap.name=apc.parent and ap.docstatus=1 {0}
- order by
- apc.assessment_criteria'''.format(cond), (args), as_dict=1)
+ for result in assessment_result:
+ if result.student not in student_details:
+ student_details[result.student] = result.student_name
- assessment_plan_list = list(set([d["name"] for d in assessment_plan]))
- if not assessment_plan_list:
- frappe.throw(_("No assessment plan linked with this assessment group"))
+ assessment_criteria_details = frappe._dict({"assessment_criteria": result.assessment_criteria,
+ "maximum_score": result.maximum_score, "score": result.score, "grade": result.grade})
- assessment_criteria_list = list(set([(d["assessment_criteria"],d["max_score"]) for d in assessment_plan]))
- student_group_list = list(set([d["student_group"] for d in assessment_plan]))
- total_maximum_score = flt(sum([flt(d[1]) for d in assessment_criteria_list]))
- grading_scale = assessment_plan[0]["grading_scale"]
+ if not formatted_assessment_result[result.student]:
+ formatted_assessment_result[result.student] = defaultdict(dict)
+ if not formatted_assessment_result[result.student][result.course]:
+ formatted_assessment_result[result.student][result.course] = defaultdict(dict)
- return assessment_plan_list, assessment_criteria_list, total_maximum_score, grading_scale, student_group_list
+ if not create_total_dict:
+ formatted_assessment_result[result.student][result.course][result.assessment_group]\
+ [result.assessment_criteria] = assessment_criteria_details
+ add_total_score(result, result.assessment_group)
+
+ # create the total of all the assessment groups criteria-wise
+ elif create_total_dict:
+ if not formatted_assessment_result[result.student][result.course][args.assessment_group]:
+ formatted_assessment_result[result.student][result.course][args.assessment_group] = defaultdict(dict)
+ formatted_assessment_result[result.student][result.course][args.assessment_group]\
+ [result.assessment_criteria] = assessment_criteria_details
+ elif result.assessment_criteria not in formatted_assessment_result[result.student][result.course][args.assessment_group]:
+ formatted_assessment_result[result.student][result.course][args.assessment_group]\
+ [result.assessment_criteria] = assessment_criteria_details
+ elif result.assessment_criteria in formatted_assessment_result[result.student][result.course][args.assessment_group]:
+ add_score_and_recalculate_grade(result, args.assessment_group, result.assessment_criteria)
+
+ add_total_score(result, args.assessment_group)
+
+ total_maximum_score = formatted_assessment_result[result.student][result.course][args.assessment_group]\
+ ["Total Score"]["maximum_score"]
+ if get_assessment_criteria:
+ assessment_criteria_dict[result.assessment_criteria] = formatted_assessment_result[result.student][result.course]\
+ [args.assessment_group][result.assessment_criteria]["maximum_score"]
+ if get_course:
+ course_dict[result.course] = total_maximum_score
+
+ if get_assessment_criteria and total_maximum_score:
+ assessment_criteria_dict["Total Score"] = total_maximum_score
+
+ return {
+ "student_details": student_details,
+ "assessment_result": formatted_assessment_result,
+ "assessment_criteria": assessment_criteria_dict,
+ "course_dict": course_dict
+ }
- # get all the result and make a dict map student as the key and value as dict of result
- def get_result_map():
- result_dict = defaultdict(dict)
- kounter = defaultdict(dict)
- assessment_result = frappe.db.sql('''select ar.student, ard.assessment_criteria, ard.grade, ard.score
- from `tabAssessment Result` ar, `tabAssessment Result Detail` ard
- where ar.assessment_plan in (%s) and ar.name=ard.parent and ar.docstatus=1
- order by ard.assessment_criteria''' %', '.join(['%s']*len(assessment_plan_list)),
- tuple(assessment_plan_list), as_dict=1)
-
- for result in assessment_result:
- if "total_score" in result_dict[result.student]:
- total_score = result_dict[result.student]["total_score"] + result.score
- else:
- total_score = result.score
- total = get_grade(grading_scale, (total_score/total_maximum_score)*100)
-
- if result.grade in kounter[result.assessment_criteria]:
- kounter[result.assessment_criteria][result.grade] += 1
- else:
- kounter[result.assessment_criteria].update({result.grade: 1})
-
- if "Total" not in kounter:
- kounter["Total"] = {}
-
- if "total" in result_dict[result.student]:
- prev_grade = result_dict[result.student]["total"]
- prev_grade_count = kounter["Total"].get(prev_grade) - 1
- kounter["Total"].update({prev_grade: prev_grade_count})
- latest_grade_count = kounter["Total"].get(total)+1 if kounter["Total"].get(total) else 1
- kounter["Total"].update({total: latest_grade_count})
-
- result_dict[result.student].update({
- frappe.scrub(result.assessment_criteria): result.grade,
- frappe.scrub(result.assessment_criteria)+"_score": result.score,
- "total_score": total_score,
- "total": total
- })
-
- return result_dict, kounter
-
- # make data from the result dict
- def get_data():
- student_list = frappe.db.sql('''select sgs.student, sgs.student_name
- from `tabStudent Group` sg, `tabStudent Group Student` sgs
- where sg.name = sgs.parent and sg.name in (%s)
- order by sgs.group_roll_number asc''' %', '.join(['%s']*len(student_group_list)),
- tuple(student_group_list), as_dict=1)
-
- for student in student_list:
- student.update(result_dict[student.student])
- return student_list
-
-
- # get chart data
- def get_chart():
- grading_scale = frappe.db.get_value("Assessment Plan", list(assessment_plan_list)[0], "grading_scale")
- grades = frappe.db.sql_list('''select grade_code from `tabGrading Scale Interval` where parent=%s''',
- (grading_scale))
- criteria_list = [d[0] for d in assessment_criteria_list] + ["Total"]
- return get_chart_data(grades, criteria_list, kounter)
-
-
- assessment_plan_list, assessment_criteria_list, total_maximum_score, grading_scale,\
- student_group_list = get_assessment_details()
- result_dict, kounter = get_result_map()
- data = get_data()
-
- columns = get_column(assessment_criteria_list, total_maximum_score)
- chart = get_chart()
- data_to_be_printed = [{
- "assessment_plan": ", ".join(assessment_plan_list)
- }]
-
- return columns, data, None, chart, data_to_be_printed
-
-def get_column(assessment_criteria, total_maximum_score):
+def get_column(assessment_criteria):
columns = [{
"fieldname": "student",
"label": _("Student ID"),
@@ -143,40 +203,28 @@ def get_column(assessment_criteria, total_maximum_score):
}]
for d in assessment_criteria:
columns.append({
- "fieldname": frappe.scrub(d[0]),
- "label": d[0],
+ "fieldname": frappe.scrub(d),
+ "label": d,
"fieldtype": "Data",
"width": 110
})
columns.append({
- "fieldname": frappe.scrub(d[0]) +"_score",
- "label": "Score(" + str(int(d[1])) + ")",
+ "fieldname": frappe.scrub(d) +"_score",
+ "label": "Score(" + str(int(assessment_criteria[d])) + ")",
"fieldtype": "Float",
"width": 100
})
- columns += [{
- "fieldname": "total",
- "label": "Total",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "fieldname": "total_score",
- "label": "Total Score("+ str(int(total_maximum_score)) + ")",
- "fieldtype": "Float",
- "width": 110
- }]
-
return columns
-def get_chart_data(grades, assessment_criteria_list, kounter):
+
+def get_chart_data(grades, criteria_list, kounter):
grades = sorted(grades)
datasets = []
for grade in grades:
tmp = frappe._dict({"values":[], "title": grade})
- for criteria in assessment_criteria_list:
+ for criteria in criteria_list:
if grade in kounter[criteria]:
tmp["values"].append(kounter[criteria][grade])
else:
@@ -185,7 +233,7 @@ def get_chart_data(grades, assessment_criteria_list, kounter):
return {
"data": {
- "labels": assessment_criteria_list,
+ "labels": criteria_list,
"datasets": datasets
},
"type": 'bar',
diff --git a/erpnext/education/report/final_assessment_grades/__init__.py b/erpnext/education/report/final_assessment_grades/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/erpnext/education/report/final_assessment_grades/final_assessment_grades.js b/erpnext/education/report/final_assessment_grades/final_assessment_grades.js
new file mode 100644
index 0000000000..ba0a42ff90
--- /dev/null
+++ b/erpnext/education/report/final_assessment_grades/final_assessment_grades.js
@@ -0,0 +1,38 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Final Assessment Grades"] = {
+ "filters": [
+ {
+ "fieldname":"academic_year",
+ "label": __("Academic Year"),
+ "fieldtype": "Link",
+ "options": "Academic Year",
+ "reqd": 1
+ },
+ {
+ "fieldname":"student_group",
+ "label": __("Student Group"),
+ "fieldtype": "Link",
+ "options": "Student Group",
+ "reqd": 1,
+ "get_query": function() {
+ return{
+ filters: {
+ "group_based_on": "Batch",
+ "academic_year": frappe.query_report_filters_by_name.academic_year.value
+ }
+ };
+ }
+ },
+ {
+ "fieldname":"assessment_group",
+ "label": __("Assessment Group"),
+ "fieldtype": "Link",
+ "options": "Assessment Group",
+ "reqd": 1
+ }
+
+ ]
+}
diff --git a/erpnext/education/report/final_assessment_grades/final_assessment_grades.json b/erpnext/education/report/final_assessment_grades/final_assessment_grades.json
new file mode 100644
index 0000000000..1efbb6e66d
--- /dev/null
+++ b/erpnext/education/report/final_assessment_grades/final_assessment_grades.json
@@ -0,0 +1,20 @@
+{
+ "add_total_row": 0,
+ "apply_user_permissions": 1,
+ "creation": "2018-01-22 17:04:43.412054",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Shishuvan Secondary School",
+ "modified": "2018-01-22 17:04:43.412054",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Final Assessment Grades",
+ "owner": "Administrator",
+ "ref_doctype": "Assessment Result",
+ "report_name": "Final Assessment Grades",
+ "report_type": "Script Report",
+ "roles": []
+}
\ No newline at end of file
diff --git a/erpnext/education/report/final_assessment_grades/final_assessment_grades.py b/erpnext/education/report/final_assessment_grades/final_assessment_grades.py
new file mode 100644
index 0000000000..efc9aff083
--- /dev/null
+++ b/erpnext/education/report/final_assessment_grades/final_assessment_grades.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from collections import defaultdict
+
+from erpnext.education.report.course_wise_assessment_report.course_wise_assessment_report import get_formatted_result
+from erpnext.education.report.course_wise_assessment_report.course_wise_assessment_report import get_chart_data
+
+
+def execute(filters=None):
+ columns, data, grades = [], [], []
+ args = frappe._dict()
+ course_wise_analysis = defaultdict(dict)
+
+ args["academic_year"] = filters.get("academic_year")
+ assessment_group = args["assessment_group"] = filters.get("assessment_group")
+
+ student_group = filters.get("student_group")
+ args.students = frappe.db.sql_list("select student from `tabStudent Group Student` where parent=%s", (student_group))
+
+ values = get_formatted_result(args, get_course=True)
+ student_details = values.get("student_details")
+ assessment_result = values.get("assessment_result")
+ course_dict = values.get("course_dict")
+
+ for student in args.students:
+ student_row = {}
+ student_row["student"] = student
+ student_row["student_name"] = student_details[student]
+ for course in course_dict:
+ scrub_course = frappe.scrub(course)
+ if assessment_group in assessment_result[student][course]:
+ student_row["grade_" + scrub_course] = assessment_result[student][course][assessment_group]["Total Score"]["grade"]
+ student_row["score_" + scrub_course] = assessment_result[student][course][assessment_group]["Total Score"]["score"]
+
+ # create the list of possible grades
+ if student_row["grade_" + scrub_course] not in grades:
+ grades.append(student_row["grade_" + scrub_course])
+
+ # create the dict of for gradewise analysis
+ if student_row["grade_" + scrub_course] not in course_wise_analysis[course]:
+ course_wise_analysis[course][student_row["grade_" + scrub_course]] = 1
+ else:
+ course_wise_analysis[course][student_row["grade_" + scrub_course]] += 1
+
+ data.append(student_row)
+
+ course_list = [d for d in course_dict]
+ columns = get_column(course_dict)
+ chart = get_chart_data(grades, course_list, course_wise_analysis)
+ return columns, data, None, chart
+
+
+def get_column(course_dict):
+ columns = [{
+ "fieldname": "student",
+ "label": _("Student ID"),
+ "fieldtype": "Link",
+ "options": "Student",
+ "width": 90
+ },
+ {
+ "fieldname": "student_name",
+ "label": _("Student Name"),
+ "fieldtype": "Data",
+ "width": 160
+ }]
+ for course in course_dict:
+ columns.append({
+ "fieldname": "grade_" + frappe.scrub(course),
+ "label": course,
+ "fieldtype": "Data",
+ "width": 110
+ })
+ columns.append({
+ "fieldname": "score_" + frappe.scrub(course),
+ "label": "Score(" + str(course_dict[course]) + ")",
+ "fieldtype": "Float",
+ "width": 100
+ })
+
+ return columns
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 846ca269b1..d300285c67 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -183,7 +183,6 @@ erpnext.patches.v5_0.index_on_account_and_gl_entry
execute:frappe.db.sql("""delete from `tabProject Task`""")
erpnext.patches.v5_0.update_item_desc_in_invoice
erpnext.patches.v5_1.fix_against_account
-erpnext.patches.v5_1.fix_credit_days_based_on
execute:frappe.rename_doc("DocType", "Salary Manager", "Process Payroll", force=True)
erpnext.patches.v5_1.rename_roles
erpnext.patches.v5_1.default_bom
@@ -489,4 +488,6 @@ erpnext.patches.v10_0.update_reserved_qty_for_purchase_order
erpnext.patches.v10_0.fichier_des_ecritures_comptables_for_france
erpnext.patches.v10_0.update_assessment_plan
erpnext.patches.v10_0.update_assessment_result
-erpnext.patches.v10_0.workflow_leave_application #2018-01-24
\ No newline at end of file
+erpnext.patches.v10_0.added_extra_gst_custom_field
+erpnext.patches.v10_0.workflow_leave_application #2018-01-24
+erpnext.patches.v10_0.set_default_payment_terms_based_on_company
diff --git a/erpnext/patches/v10_0/added_extra_gst_custom_field.py b/erpnext/patches/v10_0/added_extra_gst_custom_field.py
new file mode 100644
index 0000000000..a1512ed9b7
--- /dev/null
+++ b/erpnext/patches/v10_0/added_extra_gst_custom_field.py
@@ -0,0 +1,9 @@
+import frappe
+from erpnext.regional.india.setup import make_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ make_custom_fields()
\ No newline at end of file
diff --git a/erpnext/patches/v10_0/set_default_payment_terms_based_on_company.py b/erpnext/patches/v10_0/set_default_payment_terms_based_on_company.py
new file mode 100644
index 0000000000..a90e096390
--- /dev/null
+++ b/erpnext/patches/v10_0/set_default_payment_terms_based_on_company.py
@@ -0,0 +1,37 @@
+from __future__ import unicode_literals
+import frappe
+from erpnext.patches.v8_10.change_default_customer_credit_days import make_payment_term, make_template
+
+def execute():
+ for dt in ("Company", "Customer Group"):
+ frappe.reload_doc("setup", "doctype", frappe.scrub(dt))
+
+ credit_records = frappe.db.sql("""
+ SELECT DISTINCT `credit_days`, `credit_days_based_on`, `name`
+ from `tab{0}`
+ where
+ ((credit_days_based_on='Fixed Days' or credit_days_based_on is null) and credit_days is not null)
+ or credit_days_based_on='Last Day of the Next Month'
+ """.format(dt), as_dict=1)
+
+ for d in credit_records:
+ template = create_payment_terms_template(d)
+
+ frappe.db.sql("""
+ update `tab{0}`
+ set `payment_terms` = %s
+ where name = %s
+ """.format(dt), (template.name, d.name))
+
+def create_payment_terms_template(data):
+ if data.credit_days_based_on == "Fixed Days":
+ pyt_template_name = 'Default Payment Term - N{0}'.format(data.credit_days)
+ else:
+ pyt_template_name = 'Default Payment Term - EO2M'
+
+ if not frappe.db.exists("Payment Terms Template", pyt_template_name):
+ payment_term = make_payment_term(data.credit_days, data.credit_days_based_on)
+ template = make_template(payment_term)
+ else:
+ template = frappe.get_doc("Payment Terms Template", pyt_template_name)
+ return template
\ No newline at end of file
diff --git a/erpnext/patches/v10_0/workflow_leave_application.py b/erpnext/patches/v10_0/workflow_leave_application.py
index f2b74a35a7..8a68f891c4 100644
--- a/erpnext/patches/v10_0/workflow_leave_application.py
+++ b/erpnext/patches/v10_0/workflow_leave_application.py
@@ -9,4 +9,5 @@ def execute():
frappe.reload_doc("hr", "doctype", "leave_application")
frappe.reload_doc("workflow", "doctype", "workflow")
leave_application_workflow()
- frappe.db.sql("""update `tabLeave Application` set workflow_state = status""")
+ if frappe.db.has_column("Leave Application", "status"):
+ frappe.db.sql("""update `tabLeave Application` set workflow_state = status""")
diff --git a/erpnext/patches/v5_1/fix_credit_days_based_on.py b/erpnext/patches/v5_1/fix_credit_days_based_on.py
deleted file mode 100644
index 6df19f201a..0000000000
--- a/erpnext/patches/v5_1/fix_credit_days_based_on.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from __future__ import unicode_literals
-
-import frappe
-
-def execute():
- for dt in ("Customer", "Customer Group", "Company"):
- frappe.reload_doctype(dt, force=True)
- frappe.db.sql("""update `tab{0}` set credit_days_based_on='Fixed Days'
- where ifnull(credit_days, 0) > 0""".format(dt))
diff --git a/erpnext/patches/v8_10/change_default_customer_credit_days.py b/erpnext/patches/v8_10/change_default_customer_credit_days.py
index 640c8aa752..eddafb5634 100644
--- a/erpnext/patches/v8_10/change_default_customer_credit_days.py
+++ b/erpnext/patches/v8_10/change_default_customer_credit_days.py
@@ -17,7 +17,8 @@ def execute():
SELECT DISTINCT `credit_days`, `credit_days_based_on`, `name`
from `tab{0}`
where
- (credit_days_based_on='Fixed Days' and credit_days is not null)
+ ((credit_days_based_on='Fixed Days' or credit_days_based_on is null)
+ and credit_days is not null)
or credit_days_based_on='Last Day of the Next Month'
""".format(doctype))
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index f155e097e5..e44c3fb766 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -523,7 +523,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
callback: function(r, rt) {
if(r.message) {
- me.frm.set_value("due_date", r.message);
+ me.frm.doc.due_date = r.message;
+ refresh_field("due_date");
frappe.ui.form.trigger(me.frm.doc.doctype, "currency");
me.recalculate_terms();
}
@@ -538,7 +539,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
due_date: function() {
// due_date is to be changed, payment terms template and/or payment schedule must
// be removed as due_date is automatically changed based on payment terms
- if (this.frm.doc.due_date) {
+ if (this.frm.doc.due_date && !this.frm.updating_party_details && !this.frm.doc.is_pos) {
if (this.frm.doc.payment_terms_template ||
(this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) {
var message1 = "";
@@ -555,11 +556,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
if (message1.length !== 0) message2 = " and " + message2;
final_message = final_message + message2;
}
-
frappe.msgprint(final_message);
}
-
- }
+ }
},
recalculate_terms: function() {
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 7143bd3457..e9d91abd48 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -103,7 +103,20 @@ def make_custom_fields():
depends_on='eval:in_list(["SEZ", "Export", "Deemed Export"], doc.invoice_type)',
options='\nWith Payment of Tax\nWithout Payment of Tax'),
dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN',
- fieldtype='Data', insert_after='export_type', print_hide=1)
+ fieldtype='Data', insert_after='export_type', print_hide=1),
+ dict(fieldname='reason_for_issuing_document', label='Reason For Issuing document',
+ fieldtype='Select', insert_after='ecommerce_gstin', print_hide=1,
+ depends_on='eval:doc.is_return==1',
+ options='\n01-Sales Return\n02-Post Sale Discount\n03-Deficiency in services\n04-Correction in Invoice\n05-Change in POS\n06-Finalization of Provisional assessment\n07-Others'),
+ dict(fieldname='port_code', label='Port Code',
+ fieldtype='Data', insert_after='reason_for_issuing_document', print_hide=1,
+ depends_on="eval:doc.invoice_type=='Export' "),
+ dict(fieldname='shipping_bill_number', label=' Shipping Bill Number',
+ fieldtype='Data', insert_after='port_code', print_hide=1,
+ depends_on="eval:doc.invoice_type=='Export' "),
+ dict(fieldname='shipping_bill_date', label='Shipping Bill Date',
+ fieldtype='Date', insert_after='shipping_bill_number', print_hide=1,
+ depends_on="eval:doc.invoice_type=='Export' "),
]
purchase_invoice_gst_fields = [
diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js
index 943778661b..3a635277e4 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.js
+++ b/erpnext/regional/report/gstr_1/gstr_1.js
@@ -31,7 +31,7 @@ frappe.query_reports["GSTR-1"] = {
"label": __("Type of Business"),
"fieldtype": "Select",
"reqd": 1,
- "options": ["B2B", "B2C Large", "B2C Small"],
+ "options": ["B2B", "B2C Large", "B2C Small","CDNR", "EXPORT"],
"default": "B2B"
}
]
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 65b1b89523..b6df878690 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe, json
from frappe import _
+from datetime import date
def execute(filters=None):
return Gstr1Report(filters).run()
@@ -12,7 +13,7 @@ class Gstr1Report(object):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
self.customer_type = "Company" if self.filters.get("type_of_business") == "B2B" else "Individual"
-
+
def run(self):
self.get_columns()
self.get_data()
@@ -35,13 +36,15 @@ class Gstr1Report(object):
for rate, items in items_based_on_rate.items():
row = []
for fieldname in invoice_fields:
- if fieldname == "invoice_value":
+ if self.filters.get("type_of_business") == "CDNR" and fieldname == "invoice_value":
+ row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total))
+ elif fieldname == "invoice_value":
row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
else:
row.append(invoice_details.get(fieldname))
row += [rate,
- sum([net_amount for item_code, net_amount in self.invoice_items.get(inv).items()
+ sum([abs(net_amount) for item_code, net_amount in self.invoice_items.get(inv).items()
if item_code in items]),
self.invoice_cess.get(inv)
]
@@ -49,6 +52,10 @@ class Gstr1Report(object):
if self.filters.get("type_of_business") == "B2C Small":
row.append("E" if invoice_details.ecommerce_gstin else "OE")
+ if self.filters.get("type_of_business") == "CDNR":
+ row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
+ row.append("C" if invoice_details.return_against else "R")
+
self.data.append(row)
def get_invoice_data(self):
@@ -66,7 +73,15 @@ class Gstr1Report(object):
place_of_supply,
ecommerce_gstin,
reverse_charge,
- invoice_type
+ invoice_type,
+ return_against,
+ is_return,
+ invoice_type,
+ export_type,
+ port_code,
+ shipping_bill_number,
+ shipping_bill_date,
+ reason_for_issuing_document
from `tabSales Invoice`
where docstatus = 1 %s
order by posting_date desc
@@ -85,18 +100,27 @@ class Gstr1Report(object):
conditions += opts[1]
customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type})
- conditions += " and customer in ('{0}')".format("', '".join([frappe.db.escape(c.name)
- for c in customers]))
+
+ if self.filters.get("type_of_business") == "B2B":
+ conditions += " and invoice_type != 'Export' and is_return != 1 and customer in ('{0}')".\
+ format("', '".join([frappe.db.escape(c.name) for c in customers]))
if self.filters.get("type_of_business") == "B2C Large":
conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
- and grand_total > 250000"""
+ and grand_total > 250000 and is_return != 1 and customer in ('{0}')""".\
+ format("', '".join([frappe.db.escape(c.name) for c in customers]))
+
elif self.filters.get("type_of_business") == "B2C Small":
conditions += """ and (
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
- or grand_total <= 250000
- )"""
+ or grand_total <= 250000 ) and is_return != 1 and customer in ('{0}')""".\
+ format("', '".join([frappe.db.escape(c.name) for c in customers]))
+ elif self.filters.get("type_of_business") == "CDNR":
+ conditions += """ and is_return = 1 """
+
+ elif self.filters.get("type_of_business") == "EXPORT":
+ conditions += """ and is_return !=1 and invoice_type = 'Export' """
return conditions
def get_invoice_items(self):
@@ -118,7 +142,7 @@ class Gstr1Report(object):
where
parenttype = 'Sales Invoice' and docstatus = 1
and parent in (%s)
- and tax_amount_after_discount_amount > 0
+
order by account_head
""" % (', '.join(['%s']*len(self.invoices.keys()))), tuple(self.invoices.keys()))
@@ -152,7 +176,6 @@ class Gstr1Report(object):
.setdefault(tax_rate, [])
if item_code not in rate_based_dict:
rate_based_dict.append(item_code)
-
except ValueError:
continue
if unidentified_gst_accounts:
@@ -185,12 +208,6 @@ class Gstr1Report(object):
"label": "Taxable Value",
"fieldtype": "Currency",
"width": 100
- },
- {
- "fieldname": "cess_amount",
- "label": "Cess Amount",
- "fieldtype": "Currency",
- "width": 100
}
]
self.other_columns = []
@@ -200,33 +217,39 @@ class Gstr1Report(object):
{
"fieldname": "customer_gstin",
"label": "GSTIN/UIN of Recipient",
- "fieldtype": "Data"
+ "fieldtype": "Data",
+ "width": 150
},
{
"fieldname": "customer_name",
"label": "Receiver Name",
- "fieldtype": "Data"
+ "fieldtype": "Data",
+ "width":100
},
{
"fieldname": "invoice_number",
"label": "Invoice Number",
"fieldtype": "Link",
- "options": "Sales Invoice"
+ "options": "Sales Invoice",
+ "width":100
},
{
"fieldname": "posting_date",
"label": "Invoice date",
- "fieldtype": "Date"
+ "fieldtype": "Date",
+ "width":80
},
{
"fieldname": "invoice_value",
"label": "Invoice Value",
- "fieldtype": "Currency"
+ "fieldtype": "Currency",
+ "width":100
},
{
"fieldname": "place_of_supply",
"label": "Place of Supply",
- "fieldtype": "Data"
+ "fieldtype": "Data",
+ "width":100
},
{
"fieldname": "reverse_charge",
@@ -241,9 +264,19 @@ class Gstr1Report(object):
{
"fieldname": "ecommerce_gstin",
"label": "E-Commerce GSTIN",
- "fieldtype": "Data"
+ "fieldtype": "Data",
+ "width":120
}
]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ }
+ ]
+
elif self.filters.get("type_of_business") == "B2C Large":
self.invoice_columns = [
{
@@ -278,6 +311,93 @@ class Gstr1Report(object):
"width": 130
}
]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ }
+ ]
+ elif self.filters.get("type_of_business") == "CDNR":
+ self.invoice_columns = [
+ {
+ "fieldname": "customer_gstin",
+ "label": "GSTIN/UIN of Recipient",
+ "fieldtype": "Data",
+ "width": 150
+ },
+ {
+ "fieldname": "customer_name",
+ "label": "Receiver Name",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "return_against",
+ "label": "Invoice/Advance Receipt Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice/Advance Receipt date",
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_number",
+ "label": "Invoice/Advance Receipt Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width":120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice/Advance Receipt date",
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "reason_for_issuing_document",
+ "label": "Reason For Issuing document",
+ "fieldtype": "Data",
+ "width": 140
+ },
+ {
+ "fieldname": "place_of_supply",
+ "label": "Place of Supply",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_value",
+ "label": "Invoice Value",
+ "fieldtype": "Currency",
+ "width": 120
+ }
+ ]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ },
+ {
+ "fieldname": "pre_gst",
+ "label": "PRE GST",
+ "fieldtype": "Data",
+ "width": 80
+ },
+ {
+ "fieldname": "document_type",
+ "label": "Document Type",
+ "fieldtype": "Data",
+ "width": 80
+ }
+ ]
elif self.filters.get("type_of_business") == "B2C Small":
self.invoice_columns = [
{
@@ -294,6 +414,12 @@ class Gstr1Report(object):
}
]
self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ },
{
"fieldname": "type",
"label": "Type",
@@ -301,4 +427,50 @@ class Gstr1Report(object):
"width": 50
}
]
- self.columns = self.invoice_columns + self.tax_columns + self.other_columns
\ No newline at end of file
+ elif self.filters.get("type_of_business") == "EXPORT":
+ self.invoice_columns = [
+ {
+ "fieldname": "export_type",
+ "label": "Export Type",
+ "fieldtype": "Data",
+ "width":120
+ },
+ {
+ "fieldname": "invoice_number",
+ "label": "Invoice Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width":120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice date",
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_value",
+ "label": "Invoice Value",
+ "fieldtype": "Currency",
+ "width": 120
+ },
+ {
+ "fieldname": "port_code",
+ "label": "Port Code",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "shipping_bill_number",
+ "label": "Shipping Bill Number",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "shipping_bill_date",
+ "label": "Shipping Bill Date",
+ "fieldtype": "Date",
+ "width": 120
+ }
+ ]
+ self.columns = self.invoice_columns + self.tax_columns + self.other_columns
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 1c3354c212..5bacf28bb8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -567,10 +567,12 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
target.base_amount = target.amount * flt(source_parent.conversion_rate)
target.qty = target.amount / flt(source.rate) if (source.rate and source.billed_amt) else source.qty
- item = frappe.db.get_value("Item", target.item_code, ["item_group", "selling_cost_center"], as_dict=1)
- target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \
- or item.selling_cost_center \
- or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
+ if source_parent.project:
+ target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center")
+ if not target.cost_center and target.item_code:
+ item = frappe.db.get_value("Item", target.item_code, ["item_group", "selling_cost_center"], as_dict=1)
+ target.cost_center = item.selling_cost_center \
+ or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 3d174b78f7..5ca26181e3 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -340,7 +340,8 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
conversion_factor: function(doc, cdt, cdn, dont_fetch_price_list_rate) {
this._super(doc, cdt, cdn, dont_fetch_price_list_rate);
- if(frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
+ if(frappe.meta.get_docfield(cdt, "stock_qty", cdn) &&
+ in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
this.set_batch_number(cdt, cdn);
}
},
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 9824c706cf..d444417b16 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -1180,6 +1180,35 @@
"set_only_once": 0,
"unique": 0
},
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_26",
+ "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
+ },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -1219,8 +1248,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "column_break_26",
- "fieldtype": "Column Break",
+ "depends_on": "",
+ "fieldname": "payment_terms",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -1228,8 +1258,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
+ "label": "Default Payment Terms Template",
"length": 0,
"no_copy": 0,
+ "options": "Payment Terms Template",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -1242,69 +1274,6 @@
"set_only_once": 0,
"unique": 0
},
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "credit_days_based_on",
- "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": "Credit Days Based On",
- "length": 0,
- "no_copy": 0,
- "options": "\nFixed Days\nLast Day of the Next Month",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:(!doc.__islocal && doc.credit_days_based_on=='Fixed Days')",
- "fieldname": "credit_days",
- "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": "Credit Days",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "credit_days",
- "oldfieldtype": "Int",
- "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,
- "unique": 0
- },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -2052,7 +2021,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2017-12-07 18:40:24.646920",
+ "modified": "2018-01-29 12:40:24.646920",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json
index 316f11badf..062a49a9ad 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.json
+++ b/erpnext/setup/doctype/customer_group/customer_group.json
@@ -162,12 +162,14 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "credit_days_based_on",
- "fieldtype": "Select",
+ "depends_on": "",
+ "fieldname": "payment_terms",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -175,10 +177,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Credit Days Based On",
+ "label": "Default Payment Terms Template",
"length": 0,
"no_copy": 0,
- "options": "\nFixed Days\nLast Day of the Next Month",
+ "options": "Payment Terms Template",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -191,35 +193,6 @@
"set_only_once": 0,
"unique": 0
},
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.credit_days_based_on=='Fixed Days'",
- "fieldname": "credit_days",
- "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": "Credit Days",
- "length": 0,
- "no_copy": 0,
- "permlevel": 1,
- "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
- },
{
"allow_on_submit": 0,
"bold": 0,
@@ -411,7 +384,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-02-20 13:25:31.549874",
+ "modified": "2018-01-29 13:25:31.549874",
"modified_by": "Administrator",
"module": "Setup",
"name": "Customer Group",
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index 5fcc3a2c68..8d9bba3ff8 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -128,7 +128,7 @@ def insert_record(records):
doc.update(r)
try:
doc.insert(ignore_permissions=True)
- except frappe.DuplicateEntryError, e:
+ except frappe.DuplicateEntryError as e:
# pass DuplicateEntryError and continue
if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name:
# make sure DuplicateEntryError is for the exact same doc and not a related doc
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 23820d32b2..2748436fd8 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -75,7 +75,7 @@ erpnext.stock.ItemDashboard = Class.extend({
this.content.find('.more').addClass('hidden');
}
- // If not any stock in any warehouses provide a message to end user
+ // If not any stock in any warehouses provide a message to end user
if (context.data.length > 0) {
$(frappe.render_template('item_dashboard_list', context)).appendTo(this.result);
} else {
@@ -86,6 +86,7 @@ erpnext.stock.ItemDashboard = Class.extend({
get_item_dashboard_data: function(data, max_count, show_item) {
if(!max_count) max_count = 0;
if(!data) data = [];
+
data.forEach(function(d) {
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production;
d.pending_qty = 0;
@@ -97,9 +98,16 @@ erpnext.stock.ItemDashboard = Class.extend({
max_count = Math.max(d.actual_or_pending, d.actual_qty,
d.total_reserved, max_count);
});
+
+ var can_write = 0;
+ if(frappe.boot.user.can_write.indexOf("Stock Entry")>=0){
+ can_write = 1;
+ }
+
return {
data: data,
max_count: max_count,
+ can_write:can_write,
show_item: show_item || false
}
}
@@ -187,4 +195,4 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
frappe.set_route('Form', doc.doctype, doc.name);
})
});
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard/item_dashboard_list.html b/erpnext/stock/dashboard/item_dashboard_list.html
index 63f248043c..e0b3431839 100644
--- a/erpnext/stock/dashboard/item_dashboard_list.html
+++ b/erpnext/stock/dashboard/item_dashboard_list.html
@@ -39,6 +39,7 @@
+ {% if can_write %}
{% if d.actual_qty %}
+ {% endif %}
{% endfor %}
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 42e2ceff68..bcbc35ee7e 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -5,24 +5,31 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.model.naming import make_autoname
+from frappe.model.naming import make_autoname, revert_series_if_last
from frappe.utils import flt, cint
-class UnableToSelectBatchError(frappe.ValidationError): pass
+class UnableToSelectBatchError(frappe.ValidationError):
+ pass
def get_name_from_naming_series():
- naming_series_prefix = frappe.db.get_single_value('Stock Settings', 'naming_series_prefix')
- if not naming_series_prefix:
- naming_series_prefix = 'BATCH-'
-
- name = make_autoname(naming_series_prefix + '.#####')
+ """
+ Get a name generated for a Batch from the Batch's naming series.
+ :return: The string that was generated.
+ """
+ naming_series_prefix = _get_batch_prefix()
+ key = _make_naming_series_key(naming_series_prefix)
+ name = make_autoname(key)
return name
def get_name_from_hash():
+ """
+ Get a name for a Batch by generating a unique hash.
+ :return: The hash that was generated.
+ """
temp = None
while not temp:
temp = frappe.generate_hash()[:7].upper()
@@ -32,13 +39,66 @@ def get_name_from_hash():
return temp
+def batch_uses_naming_series():
+ """
+ Verify if the Batch is to be named using a naming series
+ :return: bool
+ """
+ use_naming_series = cint(frappe.db.get_single_value('Stock Settings', 'use_naming_series'))
+ return bool(use_naming_series)
+
+
+def _get_batch_prefix():
+ """
+ Get the naming series prefix set in Stock Settings.
+
+ It does not do any sanity checks so make sure to use it after checking if the Batch
+ is set to use naming series.
+ :return: The naming series.
+ """
+ naming_series_prefix = frappe.db.get_single_value('Stock Settings', 'naming_series_prefix')
+ if not naming_series_prefix:
+ naming_series_prefix = 'BATCH-'
+
+ return naming_series_prefix
+
+
+def _make_naming_series_key(prefix):
+ """
+ Make naming series key for a Batch.
+
+ Naming series key is in the format [prefix].[#####]
+ :param prefix: Naming series prefix gotten from Stock Settings
+ :return: The derived key. If no prefix is given, an empty string is returned
+ """
+ if not unicode(prefix):
+ return ''
+ else:
+ return prefix.upper() + '.#####'
+
+
+def get_batch_naming_series():
+ """
+ Get naming series key for a Batch.
+
+ Naming series key is in the format [prefix].[#####]
+ :return: The naming series or empty string if not available
+ """
+ series = ''
+ if batch_uses_naming_series():
+ prefix = _get_batch_prefix()
+ key = _make_naming_series_key(prefix)
+ series = key
+
+ return series
+
+
class Batch(Document):
def autoname(self):
"""Generate random ID for batch if not specified"""
if not self.batch_id:
if frappe.db.get_value('Item', self.item, 'create_new_batch'):
- use_naming_series = frappe.db.get_single_value('Stock Settings', 'use_naming_series')
- if use_naming_series:
+ if batch_uses_naming_series():
self.batch_id = get_name_from_naming_series()
else:
self.batch_id = get_name_from_hash()
@@ -50,13 +110,17 @@ class Batch(Document):
def onload(self):
self.image = frappe.db.get_value('Item', self.item, 'image')
+ def after_delete(self):
+ revert_series_if_last(get_batch_naming_series(), self.name)
+
def validate(self):
self.item_has_batch_enabled()
def item_has_batch_enabled(self):
- if frappe.db.get_value("Item",self.item,"has_batch_no") == 0:
+ if frappe.db.get_value("Item", self.item, "has_batch_no") == 0:
frappe.throw(_("The selected item cannot have Batch"))
+
@frappe.whitelist()
def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
"""Returns batch actual qty if warehouse is passed,
@@ -89,16 +153,18 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
return out
+
@frappe.whitelist()
def get_batches_by_oldest(item_code, warehouse):
"""Returns the oldest batch and qty for the given item_code and warehouse"""
- batches = get_batch_qty(item_code = item_code, warehouse = warehouse)
+ batches = get_batch_qty(item_code=item_code, warehouse=warehouse)
batches_dates = [[batch, frappe.get_value('Batch', batch.batch_no, 'expiry_date')] for batch in batches]
batches_dates.sort(key=lambda tup: tup[1])
return batches_dates
+
@frappe.whitelist()
-def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
+def split_batch(batch_no, item_code, warehouse, qty, new_batch_id=None):
"""Split the batch into a new batch"""
batch = frappe.get_doc(dict(doctype='Batch', item=item_code, batch_id=new_batch_id)).insert()
stock_entry = frappe.get_doc(dict(
@@ -106,16 +172,16 @@ def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
purpose='Repack',
items=[
dict(
- item_code = item_code,
- qty = float(qty or 0),
- s_warehouse = warehouse,
- batch_no = batch_no
+ item_code=item_code,
+ qty=float(qty or 0),
+ s_warehouse=warehouse,
+ batch_no=batch_no
),
dict(
- item_code = item_code,
- qty = float(qty or 0),
- t_warehouse = warehouse,
- batch_no = batch.name
+ item_code=item_code,
+ qty=float(qty or 0),
+ t_warehouse=warehouse,
+ batch_no=batch.name
),
]
))
@@ -124,7 +190,8 @@ def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
return batch.name
-def set_batch_nos(doc, warehouse_field, throw = False):
+
+def set_batch_nos(doc, warehouse_field, throw=False):
"""Automatically select `batch_no` for outgoing items in item table"""
for d in doc.items:
qty = d.get('stock_qty') or d.get('transfer_qty') or d.get('qty') or 0
@@ -135,8 +202,9 @@ def set_batch_nos(doc, warehouse_field, throw = False):
d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw)
else:
batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse)
- if flt(batch_qty) < flt(qty):
- frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, d.qty))
+ if flt(batch_qty, d.precision("qty")) < flt(qty, d.precision("qty")):
+ frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, d.stock_qty))
+
@frappe.whitelist()
def get_batch_no(item_code, warehouse, qty=1, throw=False):
diff --git a/erpnext/stock/doctype/batch/batch_list.js b/erpnext/stock/doctype/batch/batch_list.js
index 2138fa1da7..7ee81aa7e9 100644
--- a/erpnext/stock/doctype/batch/batch_list.js
+++ b/erpnext/stock/doctype/batch/batch_list.js
@@ -1,7 +1,7 @@
frappe.listview_settings['Batch'] = {
add_fields: ["item", "expiry_date"],
get_indicator: function(doc) {
- if(doc.expiry_date && frappe.datetime.get_diff(doc.expiry_date) <= 0) {
+ if(doc.expiry_date && frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) <= 0) {
return [__("Expired"), "red", "expiry_date,>=,Today"]
} else if(doc.expiry_date) {
return [__("Not Expired"), "green", "expiry_date,<,Today"]
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index a327b2ded9..9538781d4d 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -7,6 +7,8 @@ from frappe.exceptions import ValidationError
import unittest
from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no
+from frappe.utils import cint
+
class TestBatch(unittest.TestCase):
@@ -21,7 +23,7 @@ class TestBatch(unittest.TestCase):
def make_batch_item(cls, item_name):
from erpnext.stock.doctype.item.test_item import make_item
if not frappe.db.exists(item_name):
- make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1))
+ return make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1))
def test_purchase_receipt(self, batch_qty = 100):
'''Test automated batch creation from Purchase Receipt'''
@@ -192,3 +194,37 @@ class TestBatch(unittest.TestCase):
]
)).insert()
stock_entry.submit()
+
+ def test_batch_name_with_naming_series(self):
+ stock_settings = frappe.get_single('Stock Settings')
+ use_naming_series = cint(stock_settings.use_naming_series)
+
+ if not use_naming_series:
+ frappe.set_value('Stock Settings', 'Stock Settings', 'use_naming_series', 1)
+
+ batch = self.make_new_batch('_Test Stock Item For Batch Test1')
+ batch_name = batch.name
+
+ self.assertTrue(batch_name.startswith('BATCH-'))
+
+ batch.delete()
+ batch = self.make_new_batch('_Test Stock Item For Batch Test2')
+
+ self.assertEqual(batch_name, batch.name)
+
+ # reset Stock Settings
+ if not use_naming_series:
+ frappe.set_value('Stock Settings', 'Stock Settings', 'use_naming_series', 0)
+
+ def make_new_batch(self, item_name, batch_id=None, do_not_insert=0):
+ batch = frappe.new_doc('Batch')
+ item = self.make_batch_item(item_name)
+ batch.item = item.name
+
+ if batch_id:
+ batch.batch_id = batch_id
+
+ if not do_not_insert:
+ batch.insert()
+
+ return batch
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index dd8d99889c..80afd49f5b 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -293,7 +293,7 @@ class StockEntry(StockController):
# get basic rate
if not d.bom_no:
- if not flt(d.basic_rate) or d.s_warehouse or force:
+ if (not flt(d.basic_rate) and not d.allow_zero_valuation_rate) or d.s_warehouse or force:
basic_rate = flt(get_incoming_rate(args), self.precision("basic_rate", d))
if basic_rate > 0:
d.basic_rate = basic_rate
@@ -304,7 +304,8 @@ class StockEntry(StockController):
# get scrap items basic rate
if d.bom_no:
- if not flt(d.basic_rate) and getattr(self, "pro_doc", frappe._dict()).scrap_warehouse == d.t_warehouse:
+ if not flt(d.basic_rate) and not d.allow_zero_valuation_rate and \
+ getattr(self, "pro_doc", frappe._dict()).scrap_warehouse == d.t_warehouse:
basic_rate = flt(get_incoming_rate(args), self.precision("basic_rate", d))
if basic_rate > 0:
d.basic_rate = basic_rate
diff --git a/erpnext/utilities/user_progress_utils.py b/erpnext/utilities/user_progress_utils.py
index 18d38c0fe8..58a03e3af3 100644
--- a/erpnext/utilities/user_progress_utils.py
+++ b/erpnext/utilities/user_progress_utils.py
@@ -40,12 +40,12 @@ def create_customers(args_data):
@frappe.whitelist()
def create_letterhead(args_data):
args = json.loads(args_data)
- letterhead = args.get("letterhead").encode('utf-8')
+ letterhead = args.get("letterhead")
if letterhead:
try:
frappe.get_doc({
"doctype":"Letter Head",
- "content":"""""".format(letterhead),
+ "content":"""""".format(letterhead.encode('utf-8')),
"letter_head_name": _("Standard"),
"is_default": 1
}).insert()