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()