diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 566323a9e97..88049be32d4 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -16,3 +16,10 @@ # Whitespace fix throughout codebase 4551d7d6029b6f587f6c99d4f8df5519241c6a86 +b147b85e6ac19a9220cd1e2958a6ebd99373283a + +# sort and cleanup imports +915b34391c2066dfc83e60a5813c5a877cebe7ac + +# removing six compatibility layer +8fe5feb6a4372bf5f2dfaf65fca41bbcc25c8ce7 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 511b682f371..1cf9a5bd9b5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -23,7 +23,7 @@ If your issue is not clear or does not meet the guidelines, then it will be clos 1. **Steps to Reproduce:** The bug report must have a list of steps needed to reproduce a bug. If we cannot reproduce it, then we cannot solve it. 1. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version 1. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit" -1. **Screenshots:** Screenshots are a great way of communicating the issues. Try adding annotations or using LiceCAP to take a screencast in `gif`. +1. **Screenshots:** Screenshots are a great way of communicating issues. Try adding annotations or using LiceCAP to take a screencast in `gif`. ### Feature Request Guidelines diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index c145291b57c..00000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -name: Bug report -about: Report a bug encountered while using ERPNext -labels: bug ---- - - - -## Description of the issue - -## Context information (for bug reports) - -**Output of `bench version`** -``` -(paste here) -``` - -## Steps to reproduce the issue - -1. -2. -3. - -### Observed result - -### Expected result - -### Stacktrace / full error message - -``` -(paste here) -``` - -## Additional information - -OS version / distribution, `ERPNext` install method, etc. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 00000000000..a6e16a03d8d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,106 @@ +name: Bug Report +description: Report a bug encountered while using ERPNext +labels: ["bug"] + +body: + - type: markdown + attributes: + value: | + Welcome to ERPNext issue tracker! Before creating an issue, please heed the following: + + 1. This tracker should only be used to report bugs and request features / enhancements to ERPNext + - For questions and general support, checkout the [user manual](https://docs.erpnext.com/) or use [forum](https://discuss.erpnext.com) + - For documentation issues, propose edit on [documentation site](https://docs.erpnext.com/) directly. + 2. When making a bug report, make sure you provide all required information. The easier it is for + maintainers to reproduce, the faster it'll be fixed. + 3. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉 + + - type: textarea + id: bug-info + attributes: + label: Information about bug + description: Also tell us, what did you expect to happen? + placeholder: Please provide as much information as possible. + validations: + required: true + + - type: dropdown + id: version + attributes: + label: Version + description: Affected versions. + multiple: true + options: + - v12 + - v13 + - v14 + - develop + validations: + required: true + + - type: dropdown + id: module + attributes: + label: Module + description: Select affected module of ERPNext. + multiple: true + options: + - accounts + - stock + - buying + - selling + - ecommerce + - manufacturing + - HR + - projects + - support + - assets + - integrations + - quality + - regional + - portal + - agriculture + - education + - non-profit + validations: + required: true + + - type: textarea + id: exact-version + attributes: + label: Version + description: Share exact version number of Frappe and ERPNext you are using. + placeholder: | + Frappe version - + ERPNext Verion - + validations: + required: true + + - type: dropdown + id: install-method + attributes: + label: Installation method + options: + - docker + - easy-install + - manual install + - FrappeCloud + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output / Stack trace / Full Error Message. + description: Please copy and paste any relevant log output. This will be automatically formatted. + render: shell + + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/frappe/erpnext/blob/develop/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 6cdad356cd0..418bf3c9417 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,10 @@ --- name: Feature request about: Suggest an idea to improve ERPNext +title: '' labels: feature-request +assignees: '' + --- \n\n {%- for row in doc.drug_prescription -%}\n \n \n {%- if row.drug_name -%}{{ row.drug_name }}{%- endif -%}\n \n \t\n {%- if row.dosage -%}{{ row.dosage }}{%- endif -%}\n \n \t\n {%- if row.period -%}{{ row.period }}{%- endif -%}\n\t\t \n \n\t\t\t
\n {%- if row.comment -%}{{ row.comment }}{%- endif -%}\n
\n\t\t \n \n\t {%- endfor -%}\n \n \n\n\n {%- endif -%}\n\n\n\n
\n {% if doc.lab_test_prescription %}\n Investigations,\n \n \n \n\n {%- for row in doc.lab_test_prescription -%}\n \n \n \n \n\n\t {%- endfor -%}\n \n
\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}{%- endif -%}\n \n\t\t\t
\n {%- if row.lab_test_comment -%}{{ row.lab_test_comment }}{%- endif -%}\n
\n\t\t
\n\n\n {%- endif -%}\n
\n
\n {% if doc.encounter_comment %}\n
\n {{doc.encounter_comment}}\n {%- endif -%}\n
\n", - "idx": 0, - "line_breaks": 0, - "modified": "2018-09-04 11:52:54.473702", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Encounter Print", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Jinja", - "show_section_headings": 0, - "standard": "Yes" -} \ No newline at end of file diff --git a/erpnext/healthcare/print_format/lab_test_print/__init__.py b/erpnext/healthcare/print_format/lab_test_print/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json b/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json deleted file mode 100644 index f7d16769c66..00000000000 --- a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "align_labels_right": 0, - "creation": "2017-04-24 15:38:45.332473", - "custom_format": 1, - "disabled": 0, - "doc_type": "Lab Test", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "
\n {% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n
\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n

WORKSHEET

\n\t
\n\t
\n
\n\n
\n
\n \n
\n {% if doc.patient_name %}\n
\n {{ doc.patient_name }}\n
\n {% else %}\n
\n {{ doc.patient }}\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_age or '' }}\n
\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_sex or '' }}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner_name %}\n
\n {{ doc.practitioner_name }}\n
\n {% else %}\n\t\t\t{% if doc.referring_practitioner_name %}\n
\n {{ doc.referring_practitioner_name }}\n
\n\t\t {% endif %}\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n {{ doc.sample_date }}\n
\n
\n {%- endif -%}\n
\n
\n\n\t
\n

Department of {{ doc.department }}

\n
\n\n\t\n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n \n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n\t\n \n {%- if doc.descriptive_test_items -%}\n \n \n \n \n \n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n \n \n \n \n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResult
{{ doc.lab_test_name }}
 {{ row.lab_test_name }}
  {{ row.lab_test_particulars }}
\n
\n {% if doc.worksheet_instructions %}\n
\n Instructions\n {{ doc.worksheet_instructions }}\n {%- endif -%}\n
\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"require_test_result_approval\") == '1' and doc.status != \"Approved\") %}\n Lab Tests have to be Approved for Print .. !\n {%- else -%}\n
\n
\n\n
\n
\n \n
\n {% if doc.patient_name %}\n
\n {{ doc.patient_name }}\n
\n {% else %}\n
\n {{ doc.patient }}\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_age or '' }}\n
\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_sex or '' }}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner_name %}\n
\n {{ doc.practitioner_name }}\n
\n\t\t{% else %}\n\t\t {% if doc.referring_practitioner_name %}\n
\n {{ doc.referring_practitioner_name }}\n
\n\t\t\t{% endif %}\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n {{ doc.sample_date }}\n
\n
\n {%- endif -%}\n\n {% if doc.result_date %}\n
\n
\n \n
\n
\n {{ doc.result_date }}\n
\n
\n {%- endif -%}\n\n
\n\n
\n\n
\n

Department of {{ doc.department }}

\n
\n\n\t
\n\t\t{% if doc.result_legend and (doc.legend_print_position == \"Top\" or doc.legend_print_position == \"Both\")%}\n\t\tResult Legend:\n\t\t{{ doc.result_legend }}\n\t\t{%- endif -%}\n\t
\n\n \n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n\t\t\t\t\t{%- if row.result_value -%}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n {{ row.result_value }}\n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t{%- endif -%}\n \n\t\t\t\t\t{%- if row.secondary_uom and row.conversion_factor and row.secondary_uom_result -%}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n {{ row.secondary_uom_result }}\n  {{ row.secondary_uom }}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t \n\t\t\t\t\t{%- endif -%}\n
\n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n \n \n {%- if doc.descriptive_test_items -%}\n \n \n \n \n \n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n \n \n \n \n {%- endfor -%}\n {%- endif -%}\n\n\t\t\t{%- if doc.organisms -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- for row in doc.organisms -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n\t\t\t{%- if doc.sensitivity_test_items -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- for row in doc.sensitivity_test_items -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n \n
Name of TestResult
{{ doc.lab_test_name }}
 {{ row.lab_test_name }}
  {{ row.lab_test_particulars }} \n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n
OrganismColony Population
{{ row.organism }} \n\t\t\t\t\t{{ row.colony_population }}\n\t\t\t\t\t{% if row.colony_uom %}\n\t\t\t\t\t\t{{ row.colony_uom }}\n\t\t\t\t\t{% endif %}\n\t\t\t\t
AntibioticSensitivity
{{ row.antibiotic }} {{ row.antibiotic_sensitivity }}
\n
\n {% if doc.custom_result %}\n
\n
{{ doc.custom_result }}
\n {%- endif -%}\n
\n\n
\n {% if doc.lab_test_comment %}\n
\n Comments\n {{ doc.lab_test_comment }}\n {%- endif -%}\n
\n\n
\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n {%- if doc.employee_name -%}\n
{{ doc.employee_name }}
\n {%- endif -%}\n {%- if doc.employee_designation -%}\n
{{ doc.employee_designation }}
\n {%- endif -%}\n {%- else -%}\n {%- if frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") -%}\n
{{ frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}
\n {%- endif -%}\n {%- endif -%}\n
\n\n
\n {% if doc.result_legend and (doc.legend_print_position == \"Bottom\" or doc.legend_print_position == \"Both\" or doc.legend_print_position == \"\")%}\n
\n Result Legend\n {{ doc.result_legend }}\n {%- endif -%}\n
\n {%- endif -%}\n
", - "idx": 0, - "line_breaks": 0, - "modified": "2020-07-08 15:34:28.866798", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test Print", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Jinja", - "raw_printing": 0, - "show_section_headings": 0, - "standard": "Yes" -} \ No newline at end of file diff --git a/erpnext/healthcare/print_format/sample_id_print/__init__.py b/erpnext/healthcare/print_format/sample_id_print/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json b/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json deleted file mode 100644 index 4819e6d57ac..00000000000 --- a/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "align_labels_left": 0, - "creation": "2017-02-17 17:40:52.967840", - "custom_format": 1, - "disabled": 0, - "doc_type": "Sample Collection", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "\n{% set column = 0 %}\n\n{% for _ in range(0, doc.num_print) %}\n{% if column == 0 -%}{% endif %}\n\t\n{% if column == 0 %}{% set column = column+1 %}\n{% elif column == 2%} {%- set column = 0 %}\n{% else %}{%- set column = column+1 -%}{%- endif %}\n\t\n{% endfor %}\n
{{doc.name}}
{{doc.patient}}
\n{% if doc.patient_age %}{{doc.patient_age}}, {% endif %} {% if doc.patient_sex %}{{doc.patient_sex}}{% endif %}
{% if doc.collected_time %}{{doc.collected_time}} {% endif %}
{% if doc.collected_by %} {{doc.collected_by}} {% endif %}
", - "idx": 0, - "line_breaks": 0, - "modified": "2017-03-30 18:09:39.537609", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Sample ID Print", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Jinja", - "show_section_headings": 0, - "standard": "Yes" -} diff --git a/erpnext/healthcare/report/__init__.py b/erpnext/healthcare/report/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/report/inpatient_medication_orders/__init__.py b/erpnext/healthcare/report/inpatient_medication_orders/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js deleted file mode 100644 index a10f83760fa..00000000000 --- a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports["Inpatient Medication Orders"] = { - "filters": [ - { - fieldname: "company", - label: __("Company"), - fieldtype: "Link", - options: "Company", - default: frappe.defaults.get_user_default("Company"), - reqd: 1 - }, - { - fieldname: "from_date", - label: __("From Date"), - fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), - reqd: 1 - }, - { - fieldname: "to_date", - label: __("To Date"), - fieldtype: "Date", - default: frappe.datetime.now_date(), - reqd: 1 - }, - { - fieldname: "patient", - label: __("Patient"), - fieldtype: "Link", - options: "Patient" - }, - { - fieldname: "service_unit", - label: __("Healthcare Service Unit"), - fieldtype: "Link", - options: "Healthcare Service Unit", - get_query: () => { - var company = frappe.query_report.get_filter_value('company'); - return { - filters: { - 'company': company, - 'is_group': 0 - } - } - } - }, - { - fieldname: "show_completed_orders", - label: __("Show Completed Orders"), - fieldtype: "Check", - default: 1 - } - ] -}; diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json deleted file mode 100644 index 9217fa18919..00000000000 --- a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "add_total_row": 0, - "columns": [], - "creation": "2020-11-23 17:25:58.802949", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "filters": [], - "idx": 0, - "is_standard": "Yes", - "json": "{}", - "modified": "2020-11-23 19:40:20.227591", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Medication Orders", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Inpatient Medication Order", - "report_name": "Inpatient Medication Orders", - "report_type": "Script Report", - "roles": [ - { - "role": "System Manager" - }, - { - "role": "Healthcare Administrator" - }, - { - "role": "Nursing User" - }, - { - "role": "Physician" - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py deleted file mode 100644 index 28b60bdcc92..00000000000 --- a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit - -def execute(filters=None): - columns = get_columns() - data = get_data(filters) - chart = get_chart_data(data) - - return columns, data, None, chart - -def get_columns(): - return [ - { - "fieldname": "patient", - "fieldtype": "Link", - "label": "Patient", - "options": "Patient", - "width": 200 - }, - { - "fieldname": "healthcare_service_unit", - "fieldtype": "Link", - "label": "Healthcare Service Unit", - "options": "Healthcare Service Unit", - "width": 150 - }, - { - "fieldname": "drug", - "fieldtype": "Link", - "label": "Drug Code", - "options": "Item", - "width": 150 - }, - { - "fieldname": "drug_name", - "fieldtype": "Data", - "label": "Drug Name", - "width": 150 - }, - { - "fieldname": "dosage", - "fieldtype": "Link", - "label": "Dosage", - "options": "Prescription Dosage", - "width": 80 - }, - { - "fieldname": "dosage_form", - "fieldtype": "Link", - "label": "Dosage Form", - "options": "Dosage Form", - "width": 100 - }, - { - "fieldname": "date", - "fieldtype": "Date", - "label": "Date", - "width": 100 - }, - { - "fieldname": "time", - "fieldtype": "Time", - "label": "Time", - "width": 100 - }, - { - "fieldname": "is_completed", - "fieldtype": "Check", - "label": "Is Order Completed", - "width": 100 - }, - { - "fieldname": "healthcare_practitioner", - "fieldtype": "Link", - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner", - "width": 200 - }, - { - "fieldname": "inpatient_medication_entry", - "fieldtype": "Link", - "label": "Inpatient Medication Entry", - "options": "Inpatient Medication Entry", - "width": 200 - }, - { - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "options": "Inpatient Record", - "width": 200 - } - ] - -def get_data(filters): - conditions, values = get_conditions(filters) - - data = frappe.db.sql(""" - SELECT - parent.patient, parent.inpatient_record, parent.practitioner, - child.drug, child.drug_name, child.dosage, child.dosage_form, - child.date, child.time, child.is_completed, child.name - FROM `tabInpatient Medication Order` parent - INNER JOIN `tabInpatient Medication Order Entry` child - ON child.parent = parent.name - WHERE - parent.docstatus = 1 - {conditions} - ORDER BY date, time - """.format(conditions=conditions), values, as_dict=1) - - data = get_inpatient_details(data, filters.get("service_unit")) - - return data - -def get_conditions(filters): - conditions = "" - values = dict() - - if filters.get("company"): - conditions += " AND parent.company = %(company)s" - values["company"] = filters.get("company") - - if filters.get("from_date") and filters.get("to_date"): - conditions += " AND child.date BETWEEN %(from_date)s and %(to_date)s" - values["from_date"] = filters.get("from_date") - values["to_date"] = filters.get("to_date") - - if filters.get("patient"): - conditions += " AND parent.patient = %(patient)s" - values["patient"] = filters.get("patient") - - if not filters.get("show_completed_orders"): - conditions += " AND child.is_completed = 0" - - return conditions, values - - -def get_inpatient_details(data, service_unit): - service_unit_filtered_data = [] - - for entry in data: - entry["healthcare_service_unit"] = get_current_healthcare_service_unit(entry.inpatient_record) - if entry.is_completed: - entry["inpatient_medication_entry"] = get_inpatient_medication_entry(entry.name) - - if service_unit and entry.healthcare_service_unit and service_unit != entry.healthcare_service_unit: - service_unit_filtered_data.append(entry) - - entry.pop("name", None) - - for entry in service_unit_filtered_data: - data.remove(entry) - - return data - -def get_inpatient_medication_entry(order_entry): - return frappe.db.get_value("Inpatient Medication Entry Detail", {"against_imoe": order_entry}, "parent") - -def get_chart_data(data): - if not data: - return None - - labels = ["Pending", "Completed"] - datasets = [] - - status_wise_data = { - "Pending": 0, - "Completed": 0 - } - - for d in data: - if d.is_completed: - status_wise_data["Completed"] += 1 - else: - status_wise_data["Pending"] += 1 - - datasets.append({ - "name": "Inpatient Medication Order Status", - "values": [status_wise_data.get("Pending"), status_wise_data.get("Completed")] - }) - - chart = { - "data": { - "labels": labels, - "datasets": datasets - }, - "type": "donut", - "height": 300 - } - - chart["fieldtype"] = "Data" - - return chart diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py deleted file mode 100644 index 4b461f1a97d..00000000000 --- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import unittest -import frappe -import datetime -from frappe.utils import getdate, now_datetime -from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy -from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge -from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme -from erpnext.healthcare.report.inpatient_medication_orders.inpatient_medication_orders import execute - -class TestInpatientMedicationOrders(unittest.TestCase): - @classmethod - def setUpClass(self): - frappe.db.sql("delete from `tabInpatient Medication Order` where company='_Test Company'") - frappe.db.sql("delete from `tabInpatient Medication Entry` where company='_Test Company'") - self.patient = create_patient() - self.ip_record = create_records(self.patient) - - def test_inpatient_medication_orders_report(self): - filters = { - 'company': '_Test Company', - 'from_date': getdate(), - 'to_date': getdate(), - 'patient': '_Test IPD Patient', - 'service_unit': 'Test Service Unit Ip Occupancy - _TC' - } - - report = execute(filters) - - expected_data = [ - { - 'patient': '_Test IPD Patient', - 'inpatient_record': self.ip_record.name, - 'practitioner': None, - 'drug': 'Dextromethorphan', - 'drug_name': 'Dextromethorphan', - 'dosage': 1.0, - 'dosage_form': 'Tablet', - 'date': getdate(), - 'time': datetime.timedelta(seconds=32400), - 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' - }, - { - 'patient': '_Test IPD Patient', - 'inpatient_record': self.ip_record.name, - 'practitioner': None, - 'drug': 'Dextromethorphan', - 'drug_name': 'Dextromethorphan', - 'dosage': 1.0, - 'dosage_form': 'Tablet', - 'date': getdate(), - 'time': datetime.timedelta(seconds=50400), - 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' - }, - { - 'patient': '_Test IPD Patient', - 'inpatient_record': self.ip_record.name, - 'practitioner': None, - 'drug': 'Dextromethorphan', - 'drug_name': 'Dextromethorphan', - 'dosage': 1.0, - 'dosage_form': 'Tablet', - 'date': getdate(), - 'time': datetime.timedelta(seconds=75600), - 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' - } - ] - - self.assertEqual(expected_data, report[1]) - - filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time='', to_time='') - ipme = create_ipme(filters) - ipme.submit() - - filters = { - 'company': '_Test Company', - 'from_date': getdate(), - 'to_date': getdate(), - 'patient': '_Test IPD Patient', - 'service_unit': 'Test Service Unit Ip Occupancy - _TC', - 'show_completed_orders': 0 - } - - report = execute(filters) - self.assertEqual(len(report[1]), 0) - - def tearDown(self): - if frappe.db.get_value('Patient', self.patient, 'inpatient_record'): - # cleanup - Discharge - schedule_discharge(frappe.as_json({'patient': self.patient})) - self.ip_record.reload() - mark_invoiced_inpatient_occupancy(self.ip_record) - - self.ip_record.reload() - discharge_patient(self.ip_record) - - for entry in frappe.get_all('Inpatient Medication Entry'): - doc = frappe.get_doc('Inpatient Medication Entry', entry.name) - doc.cancel() - doc.delete() - - for entry in frappe.get_all('Inpatient Medication Order'): - doc = frappe.get_doc('Inpatient Medication Order', entry.name) - doc.cancel() - doc.delete() - - -def create_records(patient): - frappe.db.sql("""delete from `tabInpatient Record`""") - - # Admit - ip_record = create_inpatient(patient) - ip_record.expected_length_of_stay = 0 - ip_record.save() - ip_record.reload() - service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') - admit_patient(ip_record, service_unit, now_datetime()) - - ipmo = create_ipmo(patient) - ipmo.submit() - - return ip_record diff --git a/erpnext/healthcare/report/lab_test_report/__init__.py b/erpnext/healthcare/report/lab_test_report/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.js b/erpnext/healthcare/report/lab_test_report/lab_test_report.js deleted file mode 100644 index 7754e2e1962..00000000000 --- a/erpnext/healthcare/report/lab_test_report/lab_test_report.js +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016, ESS -// License: See license.txt - -frappe.query_reports["Lab Test Report"] = { - "filters": [ - { - "fieldname": "from_date", - "label": __("From Date"), - "fieldtype": "Date", - "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), - "reqd": 1 - }, - { - "fieldname": "to_date", - "label": __("To Date"), - "fieldtype": "Date", - "default": frappe.datetime.now_date(), - "reqd": 1 - }, - { - "fieldname": "company", - "label": __("Company"), - "fieldtype": "Link", - "default": frappe.defaults.get_default("Company"), - "options": "Company" - }, - { - "fieldname": "template", - "label": __("Lab Test Template"), - "fieldtype": "Link", - "options": "Lab Test Template" - }, - { - "fieldname": "patient", - "label": __("Patient"), - "fieldtype": "Link", - "options": "Patient" - }, - { - "fieldname": "department", - "label": __("Medical Department"), - "fieldtype": "Link", - "options": "Medical Department" - }, - { - "fieldname": "status", - "label": __("Status"), - "fieldtype": "Select", - "options": "\nCompleted\nApproved\nRejected" - }, - { - "fieldname": "invoiced", - "label": __("Invoiced"), - "fieldtype": "Check" - } - ] -}; diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.json b/erpnext/healthcare/report/lab_test_report/lab_test_report.json deleted file mode 100644 index aeb42897b8a..00000000000 --- a/erpnext/healthcare/report/lab_test_report/lab_test_report.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "add_total_row": 0, - "creation": "2013-04-23 18:15:29", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 1, - "is_standard": "Yes", - "modified": "2020-07-30 18:53:20.102873", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test Report", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Lab Test", - "report_name": "Lab Test Report", - "report_type": "Script Report", - "roles": [ - { - "role": "Laboratory User" - }, - { - "role": "Nursing User" - }, - { - "role": "LabTest Approver" - }, - { - "role": "Healthcare Administrator" - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.py b/erpnext/healthcare/report/lab_test_report/lab_test_report.py deleted file mode 100644 index 2e59bed0373..00000000000 --- a/erpnext/healthcare/report/lab_test_report/lab_test_report.py +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright (c) 2016, ESS -# License: See license.txt - -from __future__ import unicode_literals -import frappe -from frappe import msgprint, _ - -def execute(filters=None): - if not filters: filters = {} - - data, columns = [], [] - - columns = get_columns() - lab_test_list = get_lab_tests(filters) - - if not lab_test_list: - msgprint(_('No records found')) - return columns, lab_test_list - - data = [] - for lab_test in lab_test_list: - row = frappe._dict({ - 'test': lab_test.name, - 'template': lab_test.template, - 'company': lab_test.company, - 'patient': lab_test.patient, - 'patient_name': lab_test.patient_name, - 'practitioner': lab_test.practitioner, - 'employee': lab_test.employee, - 'status': lab_test.status, - 'invoiced': lab_test.invoiced, - 'result_date': lab_test.result_date, - 'department': lab_test.department - }) - data.append(row) - - chart = get_chart_data(data) - report_summary = get_report_summary(data) - return columns, data, None, chart, report_summary - - -def get_columns(): - return [ - { - 'fieldname': 'test', - 'label': _('Lab Test'), - 'fieldtype': 'Link', - 'options': 'Lab Test', - 'width': '120' - }, - { - 'fieldname': 'template', - 'label': _('Lab Test Template'), - 'fieldtype': 'Link', - 'options': 'Lab Test Template', - 'width': '120' - }, - { - 'fieldname': 'company', - 'label': _('Company'), - 'fieldtype': 'Link', - 'options': 'Company', - 'width': '120' - }, - { - 'fieldname': 'patient', - 'label': _('Patient'), - 'fieldtype': 'Link', - 'options': 'Patient', - 'width': '120' - }, - { - 'fieldname': 'patient_name', - 'label': _('Patient Name'), - 'fieldtype': 'Data', - 'width': '120' - }, - { - 'fieldname': 'employee', - 'label': _('Lab Technician'), - 'fieldtype': 'Link', - 'options': 'Employee', - 'width': '120' - }, - { - 'fieldname': 'status', - 'label': _('Status'), - 'fieldtype': 'Data', - 'width': '100' - }, - { - 'fieldname': 'invoiced', - 'label': _('Invoiced'), - 'fieldtype': 'Check', - 'width': '100' - }, - { - 'fieldname': 'result_date', - 'label': _('Result Date'), - 'fieldtype': 'Date', - 'width': '100' - }, - { - 'fieldname': 'practitioner', - 'label': _('Requesting Practitioner'), - 'fieldtype': 'Link', - 'options': 'Healthcare Practitioner', - 'width': '120' - }, - { - 'fieldname': 'department', - 'label': _('Medical Department'), - 'fieldtype': 'Link', - 'options': 'Medical Department', - 'width': '100' - } - ] - -def get_lab_tests(filters): - conditions = get_conditions(filters) - data = frappe.get_all( - doctype='Lab Test', - fields=['name', 'template', 'company', 'patient', 'patient_name', 'practitioner', 'employee', 'status', 'invoiced', 'result_date', 'department'], - filters=conditions, - order_by='submitted_date desc' - ) - return data - -def get_conditions(filters): - conditions = { - 'docstatus': ('=', 1) - } - - if filters.get('from_date') and filters.get('to_date'): - conditions['result_date'] = ('between', (filters.get('from_date'), filters.get('to_date'))) - filters.pop('from_date') - filters.pop('to_date') - - for key, value in filters.items(): - if filters.get(key): - conditions[key] = value - - return conditions - -def get_chart_data(data): - if not data: - return None - - labels = ['Completed', 'Approved', 'Rejected'] - - status_wise_data = { - 'Completed': 0, - 'Approved': 0, - 'Rejected': 0 - } - - datasets = [] - - for entry in data: - status_wise_data[entry.status] += 1 - - datasets.append({ - 'name': 'Lab Test Status', - 'values': [status_wise_data.get('Completed'), status_wise_data.get('Approved'), status_wise_data.get('Rejected')] - }) - - chart = { - 'data': { - 'labels': labels, - 'datasets': datasets - }, - 'type': 'donut', - 'height': 300, - } - - return chart - - -def get_report_summary(data): - if not data: - return None - - total_lab_tests = len(data) - invoiced_lab_tests, unbilled_lab_tests = 0, 0 - - for entry in data: - if entry.invoiced: - invoiced_lab_tests += 1 - else: - unbilled_lab_tests += 1 - - return [ - { - 'value': total_lab_tests, - 'indicator': 'Blue', - 'label': 'Total Lab Tests', - 'datatype': 'Int', - }, - { - 'value': invoiced_lab_tests, - 'indicator': 'Green', - 'label': 'Invoiced Lab Tests', - 'datatype': 'Int', - }, - { - 'value': unbilled_lab_tests, - 'indicator': 'Red', - 'label': 'Unbilled Lab Tests', - 'datatype': 'Int', - } - ] diff --git a/erpnext/healthcare/report/patient_appointment_analytics/__init__.py b/erpnext/healthcare/report/patient_appointment_analytics/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js deleted file mode 100644 index 18d252ede13..00000000000 --- a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports['Patient Appointment Analytics'] = { - "filters": [ - { - fieldname: 'tree_type', - label: __('Tree Type'), - fieldtype: 'Select', - options: ['Healthcare Practitioner', 'Medical Department'], - default: 'Healthcare Practitioner', - reqd: 1 - }, - { - fieldname: 'status', - label: __('Appointment Status'), - fieldtype: 'Select', - options:[ - {label: __('Scheduled'), value: 'Scheduled'}, - {label: __('Open'), value: 'Open'}, - {label: __('Closed'), value: 'Closed'}, - {label: __('Expired'), value: 'Expired'}, - {label: __('Cancelled'), value: 'Cancelled'} - ] - }, - { - fieldname: 'appointment_type', - label: __('Appointment Type'), - fieldtype: 'Link', - options: 'Appointment Type' - }, - { - fieldname: 'practitioner', - label: __('Healthcare Practitioner'), - fieldtype: 'Link', - options: 'Healthcare Practitioner' - }, - { - fieldname: 'department', - label: __('Medical Department'), - fieldtype: 'Link', - options: 'Medical Department' - }, - { - fieldname: 'from_date', - label: __('From Date'), - fieldtype: 'Date', - default: frappe.defaults.get_user_default('year_start_date'), - reqd: 1 - }, - { - fieldname: 'to_date', - label: __('To Date'), - fieldtype: 'Date', - default: frappe.defaults.get_user_default('year_end_date'), - reqd: 1 - }, - { - fieldname: 'range', - label: __('Range'), - fieldtype: 'Select', - options:[ - {label: __('Weekly'), value: 'Weekly'}, - {label: __('Monthly'), value: 'Monthly'}, - {label: __('Quarterly'), value: 'Quarterly'}, - {label: __('Yearly'), value: 'Yearly'} - ], - default: 'Monthly', - reqd: 1 - } - ], - after_datatable_render: function(datatable_obj) { - $(datatable_obj.wrapper).find(".dt-row-0").find('input[type=checkbox]').click(); - }, - get_datatable_options(options) { - return Object.assign(options, { - checkboxColumn: true, - events: { - onCheckRow: function(data) { - row_name = data[2].content; - length = data.length; - - row_values = data.slice(3,length-1).map(function (column) { - return column.content; - }) - - entry = { - 'name': row_name, - 'values': row_values - } - - let raw_data = frappe.query_report.chart.data; - let new_datasets = raw_data.datasets; - - let found = false; - for (let i=0; i < new_datasets.length;i++) { - if (new_datasets[i].name == row_name) { - found = true; - new_datasets.splice(i,1); - break; - } - } - - if (!found) { - new_datasets.push(entry); - } - - let new_data = { - labels: raw_data.labels, - datasets: new_datasets - } - - setTimeout(() => { - frappe.query_report.chart.update(new_data) - }, 500) - - - setTimeout(() => { - frappe.query_report.chart.draw(true); - }, 1000) - - frappe.query_report.raw_chart_data = new_data; - }, - } - }) - }, -}; diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json deleted file mode 100644 index 64750c012f1..00000000000 --- a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "add_total_row": 1, - "creation": "2020-03-02 15:13:16.273493", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2020-03-02 15:13:16.273493", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Appointment Analytics", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Patient Appointment", - "report_name": "Patient Appointment Analytics", - "report_type": "Script Report", - "roles": [ - { - "role": "Healthcare Administrator" - }, - { - "role": "LabTest Approver" - }, - { - "role": "Physician" - }, - { - "role": "Nursing User" - }, - { - "role": "Laboratory User" - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py deleted file mode 100644 index 9a4840acfea..00000000000 --- a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py +++ /dev/null @@ -1,194 +0,0 @@ -# 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.utils import getdate, flt, add_to_date, add_days -from frappe import _ , scrub -from six import iteritems -from erpnext.accounts.utils import get_fiscal_year - -def execute(filters=None): - return Analytics(filters).run() - -class Analytics(object): - def __init__(self, filters=None): - """Patient Appointment Analytics Report.""" - self.filters = frappe._dict(filters or {}) - self.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - self.get_period_date_ranges() - - def run(self): - self.get_columns() - self.get_data() - self.get_chart_data() - - return self.columns, self.data, None, self.chart - - def get_period_date_ranges(self): - from dateutil.relativedelta import relativedelta, MO - from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date) - - increment = { - 'Monthly': 1, - 'Quarterly': 3, - 'Half-Yearly': 6, - 'Yearly': 12 - }.get(self.filters.range, 1) - - if self.filters.range in ['Monthly', 'Quarterly']: - from_date = from_date.replace(day=1) - elif self.filters.range == 'Yearly': - from_date = get_fiscal_year(from_date)[1] - else: - from_date = from_date + relativedelta(from_date, weekday=MO(-1)) - - self.periodic_daterange = [] - for dummy in range(1, 53): - if self.filters.range == 'Weekly': - period_end_date = add_days(from_date, 6) - else: - period_end_date = add_to_date(from_date, months=increment, days=-1) - - if period_end_date > to_date: - period_end_date = to_date - - self.periodic_daterange.append(period_end_date) - - from_date = add_days(period_end_date, 1) - if period_end_date == to_date: - break - - def get_columns(self): - self.columns = [] - - if self.filters.tree_type == 'Healthcare Practitioner': - self.columns.append({ - 'label': _('Healthcare Practitioner'), - 'options': 'Healthcare Practitioner', - 'fieldname': 'practitioner', - 'fieldtype': 'Link', - 'width': 200 - }) - - elif self.filters.tree_type == 'Medical Department': - self.columns.append({ - 'label': _('Medical Department'), - 'fieldname': 'department', - 'fieldtype': 'Link', - 'options': 'Medical Department', - 'width': 150 - }) - - for end_date in self.periodic_daterange: - period = self.get_period(end_date) - self.columns.append({ - 'label': _(period), - 'fieldname': scrub(period), - 'fieldtype': 'Int', - 'width': 120 - }) - - self.columns.append({ - 'label': _('Total'), - 'fieldname': 'total', - 'fieldtype': 'Int', - 'width': 120 - }) - - def get_data(self): - if self.filters.tree_type == 'Healthcare Practitioner': - self.get_appointments_based_on_healthcare_practitioner() - self.get_rows() - - elif self.filters.tree_type == 'Medical Department': - self.get_appointments_based_on_medical_department() - self.get_rows() - - def get_period(self, appointment_date): - if self.filters.range == 'Weekly': - period = 'Week ' + str(appointment_date.isocalendar()[1]) - elif self.filters.range == 'Monthly': - period = str(self.months[appointment_date.month - 1]) - elif self.filters.range == 'Quarterly': - period = 'Quarter ' + str(((appointment_date.month - 1) // 3) + 1) - else: - year = get_fiscal_year(appointment_date, company=self.filters.company) - period = str(year[0]) - - if getdate(self.filters.from_date).year != getdate(self.filters.to_date).year: - period += ' ' + str(appointment_date.year) - - return period - - def get_appointments_based_on_healthcare_practitioner(self): - filters = self.get_common_filters() - - self.entries = frappe.db.get_all('Patient Appointment', - fields=['appointment_date', 'name', 'patient', 'practitioner'], - filters=filters - ) - - def get_appointments_based_on_medical_department(self): - filters = self.get_common_filters() - if not filters.get('department'): - filters['department'] = ('!=', '') - - self.entries = frappe.db.get_all('Patient Appointment', - fields=['appointment_date', 'name', 'patient', 'practitioner', 'department'], - filters=filters - ) - - def get_common_filters(self): - filters = {} - filters['appointment_date'] = ('between', [self.filters.from_date, self.filters.to_date]) - for entry in ['appointment_type', 'practitioner', 'department', 'status']: - if self.filters.get(entry): - filters[entry] = self.filters.get(entry) - - return filters - - def get_rows(self): - self.data = [] - self.get_periodic_data() - - for entity, period_data in iteritems(self.appointment_periodic_data): - if self.filters.tree_type == 'Healthcare Practitioner': - row = {'practitioner': entity} - elif self.filters.tree_type == 'Medical Department': - row = {'department': entity} - - total = 0 - for end_date in self.periodic_daterange: - period = self.get_period(end_date) - amount = flt(period_data.get(period, 0.0)) - row[scrub(period)] = amount - total += amount - - row['total'] = total - - self.data.append(row) - - def get_periodic_data(self): - self.appointment_periodic_data = frappe._dict() - - for d in self.entries: - period = self.get_period(d.get('appointment_date')) - if self.filters.tree_type == 'Healthcare Practitioner': - self.appointment_periodic_data.setdefault(d.practitioner, frappe._dict()).setdefault(period, 0.0) - self.appointment_periodic_data[d.practitioner][period] += 1 - - elif self.filters.tree_type == 'Medical Department': - self.appointment_periodic_data.setdefault(d.department, frappe._dict()).setdefault(period, 0.0) - self.appointment_periodic_data[d.department][period] += 1 - - def get_chart_data(self): - length = len(self.columns) - labels = [d.get("label") for d in self.columns[1:length - 1]] - self.chart = { - "data": { - 'labels': labels, - 'datasets': [] - }, - "type": "line" - } diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py deleted file mode 100644 index 891272ddf81..00000000000 --- a/erpnext/healthcare/setup.py +++ /dev/null @@ -1,295 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe import _ -from erpnext.setup.utils import insert_record - -def setup_healthcare(): - if frappe.db.exists('Medical Department', 'Cardiology'): - # already setup - return - create_medical_departments() - create_antibiotics() - create_lab_test_uom() - create_duration() - create_dosage() - create_healthcare_item_groups() - create_sensitivity() - add_healthcare_service_unit_tree_root() - setup_patient_history_settings() - -def create_medical_departments(): - departments = [ - "Accident And Emergency Care" ,"Anaesthetics", "Biochemistry", "Cardiology", "Dermatology", - "Diagnostic Imaging", "ENT", "Gastroenterology", "General Surgery", "Gynaecology", - "Haematology", "Maternity", "Microbiology", "Nephrology", "Neurology", "Oncology", - "Orthopaedics", "Pathology", "Physiotherapy", "Rheumatology", "Serology", "Urology" - ] - for department in departments: - mediacal_department = frappe.new_doc("Medical Department") - mediacal_department.department = _(department) - try: - mediacal_department.save() - except frappe.DuplicateEntryError: - pass - -def create_antibiotics(): - abt = [ - "Amoxicillin", "Ampicillin", "Bacampicillin", "Carbenicillin", "Cloxacillin", "Dicloxacillin", - "Flucloxacillin", "Mezlocillin", "Nafcillin", "Oxacillin", "Penicillin G", "Penicillin V", - "Piperacillin", "Pivampicillin", "Pivmecillinam", "Ticarcillin", "Cefacetrile (cephacetrile)", - "Cefadroxil (cefadroxyl)", "Cefalexin (cephalexin)", "Cefaloglycin (cephaloglycin)", - "Cefalonium (cephalonium)", "Cefaloridine (cephaloradine)", "Cefalotin (cephalothin)", - "Cefapirin (cephapirin)", "Cefatrizine", "Cefazaflur", "Cefazedone", "Cefazolin (cephazolin)", - "Cefradine (cephradine)", "Cefroxadine", "Ceftezole", "Cefaclor", "Cefamandole", "Cefmetazole", - "Cefonicid", "Cefotetan", "Cefoxitin", "Cefprozil (cefproxil)", "Cefuroxime", "Cefuzonam", - "Cefcapene", "Cefdaloxime", "Cefdinir", "Cefditoren", "Cefetamet", "Cefixime", "Cefmenoxime", - "Cefodizime", "Cefotaxime", "Cefpimizole", "Cefpodoxime", "Cefteram", "Ceftibuten", "Ceftiofur", - "Ceftiolene", "Ceftizoxime", "Ceftriaxone", "Cefoperazone", "Ceftazidime", "Cefclidine", "Cefepime", - "Cefluprenam", "Cefoselis", "Cefozopran", "Cefpirome", "Cefquinome", "Ceftobiprole", "Ceftaroline", - "Cefaclomezine","Cefaloram", "Cefaparole", "Cefcanel", "Cefedrolor", "Cefempidone", "Cefetrizole", - "Cefivitril", "Cefmatilen", "Cefmepidium", "Cefovecin", "Cefoxazole", "Cefrotil", "Cefsumide", - "Cefuracetime", "Ceftioxide", "Ceftazidime/Avibactam", "Ceftolozane/Tazobactam", "Aztreonam", - "Imipenem", "Imipenem/cilastatin", "Doripenem", "Meropenem", "Ertapenem", "Azithromycin", - "Erythromycin", "Clarithromycin", "Dirithromycin", "Roxithromycin", "Telithromycin", "Clindamycin", - "Lincomycin", "Pristinamycin", "Quinupristin/dalfopristin", "Amikacin", "Gentamicin", "Kanamycin", - "Neomycin", "Netilmicin", "Paromomycin", "Streptomycin", "Tobramycin", "Flumequine", "Nalidixic acid", - "Oxolinic acid", "Piromidic acid", "Pipemidic acid", "Rosoxacin", "Ciprofloxacin", "Enoxacin", - "Lomefloxacin", "Nadifloxacin", "Norfloxacin", "Ofloxacin", "Pefloxacin", "Rufloxacin", "Balofloxacin", - "Gatifloxacin", "Grepafloxacin", "Levofloxacin", "Moxifloxacin", "Pazufloxacin", "Sparfloxacin", - "Temafloxacin", "Tosufloxacin", "Besifloxacin", "Clinafloxacin", "Gemifloxacin", - "Sitafloxacin", "Trovafloxacin", "Prulifloxacin", "Sulfamethizole", "Sulfamethoxazole", - "Sulfisoxazole", "Trimethoprim-Sulfamethoxazole", "Demeclocycline", "Doxycycline", "Minocycline", - "Oxytetracycline", "Tetracycline", "Tigecycline", "Chloramphenicol", "Metronidazole", - "Tinidazole", "Nitrofurantoin", "Vancomycin", "Teicoplanin", "Telavancin", "Linezolid", - "Cycloserine 2", "Rifampin", "Rifabutin", "Rifapentine", "Rifalazil", "Bacitracin", "Polymyxin B", - "Viomycin", "Capreomycin" - ] - - for a in abt: - antibiotic = frappe.new_doc("Antibiotic") - antibiotic.antibiotic_name = a - try: - antibiotic.save() - except frappe.DuplicateEntryError: - pass - -def create_lab_test_uom(): - records = [ - {"doctype": "Lab Test UOM", "name": "umol/L", "lab_test_uom": "umol/L", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "mg/L", "lab_test_uom": "mg/L", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "mg / dl", "lab_test_uom": "mg / dl", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "pg / ml", "lab_test_uom": "pg / ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "U/ml", "lab_test_uom": "U/ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "/HPF", "lab_test_uom": "/HPF", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Million Cells / cumm", "lab_test_uom": "Million Cells / cumm", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Lakhs Cells / cumm", "lab_test_uom": "Lakhs Cells / cumm", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "U / L", "lab_test_uom": "U / L", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "g / L", "lab_test_uom": "g / L", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "IU / ml", "lab_test_uom": "IU / ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "gm %", "lab_test_uom": "gm %", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Microgram", "lab_test_uom": "Microgram", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Micron", "lab_test_uom": "Micron", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Cells / cumm", "lab_test_uom": "Cells / cumm", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "%", "lab_test_uom": "%", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "mm / dl", "lab_test_uom": "mm / dl", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "mm / hr", "lab_test_uom": "mm / hr", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "ulU / ml", "lab_test_uom": "ulU / ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "ng / ml", "lab_test_uom": "ng / ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "ng / dl", "lab_test_uom": "ng / dl", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "ug / dl", "lab_test_uom": "ug / dl", "uom_description": None } - ] - - insert_record(records) - -def create_duration(): - records = [ - {"doctype": "Prescription Duration", "name": "3 Month", "number": "3", "period": "Month" }, - {"doctype": "Prescription Duration", "name": "2 Month", "number": "2", "period": "Month" }, - {"doctype": "Prescription Duration", "name": "1 Month", "number": "1", "period": "Month" }, - {"doctype": "Prescription Duration", "name": "12 Hour", "number": "12", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "11 Hour", "number": "11", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "10 Hour", "number": "10", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "9 Hour", "number": "9", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "8 Hour", "number": "8", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "7 Hour", "number": "7", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "6 Hour", "number": "6", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "5 Hour", "number": "5", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "4 Hour", "number": "4", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "3 Hour", "number": "3", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "2 Hour", "number": "2", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "1 Hour", "number": "1", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "5 Week", "number": "5", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "4 Week", "number": "4", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "3 Week", "number": "3", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "2 Week", "number": "2", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "1 Week", "number": "1", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "6 Day", "number": "6", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "5 Day", "number": "5", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "4 Day", "number": "4", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "3 Day", "number": "3", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "2 Day", "number": "2", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "1 Day", "number": "1", "period": "Day" } - ] - insert_record(records) - -def create_dosage(): - records = [ - {"doctype": "Prescription Dosage", "name": "1-1-1-1", "dosage": "1-1-1-1","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "13:00:00"},{"strength": "1.0","strength_time": "17:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "0-0-1", "dosage": "0-0-1","dosage_strength": - [{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "1-0-0", "dosage": "1-0-0","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "0-1-0", "dosage": "0-1-0","dosage_strength": - [{"strength": "1.0","strength_time": "14:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "1-1-1", "dosage": "1-1-1","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "14:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "1-0-1", "dosage": "1-0-1","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "Once Bedtime", "dosage": "Once Bedtime","dosage_strength": - [{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "5 times a day", "dosage": "5 times a day","dosage_strength": - [{"strength": "1.0","strength_time": "5:00:00"}, {"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "13:00:00"},{"strength": "1.0","strength_time": "17:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "QID", "dosage": "QID","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "13:00:00"},{"strength": "1.0","strength_time": "17:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "TID", "dosage": "TID","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "14:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "BID", "dosage": "BID","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "Once Daily", "dosage": "Once Daily","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}] - } - ] - insert_record(records) - -def create_healthcare_item_groups(): - records = [ - {'doctype': 'Item Group', 'item_group_name': _('Laboratory'), - 'is_group': 0, 'parent_item_group': _('All Item Groups') }, - {'doctype': 'Item Group', 'item_group_name': _('Drug'), - 'is_group': 0, 'parent_item_group': _('All Item Groups') } - ] - insert_record(records) - -def create_sensitivity(): - records = [ - {"doctype": "Sensitivity", "sensitivity": _("Low Sensitivity")}, - {"doctype": "Sensitivity", "sensitivity": _("High Sensitivity")}, - {"doctype": "Sensitivity", "sensitivity": _("Moderate Sensitivity")}, - {"doctype": "Sensitivity", "sensitivity": _("Susceptible")}, - {"doctype": "Sensitivity", "sensitivity": _("Resistant")}, - {"doctype": "Sensitivity", "sensitivity": _("Intermediate")} - ] - insert_record(records) - -def add_healthcare_service_unit_tree_root(): - record = [ - { - "doctype": "Healthcare Service Unit", - "healthcare_service_unit_name": "All Healthcare Service Units", - "is_group": 1, - "company": get_company() - } - ] - insert_record(record) - -def get_company(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company = frappe.get_list("Company", limit=1) - if company: - return company[0].name - return None - -def setup_patient_history_settings(): - import json - - settings = frappe.get_single('Patient History Settings') - configuration = get_patient_history_config() - for dt, config in configuration.items(): - settings.append("standard_doctypes", { - "document_type": dt, - "date_fieldname": config[0], - "selected_fields": json.dumps(config[1]) - }) - settings.save() - -def get_patient_history_config(): - return { - "Patient Encounter": ("encounter_date", [ - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"}, - {"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"}, - {"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"}, - {"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"}, - {"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"}, - {"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"}, - {"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"} - ]), - "Clinical Procedure": ("start_date", [ - {"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"}, - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"}, - {"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"}, - {"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"}, - {"label": "Sample", "fieldname": "sample", "fieldtype": "Link"} - ]), - "Lab Test": ("result_date", [ - {"label": "Test Template", "fieldname": "template", "fieldtype": "Link"}, - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"}, - {"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"}, - {"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"}, - {"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"}, - {"label": "Descriptive Test Result", "fieldname": "descriptive_test_items", "fieldtype": "Table"}, - {"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"}, - {"label": "Sensitivity Test Result", "fieldname": "sensitivity_test_items", "fieldtype": "Table"}, - {"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"} - ]), - "Therapy Session": ("start_date", [ - {"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"}, - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"}, - {"label": "Duration", "fieldname": "duration", "fieldtype": "Int"}, - {"label": "Location", "fieldname": "location", "fieldtype": "Link"}, - {"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"}, - {"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"}, - {"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"}, - {"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"}, - {"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"} - ]), - "Vital Signs": ("signs_date", [ - {"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"}, - {"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"}, - {"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"}, - {"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"}, - {"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"}, - {"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"}, - {"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"}, - {"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"}, - {"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"}, - {"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"}, - {"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"} - ]), - "Inpatient Medication Order": ("start_date", [ - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"}, - {"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"}, - {"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"}, - {"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"} - ]) - } diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py deleted file mode 100644 index d3d22c80b67..00000000000 --- a/erpnext/healthcare/utils.py +++ /dev/null @@ -1,719 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, earthians and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import math -import frappe -import json -from frappe import _ -from frappe.utils.formatters import format_value -from frappe.utils import time_diff_in_hours, rounded -from six import string_types -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account -from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity -from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple - -@frappe.whitelist() -def get_healthcare_services_to_invoice(patient, company): - patient = frappe.get_doc('Patient', patient) - items_to_invoice = [] - if patient: - validate_customer_created(patient) - # Customer validated, build a list of billable services - items_to_invoice += get_appointments_to_invoice(patient, company) - items_to_invoice += get_encounters_to_invoice(patient, company) - items_to_invoice += get_lab_tests_to_invoice(patient, company) - items_to_invoice += get_clinical_procedures_to_invoice(patient, company) - items_to_invoice += get_inpatient_services_to_invoice(patient, company) - items_to_invoice += get_therapy_plans_to_invoice(patient, company) - items_to_invoice += get_therapy_sessions_to_invoice(patient, company) - - return items_to_invoice - - -def validate_customer_created(patient): - if not frappe.db.get_value('Patient', patient.name, 'customer'): - msg = _("Please set a Customer linked to the Patient") - msg += " {0}".format(patient.name) - frappe.throw(msg, title=_('Customer Not Found')) - - -def get_appointments_to_invoice(patient, company): - appointments_to_invoice = [] - patient_appointments = frappe.get_list( - 'Patient Appointment', - fields = '*', - filters = {'patient': patient.name, 'company': company, 'invoiced': 0, 'status': ['not in', 'Cancelled']}, - order_by = 'appointment_date' - ) - - for appointment in patient_appointments: - # Procedure Appointments - if appointment.procedure_template: - if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'): - appointments_to_invoice.append({ - 'reference_type': 'Patient Appointment', - 'reference_name': appointment.name, - 'service': appointment.procedure_template - }) - # Consultation Appointments, should check fee validity - else: - if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \ - frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}): - continue # Skip invoicing, fee validty present - practitioner_charge = 0 - income_account = None - service_item = None - if appointment.practitioner: - details = get_service_item_and_practitioner_charge(appointment) - service_item = details.get('service_item') - practitioner_charge = details.get('practitioner_charge') - income_account = get_income_account(appointment.practitioner, appointment.company) - appointments_to_invoice.append({ - 'reference_type': 'Patient Appointment', - 'reference_name': appointment.name, - 'service': service_item, - 'rate': practitioner_charge, - 'income_account': income_account - }) - - return appointments_to_invoice - - -def get_encounters_to_invoice(patient, company): - if not isinstance(patient, str): - patient = patient.name - encounters_to_invoice = [] - encounters = frappe.get_list( - 'Patient Encounter', - fields=['*'], - filters={'patient': patient, 'company': company, 'invoiced': False, 'docstatus': 1} - ) - if encounters: - for encounter in encounters: - if not encounter.appointment: - practitioner_charge = 0 - income_account = None - service_item = None - if encounter.practitioner: - if encounter.inpatient_record and \ - frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'): - continue - - details = get_service_item_and_practitioner_charge(encounter) - service_item = details.get('service_item') - practitioner_charge = details.get('practitioner_charge') - income_account = get_income_account(encounter.practitioner, encounter.company) - - encounters_to_invoice.append({ - 'reference_type': 'Patient Encounter', - 'reference_name': encounter.name, - 'service': service_item, - 'rate': practitioner_charge, - 'income_account': income_account - }) - - return encounters_to_invoice - - -def get_lab_tests_to_invoice(patient, company): - lab_tests_to_invoice = [] - lab_tests = frappe.get_list( - 'Lab Test', - fields=['name', 'template'], - filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} - ) - for lab_test in lab_tests: - item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.template, ['item', 'is_billable']) - if is_billable: - lab_tests_to_invoice.append({ - 'reference_type': 'Lab Test', - 'reference_name': lab_test.name, - 'service': item - }) - - lab_prescriptions = frappe.db.sql( - ''' - SELECT - lp.name, lp.lab_test_code - FROM - `tabPatient Encounter` et, `tabLab Prescription` lp - WHERE - et.patient=%s - and lp.parent=et.name - and lp.lab_test_created=0 - and lp.invoiced=0 - ''', (patient.name), as_dict=1) - - for prescription in lab_prescriptions: - item, is_billable = frappe.get_cached_value('Lab Test Template', prescription.lab_test_code, ['item', 'is_billable']) - if prescription.lab_test_code and is_billable: - lab_tests_to_invoice.append({ - 'reference_type': 'Lab Prescription', - 'reference_name': prescription.name, - 'service': item - }) - - return lab_tests_to_invoice - - -def get_clinical_procedures_to_invoice(patient, company): - clinical_procedures_to_invoice = [] - procedures = frappe.get_list( - 'Clinical Procedure', - fields='*', - filters={'patient': patient.name, 'company': company, 'invoiced': False} - ) - for procedure in procedures: - if not procedure.appointment: - item, is_billable = frappe.get_cached_value('Clinical Procedure Template', procedure.procedure_template, ['item', 'is_billable']) - if procedure.procedure_template and is_billable: - clinical_procedures_to_invoice.append({ - 'reference_type': 'Clinical Procedure', - 'reference_name': procedure.name, - 'service': item - }) - - # consumables - if procedure.invoice_separately_as_consumables and procedure.consume_stock \ - and procedure.status == 'Completed' and not procedure.consumption_invoiced: - - service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') - if not service_item: - msg = _('Please Configure Clinical Procedure Consumable Item in ') - msg += '''Healthcare Settings''' - frappe.throw(msg, title=_('Missing Configuration')) - - clinical_procedures_to_invoice.append({ - 'reference_type': 'Clinical Procedure', - 'reference_name': procedure.name, - 'service': service_item, - 'rate': procedure.consumable_total_amount, - 'description': procedure.consumption_details - }) - - procedure_prescriptions = frappe.db.sql( - ''' - SELECT - pp.name, pp.procedure - FROM - `tabPatient Encounter` et, `tabProcedure Prescription` pp - WHERE - et.patient=%s - and pp.parent=et.name - and pp.procedure_created=0 - and pp.invoiced=0 - and pp.appointment_booked=0 - ''', (patient.name), as_dict=1) - - for prescription in procedure_prescriptions: - item, is_billable = frappe.get_cached_value('Clinical Procedure Template', prescription.procedure, ['item', 'is_billable']) - if is_billable: - clinical_procedures_to_invoice.append({ - 'reference_type': 'Procedure Prescription', - 'reference_name': prescription.name, - 'service': item - }) - - return clinical_procedures_to_invoice - - -def get_inpatient_services_to_invoice(patient, company): - services_to_invoice = [] - inpatient_services = frappe.db.sql( - ''' - SELECT - io.* - FROM - `tabInpatient Record` ip, `tabInpatient Occupancy` io - WHERE - ip.patient=%s - and ip.company=%s - and io.parent=ip.name - and io.left=1 - and io.invoiced=0 - ''', (patient.name, company), as_dict=1) - - for inpatient_occupancy in inpatient_services: - service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type') - service_unit_type = frappe.get_cached_doc('Healthcare Service Unit Type', service_unit_type) - if service_unit_type and service_unit_type.is_billable: - hours_occupied = time_diff_in_hours(inpatient_occupancy.check_out, inpatient_occupancy.check_in) - qty = 0.5 - if hours_occupied > 0: - actual_qty = hours_occupied / service_unit_type.no_of_hours - floor = math.floor(actual_qty) - decimal_part = actual_qty - floor - if decimal_part > 0.5: - qty = rounded(floor + 1, 1) - elif decimal_part < 0.5 and decimal_part > 0: - qty = rounded(floor + 0.5, 1) - if qty <= 0: - qty = 0.5 - services_to_invoice.append({ - 'reference_type': 'Inpatient Occupancy', - 'reference_name': inpatient_occupancy.name, - 'service': service_unit_type.item, 'qty': qty - }) - - return services_to_invoice - - -def get_therapy_plans_to_invoice(patient, company): - therapy_plans_to_invoice = [] - therapy_plans = frappe.get_list( - 'Therapy Plan', - fields=['therapy_plan_template', 'name'], - filters={ - 'patient': patient.name, - 'invoiced': 0, - 'company': company, - 'therapy_plan_template': ('!=', '') - } - ) - for plan in therapy_plans: - therapy_plans_to_invoice.append({ - 'reference_type': 'Therapy Plan', - 'reference_name': plan.name, - 'service': frappe.db.get_value('Therapy Plan Template', plan.therapy_plan_template, 'linked_item') - }) - - return therapy_plans_to_invoice - - -def get_therapy_sessions_to_invoice(patient, company): - therapy_sessions_to_invoice = [] - therapy_plans = frappe.db.get_all('Therapy Plan', {'therapy_plan_template': ('!=', '')}) - therapy_plans_created_from_template = [] - for entry in therapy_plans: - therapy_plans_created_from_template.append(entry.name) - - therapy_sessions = frappe.get_list( - 'Therapy Session', - fields='*', - filters={ - 'patient': patient.name, - 'invoiced': 0, - 'company': company, - 'therapy_plan': ('not in', therapy_plans_created_from_template) - } - ) - for therapy in therapy_sessions: - if not therapy.appointment: - if therapy.therapy_type and frappe.db.get_value('Therapy Type', therapy.therapy_type, 'is_billable'): - therapy_sessions_to_invoice.append({ - 'reference_type': 'Therapy Session', - 'reference_name': therapy.name, - 'service': frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item') - }) - - return therapy_sessions_to_invoice - -@frappe.whitelist() -def get_service_item_and_practitioner_charge(doc): - if isinstance(doc, string_types): - doc = json.loads(doc) - doc = frappe.get_doc(doc) - - service_item = None - practitioner_charge = None - department = doc.medical_department if doc.doctype == 'Patient Encounter' else doc.department - - is_inpatient = doc.inpatient_record - - if doc.get('appointment_type'): - service_item, practitioner_charge = get_appointment_type_service_item(doc.appointment_type, department, is_inpatient) - - if not service_item and not practitioner_charge: - service_item, practitioner_charge = get_practitioner_service_item(doc.practitioner, is_inpatient) - if not service_item: - service_item = get_healthcare_service_item(is_inpatient) - - if not service_item: - throw_config_service_item(is_inpatient) - - if not practitioner_charge: - throw_config_practitioner_charge(is_inpatient, doc.practitioner) - - return {'service_item': service_item, 'practitioner_charge': practitioner_charge} - - -def get_appointment_type_service_item(appointment_type, department, is_inpatient): - from erpnext.healthcare.doctype.appointment_type.appointment_type import get_service_item_based_on_department - - item_list = get_service_item_based_on_department(appointment_type, department) - service_item = None - practitioner_charge = None - - if item_list: - if is_inpatient: - service_item = item_list.get('inpatient_visit_charge_item') - practitioner_charge = item_list.get('inpatient_visit_charge') - else: - service_item = item_list.get('op_consulting_charge_item') - practitioner_charge = item_list.get('op_consulting_charge') - - return service_item, practitioner_charge - - -def throw_config_service_item(is_inpatient): - service_item_label = _('Out Patient Consulting Charge Item') - if is_inpatient: - service_item_label = _('Inpatient Visit Charge Item') - - msg = _(('Please Configure {0} in ').format(service_item_label) \ - + '''Healthcare Settings''') - frappe.throw(msg, title=_('Missing Configuration')) - - -def throw_config_practitioner_charge(is_inpatient, practitioner): - charge_name = _('OP Consulting Charge') - if is_inpatient: - charge_name = _('Inpatient Visit Charge') - - msg = _(('Please Configure {0} for Healthcare Practitioner').format(charge_name) \ - + ''' {0}'''.format(practitioner)) - frappe.throw(msg, title=_('Missing Configuration')) - - -def get_practitioner_service_item(practitioner, is_inpatient): - service_item = None - practitioner_charge = None - - if is_inpatient: - service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['inpatient_visit_charge_item', 'inpatient_visit_charge']) - else: - service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['op_consulting_charge_item', 'op_consulting_charge']) - - return service_item, practitioner_charge - - -def get_healthcare_service_item(is_inpatient): - service_item = None - - if is_inpatient: - service_item = frappe.db.get_single_value('Healthcare Settings', 'inpatient_visit_charge_item') - else: - service_item = frappe.db.get_single_value('Healthcare Settings', 'op_consulting_charge_item') - - return service_item - - -def get_practitioner_charge(practitioner, is_inpatient): - if is_inpatient: - practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, 'inpatient_visit_charge') - else: - practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, 'op_consulting_charge') - if practitioner_charge: - return practitioner_charge - return False - - -def manage_invoice_submit_cancel(doc, method): - if doc.items: - for item in doc.items: - if item.get('reference_dt') and item.get('reference_dn'): - if frappe.get_meta(item.reference_dt).has_field('invoiced'): - set_invoiced(item, method, doc.name) - - if method=='on_submit' and frappe.db.get_single_value('Healthcare Settings', 'create_lab_test_on_si_submit'): - create_multiple('Sales Invoice', doc.name) - - -def set_invoiced(item, method, ref_invoice=None): - invoiced = False - if method=='on_submit': - validate_invoiced_on_submit(item) - invoiced = True - - if item.reference_dt == 'Clinical Procedure': - service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') - if service_item == item.item_code: - frappe.db.set_value(item.reference_dt, item.reference_dn, 'consumption_invoiced', invoiced) - else: - frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced) - else: - frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced) - - if item.reference_dt == 'Patient Appointment': - if frappe.db.get_value('Patient Appointment', item.reference_dn, 'procedure_template'): - dt_from_appointment = 'Clinical Procedure' - else: - dt_from_appointment = 'Patient Encounter' - manage_doc_for_appointment(dt_from_appointment, item.reference_dn, invoiced) - - elif item.reference_dt == 'Lab Prescription': - manage_prescriptions(invoiced, item.reference_dt, item.reference_dn, 'Lab Test', 'lab_test_created') - - elif item.reference_dt == 'Procedure Prescription': - manage_prescriptions(invoiced, item.reference_dt, item.reference_dn, 'Clinical Procedure', 'procedure_created') - - -def validate_invoiced_on_submit(item): - if item.reference_dt == 'Clinical Procedure' and \ - frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') == item.item_code: - is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'consumption_invoiced') - else: - is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'invoiced') - if is_invoiced: - frappe.throw(_('The item referenced by {0} - {1} is already invoiced').format( - item.reference_dt, item.reference_dn)) - - -def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field): - created = frappe.db.get_value(ref_dt, ref_dn, created_check_field) - if created: - # Fetch the doc created for the prescription - doc_created = frappe.db.get_value(dt, {'prescription': ref_dn}) - frappe.db.set_value(dt, doc_created, 'invoiced', invoiced) - - -def check_fee_validity(appointment): - if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): - return - - validity = frappe.db.exists('Fee Validity', { - 'practitioner': appointment.practitioner, - 'patient': appointment.patient, - 'valid_till': ('>=', appointment.appointment_date) - }) - if not validity: - return - - validity = frappe.get_doc('Fee Validity', validity) - return validity - - -def manage_fee_validity(appointment): - fee_validity = check_fee_validity(appointment) - - if fee_validity: - if appointment.status == 'Cancelled' and fee_validity.visited > 0: - fee_validity.visited -= 1 - frappe.db.delete('Fee Validity Reference', {'appointment': appointment.name}) - elif fee_validity.status == 'Completed': - return - else: - fee_validity.visited += 1 - fee_validity.append('ref_appointments', { - 'appointment': appointment.name - }) - fee_validity.save(ignore_permissions=True) - else: - fee_validity = create_fee_validity(appointment) - return fee_validity - - -def manage_doc_for_appointment(dt_from_appointment, appointment, invoiced): - dn_from_appointment = frappe.db.get_value( - dt_from_appointment, - filters={'appointment': appointment} - ) - if dn_from_appointment: - frappe.db.set_value(dt_from_appointment, dn_from_appointment, 'invoiced', invoiced) - - -@frappe.whitelist() -def get_drugs_to_invoice(encounter): - encounter = frappe.get_doc('Patient Encounter', encounter) - if encounter: - patient = frappe.get_doc('Patient', encounter.patient) - if patient: - if patient.customer: - items_to_invoice = [] - for drug_line in encounter.drug_prescription: - if drug_line.drug_code: - qty = 1 - if frappe.db.get_value('Item', drug_line.drug_code, 'stock_uom') == 'Nos': - qty = drug_line.get_quantity() - - description = '' - if drug_line.dosage and drug_line.period: - description = _('{0} for {1}').format(drug_line.dosage, drug_line.period) - - items_to_invoice.append({ - 'drug_code': drug_line.drug_code, - 'quantity': qty, - 'description': description - }) - return items_to_invoice - else: - validate_customer_created(patient) - - -@frappe.whitelist() -def get_children(doctype, parent, company, is_root=False): - parent_fieldname = "parent_" + doctype.lower().replace(" ", "_") - fields = [ - "name as value", - "is_group as expandable", - "lft", - "rgt" - ] - # fields = [ "name", "is_group", "lft", "rgt" ] - filters = [["ifnull(`{0}`,'')".format(parent_fieldname), "=", "" if is_root else parent]] - - if is_root: - fields += ["service_unit_type"] if doctype == "Healthcare Service Unit" else [] - filters.append(["company", "=", company]) - - else: - fields += ["service_unit_type", "allow_appointments", "inpatient_occupancy", "occupancy_status"] if doctype == "Healthcare Service Unit" else [] - fields += [parent_fieldname + " as parent"] - - hc_service_units = frappe.get_list(doctype, fields=fields, filters=filters) - - if doctype == "Healthcare Service Unit": - for each in hc_service_units: - occupancy_msg = "" - if each["expandable"] == 1: - occupied = False - vacant = False - child_list = frappe.db.sql( - ''' - SELECT - name, occupancy_status - FROM - `tabHealthcare Service Unit` - WHERE - inpatient_occupancy = 1 - and lft > %s and rgt < %s - ''', (each['lft'], each['rgt'])) - - for child in child_list: - if not occupied: - occupied = 0 - if child[1] == "Occupied": - occupied += 1 - if not vacant: - vacant = 0 - if child[1] == "Vacant": - vacant += 1 - if vacant and occupied: - occupancy_total = vacant + occupied - occupancy_msg = str(occupied) + " Occupied out of " + str(occupancy_total) - each["occupied_out_of_vacant"] = occupancy_msg - return hc_service_units - - -@frappe.whitelist() -def get_patient_vitals(patient, from_date=None, to_date=None): - if not patient: return - - vitals = frappe.db.get_all('Vital Signs', filters={ - 'docstatus': 1, - 'patient': patient - }, order_by='signs_date, signs_time', fields=['*']) - - if len(vitals): - return vitals - return False - - -@frappe.whitelist() -def render_docs_as_html(docs): - # docs key value pair {doctype: docname} - docs_html = "
" - for doc in docs: - docs_html += render_doc_as_html(doc['doctype'], doc['docname'])['html'] + '
' - return {'html': docs_html} - - -@frappe.whitelist() -def render_doc_as_html(doctype, docname, exclude_fields = []): - #render document as html, three column layout will break - doc = frappe.get_doc(doctype, docname) - meta = frappe.get_meta(doctype) - doc_html = "
" - section_html = '' - section_label = '' - html = '' - sec_on = False - col_on = 0 - has_data = False - for df in meta.fields: - #on section break append append previous section and html to doc html - if df.fieldtype == "Section Break": - if has_data and col_on and sec_on: - doc_html += section_html + html + "
" - elif has_data and not col_on and sec_on: - doc_html += "
" \ - + section_html + html +"
" - while col_on: - doc_html += "
" - col_on -= 1 - sec_on = True - has_data= False - col_on = 0 - section_html = '' - html = '' - if df.label: - section_label = df.label - continue - #on column break append html to section html or doc html - if df.fieldtype == "Column Break": - if sec_on and has_data: - section_html += "
" + section_label + "" + html + "
" - elif has_data: - doc_html += "
" + html + "
" - elif sec_on and not col_on: - section_html += "
" - html = '' - col_on += 1 - if df.label: - html += '
' + df.label - continue - #on table iterate in items and create table based on in_list_view, append to section html or doc html - if df.fieldtype == 'Table': - items = doc.get(df.fieldname) - if not items: continue - child_meta = frappe.get_meta(df.options) - if not has_data : has_data = True - table_head = '' - table_row = '' - create_head = True - for item in items: - table_row += '' - for cdf in child_meta.fields: - if cdf.in_list_view: - if create_head: - table_head += '' + cdf.label + '' - if item.get(cdf.fieldname): - table_row += '' + str(item.get(cdf.fieldname)) \ - + '' - else: - table_row += '' - create_head = False - table_row += '' - if sec_on: - section_html += "" + table_head + table_row + '
' - else: - html += "" \ - + table_head + table_row + "
" - continue - - #on other field types add label and value to html - if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields: - if doc.get(df.fieldname): - formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc) - html += '
{0} : {1}'.format(df.label or df.fieldname, formatted_value) - - if not has_data : has_data = True - - if sec_on and col_on and has_data: - doc_html += section_html + html + '
' - elif sec_on and not col_on and has_data: - doc_html += "
" \ - + section_html + html +'
' - if doc_html: - doc_html = "
" %(doctype, docname) + doc_html + '
' - - return {'html': doc_html} diff --git a/erpnext/healthcare/web_form/__init__.py b/erpnext/healthcare/web_form/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/web_form/lab_test/__init__.py b/erpnext/healthcare/web_form/lab_test/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.js b/erpnext/healthcare/web_form/lab_test/lab_test.js deleted file mode 100644 index efcd8abc896..00000000000 --- a/erpnext/healthcare/web_form/lab_test/lab_test.js +++ /dev/null @@ -1,34 +0,0 @@ -frappe.ready(function() { - // bind events here - var normal_test_items = $('div[data-fieldname = "normal_test_items"]'); - var normal_test_items_add_btn = $('button[data-fieldname = "normal_test_items"]'); - var special_test_items = $('div[data-fieldname = "special_test_items"]'); - var special_test_items_add_btn = $('button[data-fieldname = "special_test_items"]'); - var sensitivity_test_items = $('div[data-fieldname = "sensitivity_test_items"]'); - var sensitivity_test_items_add_btn = $('button[data-fieldname = "sensitivity_test_items"]'); - var sensitivity_toggle = $('input[name = "sensitivity_toggle"]'); - var special_toggle = $('input[name = "special_toggle"]'); - var normal_toggle = $('input[name = "normal_toggle"]'); - if(normal_toggle.val() == 1){ - // normal_test_items[0].style.display = "none"; - // normal_test_items[0].setAttribute("hidden", true); - // normal_test_items_add_btn[0].style.visibility = "hidden"; - special_test_items[0].style.display = "none"; - special_test_items_add_btn[0].style.display = "none"; - sensitivity_test_items[0].style.display = "none"; - sensitivity_test_items_add_btn[0].style.display = "none"; - normal_test_items_add_btn[0].style.display = "none"; - }else if(sensitivity_toggle.val() == 1){ - special_test_items[0].style.display = "none"; - special_test_items_add_btn[0].style.display = "none"; - normal_test_items[0].style.display = "none"; - normal_test_items_add_btn[0].style.display = "none"; - sensitivity_test_items_add_btn[0].style.display = "none"; - }else if(special_toggle.val() == 1){ - normal_test_items[0].style.display = "none"; - normal_test_items_add_btn[0].style.display = "none"; - sensitivity_test_items[0].style.display = "none"; - sensitivity_test_items_add_btn[0].style.display = "none"; - special_test_items_add_btn[0].style.display = "none"; - } -}); diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.json b/erpnext/healthcare/web_form/lab_test/lab_test.json deleted file mode 100644 index 35099174e8d..00000000000 --- a/erpnext/healthcare/web_form/lab_test/lab_test.json +++ /dev/null @@ -1,460 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 1, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 1, - "allow_print": 1, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2017-06-06 16:12:33.052258", - "currency": "INR", - "doc_type": "Lab Test", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "Lab Test", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2020-06-22 12:59:49.126398", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "lab-test", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "print_format": "Lab Test Print", - "published": 1, - "route": "lab-test", - "route_to_success_link": 0, - "show_attachments": 0, - "show_in_grid": 0, - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/lab-test", - "title": "Lab Test", - "web_form_fields": [ - { - "allow_read_on_all_link_options": 0, - "fieldname": "lab_test_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Test Name", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "label": "Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "column_break_26", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "label": "Company", - "max_length": 0, - "max_value": 0, - "options": "Company", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "label": "Status", - "max_length": 0, - "max_value": 0, - "options": "Draft\nCompleted\nApproved\nRejected\nCancelled", - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "submitted_date", - "fieldtype": "Datetime", - "hidden": 0, - "label": "Submitted Date", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_first", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "label": "Patient", - "max_length": 0, - "max_value": 0, - "options": "Patient", - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Patient Name", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "patient_age", - "fieldtype": "Data", - "hidden": 0, - "label": "Age", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "patient_sex", - "fieldtype": "Link", - "hidden": 0, - "label": "Gender", - "max_length": 0, - "max_value": 0, - "options": "Gender", - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "inpatient_record", - "fieldtype": "Link", - "hidden": 0, - "label": "Inpatient Record", - "max_length": 0, - "max_value": 0, - "options": "Inpatient Record", - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "report_preference", - "fieldtype": "Data", - "hidden": 0, - "label": "Report Preference", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "email", - "fieldtype": "Data", - "hidden": 1, - "label": "Email", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 1, - "label": "Mobile", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "c_b", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "practitioner", - "fieldtype": "Link", - "hidden": 0, - "label": "Requesting Practitioner", - "max_length": 0, - "max_value": 0, - "options": "Healthcare Practitioner", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "practitioner_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Requesting Practitioner", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "requesting_department", - "fieldtype": "Link", - "hidden": 0, - "label": "Requesting Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "label": "Employee (Lab Technician)", - "max_length": 0, - "max_value": 0, - "options": "Employee", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "employee_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Lab Technician Name", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "employee_designation", - "fieldtype": "Data", - "hidden": 0, - "label": "Lab Technician Designation", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_normal", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "lab_test_html", - "fieldtype": "HTML", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "normal_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Normal Test Result", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_descriptive", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "descriptive_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Descriptive Test Result", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "depends_on": "special_toggle", - "fieldname": "organisms_section", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "organisms", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Organism Test Result", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_sensitivity", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sensitivity_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Sensitivity Test Result", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_comments", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "lab_test_comment", - "fieldtype": "Text", - "hidden": 0, - "label": "Comments", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_customresult", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Custom Result", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "custom_result", - "fieldtype": "Text Editor", - "hidden": 0, - "label": "Custom Result", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.py b/erpnext/healthcare/web_form/lab_test/lab_test.py deleted file mode 100644 index 5a8c8a421cd..00000000000 --- a/erpnext/healthcare/web_form/lab_test/lab_test.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def get_context(context): - context.read_only = 1 - -def get_list_context(context): - context.row_template = "erpnext/templates/includes/healthcare/lab_test_row_template.html" - context.get_list = get_lab_test_list - -def get_lab_test_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by='modified desc'): - patient = get_patient() - lab_tests = frappe.db.sql("""select * from `tabLab Test` - where patient = %s order by result_date""", patient, as_dict = True) - return lab_tests - -def get_patient(): - return frappe.get_value("Patient",{"email": frappe.session.user}, "name") - -def has_website_permission(doc, ptype, user, verbose=False): - if doc.patient == get_patient(): - return True - else: - return False diff --git a/erpnext/healthcare/web_form/patient_appointments/__init__.py b/erpnext/healthcare/web_form/patient_appointments/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.js b/erpnext/healthcare/web_form/patient_appointments/patient_appointments.js deleted file mode 100644 index f09e5409192..00000000000 --- a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}); diff --git a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.json b/erpnext/healthcare/web_form/patient_appointments/patient_appointments.json deleted file mode 100644 index e9cf7a8c97f..00000000000 --- a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 1, - "allow_print": 1, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2017-06-07 15:30:44.984832", - "currency": "INR", - "doc_type": "Patient Appointment", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "Patient Appointments", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2018-07-16 13:11:08.626316", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "patient-appointments", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "published": 1, - "route": "patient-appointments", - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/patient-appointments", - "title": "Patient Appointments", - "web_form_fields": [ - { - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "label": "Patient", - "max_length": 0, - "max_value": 0, - "options": "Patient", - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "practitioner", - "fieldtype": "Link", - "hidden": 0, - "label": "Healthcare Practitioner", - "max_length": 0, - "max_value": 0, - "options": "Healthcare Practitioner", - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "appointment_date", - "fieldtype": "Date", - "hidden": 0, - "label": "Date", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "appointment_time", - "fieldtype": "Data", - "hidden": 0, - "label": "Time", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, - { - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "label": "Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 0, - "reqd": 0 - }, - { - "fieldname": "appointment_type", - "fieldtype": "Link", - "hidden": 0, - "label": "Type", - "max_length": 0, - "max_value": 0, - "options": "Appointment Type", - "read_only": 0, - "reqd": 0 - }, - { - "default": "Scheduled", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "label": "Status", - "max_length": 0, - "max_value": 0, - "options": "\nScheduled\nOpen\nClosed\nPending\nCancelled", - "read_only": 1, - "reqd": 0 - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.py b/erpnext/healthcare/web_form/patient_appointments/patient_appointments.py deleted file mode 100644 index 09bcb42b579..00000000000 --- a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def get_context(context): - context.read_only = 1 - -def get_list_context(context): - context.row_template = "erpnext/templates/includes/healthcare/appointment_row_template.html" - context.get_list = get_appointment_list - -def get_appointment_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by='modified desc'): - patient = get_patient() - lab_tests = frappe.db.sql("""select * from `tabPatient Appointment` - where patient = %s and (status = 'Open' or status = 'Scheduled') order by appointment_date""", patient, as_dict = True) - return lab_tests - -def get_patient(): - return frappe.get_value("Patient",{"email": frappe.session.user}, "name") - -def has_website_permission(doc, ptype, user, verbose=False): - if doc.patient == get_patient(): - return True - else: - return False diff --git a/erpnext/healthcare/web_form/patient_registration/__init__.py b/erpnext/healthcare/web_form/patient_registration/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/web_form/patient_registration/patient_registration.js b/erpnext/healthcare/web_form/patient_registration/patient_registration.js deleted file mode 100644 index f09e5409192..00000000000 --- a/erpnext/healthcare/web_form/patient_registration/patient_registration.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}); diff --git a/erpnext/healthcare/web_form/patient_registration/patient_registration.json b/erpnext/healthcare/web_form/patient_registration/patient_registration.json deleted file mode 100644 index 9ed92de16f5..00000000000 --- a/erpnext/healthcare/web_form/patient_registration/patient_registration.json +++ /dev/null @@ -1,397 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 0, - "allow_print": 0, - "amount": 0.0, - "amount_based_on_field": 0, - "button_label": "Register", - "creation": "2020-03-03 01:01:16.250607", - "currency": "INR", - "doc_type": "Patient", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "", - "is_standard": 1, - "login_required": 0, - "max_attachment_size": 0, - "modified": "2020-03-26 17:25:15.361918", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "patient-registration", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "published": 1, - "route": "patient-registration", - "route_to_success_link": 0, - "show_attachments": 0, - "show_in_grid": 0, - "show_sidebar": 1, - "sidebar_items": [], - "success_message": "Registration Successfully. Thank You!", - "success_url": "/patient-registration", - "title": "Patient Registration", - "web_form_fields": [ - { - "allow_read_on_all_link_options": 0, - "fieldname": "basic_info", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Patient Demographics", - "max_length": 0, - "max_value": 0, - "options": "fa fa-user", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "first_name", - "fieldtype": "Data", - "hidden": 0, - "label": "First Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "middle_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Middle Name (optional)", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "last_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Last Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sex", - "fieldtype": "Link", - "hidden": 0, - "label": "Gender", - "max_length": 0, - "max_value": 0, - "options": "Gender", - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "blood_group", - "fieldtype": "Select", - "hidden": 0, - "label": "Blood Group", - "max_length": 0, - "max_value": 0, - "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "dob", - "fieldtype": "Date", - "hidden": 0, - "label": "Date of birth", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 0, - "label": "Mobile", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "label": "Email", - "max_length": 0, - "max_value": 0, - "options": "Email", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "phone", - "fieldtype": "Data", - "hidden": 0, - "label": "Phone", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Personal Details", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "occupation", - "fieldtype": "Data", - "hidden": 0, - "label": "Occupation", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "marital_status", - "fieldtype": "Select", - "hidden": 0, - "label": "Marital Status", - "max_length": 0, - "max_value": 0, - "options": "\nSingle\nMarried\nDivorced\nWidow", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "allergy_medical_and_surgical_history", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Allergies, Medical and Surgical History", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "allergies", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Allergies", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "medication", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Medication", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "column_break_20", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "medical_history", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Medical History", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "surgical_history", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Surgical History", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "risk_factors", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Risk Factors", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "default": "0", - "fieldname": "tobacco_past_use", - "fieldtype": "Check", - "hidden": 0, - "label": "Check if you have a history of Tobacco Consumption", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "default": "0", - "fieldname": "tobacco_current_use", - "fieldtype": "Check", - "hidden": 0, - "label": "Check if you consume Tobacco", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "default": "0", - "fieldname": "alcohol_past_use", - "fieldtype": "Check", - "hidden": 0, - "label": "Check if you have a history of Alcohol Consumption", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "default": "0", - "fieldname": "alcohol_current_use", - "fieldtype": "Check", - "hidden": 0, - "label": "Check if you consume Alcohol", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "column_break_32", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "surrounding_factors", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Occupational Hazards and Environmental Factors", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "other_risk_factors", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Other Risk Factors", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/web_form/patient_registration/patient_registration.py b/erpnext/healthcare/web_form/patient_registration/patient_registration.py deleted file mode 100644 index 1bc4d1874c8..00000000000 --- a/erpnext/healthcare/web_form/patient_registration/patient_registration.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals - -def get_context(context): - # do your magic here - pass diff --git a/erpnext/healthcare/web_form/personal_details/__init__.py b/erpnext/healthcare/web_form/personal_details/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/web_form/personal_details/personal_details.js b/erpnext/healthcare/web_form/personal_details/personal_details.js deleted file mode 100644 index f09e5409192..00000000000 --- a/erpnext/healthcare/web_form/personal_details/personal_details.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}); diff --git a/erpnext/healthcare/web_form/personal_details/personal_details.json b/erpnext/healthcare/web_form/personal_details/personal_details.json deleted file mode 100644 index aad987aeb9e..00000000000 --- a/erpnext/healthcare/web_form/personal_details/personal_details.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 0, - "allow_print": 0, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2018-07-03 19:33:23.332661", - "currency": "INR", - "doc_type": "Patient", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2018-07-04 17:22:28.936442", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "personal-details", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "published": 1, - "route": "personal-details", - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/personal-details", - "title": "Personal Details", - "web_form_fields": [ - { - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Full Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "sex", - "fieldtype": "Select", - "hidden": 0, - "label": "Gender", - "max_length": 0, - "max_value": 0, - "options": "\nMale\nFemale\nOther", - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "dob", - "fieldtype": "Date", - "hidden": 0, - "label": "Date of birth", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 0, - "label": "Mobile", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, - { - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "label": "Email", - "max_length": 0, - "max_value": 0, - "options": "Email", - "read_only": 1, - "reqd": 0 - } - ] -} diff --git a/erpnext/healthcare/web_form/personal_details/personal_details.py b/erpnext/healthcare/web_form/personal_details/personal_details.py deleted file mode 100644 index fe46d7b22d7..00000000000 --- a/erpnext/healthcare/web_form/personal_details/personal_details.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from frappe import _ - -no_cache = 1 - -def get_context(context): - if frappe.session.user=='Guest': - frappe.throw(_("You need to be logged in to access this page"), frappe.PermissionError) - - context.show_sidebar=True - - if frappe.db.exists("Patient", {'email': frappe.session.user}): - patient = frappe.get_doc("Patient", {'email': frappe.session.user}) - context.doc = patient - frappe.form_dict.new = 0 - frappe.form_dict.name = patient.name - -def get_patient(): - return frappe.get_value("Patient",{"email": frappe.session.user}, "name") - -def has_website_permission(doc, ptype, user, verbose=False): - if doc.name == get_patient(): - return True - else: - return False diff --git a/erpnext/healthcare/web_form/prescription/__init__.py b/erpnext/healthcare/web_form/prescription/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/healthcare/web_form/prescription/prescription.js b/erpnext/healthcare/web_form/prescription/prescription.js deleted file mode 100644 index f09e5409192..00000000000 --- a/erpnext/healthcare/web_form/prescription/prescription.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}); diff --git a/erpnext/healthcare/web_form/prescription/prescription.json b/erpnext/healthcare/web_form/prescription/prescription.json deleted file mode 100644 index 8e19e325edd..00000000000 --- a/erpnext/healthcare/web_form/prescription/prescription.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 1, - "allow_print": 1, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2017-06-06 17:13:19.101374", - "currency": "INR", - "doc_type": "Patient Encounter", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "Patient Prescriptions", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2018-09-04 11:53:40.954517", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "prescription", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "print_format": "Encounter Print", - "published": 1, - "route": "prescription", - "show_in_grid": 0, - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/prescription", - "title": "Prescription", - "web_form_fields": [ - { - "fieldname": "practitioner", - "fieldtype": "Link", - "hidden": 0, - "label": "Healthcare Practitioner", - "max_length": 0, - "max_value": 0, - "options": "Healthcare Practitioner", - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "fieldname": "visit_department", - "fieldtype": "Link", - "hidden": 0, - "label": "Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "default": "Today", - "fieldname": "encounter_date", - "fieldtype": "Date", - "hidden": 0, - "label": "Encounter Date", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "default": "", - "fieldname": "encounter_time", - "fieldtype": "Data", - "hidden": 0, - "label": "Encounter Time", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "fieldname": "drug_prescription", - "fieldtype": "Table", - "hidden": 0, - "label": "Drug Prescription", - "max_length": 0, - "max_value": 0, - "options": "Drug Prescription", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "fieldname": "lab_test_prescription", - "fieldtype": "Table", - "hidden": 0, - "label": "Investigations", - "max_length": 0, - "max_value": 0, - "options": "Lab Prescription", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "fieldname": "encounter_comment", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Review Details", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/web_form/prescription/prescription.py b/erpnext/healthcare/web_form/prescription/prescription.py deleted file mode 100644 index efdeaa906ae..00000000000 --- a/erpnext/healthcare/web_form/prescription/prescription.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def get_context(context): - context.read_only = 1 - -def get_list_context(context): - context.row_template = "erpnext/templates/includes/healthcare/prescription_row_template.html" - context.get_list = get_encounter_list - -def get_encounter_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by='modified desc'): - patient = get_patient() - encounters = frappe.db.sql("""select * from `tabPatient Encounter` - where patient = %s order by creation desc""", patient, as_dict = True) - return encounters - -def get_patient(): - return frappe.get_value("Patient",{"email": frappe.session.user}, "name") - -def has_website_permission(doc, ptype, user, verbose=False): - if doc.patient == get_patient(): - return True - else: - return False diff --git a/erpnext/healthcare/workspace/healthcare/healthcare.json b/erpnext/healthcare/workspace/healthcare/healthcare.json deleted file mode 100644 index 55132f3695e..00000000000 --- a/erpnext/healthcare/workspace/healthcare/healthcare.json +++ /dev/null @@ -1,594 +0,0 @@ -{ - "category": "", - "charts": [ - { - "chart_name": "Patient Appointments", - "label": "Patient Appointments" - } - ], - "charts_label": "", - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Healthcare\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Patient Appointments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient Appointment\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Service Unit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Practitioner\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient History\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Rehabilitation and Physiotherapy\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Records and History\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", - "creation": "2020-03-02 17:23:17.919682", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, - "for_user": "", - "hide_custom": 0, - "icon": "healthcare", - "idx": 0, - "is_default": 0, - "is_standard": 0, - "label": "Healthcare", - "links": [ - { - "hidden": 0, - "is_query_report": 0, - "label": "Masters", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient", - "link_count": 0, - "link_to": "Patient", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Practitioner", - "link_count": 0, - "link_to": "Healthcare Practitioner", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Practitioner Schedule", - "link_count": 0, - "link_to": "Practitioner Schedule", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Medical Department", - "link_count": 0, - "link_to": "Medical Department", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Service Unit Type", - "link_count": 0, - "link_to": "Healthcare Service Unit Type", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Service Unit", - "link_count": 0, - "link_to": "Healthcare Service Unit", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Medical Code Standard", - "link_count": 0, - "link_to": "Medical Code Standard", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Medical Code", - "link_count": 0, - "link_to": "Medical Code", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Consultation Setup", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Appointment Type", - "link_count": 0, - "link_to": "Appointment Type", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Clinical Procedure Template", - "link_count": 0, - "link_to": "Clinical Procedure Template", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Prescription Dosage", - "link_count": 0, - "link_to": "Prescription Dosage", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Prescription Duration", - "link_count": 0, - "link_to": "Prescription Duration", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Antibiotic", - "link_count": 0, - "link_to": "Antibiotic", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Consultation", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Appointment", - "link_count": 0, - "link_to": "Patient Appointment", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Clinical Procedure", - "link_count": 0, - "link_to": "Clinical Procedure", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Encounter", - "link_count": 0, - "link_to": "Patient Encounter", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Vital Signs", - "link_count": 0, - "link_to": "Vital Signs", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Complaint", - "link_count": 0, - "link_to": "Complaint", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Diagnosis", - "link_count": 0, - "link_to": "Diagnosis", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Fee Validity", - "link_count": 0, - "link_to": "Fee Validity", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Settings", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Settings", - "link_count": 0, - "link_to": "Healthcare Settings", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Laboratory Setup", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Lab Test Template", - "link_count": 0, - "link_to": "Lab Test Template", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Lab Test Sample", - "link_count": 0, - "link_to": "Lab Test Sample", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Lab Test UOM", - "link_count": 0, - "link_to": "Lab Test UOM", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Sensitivity", - "link_count": 0, - "link_to": "Sensitivity", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Laboratory", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Lab Test", - "link_count": 0, - "link_to": "Lab Test", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Sample Collection", - "link_count": 0, - "link_to": "Sample Collection", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Dosage Form", - "link_count": 0, - "link_to": "Dosage Form", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Rehabilitation and Physiotherapy", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Exercise Type", - "link_count": 0, - "link_to": "Exercise Type", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Therapy Type", - "link_count": 0, - "link_to": "Therapy Type", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Therapy Plan", - "link_count": 0, - "link_to": "Therapy Plan", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Therapy Session", - "link_count": 0, - "link_to": "Therapy Session", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Assessment Template", - "link_count": 0, - "link_to": "Patient Assessment Template", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Assessment", - "link_count": 0, - "link_to": "Patient Assessment", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Records and History", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient History", - "link_count": 0, - "link_to": "patient_history", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Progress", - "link_count": 0, - "link_to": "patient-progress", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Medical Record", - "link_count": 0, - "link_to": "Patient Medical Record", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Inpatient Record", - "link_count": 0, - "link_to": "Inpatient Record", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Reports", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 1, - "label": "Patient Appointment Analytics", - "link_count": 0, - "link_to": "Patient Appointment Analytics", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 1, - "label": "Lab Test Report", - "link_count": 0, - "link_to": "Lab Test Report", - "link_type": "Report", - "onboard": 0, - "type": "Link" - } - ], - "modified": "2021-08-05 12:15:59.434612", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare", - "onboarding": "Healthcare", - "owner": "Administrator", - "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, - "public": 1, - "restrict_to_domain": "Healthcare", - "roles": [], - "sequence_id": 13, - "shortcuts": [ - { - "color": "Orange", - "format": "{} Open", - "label": "Patient Appointment", - "link_to": "Patient Appointment", - "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}", - "type": "DocType" - }, - { - "color": "Orange", - "format": "{} Active", - "label": "Patient", - "link_to": "Patient", - "stats_filter": "{\n \"status\": \"Active\"\n}", - "type": "DocType" - }, - { - "color": "Green", - "format": "{} Vacant", - "label": "Healthcare Service Unit", - "link_to": "Healthcare Service Unit", - "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}", - "type": "DocType" - }, - { - "label": "Healthcare Practitioner", - "link_to": "Healthcare Practitioner", - "type": "DocType" - }, - { - "label": "Patient History", - "link_to": "patient_history", - "type": "Page" - }, - { - "label": "Dashboard", - "link_to": "Healthcare", - "type": "Dashboard" - } - ], - "title": "Healthcare" -} \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 4854bfd1e16..63530ea29f3 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from frappe import _ app_name = "erpnext" @@ -61,6 +60,7 @@ treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Grou # website update_website_context = ["erpnext.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"] my_account_context = "erpnext.shopping_cart.utils.update_my_account_context" +webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context" calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"] @@ -68,7 +68,6 @@ domains = { 'Agriculture': 'erpnext.domains.agriculture', 'Distribution': 'erpnext.domains.distribution', 'Education': 'erpnext.domains.education', - 'Healthcare': 'erpnext.domains.healthcare', 'Hospitality': 'erpnext.domains.hospitality', 'Manufacturing': 'erpnext.domains.manufacturing', 'Non Profit': 'erpnext.domains.non_profit', @@ -80,7 +79,7 @@ website_generators = ["Item Group", "Item", "BOM", "Sales Partner", "Job Opening", "Student Admission"] website_context = { - "favicon": "/assets/erpnext/images/erpnext-favicon.svg", + "favicon": "/assets/erpnext/images/erpnext-favicon.svg", "splash_image": "/assets/erpnext/images/erpnext-logo.svg" } @@ -163,7 +162,6 @@ website_route_rules = [ ] standard_portal_menu_items = [ - {"title": _("Personal Details"), "route": "/personal-details", "reference_doctype": "Patient", "role": "Patient"}, {"title": _("Projects"), "route": "/project", "reference_doctype": "Project"}, {"title": _("Request for Quotations"), "route": "/rfq", "reference_doctype": "Request for Quotation", "role": "Supplier"}, {"title": _("Supplier Quotation"), "route": "/supplier-quotations", "reference_doctype": "Supplier Quotation", "role": "Supplier"}, @@ -176,9 +174,6 @@ standard_portal_menu_items = [ {"title": _("Issues"), "route": "/issues", "reference_doctype": "Issue", "role":"Customer"}, {"title": _("Addresses"), "route": "/addresses", "reference_doctype": "Address"}, {"title": _("Timesheets"), "route": "/timesheets", "reference_doctype": "Timesheet", "role":"Customer"}, - {"title": _("Lab Test"), "route": "/lab-test", "reference_doctype": "Lab Test", "role":"Patient"}, - {"title": _("Prescription"), "route": "/prescription", "reference_doctype": "Patient Encounter", "role":"Patient"}, - {"title": _("Patient Appointment"), "route": "/patient-appointments", "reference_doctype": "Patient Appointment", "role":"Patient"}, {"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role":"Student"}, {"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"}, {"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"}, @@ -213,10 +208,6 @@ has_website_permission = { "Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission", "Issue": "erpnext.support.doctype.issue.issue.has_website_permission", "Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission", - "Lab Test": "erpnext.healthcare.web_form.lab_test.lab_test.has_website_permission", - "Patient Encounter": "erpnext.healthcare.web_form.prescription.prescription.has_website_permission", - "Patient Appointment": "erpnext.healthcare.web_form.patient_appointments.patient_appointments.has_website_permission", - "Patient": "erpnext.healthcare.web_form.personal_details.personal_details.has_website_permission" } dump_report_map = "erpnext.startup.report_data_map.data_map" @@ -225,15 +216,11 @@ before_tests = "erpnext.setup.utils.before_tests" standard_queries = { "Customer": "erpnext.selling.doctype.customer.customer.get_customer_list", - "Healthcare Practitioner": "erpnext.healthcare.doctype.healthcare_practitioner.healthcare_practitioner.get_practitioner_list" } doc_events = { "*": { "validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply", - "on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", - "on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", - "on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record" }, "Stock Entry": { "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", @@ -264,11 +251,13 @@ doc_events = { "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", + "erpnext.regional.saudi_arabia.utils.create_qr_code", "erpnext.erpnext_integrations.taxjar_integration.create_transaction" ], "on_cancel": [ "erpnext.regional.italy.utils.sales_invoice_on_cancel", - "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" + "erpnext.erpnext_integrations.taxjar_integration.delete_transaction", + "erpnext.regional.saudi_arabia.utils.delete_qr_code_file" ], "on_trash": "erpnext.regional.check_deletion_permission", "validate": [ @@ -276,6 +265,9 @@ doc_events = { "erpnext.regional.india.utils.update_taxable_values" ] }, + "POS Invoice": { + "on_submit": ["erpnext.regional.saudi_arabia.utils.create_qr_code"] + }, "Purchase Invoice": { "validate": [ "erpnext.regional.india.utils.validate_reverse_charge_transaction", @@ -286,11 +278,16 @@ doc_events = { ] }, "Payment Entry": { + "validate": "erpnext.regional.india.utils.update_place_of_supply", "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"], "on_trash": "erpnext.regional.check_deletion_permission" }, 'Address': { - 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category'] + 'validate': [ + 'erpnext.regional.india.utils.validate_gstin_for_india', + 'erpnext.regional.italy.utils.set_state_code', + 'erpnext.regional.india.utils.update_gst_category', + ], }, 'Supplier': { 'validate': 'erpnext.regional.india.utils.validate_pan_for_india' @@ -301,7 +298,7 @@ doc_events = { "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", - "validate": "erpnext.crm.utils.update_lead_phone_numbers" + "validate": ["erpnext.crm.utils.update_lead_phone_numbers"] }, "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" @@ -310,7 +307,11 @@ doc_events = { 'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"] }, "Company": { - "on_trash": "erpnext.regional.india.utils.delete_gst_settings_for_company" + "on_trash": ["erpnext.regional.india.utils.delete_gst_settings_for_company", + "erpnext.regional.saudi_arabia.utils.delete_vat_settings_for_company"] + }, + "Integration Request": { + "validate": "erpnext.accounts.doctype.payment_request.payment_request.validate_payment" } } @@ -319,7 +320,6 @@ doc_events = { # if payment entry not in auto cancel exempted doctypes it will cancel payment entry. auto_cancel_exempted_doctypes= [ "Payment Entry", - "Inpatient Medication Entry" ] after_migrate = ["erpnext.setup.install.update_select_perm_after_install"] @@ -332,7 +332,7 @@ scheduler_events = { }, "all": [ "erpnext.projects.doctype.project.project.project_status_update_reminder", - "erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder", + "erpnext.hr.doctype.interview.interview.send_interview_reminder", "erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts" ], "hourly": [ @@ -373,10 +373,10 @@ scheduler_events = { "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts", "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status", "erpnext.selling.doctype.quotation.quotation.set_expired_status", - "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status", "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.non_profit.doctype.membership.membership.set_expired_status" + "erpnext.hr.doctype.interview.interview.send_daily_feedback_reminder" ], "daily_long": [ "erpnext.setup.doctype.email_digest.email_digest.send", @@ -431,7 +431,7 @@ accounting_dimension_doctypes = ["GL Entry", "Sales Invoice", "Purchase Invoice" "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", - "Subscription Plan" + "Subscription Plan", "POS Invoice", "POS Invoice Item" ] regional_overrides = { @@ -526,32 +526,6 @@ global_search_doctypes = { {"doctype": "Maintenance Visit", "index": 46}, {"doctype": "Warranty Claim", "index": 47}, ], - "Healthcare": [ - {'doctype': 'Patient', 'index': 1}, - {'doctype': 'Medical Department', 'index': 2}, - {'doctype': 'Vital Signs', 'index': 3}, - {'doctype': 'Healthcare Practitioner', 'index': 4}, - {'doctype': 'Patient Appointment', 'index': 5}, - {'doctype': 'Healthcare Service Unit', 'index': 6}, - {'doctype': 'Patient Encounter', 'index': 7}, - {'doctype': 'Antibiotic', 'index': 8}, - {'doctype': 'Diagnosis', 'index': 9}, - {'doctype': 'Lab Test', 'index': 10}, - {'doctype': 'Clinical Procedure', 'index': 11}, - {'doctype': 'Inpatient Record', 'index': 12}, - {'doctype': 'Sample Collection', 'index': 13}, - {'doctype': 'Patient Medical Record', 'index': 14}, - {'doctype': 'Appointment Type', 'index': 15}, - {'doctype': 'Fee Validity', 'index': 16}, - {'doctype': 'Practitioner Schedule', 'index': 17}, - {'doctype': 'Dosage Form', 'index': 18}, - {'doctype': 'Lab Test Sample', 'index': 19}, - {'doctype': 'Prescription Duration', 'index': 20}, - {'doctype': 'Prescription Dosage', 'index': 21}, - {'doctype': 'Sensitivity', 'index': 22}, - {'doctype': 'Complaint', 'index': 23}, - {'doctype': 'Medical Code', 'index': 24}, - ], "Education": [ {'doctype': 'Article', 'index': 1}, {'doctype': 'Video', 'index': 2}, diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.py b/erpnext/hotels/doctype/hotel_room/hotel_room.py index 6a2fc02574f..e4bd1c88462 100644 --- a/erpnext/hotels/doctype/hotel_room/hotel_room.py +++ b/erpnext/hotels/doctype/hotel_room/hotel_room.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class HotelRoom(Document): def validate(self): if not self.capacity: diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js b/erpnext/hotels/doctype/hotel_room/test_hotel_room.js deleted file mode 100644 index 8b2b83330f3..00000000000 --- a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room - () => frappe.tests.make('Hotel Room', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py b/erpnext/hotels/doctype/hotel_room/test_hotel_room.py index e307b5ac37a..95efe2c6068 100644 --- a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py +++ b/erpnext/hotels/doctype/hotel_room/test_hotel_room.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + test_dependencies = ["Hotel Room Package"] test_records = [ dict(doctype="Hotel Room", name="1001", diff --git a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py index 69da007fc6b..166493124a7 100644 --- a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py +++ b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class HotelRoomAmenity(Document): pass diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py index 8a62eea8fa1..aedc83a8468 100644 --- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py +++ b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class HotelRoomPackage(Document): def validate(self): if not self.item: diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js deleted file mode 100644 index f1ebad41d41..00000000000 --- a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Package", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Package - () => frappe.tests.make('Hotel Room Package', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py index ebf7f2b7e9f..749731f4918 100644 --- a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py +++ b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + test_records = [ dict(doctype='Item', item_code='Breakfast', item_group='Products', is_stock_item=0), diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py index 8eee0f24c5c..d28e5734264 100644 --- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py +++ b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class HotelRoomPricing(Document): pass diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js deleted file mode 100644 index ba0d1fd3e96..00000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Pricing", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Pricing - () => frappe.tests.make('Hotel Room Pricing', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py index b73fd44cd5e..34550096dd9 100644 --- a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py +++ b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + test_dependencies = ["Hotel Room Package"] test_records = [ dict(doctype="Hotel Room Pricing", enabled=1, diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py index 6bf01bf941b..2e6bb5fac29 100644 --- a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py +++ b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class HotelRoomPricingItem(Document): pass diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py index 9ae9fcfaf83..ebbdb6ec6c5 100644 --- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py +++ b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class HotelRoomPricingPackage(Document): pass diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js deleted file mode 100644 index 73a561c408c..00000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Pricing Package", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Pricing Package - () => frappe.tests.make('Hotel Room Pricing Package', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py index fec1c86ad02..196e6504b51 100644 --- a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py +++ b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestHotelRoomPricingPackage(unittest.TestCase): pass diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py index a8ebe8610ec..7725955396b 100644 --- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py +++ b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py @@ -1,12 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, json -from frappe.model.document import Document + +import json + +import frappe from frappe import _ -from frappe.utils import date_diff, add_days, flt +from frappe.model.document import Document +from frappe.utils import add_days, date_diff, flt + class HotelRoomUnavailableError(frappe.ValidationError): pass class HotelRoomPricingNotSetError(frappe.ValidationError): pass diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js deleted file mode 100644 index 2897139359b..00000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Reservation", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Reservation - () => frappe.tests.make('Hotel Room Reservation', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py index d4979968e61..bb32a27fa7c 100644 --- a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py +++ b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py @@ -1,11 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import HotelRoomPricingNotSetError, HotelRoomUnavailableError + +from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import ( + HotelRoomPricingNotSetError, + HotelRoomUnavailableError, +) + test_dependencies = ["Hotel Room Package", "Hotel Room Pricing", "Hotel Room"] class TestHotelRoomReservation(unittest.TestCase): diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py index 3406faea0e6..41d86ddca65 100644 --- a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py +++ b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class HotelRoomReservationItem(Document): pass diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py index 1fc1303f393..7ab529fee96 100644 --- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py +++ b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class HotelRoomType(Document): pass diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js deleted file mode 100644 index e2dd5780e3c..00000000000 --- a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Type - () => frappe.tests.make('Hotel Room Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py index 3b243e95669..8d1147d0f20 100644 --- a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py +++ b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestHotelRoomType(unittest.TestCase): pass diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/hotel_settings.py index d78bca149df..8376d509693 100644 --- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.py +++ b/erpnext/hotels/doctype/hotel_settings/hotel_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class HotelSettings(Document): pass diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js deleted file mode 100644 index bc0b7f8341d..00000000000 --- a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Settings - () => frappe.tests.make('Hotel Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py index a081acc0e08..e76c00ce101 100644 --- a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py +++ b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestHotelSettings(unittest.TestCase): pass diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py index 259edb9c06d..c43589d2a8d 100644 --- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py +++ b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py @@ -1,13 +1,14 @@ # 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 frappe.utils import add_days, date_diff from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import get_rooms_booked + def execute(filters=None): columns = get_columns(filters) data = get_data(filters) diff --git a/erpnext/hr/doctype/__init__.py b/erpnext/hr/doctype/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/__init__.py +++ b/erpnext/hr/doctype/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/appointment_letter/appointment_letter.py b/erpnext/hr/doctype/appointment_letter/appointment_letter.py index 85b82c50145..0120188d31c 100644 --- a/erpnext/hr/doctype/appointment_letter/appointment_letter.py +++ b/erpnext/hr/doctype/appointment_letter/appointment_letter.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class AppointmentLetter(Document): pass diff --git a/erpnext/hr/doctype/appointment_letter/test_appointment_letter.py b/erpnext/hr/doctype/appointment_letter/test_appointment_letter.py index b9ce9819c5a..e0f65b45d48 100644 --- a/erpnext/hr/doctype/appointment_letter/test_appointment_letter.py +++ b/erpnext/hr/doctype/appointment_letter/test_appointment_letter.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestAppointmentLetter(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/appointment_letter_content/appointment_letter_content.py b/erpnext/hr/doctype/appointment_letter_content/appointment_letter_content.py index a1a49e536b6..d158013d746 100644 --- a/erpnext/hr/doctype/appointment_letter_content/appointment_letter_content.py +++ b/erpnext/hr/doctype/appointment_letter_content/appointment_letter_content.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class AppointmentLettercontent(Document): pass diff --git a/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.py b/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.py index c23881f8007..9ac726e06d3 100644 --- a/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.py +++ b/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class AppointmentLetterTemplate(Document): pass diff --git a/erpnext/hr/doctype/appointment_letter_template/test_appointment_letter_template.py b/erpnext/hr/doctype/appointment_letter_template/test_appointment_letter_template.py index 3d061ac8e93..aa87da323fc 100644 --- a/erpnext/hr/doctype/appointment_letter_template/test_appointment_letter_template.py +++ b/erpnext/hr/doctype/appointment_letter_template/test_appointment_letter_template.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestAppointmentLetterTemplate(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/appraisal/__init__.py b/erpnext/hr/doctype/appraisal/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/appraisal/__init__.py +++ b/erpnext/hr/doctype/appraisal/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py index c2ed4579844..83273f86544 100644 --- a/erpnext/hr/doctype/appraisal/appraisal.py +++ b/erpnext/hr/doctype/appraisal/appraisal.py @@ -1,16 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc from frappe.utils import flt, getdate -from frappe import _ -from frappe.model.mapper import get_mapped_doc -from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, validate_active_employee + class Appraisal(Document): def validate(self): if not self.status: diff --git a/erpnext/hr/doctype/appraisal/test_appraisal.py b/erpnext/hr/doctype/appraisal/test_appraisal.py index f70dc481c80..90c30ef3475 100644 --- a/erpnext/hr/doctype/appraisal/test_appraisal.py +++ b/erpnext/hr/doctype/appraisal/test_appraisal.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Appraisal') diff --git a/erpnext/hr/doctype/appraisal_goal/__init__.py b/erpnext/hr/doctype/appraisal_goal/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/appraisal_goal/__init__.py +++ b/erpnext/hr/doctype/appraisal_goal/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py b/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py index 11d9f3944d5..3cbc9188eee 100644 --- a/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py +++ b/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class AppraisalGoal(Document): pass diff --git a/erpnext/hr/doctype/appraisal_template/__init__.py b/erpnext/hr/doctype/appraisal_template/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/appraisal_template/__init__.py +++ b/erpnext/hr/doctype/appraisal_template/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template.py b/erpnext/hr/doctype/appraisal_template/appraisal_template.py index d0dfad4be31..6b5921e6a6e 100644 --- a/erpnext/hr/doctype/appraisal_template/appraisal_template.py +++ b/erpnext/hr/doctype/appraisal_template/appraisal_template.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import cint, flt -from frappe import _ +import frappe +from frappe import _ from frappe.model.document import Document +from frappe.utils import cint, flt + class AppraisalTemplate(Document): def validate(self): diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py index 392b370e6c3..116a3f9118a 100644 --- a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py +++ b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'kra_template', diff --git a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py index e3029d980a0..d0e81a7dc5b 100644 --- a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py +++ b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Appraisal Template') diff --git a/erpnext/hr/doctype/appraisal_template_goal/__init__.py b/erpnext/hr/doctype/appraisal_template_goal/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/appraisal_template_goal/__init__.py +++ b/erpnext/hr/doctype/appraisal_template_goal/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py b/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py index b3c5704fa53..e6c5f64e08e 100644 --- a/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py +++ b/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class AppraisalTemplateGoal(Document): pass diff --git a/erpnext/hr/doctype/attendance/__init__.py b/erpnext/hr/doctype/attendance/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/attendance/__init__.py +++ b/erpnext/hr/doctype/attendance/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index c1a7c8f88a5..7dcfac249f4 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -1,15 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import getdate, nowdate +import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cstr, get_datetime, formatdate +from frappe.utils import cstr, formatdate, get_datetime, getdate, nowdate + from erpnext.hr.utils import validate_active_employee + class Attendance(Document): def validate(self): from erpnext.controllers.status_updater import validate_status @@ -134,7 +134,6 @@ def mark_attendance(employee, attendance_date, status, shift=None, leave_type=No @frappe.whitelist() def mark_bulk_attendance(data): import json - from pprint import pprint if isinstance(data, str): data = json.loads(data) data = frappe._dict(data) diff --git a/erpnext/hr/doctype/attendance/attendance_dashboard.py b/erpnext/hr/doctype/attendance/attendance_dashboard.py index 5dd9403674e..4bb36a0d7fd 100644 --- a/erpnext/hr/doctype/attendance/attendance_dashboard.py +++ b/erpnext/hr/doctype/attendance/attendance_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'attendance', diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 9a3bac0eb23..6b3c29a76b4 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -9,83 +9,86 @@ frappe.listview_settings['Attendance'] = { return [__(doc.status), "orange", "status,=," + doc.status]; } }, + onload: function(list_view) { let me = this; - const months = moment.months() - list_view.page.add_inner_button( __("Mark Attendance"), function() { + const months = moment.months(); + list_view.page.add_inner_button(__("Mark Attendance"), function() { let dialog = new frappe.ui.Dialog({ title: __("Mark Attendance"), - fields: [ - { - fieldname: 'employee', - label: __('For Employee'), - fieldtype: 'Link', - options: 'Employee', - get_query: () => { - return {query: "erpnext.controllers.queries.employee_query"} - }, - reqd: 1, - onchange: function() { - dialog.set_df_property("unmarked_days", "hidden", 1); - dialog.set_df_property("status", "hidden", 1); - dialog.set_df_property("month", "value", ''); + fields: [{ + fieldname: 'employee', + label: __('For Employee'), + fieldtype: 'Link', + options: 'Employee', + get_query: () => { + return {query: "erpnext.controllers.queries.employee_query"}; + }, + reqd: 1, + onchange: function() { + dialog.set_df_property("unmarked_days", "hidden", 1); + dialog.set_df_property("status", "hidden", 1); + dialog.set_df_property("month", "value", ''); + dialog.set_df_property("unmarked_days", "options", []); + dialog.no_unmarked_days_left = false; + } + }, + { + label: __("For Month"), + fieldtype: "Select", + fieldname: "month", + options: months, + reqd: 1, + onchange: function() { + if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { + dialog.set_df_property("status", "hidden", 0); dialog.set_df_property("unmarked_days", "options", []); dialog.no_unmarked_days_left = false; + me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options => { + if (options.length > 0) { + dialog.set_df_property("unmarked_days", "hidden", 0); + dialog.set_df_property("unmarked_days", "options", options); + } else { + dialog.no_unmarked_days_left = true; + } + }); } - }, - { - label: __("For Month"), - fieldtype: "Select", - fieldname: "month", - options: months, - reqd: 1, - onchange: function() { - if(dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { - dialog.set_df_property("status", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", []); - dialog.no_unmarked_days_left = false; - me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options =>{ - if (options.length > 0) { - dialog.set_df_property("unmarked_days", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", options); - } else { - dialog.no_unmarked_days_left = true; - } - }); - } - } - }, - { - label: __("Status"), - fieldtype: "Select", - fieldname: "status", - options: ["Present", "Absent", "Half Day", "Work From Home"], - hidden:1, - reqd: 1, + } + }, + { + label: __("Status"), + fieldtype: "Select", + fieldname: "status", + options: ["Present", "Absent", "Half Day", "Work From Home"], + hidden: 1, + reqd: 1, - }, - { - label: __("Unmarked Attendance for days"), - fieldname: "unmarked_days", - fieldtype: "MultiCheck", - options: [], - columns: 2, - hidden: 1 - }, - ], - primary_action(data) { + }, + { + label: __("Unmarked Attendance for days"), + fieldname: "unmarked_days", + fieldtype: "MultiCheck", + options: [], + columns: 2, + hidden: 1 + }], + primary_action(data) { if (cur_dialog.no_unmarked_days_left) { - frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}",[dialog.fields_dict.month.value, dialog.fields_dict.employee.value])); + frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}", + [dialog.fields_dict.month.value, dialog.fields_dict.employee.value])); } else { - frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status,data.month]), () => { + frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status, data.month]), () => { frappe.call({ method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance", args: { data: data }, - callback: function(r) { + callback: function (r) { if (r.message === 1) { - frappe.show_alert({message: __("Attendance Marked"), indicator: 'blue'}); + frappe.show_alert({ + message: __("Attendance Marked"), + indicator: 'blue' + }); cur_dialog.hide(); } } @@ -101,21 +104,26 @@ frappe.listview_settings['Attendance'] = { dialog.show(); }); }, - get_multi_select_options: function(employee, month){ + + get_multi_select_options: function(employee, month) { return new Promise(resolve => { frappe.call({ method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days', async: false, - args:{ + args: { employee: employee, month: month, } }).then(r => { var options = []; - for(var d in r.message){ + for (var d in r.message) { var momentObj = moment(r.message[d], 'YYYY-MM-DD'); var date = momentObj.format('DD-MM-YYYY'); - options.push({ "label":date, "value": r.message[d] , "checked": 1}); + options.push({ + "label": date, + "value": r.message[d], + "checked": 1 + }); } resolve(options); }); diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py index 838b704c5a5..a770d70ffa9 100644 --- a/erpnext/hr/doctype/attendance/test_attendance.py +++ b/erpnext/hr/doctype/attendance/test_attendance.py @@ -1,9 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest from frappe.utils import nowdate test_records = frappe.get_test_records('Attendance') diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py index 7f88fed73a2..8fbe7c7a9ad 100644 --- a/erpnext/hr/doctype/attendance_request/attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/attendance_request.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import date_diff, add_days, getdate +from frappe.utils import add_days, date_diff, getdate + from erpnext.hr.doctype.employee.employee import is_holiday -from erpnext.hr.utils import validate_dates, validate_active_employee +from erpnext.hr.utils import validate_active_employee, validate_dates + class AttendanceRequest(Document): def validate(self): diff --git a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py index 2d3eb000119..91970575a13 100644 --- a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py +++ b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_data(): return { 'fieldname': 'attendance_request', diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.js b/erpnext/hr/doctype/attendance_request/test_attendance_request.js deleted file mode 100644 index d40ec61b086..00000000000 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Attendance Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Attendance Request - () => frappe.tests.make('Attendance Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py index 9e668aa72fb..3f0442c7d69 100644 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest +from datetime import date import frappe -import unittest from frappe.utils import nowdate -from datetime import date test_dependencies = ["Employee"] diff --git a/erpnext/hr/doctype/branch/__init__.py b/erpnext/hr/doctype/branch/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/branch/__init__.py +++ b/erpnext/hr/doctype/branch/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/branch/branch.py b/erpnext/hr/doctype/branch/branch.py index a847c8e2174..133ada05bf0 100644 --- a/erpnext/hr/doctype/branch/branch.py +++ b/erpnext/hr/doctype/branch/branch.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class Branch(Document): pass diff --git a/erpnext/hr/doctype/branch/test_branch.js b/erpnext/hr/doctype/branch/test_branch.js deleted file mode 100644 index 82a6ae103ee..00000000000 --- a/erpnext/hr/doctype/branch/test_branch.js +++ /dev/null @@ -1,23 +0,0 @@ -QUnit.module('hr'); - -QUnit.test("Test: Branch [HR]", function (assert) { - assert.expect(1); - let done = assert.async(); - - frappe.run_serially([ - // test branch creation - () => frappe.set_route("List", "Branch", "List"), - () => frappe.new_doc("Branch"), - () => frappe.timeout(1), - () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(), - () => frappe.timeout(1), - () => cur_frm.set_value("branch", "Test Branch"), - - // save form - () => cur_frm.save(), - () => frappe.timeout(1), - () => assert.equal("Test Branch", cur_frm.doc.branch, - 'name of branch correctly saved'), - () => done() - ]); -}); diff --git a/erpnext/hr/doctype/branch/test_branch.py b/erpnext/hr/doctype/branch/test_branch.py index 807698ba0a2..e84c6e4c60e 100644 --- a/erpnext/hr/doctype/branch/test_branch.py +++ b/erpnext/hr/doctype/branch/test_branch.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Branch') diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index 3db81654a65..7d6051508ad 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -1,14 +1,21 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import date_diff, add_days, getdate, cint, format_date from frappe.model.document import Document -from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \ - create_additional_leave_ledger_entry, get_holiday_dates_for_employee +from frappe.utils import add_days, cint, date_diff, format_date, getdate + +from erpnext.hr.utils import ( + create_additional_leave_ledger_entry, + get_holiday_dates_for_employee, + get_leave_period, + validate_active_employee, + validate_dates, + validate_overlap, +) + class CompensatoryLeaveRequest(Document): diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js deleted file mode 100644 index bebcaac400e..00000000000 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Compensatory Leave Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Compensatory Leave Request - () => frappe.tests.make('Compensatory Leave Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py index 3b99c57051a..5e51879328b 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import today, add_months, add_days +from frappe.utils import add_days, add_months, today + from erpnext.hr.doctype.attendance_request.test_attendance_request import get_employee -from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on +from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period test_dependencies = ["Employee"] diff --git a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py index 1cc23812f75..fe11c47e60c 100644 --- a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py +++ b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py @@ -1,15 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document -from frappe import _ from email_reply_parser import EmailReplyParser -from erpnext.hr.doctype.employee.employee import is_holiday +from frappe import _ +from frappe.model.document import Document from frappe.utils import global_date_format -from six import string_types class DailyWorkSummary(Document): @@ -82,7 +79,7 @@ class DailyWorkSummary(Document): crop=True ) d.image = thumbnail_image - except: + except Exception: d.image = original_image if d.sender in did_not_reply: @@ -109,7 +106,7 @@ def get_user_emails_from_group(group): :param group: Daily Work Summary Group `name`''' group_doc = group - if isinstance(group_doc, string_types): + if isinstance(group_doc, str): group_doc = frappe.get_doc('Daily Work Summary Group', group) emails = get_users_email(group_doc) diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js deleted file mode 100644 index 15335171473..00000000000 --- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Daily Work Summary", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Daily Work Summary - () => frappe.tests.make('Daily Work Summary', [ - // values to be set - { key: 'value' } - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py index 38684799763..5edfb315564 100644 --- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py +++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import os -import frappe import unittest + +import frappe import frappe.utils # test_records = frappe.get_test_records('Daily Work Summary') @@ -64,8 +63,7 @@ class TestDailyWorkSummary(unittest.TestCase): filters=dict(email=('!=', 'test@example.com'))) self.setup_groups(hour) - from erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group \ - import trigger_emails + from erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group import trigger_emails trigger_emails() # check if emails are created @@ -74,7 +72,6 @@ class TestDailyWorkSummary(unittest.TestCase): from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \ where q.name = r.parent""", as_dict=1) - frappe.db.commit() def setup_groups(self, hour=None): # setup email to trigger at this hour diff --git a/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py b/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py index ece331aa718..ed98168aef7 100644 --- a/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py +++ b/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py @@ -1,15 +1,16 @@ -# # -*- coding: utf-8 -*- # # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document import frappe.utils from frappe import _ +from frappe.model.document import Document + from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_user_emails_from_group from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday + class DailyWorkSummaryGroup(Document): def validate(self): if self.users: diff --git a/erpnext/hr/doctype/daily_work_summary_group_user/daily_work_summary_group_user.py b/erpnext/hr/doctype/daily_work_summary_group_user/daily_work_summary_group_user.py index eefcc0c3a65..6e0809af332 100644 --- a/erpnext/hr/doctype/daily_work_summary_group_user/daily_work_summary_group_user.py +++ b/erpnext/hr/doctype/daily_work_summary_group_user/daily_work_summary_group_user.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class DailyWorkSummaryGroupUser(Document): pass diff --git a/erpnext/hr/doctype/department/__init__.py b/erpnext/hr/doctype/department/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/department/__init__.py +++ b/erpnext/hr/doctype/department/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py index 539a360269f..ed0bfcf0d5a 100644 --- a/erpnext/hr/doctype/department/department.py +++ b/erpnext/hr/doctype/department/department.py @@ -1,11 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.utils.nestedset import NestedSet, get_root_of + from erpnext.utilities.transaction_base import delete_events -from frappe.model.document import Document + class Department(NestedSet): nsm_parent_field = 'parent_department' diff --git a/erpnext/hr/doctype/department/test_department.js b/erpnext/hr/doctype/department/test_department.js deleted file mode 100644 index e73779c97c6..00000000000 --- a/erpnext/hr/doctype/department/test_department.js +++ /dev/null @@ -1,23 +0,0 @@ -QUnit.module('hr'); - -QUnit.test("Test: Department [HR]", function (assert) { - assert.expect(1); - let done = assert.async(); - - frappe.run_serially([ - // test department creation - () => frappe.set_route("List", "Department", "List"), - () => frappe.new_doc("Department"), - () => frappe.timeout(1), - () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(), - () => frappe.timeout(1), - () => cur_frm.set_value("department_name", "Test Department"), - () => cur_frm.set_value("leave_block_list", "Test Leave block list"), - // save form - () => cur_frm.save(), - () => frappe.timeout(1), - () => assert.equal("Test Department", cur_frm.doc.department_name, - 'name of department correctly saved'), - () => done() - ]); -}); diff --git a/erpnext/hr/doctype/department/test_department.py b/erpnext/hr/doctype/department/test_department.py index e4f6645ee42..95bf6635011 100644 --- a/erpnext/hr/doctype/department/test_department.py +++ b/erpnext/hr/doctype/department/test_department.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + import unittest +import frappe + test_ignore = ["Leave Block List"] class TestDepartment(unittest.TestCase): def test_remove_department_data(self): diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index d337959d534..375ae7c70ae 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document + class DepartmentApprover(Document): pass diff --git a/erpnext/hr/doctype/designation/__init__.py b/erpnext/hr/doctype/designation/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/designation/__init__.py +++ b/erpnext/hr/doctype/designation/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/designation/designation.py b/erpnext/hr/doctype/designation/designation.py index a3f84aab5f0..d7be55cad69 100644 --- a/erpnext/hr/doctype/designation/designation.py +++ b/erpnext/hr/doctype/designation/designation.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class Designation(Document): pass diff --git a/erpnext/hr/doctype/designation/test_designation.js b/erpnext/hr/doctype/designation/test_designation.js deleted file mode 100644 index 00adf8293f7..00000000000 --- a/erpnext/hr/doctype/designation/test_designation.js +++ /dev/null @@ -1,23 +0,0 @@ -QUnit.module('hr'); - -QUnit.test("Test: Designation [HR]", function (assert) { - assert.expect(1); - let done = assert.async(); - - frappe.run_serially([ - // test designation creation - () => frappe.set_route("List", "Designation", "List"), - () => frappe.new_doc("Designation"), - () => frappe.timeout(1), - () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(), - () => frappe.timeout(1), - () => cur_frm.set_value("designation_name", "Test Designation"), - () => cur_frm.set_value("description", "This designation is just for testing."), - // save form - () => cur_frm.save(), - () => frappe.timeout(1), - () => assert.equal("Test Designation", cur_frm.doc.designation_name, - 'name of designation correctly saved'), - () => done() - ]); -}); diff --git a/erpnext/hr/doctype/designation/test_designation.py b/erpnext/hr/doctype/designation/test_designation.py index 2778862a1c2..f2d6d36ff88 100644 --- a/erpnext/hr/doctype/designation/test_designation.py +++ b/erpnext/hr/doctype/designation/test_designation.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + # test_records = frappe.get_test_records('Designation') def create_designation(**args): diff --git a/erpnext/hr/doctype/designation_skill/designation_skill.py b/erpnext/hr/doctype/designation_skill/designation_skill.py index c37d21f454e..c35223b3c10 100644 --- a/erpnext/hr/doctype/designation_skill/designation_skill.py +++ b/erpnext/hr/doctype/designation_skill/designation_skill.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class DesignationSkill(Document): pass diff --git a/erpnext/hr/doctype/driver/driver.py b/erpnext/hr/doctype/driver/driver.py index 2cd22cd5480..26981890ed6 100644 --- a/erpnext/hr/doctype/driver/driver.py +++ b/erpnext/hr/doctype/driver/driver.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class Driver(Document): pass diff --git a/erpnext/hr/doctype/driver/test_driver.js b/erpnext/hr/doctype/driver/test_driver.js deleted file mode 100644 index ff9f61e66a6..00000000000 --- a/erpnext/hr/doctype/driver/test_driver.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Driver", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Driver - () => frappe.tests.make('Driver', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/driver/test_driver.py b/erpnext/hr/doctype/driver/test_driver.py index 4bc4a8fd57b..22707293a05 100644 --- a/erpnext/hr/doctype/driver/test_driver.py +++ b/erpnext/hr/doctype/driver/test_driver.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestDriver(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/driving_license_category/driving_license_category.py b/erpnext/hr/doctype/driving_license_category/driving_license_category.py index 33ba138e27c..a1a6d55cb4c 100644 --- a/erpnext/hr/doctype/driving_license_category/driving_license_category.py +++ b/erpnext/hr/doctype/driving_license_category/driving_license_category.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class DrivingLicenseCategory(Document): pass diff --git a/erpnext/hr/doctype/employee/__init__.py b/erpnext/hr/doctype/employee/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/employee/__init__.py +++ b/erpnext/hr/doctype/employee/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/employee/employee.js b/erpnext/hr/doctype/employee/employee.js index 5639cc9ea46..13b33e2e74c 100755 --- a/erpnext/hr/doctype/employee/employee.js +++ b/erpnext/hr/doctype/employee/employee.js @@ -15,19 +15,20 @@ erpnext.hr.EmployeeController = class EmployeeController extends frappe.ui.form. } refresh() { - var me = this; erpnext.toggle_naming_series(); } date_of_birth() { return cur_frm.call({ method: "get_retirement_date", - args: {date_of_birth: this.frm.doc.date_of_birth} + args: { + date_of_birth: this.frm.doc.date_of_birth + } }); } salutation() { - if(this.frm.doc.salutation) { + if (this.frm.doc.salutation) { this.frm.set_value("gender", { "Mr": "Male", "Ms": "Female" @@ -36,8 +37,9 @@ erpnext.hr.EmployeeController = class EmployeeController extends frappe.ui.form. } }; -frappe.ui.form.on('Employee',{ - setup: function(frm) { + +frappe.ui.form.on('Employee', { + setup: function (frm) { frm.set_query("leave_policy", function() { return { "filters": { @@ -46,7 +48,7 @@ frappe.ui.form.on('Employee',{ }; }); }, - onload:function(frm) { + onload: function (frm) { frm.set_query("department", function() { return { "filters": { @@ -55,23 +57,28 @@ frappe.ui.form.on('Employee',{ }; }); }, - prefered_contact_email:function(frm){ - frm.events.update_contact(frm) + prefered_contact_email: function(frm) { + frm.events.update_contact(frm); }, - personal_email:function(frm){ - frm.events.update_contact(frm) + + personal_email: function(frm) { + frm.events.update_contact(frm); }, - company_email:function(frm){ - frm.events.update_contact(frm) + + company_email: function(frm) { + frm.events.update_contact(frm); }, - user_id:function(frm){ - frm.events.update_contact(frm) + + user_id: function(frm) { + frm.events.update_contact(frm); }, - update_contact:function(frm){ + + update_contact: function(frm) { var prefered_email_fieldname = frappe.model.scrub(frm.doc.prefered_contact_email) || 'user_id'; frm.set_value("prefered_email", - frm.fields_dict[prefered_email_fieldname].value) + frm.fields_dict[prefered_email_fieldname].value); }, + status: function(frm) { return frm.call({ method: "deactivate_sales_person", @@ -81,19 +88,63 @@ frappe.ui.form.on('Employee',{ } }); }, + create_user: function(frm) { - if (!frm.doc.prefered_email) - { - frappe.throw(__("Please enter Preferred Contact Email")) + if (!frm.doc.prefered_email) { + frappe.throw(__("Please enter Preferred Contact Email")); } frappe.call({ method: "erpnext.hr.doctype.employee.employee.create_user", - args: { employee: frm.doc.name, email: frm.doc.prefered_email }, - callback: function(r) - { - frm.set_value("user_id", r.message) + args: { + employee: frm.doc.name, + email: frm.doc.prefered_email + }, + callback: function (r) { + frm.set_value("user_id", r.message); } }); } }); -cur_frm.cscript = new erpnext.hr.EmployeeController({frm: cur_frm}); + +cur_frm.cscript = new erpnext.hr.EmployeeController({ + frm: cur_frm +}); + + +frappe.tour['Employee'] = [ + { + fieldname: "first_name", + title: "First Name", + description: __("Enter First and Last name of Employee, based on Which Full Name will be updated. IN transactions, it will be Full Name which will be fetched.") + }, + { + fieldname: "company", + title: "Company", + description: __("Select a Company this Employee belongs to. Other HR features like Payroll. Expense Claims and Leaves for this Employee will be created for a given company only.") + }, + { + fieldname: "date_of_birth", + title: "Date of Birth", + description: __("Select Date of Birth. This will validate Employees age and prevent hiring of under-age staff.") + }, + { + fieldname: "date_of_joining", + title: "Date of Joining", + description: __("Select Date of joining. It will have impact on the first salary calculation, Leave allocation on pro-rata bases.") + }, + { + fieldname: "holiday_list", + title: "Holiday List", + description: __("Select a default Holiday List for this Employee. The days listed in Holiday List will not be counted in Leave Application.") + }, + { + fieldname: "reports_to", + title: "Reports To", + description: __("Here, you can select a senior of this Employee. Based on this, Organization Chart will be populated.") + }, + { + fieldname: "leave_approver", + title: "Leave Approver", + description: __("Select Leave Approver for an employee. The user one who will look after his/her Leave application") + }, +]; diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 643f3da2ff7..88e5ca9d4c5 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -1,15 +1,21 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt import frappe - -from frappe.utils import getdate, validate_email_address, today, add_years, cstr +from frappe import _, scrub, throw from frappe.model.naming import set_name_by_naming_series -from frappe import throw, _, scrub -from frappe.permissions import add_user_permission, remove_user_permission, \ - set_user_permission_if_allowed, has_permission, get_doc_permissions -from erpnext.utilities.transaction_base import delete_events +from frappe.permissions import ( + add_user_permission, + get_doc_permissions, + has_permission, + remove_user_permission, + set_user_permission_if_allowed, +) +from frappe.utils import add_years, cstr, getdate, today, validate_email_address from frappe.utils.nestedset import NestedSet +from erpnext.utilities.transaction_base import delete_events + + class EmployeeUserDisabledError(frappe.ValidationError): pass class InactiveEmployeeStatusError(frappe.ValidationError): @@ -90,15 +96,8 @@ class Employee(NestedSet): 'user': self.user_id }) - if employee_user_permission_exists: return - - employee_user_permission_exists = frappe.db.exists('User Permission', { - 'allow': 'Employee', - 'for_value': self.name, - 'user': self.user_id - }) - - if employee_user_permission_exists: return + if employee_user_permission_exists: + return add_user_permission("Employee", self.name, self.user_id) set_user_permission_if_allowed("Company", self.company, self.user_id) diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py index e853bee69f2..a4c0af0a4bf 100644 --- a/erpnext/hr/doctype/employee/employee_dashboard.py +++ b/erpnext/hr/doctype/employee/employee_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'heatmap': True, diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py index 2155c027a9b..559bd393e62 100644 --- a/erpnext/hr/doctype/employee/employee_reminders.py +++ b/erpnext/hr/doctype/employee/employee_reminders.py @@ -3,15 +3,17 @@ import frappe from frappe import _ -from frappe.utils import comma_sep, getdate, today, add_months, add_days +from frappe.utils import add_days, add_months, comma_sep, getdate, today + from erpnext.hr.doctype.employee.employee import get_all_employee_emails, get_employee_email from erpnext.hr.utils import get_holidays_for_employee + # ----------------- # HOLIDAY REMINDERS # ----------------- def send_reminders_in_advance_weekly(): - to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1) + to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders")) frequency = frappe.db.get_single_value("HR Settings", "frequency") if not (to_send_in_advance and frequency == "Weekly"): return @@ -19,7 +21,7 @@ def send_reminders_in_advance_weekly(): send_advance_holiday_reminders("Weekly") def send_reminders_in_advance_monthly(): - to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1) + to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders")) frequency = frappe.db.get_single_value("HR Settings", "frequency") if not (to_send_in_advance and frequency == "Monthly"): return @@ -77,7 +79,7 @@ def send_holidays_reminder_in_advance(employee, holidays): # ------------------ def send_birthday_reminders(): """Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set.""" - to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders") or 1) + to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders")) if not to_send: return @@ -154,6 +156,8 @@ def get_employees_having_an_event_today(event_type): DAY({condition_column}) = DAY(%(today)s) AND MONTH({condition_column}) = MONTH(%(today)s) + AND + YEAR({condition_column}) < YEAR(%(today)s) AND `status` = 'Active' """, @@ -164,6 +168,8 @@ def get_employees_having_an_event_today(event_type): DATE_PART('day', {condition_column}) = date_part('day', %(today)s) AND DATE_PART('month', {condition_column}) = date_part('month', %(today)s) + AND + DATE_PART('year', {condition_column}) < date_part('year', %(today)s) AND "status" = 'Active' """, @@ -182,7 +188,7 @@ def get_employees_having_an_event_today(event_type): # -------------------------- def send_work_anniversary_reminders(): """Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked""" - to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders") or 1) + to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders")) if not to_send: return diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 5feb6de8f2b..8a2da0866e9 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -1,10 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import frappe -import erpnext import unittest + +import frappe import frappe.utils + +import erpnext from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError test_records = frappe.get_test_records('Employee') @@ -23,9 +25,9 @@ class TestEmployee(unittest.TestCase): self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save) def test_employee_status_inactive(self): - from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure - from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure employee = make_employee("test_employee_status@company.com") employee_doc = frappe.get_doc("Employee", employee) @@ -53,6 +55,7 @@ def make_employee(user, company=None, **kwargs): "email": user, "first_name": user, "new_password": "password", + "send_welcome_email": 0, "roles": [{"doctype": "Has Role", "role": "Employee"}] }).insert() diff --git a/erpnext/hr/doctype/employee/test_employee_reminders.py b/erpnext/hr/doctype/employee/test_employee_reminders.py index 7e560f512d1..52c00982443 100644 --- a/erpnext/hr/doctype/employee/test_employee_reminders.py +++ b/erpnext/hr/doctype/employee/test_employee_reminders.py @@ -1,11 +1,12 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import frappe import unittest - -from frappe.utils import getdate from datetime import timedelta + +import frappe +from frappe.utils import getdate + from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.hr_settings.hr_settings import set_proceed_with_frequency_change @@ -18,7 +19,7 @@ class TestEmployeeReminders(unittest.TestCase): # Create a test holiday list test_holiday_dates = cls.get_test_holiday_dates() test_holiday_list = make_holiday_list( - 'TestHolidayRemindersList', + 'TestHolidayRemindersList', holiday_dates=[ {'holiday_date': test_holiday_dates[0], 'description': 'test holiday1'}, {'holiday_date': test_holiday_dates[1], 'description': 'test holiday2'}, @@ -49,8 +50,8 @@ class TestEmployeeReminders(unittest.TestCase): def get_test_holiday_dates(cls): today_date = getdate() return [ - today_date, - today_date-timedelta(days=4), + today_date, + today_date-timedelta(days=4), today_date-timedelta(days=3), today_date+timedelta(days=1), today_date+timedelta(days=3), @@ -63,7 +64,7 @@ class TestEmployeeReminders(unittest.TestCase): def test_is_holiday(self): from erpnext.hr.doctype.employee.employee import is_holiday - + self.assertTrue(is_holiday(self.test_employee.name)) self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[1])) self.assertFalse(is_holiday(self.test_employee.name, date=getdate()-timedelta(days=1))) @@ -84,7 +85,10 @@ class TestEmployeeReminders(unittest.TestCase): employee.company = "_Test Company" employee.save() - from erpnext.hr.doctype.employee.employee_reminders import get_employees_who_are_born_today, send_birthday_reminders + from erpnext.hr.doctype.employee.employee_reminders import ( + get_employees_who_are_born_today, + send_birthday_reminders, + ) employees_born_today = get_employees_who_are_born_today() self.assertTrue(employees_born_today.get("_Test Company")) @@ -105,7 +109,10 @@ class TestEmployeeReminders(unittest.TestCase): employee.company = "_Test Company" employee.save() - from erpnext.hr.doctype.employee.employee_reminders import get_employees_having_an_event_today, send_work_anniversary_reminders + from erpnext.hr.doctype.employee.employee_reminders import ( + get_employees_having_an_event_today, + send_work_anniversary_reminders, + ) employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary') self.assertTrue(employees_having_work_anniversary.get("_Test Company")) @@ -118,10 +125,10 @@ class TestEmployeeReminders(unittest.TestCase): email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) self.assertTrue("Subject: Work Anniversary Reminder" in email_queue[0].message) - + def test_send_holidays_reminder_in_advance(self): - from erpnext.hr.utils import get_holidays_for_employee from erpnext.hr.doctype.employee.employee_reminders import send_holidays_reminder_in_advance + from erpnext.hr.utils import get_holidays_for_employee # Get HR settings and enable advance holiday reminders hr_settings = frappe.get_doc("HR Settings", "HR Settings") @@ -133,10 +140,10 @@ class TestEmployeeReminders(unittest.TestCase): holidays = get_holidays_for_employee( self.test_employee.get('name'), getdate(), getdate() + timedelta(days=3), - only_non_weekly=True, + only_non_weekly=True, raise_exception=False ) - + send_holidays_reminder_in_advance( self.test_employee.get('name'), holidays @@ -147,6 +154,7 @@ class TestEmployeeReminders(unittest.TestCase): def test_advance_holiday_reminders_monthly(self): from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_monthly + # Get HR settings and enable advance holiday reminders hr_settings = frappe.get_doc("HR Settings", "HR Settings") hr_settings.send_holiday_reminders = 1 @@ -158,9 +166,10 @@ class TestEmployeeReminders(unittest.TestCase): email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) self.assertTrue(len(email_queue) > 0) - + def test_advance_holiday_reminders_weekly(self): from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_weekly + # Get HR settings and enable advance holiday reminders hr_settings = frappe.get_doc("HR Settings", "HR Settings") hr_settings.send_holiday_reminders = 1 diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index fa4b06aad37..7d1c7cbf4a8 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -73,7 +73,7 @@ frappe.ui.form.on('Employee Advance', { frm.trigger('make_return_entry'); }, __('Create')); } else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) { - frm.add_custom_button(__("Deduction from salary"), function() { + frm.add_custom_button(__("Deduction from Salary"), function() { frm.events.make_deduction_via_additional_salary(frm); }, __('Create')); } diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json index ea25aa720ad..04754530c3a 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.json +++ b/erpnext/hr/doctype/employee_advance/employee_advance.json @@ -170,7 +170,7 @@ "default": "0", "fieldname": "repay_unclaimed_amount_from_salary", "fieldtype": "Check", - "label": "Repay unclaimed amount from salary" + "label": "Repay Unclaimed Amount from Salary" }, { "depends_on": "eval:cur_frm.doc.employee", @@ -200,10 +200,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:31:53.746659", + "modified": "2021-09-11 18:38:38.617478", "modified_by": "Administrator", "module": "HR", "name": "Employee Advance", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index cbb3cc813b4..7aac2b63ed3 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -1,15 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ from frappe.model.document import Document +from frappe.query_builder.functions import Sum from frappe.utils import flt, nowdate + +import erpnext from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.hr.utils import validate_active_employee + class EmployeeAdvanceOverPayment(frappe.ValidationError): pass @@ -39,24 +42,34 @@ class EmployeeAdvance(Document): self.status = "Cancelled" def set_total_advance_paid(self): - paid_amount = frappe.db.sql(""" - select ifnull(sum(debit), 0) as paid_amount - from `tabGL Entry` - where against_voucher_type = 'Employee Advance' - and against_voucher = %s - and party_type = 'Employee' - and party = %s - """, (self.name, self.employee), as_dict=1)[0].paid_amount + gle = frappe.qb.DocType("GL Entry") - return_amount = frappe.db.sql(""" - select ifnull(sum(credit), 0) as return_amount - from `tabGL Entry` - where against_voucher_type = 'Employee Advance' - and voucher_type != 'Expense Claim' - and against_voucher = %s - and party_type = 'Employee' - and party = %s - """, (self.name, self.employee), as_dict=1)[0].return_amount + paid_amount = ( + frappe.qb.from_(gle) + .select(Sum(gle.debit).as_("paid_amount")) + .where( + (gle.against_voucher_type == 'Employee Advance') + & (gle.against_voucher == self.name) + & (gle.party_type == 'Employee') + & (gle.party == self.employee) + & (gle.docstatus == 1) + & (gle.is_cancelled == 0) + ) + ).run(as_dict=True)[0].paid_amount or 0 + + return_amount = ( + frappe.qb.from_(gle) + .select(Sum(gle.credit).as_("return_amount")) + .where( + (gle.against_voucher_type == 'Employee Advance') + & (gle.voucher_type != 'Expense Claim') + & (gle.against_voucher == self.name) + & (gle.party_type == 'Employee') + & (gle.party == self.employee) + & (gle.docstatus == 1) + & (gle.is_cancelled == 0) + ) + ).run(as_dict=True)[0].return_amount or 0 if paid_amount != 0: paid_amount = flt(paid_amount) / flt(self.exchange_rate) @@ -168,7 +181,10 @@ def get_paying_amount_paying_exchange_rate(payment_account, doc): @frappe.whitelist() def create_return_through_additional_salary(doc): import json - doc = frappe._dict(json.loads(doc)) + + if isinstance(doc, str): + doc = frappe._dict(json.loads(doc)) + additional_salary = frappe.new_doc('Additional Salary') additional_salary.employee = doc.employee additional_salary.currency = doc.currency diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py index 2f493e2d4e6..9450258a8a2 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'employee_advance', diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.js b/erpnext/hr/doctype/employee_advance/test_employee_advance.js deleted file mode 100644 index 1b9ec6f6d0c..00000000000 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Advance", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Advance - () => frappe.tests.make('Employee Advance', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py index 100968bb7aa..5f2e720eb46 100644 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py @@ -1,14 +1,21 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe, erpnext import unittest + +import frappe from frappe.utils import nowdate -from erpnext.hr.doctype.employee_advance.employee_advance import make_bank_entry -from erpnext.hr.doctype.employee_advance.employee_advance import EmployeeAdvanceOverPayment + +import erpnext from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.employee_advance.employee_advance import ( + EmployeeAdvanceOverPayment, + create_return_through_additional_salary, + make_bank_entry, +) +from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component +from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + class TestEmployeeAdvance(unittest.TestCase): def test_paid_amount_and_status(self): @@ -27,6 +34,64 @@ class TestEmployeeAdvance(unittest.TestCase): journal_entry1 = make_payment_entry(advance) self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit) + def test_paid_amount_on_pe_cancellation(self): + employee_name = make_employee("_T@employe.advance") + advance = make_employee_advance(employee_name) + + pe = make_payment_entry(advance) + pe.submit() + + advance.reload() + + self.assertEqual(advance.paid_amount, 1000) + self.assertEqual(advance.status, "Paid") + + pe.cancel() + advance.reload() + + self.assertEqual(advance.paid_amount, 0) + self.assertEqual(advance.status, "Unpaid") + + def test_repay_unclaimed_amount_from_salary(self): + employee_name = make_employee("_T@employe.advance") + advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) + + args = {"type": "Deduction"} + create_salary_component("Advance Salary - Deduction", **args) + make_salary_structure("Test Additional Salary for Advance Return", "Monthly", employee=employee_name) + + # additional salary for 700 first + advance.reload() + additional_salary = create_return_through_additional_salary(advance) + additional_salary.salary_component = "Advance Salary - Deduction" + additional_salary.payroll_date = nowdate() + additional_salary.amount = 700 + additional_salary.insert() + additional_salary.submit() + + advance.reload() + self.assertEqual(advance.return_amount, 700) + + # additional salary for remaining 300 + additional_salary = create_return_through_additional_salary(advance) + additional_salary.salary_component = "Advance Salary - Deduction" + additional_salary.payroll_date = nowdate() + additional_salary.amount = 300 + additional_salary.insert() + additional_salary.submit() + + advance.reload() + self.assertEqual(advance.return_amount, 1000) + + # update advance return amount on additional salary cancellation + additional_salary.cancel() + advance.reload() + self.assertEqual(advance.return_amount, 700) + + def tearDown(self): + frappe.db.rollback() + + def make_payment_entry(advance): journal_entry = frappe.get_doc(make_bank_entry("Employee Advance", advance.name)) journal_entry.cheque_no = "123123" @@ -35,7 +100,7 @@ def make_payment_entry(advance): return journal_entry -def make_employee_advance(employee_name): +def make_employee_advance(employee_name, args=None): doc = frappe.new_doc("Employee Advance") doc.employee = employee_name doc.company = "_Test company" @@ -45,6 +110,10 @@ def make_employee_advance(employee_name): doc.advance_amount = 1000 doc.posting_date = nowdate() doc.advance_account = "_Test Employee Advance - _TC" + + if args: + doc.update(args) + doc.insert() doc.submit() diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py index 16c1a32b9b5..af2ca50b78a 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + import json + +import frappe from frappe.model.document import Document from frappe.utils import getdate @@ -53,8 +53,7 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa else: leave_type = None - if not company: - company = frappe.db.get_value("Employee", employee['employee'], "Company") + company = frappe.db.get_value("Employee", employee['employee'], "Company", cache=True) attendance=frappe.get_doc(dict( doctype='Attendance', @@ -66,4 +65,4 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa company=company )) attendance.insert() - attendance.submit() + attendance.submit() \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json index 65792b42fb8..044a5a9886b 100644 --- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json +++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2018-05-09 05:37:18.439763", "doctype": "DocType", "editable_grid": 1, @@ -7,6 +8,8 @@ "activity_name", "user", "role", + "begin_on", + "duration", "column_break_3", "task", "task_weight", @@ -16,12 +19,16 @@ ], "fields": [ { + "columns": 3, "fieldname": "activity_name", "fieldtype": "Data", "in_list_view": 1, - "label": "Activity Name" + "label": "Activity Name", + "reqd": 1 }, { + "columns": 2, + "depends_on": "eval:!doc.role", "fieldname": "user", "fieldtype": "Link", "in_list_view": 1, @@ -29,9 +36,10 @@ "options": "User" }, { + "columns": 1, + "depends_on": "eval:!doc.user", "fieldname": "role", "fieldtype": "Link", - "in_list_view": 1, "label": "Role", "options": "Role" }, @@ -67,10 +75,25 @@ "fieldname": "description", "fieldtype": "Text Editor", "label": "Description" + }, + { + "columns": 2, + "fieldname": "duration", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Duration (Days)" + }, + { + "columns": 2, + "fieldname": "begin_on", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Begin On (Days)" } ], "istable": 1, - "modified": "2019-06-03 19:22:42.965762", + "links": [], + "modified": "2021-07-30 15:55:22.470102", "modified_by": "Administrator", "module": "HR", "name": "Employee Boarding Activity", diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.py b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.py index 496f1653ba0..e824081327d 100644 --- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.py +++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class EmployeeBoardingActivity(Document): pass diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index 6c0cd4f963b..c1d4ac7fded 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -1,16 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import now, cint, get_datetime -from frappe.model.document import Document -from frappe import _ -from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cint, get_datetime + +from erpnext.hr.doctype.shift_assignment.shift_assignment import ( + get_actual_start_end_datetime_of_shift, +) from erpnext.hr.utils import validate_active_employee + class EmployeeCheckin(Document): def validate(self): validate_active_employee(self.employee) diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py index 7ba511f08d5..254bf9e2569 100644 --- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py @@ -1,15 +1,19 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import now_datetime, nowdate, to_timedelta import unittest from datetime import timedelta -from erpnext.hr.doctype.employee_checkin.employee_checkin import add_log_based_on_employee_field, mark_attendance_and_link_log, calculate_working_hours +import frappe +from frappe.utils import now_datetime, nowdate + from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.employee_checkin.employee_checkin import ( + add_log_based_on_employee_field, + calculate_working_hours, + mark_attendance_and_link_log, +) + class TestEmployeeCheckin(unittest.TestCase): def test_add_log_based_on_employee_field(self): diff --git a/erpnext/hr/doctype/employee_education/__init__.py b/erpnext/hr/doctype/employee_education/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/employee_education/__init__.py +++ b/erpnext/hr/doctype/employee_education/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/employee_education/employee_education.py b/erpnext/hr/doctype/employee_education/employee_education.py index f0a76172b2c..ded583bf01d 100644 --- a/erpnext/hr/doctype/employee_education/employee_education.py +++ b/erpnext/hr/doctype/employee_education/employee_education.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class EmployeeEducation(Document): pass diff --git a/erpnext/hr/doctype/employee_external_work_history/__init__.py b/erpnext/hr/doctype/employee_external_work_history/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/employee_external_work_history/__init__.py +++ b/erpnext/hr/doctype/employee_external_work_history/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py b/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py index 517ef57be85..d594fbf0f0c 100644 --- a/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py +++ b/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class EmployeeExternalWorkHistory(Document): pass diff --git a/erpnext/hr/doctype/employee_grade/employee_grade.py b/erpnext/hr/doctype/employee_grade/employee_grade.py index 42a9f161359..41b7915c95a 100644 --- a/erpnext/hr/doctype/employee_grade/employee_grade.py +++ b/erpnext/hr/doctype/employee_grade/employee_grade.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class EmployeeGrade(Document): pass diff --git a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py index df679104185..6825dae2506 100644 --- a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py +++ b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_data(): return { 'transactions': [ diff --git a/erpnext/hr/doctype/employee_grade/test_employee_grade.js b/erpnext/hr/doctype/employee_grade/test_employee_grade.js deleted file mode 100644 index d684fb2ad1b..00000000000 --- a/erpnext/hr/doctype/employee_grade/test_employee_grade.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Grade", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Grade - () => frappe.tests.make('Employee Grade', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_grade/test_employee_grade.py b/erpnext/hr/doctype/employee_grade/test_employee_grade.py index 93058cf1083..a70d6853487 100644 --- a/erpnext/hr/doctype/employee_grade/test_employee_grade.py +++ b/erpnext/hr/doctype/employee_grade/test_employee_grade.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestEmployeeGrade(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py index 17055829efb..fd9a33b3771 100644 --- a/erpnext/hr/doctype/employee_grievance/employee_grievance.py +++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py @@ -5,6 +5,7 @@ import frappe from frappe import _, bold from frappe.model.document import Document + class EmployeeGrievance(Document): def on_submit(self): if self.status not in ["Invalid", "Resolved"]: diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py index ed897ee1032..e2d0002aa62 100644 --- a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py +++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py @@ -1,10 +1,14 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import frappe import unittest + +import frappe from frappe.utils import today + from erpnext.hr.doctype.employee.test_employee import make_employee + + class TestEmployeeGrievance(unittest.TestCase): def test_create_employee_grievance(self): create_employee_grievance() diff --git a/erpnext/hr/doctype/employee_group/employee_group.py b/erpnext/hr/doctype/employee_group/employee_group.py index 3025877b8e1..c4ce083c942 100644 --- a/erpnext/hr/doctype/employee_group/employee_group.py +++ b/erpnext/hr/doctype/employee_group/employee_group.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class EmployeeGroup(Document): pass diff --git a/erpnext/hr/doctype/employee_group/test_employee_group.py b/erpnext/hr/doctype/employee_group/test_employee_group.py index 26a61c407b2..a87f4007bd8 100644 --- a/erpnext/hr/doctype/employee_group/test_employee_group.py +++ b/erpnext/hr/doctype/employee_group/test_employee_group.py @@ -1,11 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe + import unittest + +import frappe + from erpnext.hr.doctype.employee.test_employee import make_employee + class TestEmployeeGroup(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/employee_group_table/employee_group_table.py b/erpnext/hr/doctype/employee_group_table/employee_group_table.py index 816611d018d..adf6ca2668c 100644 --- a/erpnext/hr/doctype/employee_group_table/employee_group_table.py +++ b/erpnext/hr/doctype/employee_group_table/employee_group_table.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class EmployeeGroupTable(Document): pass diff --git a/erpnext/hr/doctype/employee_health_insurance/employee_health_insurance.py b/erpnext/hr/doctype/employee_health_insurance/employee_health_insurance.py index abc01ef8d4b..4a8c437d64c 100644 --- a/erpnext/hr/doctype/employee_health_insurance/employee_health_insurance.py +++ b/erpnext/hr/doctype/employee_health_insurance/employee_health_insurance.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class EmployeeHealthInsurance(Document): pass diff --git a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js b/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js deleted file mode 100644 index 245cb32971f..00000000000 --- a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Health Insurance", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Health Insurance - () => frappe.tests.make('Employee Health Insurance', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.py b/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.py index f0787f52d20..4f042b70798 100644 --- a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.py +++ b/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestEmployeeHealthInsurance(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/employee_internal_work_history/__init__.py b/erpnext/hr/doctype/employee_internal_work_history/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/employee_internal_work_history/__init__.py +++ b/erpnext/hr/doctype/employee_internal_work_history/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py b/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py index 2f385a8113e..6225de60148 100644 --- a/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py +++ b/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class EmployeeInternalWorkHistory(Document): pass diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json index 673e228395e..fd877a68d85 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json @@ -8,20 +8,24 @@ "field_order": [ "job_applicant", "job_offer", - "employee_name", - "employee", - "date_of_joining", - "boarding_status", - "notify_users_by_email", - "column_break_7", "employee_onboarding_template", + "column_break_7", "company", + "boarding_status", + "project", + "details_section", + "employee", + "employee_name", "department", "designation", "employee_grade", - "project", + "holiday_list", + "column_break_13", + "date_of_joining", + "boarding_begins_on", "table_for_activity", "activities", + "notify_users_by_email", "amended_from" ], "fields": [ @@ -58,7 +62,8 @@ "fieldname": "date_of_joining", "fieldtype": "Date", "in_list_view": 1, - "label": "Date of Joining" + "label": "Date of Joining", + "reqd": 1 }, { "allow_on_submit": 1, @@ -90,7 +95,8 @@ "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company" + "options": "Company", + "reqd": 1 }, { "fieldname": "department", @@ -121,7 +127,8 @@ }, { "fieldname": "table_for_activity", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Onboarding Activities" }, { "allow_on_submit": 1, @@ -138,11 +145,32 @@ "options": "Employee Onboarding", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Employee Details" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "boarding_begins_on", + "fieldtype": "Date", + "label": "Onboarding Begins On", + "reqd": 1 + }, + { + "fieldname": "holiday_list", + "fieldtype": "Link", + "label": "Holiday List", + "options": "Holiday List" } ], "is_submittable": 1, "links": [], - "modified": "2021-06-03 18:01:51.097927", + "modified": "2021-07-30 14:55:04.560683", "modified_by": "Administrator", "module": "HR", "name": "Employee Onboarding", diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py index ca9b2987a6f..eba2a03193c 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController from frappe.model.mapper import get_mapped_doc +from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController + + class IncompleteTaskError(frappe.ValidationError): pass class EmployeeOnboarding(EmployeeBoardingController): diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js deleted file mode 100644 index d15cef77dca..00000000000 --- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Onboarding", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Onboarding - () => frappe.tests.make('Employee Onboarding', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py index 0445270b9ff..cb1b56048b7 100644 --- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py @@ -1,14 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import nowdate -from erpnext.hr.doctype.employee_onboarding.employee_onboarding import make_employee -from erpnext.hr.doctype.employee_onboarding.employee_onboarding import IncompleteTaskError +from frappe.utils import getdate + +from erpnext.hr.doctype.employee_onboarding.employee_onboarding import ( + IncompleteTaskError, + make_employee, +) from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer +from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + class TestEmployeeOnboarding(unittest.TestCase): def setUp(self): @@ -46,7 +50,7 @@ class TestEmployeeOnboarding(unittest.TestCase): onboarding.reload() employee = make_employee(onboarding.name) employee.first_name = employee.employee_name - employee.date_of_joining = nowdate() + employee.date_of_joining = getdate() employee.date_of_birth = '1990-05-08' employee.gender = 'Female' employee.insert() @@ -65,6 +69,7 @@ def get_job_applicant(): applicant = frappe.new_doc('Job Applicant') applicant.applicant_name = 'Test Researcher' applicant.email_id = 'test@researcher.com' + applicant.designation = 'Researcher' applicant.status = 'Open' applicant.cover_letter = 'I am a great Researcher.' applicant.insert() @@ -82,11 +87,14 @@ def get_job_offer(applicant_name): def create_employee_onboarding(): applicant = get_job_applicant() job_offer = get_job_offer(applicant.name) + holiday_list = make_holiday_list() onboarding = frappe.new_doc('Employee Onboarding') onboarding.job_applicant = applicant.name onboarding.job_offer = job_offer.name + onboarding.date_of_joining = onboarding.boarding_begins_on = getdate() onboarding.company = '_Test Company' + onboarding.holiday_list = holiday_list onboarding.designation = 'Researcher' onboarding.append('activities', { 'activity_name': 'Assign ID Card', diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/hr/doctype/employee_onboarding_activity/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json deleted file mode 100644 index 4e91b723844..00000000000 --- a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json +++ /dev/null @@ -1,290 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-05-09 05:37:18.439763", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "activity_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Activity Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "role", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Role", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.parenttype == \"Employee Onboarding\"", - "fieldname": "completed", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Completed", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "required_for_employee_creation", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Required for Employee Creation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-09 06:15:41.768236", - "modified_by": "Administrator", - "module": "HR", - "name": "Employee Onboarding Activity", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py deleted file mode 100644 index d1706318191..00000000000 --- a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class EmployeeOnboardingActivity(Document): - pass diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.py b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.py index 6f1c3167315..199013a5a13 100644 --- a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.py +++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class EmployeeOnboardingTemplate(Document): pass diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py index ab0eb2f5dce..3b846a0e406 100644 --- a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py +++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'employee_onboarding_template', diff --git a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js deleted file mode 100644 index 10912edb6ac..00000000000 --- a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Onboarding Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Onboarding Template - () => frappe.tests.make('Employee Onboarding Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py index f4b5b883428..db09011c878 100644 --- a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py +++ b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestEmployeeOnboardingTemplate(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py index a3a61834c8c..cf6156e3264 100644 --- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee, validate_active_employee + +from erpnext.hr.utils import update_employee_work_history, validate_active_employee + class EmployeePromotion(Document): def validate(self): @@ -20,10 +21,10 @@ class EmployeePromotion(Document): def on_submit(self): employee = frappe.get_doc("Employee", self.employee) - employee = update_employee(employee, self.promotion_details, date=self.promotion_date) + employee = update_employee_work_history(employee, self.promotion_details, date=self.promotion_date) employee.save() def on_cancel(self): employee = frappe.get_doc("Employee", self.employee) - employee = update_employee(employee, self.promotion_details, cancel=True) + employee = update_employee_work_history(employee, self.promotion_details, cancel=True) employee.save() diff --git a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js deleted file mode 100644 index 5f0a5baf818..00000000000 --- a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Promotion", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Promotion - () => frappe.tests.make('Employee Promotion', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py index 9e7d3186b88..fc9d195a3f3 100644 --- a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import getdate, add_days +from frappe.utils import add_days, getdate + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee + class TestEmployeePromotion(unittest.TestCase): def setUp(self): self.employee = make_employee("employee@promotions.com") diff --git a/erpnext/hr/doctype/employee_property_history/employee_property_history.py b/erpnext/hr/doctype/employee_property_history/employee_property_history.py index fb67852d164..345899e43cf 100644 --- a/erpnext/hr/doctype/employee_property_history/employee_property_history.py +++ b/erpnext/hr/doctype/employee_property_history/employee_property_history.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class EmployeePropertyHistory(Document): pass diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js index 9c99bbbefad..8722019fb19 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral.js +++ b/erpnext/hr/doctype/employee_referral/employee_referral.js @@ -43,8 +43,6 @@ frappe.ui.form.on("Employee Referral", { }); } - - }, create_job_applicant: function(frm) { frappe.model.open_mapped_doc({ diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py index 547a95e3bdf..eaa42c7b4ca 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import get_link_to_form from frappe.model.document import Document +from frappe.utils import get_link_to_form + from erpnext.hr.utils import validate_active_employee + class EmployeeReferral(Document): def validate(self): validate_active_employee(self.referrer) @@ -35,8 +36,10 @@ def create_job_applicant(source_name, target_doc=None): status = "Open" job_applicant = frappe.new_doc("Job Applicant") + job_applicant.source = "Employee Referral" job_applicant.employee_referral = emp_ref.name job_applicant.status = status + job_applicant.designation = emp_ref.for_designation job_applicant.applicant_name = emp_ref.full_name job_applicant.email_id = emp_ref.email job_applicant.phone_number = emp_ref.contact_no @@ -56,9 +59,9 @@ def create_job_applicant(source_name, target_doc=None): @frappe.whitelist() def create_additional_salary(doc): import json - from six import string_types - if isinstance(doc, string_types): + + if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}): diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py index caca2961a1a..07c2402e1a4 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py +++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_data(): return { 'fieldname': 'employee_referral', diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py index 599f3262240..529e3551454 100644 --- a/erpnext/hr/doctype/employee_referral/test_employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py @@ -1,16 +1,25 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe from frappe.utils import today + from erpnext.hr.doctype.designation.test_designation import create_designation -from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary from erpnext.hr.doctype.employee.test_employee import make_employee -import unittest +from erpnext.hr.doctype.employee_referral.employee_referral import ( + create_additional_salary, + create_job_applicant, +) + class TestEmployeeReferral(unittest.TestCase): + + def setUp(self): + frappe.db.sql("DELETE FROM `tabJob Applicant`") + frappe.db.sql("DELETE FROM `tabEmployee Referral`") + def test_workflow_and_status_sync(self): emp_ref = create_employee_referral() @@ -44,6 +53,10 @@ class TestEmployeeReferral(unittest.TestCase): add_sal = create_additional_salary(emp_ref) self.assertTrue(add_sal.ref_docname, emp_ref.name) + def tearDown(self): + frappe.db.sql("DELETE FROM `tabJob Applicant`") + frappe.db.sql("DELETE FROM `tabEmployee Referral`") + def create_employee_referral(): emp_ref = frappe.new_doc("Employee Referral") diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json index c10da5c35e7..c240493e820 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation.json +++ b/erpnext/hr/doctype/employee_separation/employee_separation.json @@ -15,6 +15,7 @@ "company", "boarding_status", "resignation_letter_date", + "boarding_begins_on", "project", "table_for_activity", "employee_separation_template", @@ -144,11 +145,17 @@ "options": "Employee Separation", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "boarding_begins_on", + "fieldtype": "Date", + "label": "Separation Begins On", + "reqd": 1 } ], "is_submittable": 1, "links": [], - "modified": "2021-06-03 18:02:54.007313", + "modified": "2021-07-30 14:03:51.218791", "modified_by": "Administrator", "module": "HR", "name": "Employee Separation", diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.py b/erpnext/hr/doctype/employee_separation/employee_separation.py index 8afee25d31c..915e9a876e7 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation.py +++ b/erpnext/hr/doctype/employee_separation/employee_separation.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController + class EmployeeSeparation(EmployeeBoardingController): def validate(self): super(EmployeeSeparation, self).validate() diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.js b/erpnext/hr/doctype/employee_separation/test_employee_separation.js deleted file mode 100644 index d6c635951f7..00000000000 --- a/erpnext/hr/doctype/employee_separation/test_employee_separation.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Separation", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Separation - () => frappe.tests.make('Employee Separation', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py index d63501a9314..f83c1e57b85 100644 --- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py +++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest +from frappe.utils import getdate test_dependencies = ['Employee Onboarding'] @@ -34,9 +34,10 @@ class TestEmployeeSeparation(unittest.TestCase): doc.delete() def create_employee_separation(): - employee = frappe.db.get_value('Employee', {'status': 'Active'}) + employee = frappe.db.get_value('Employee', {'status': 'Active', 'company': '_Test Company'}) separation = frappe.new_doc('Employee Separation') separation.employee = employee + separation.boarding_begins_on = getdate() separation.company = '_Test Company' separation.append('activities', { 'activity_name': 'Deactivate Employee', diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template.py b/erpnext/hr/doctype/employee_separation_template/employee_separation_template.py index 0508fc462e2..70b84b1755d 100644 --- a/erpnext/hr/doctype/employee_separation_template/employee_separation_template.py +++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class EmployeeSeparationTemplate(Document): pass diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py index 75f985cec39..6e2a83eaa70 100644 --- a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py +++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'employee_separation_template', diff --git a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js deleted file mode 100644 index 66fd4508046..00000000000 --- a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Separation Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Separation Template - () => frappe.tests.make('Employee Separation Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.py b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.py index 3fd3d398bd1..6a0c9479db6 100644 --- a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.py +++ b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestEmployeeSeparationTemplate(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/employee_skill/employee_skill.py b/erpnext/hr/doctype/employee_skill/employee_skill.py index ac05fba624b..13bee342532 100644 --- a/erpnext/hr/doctype/employee_skill/employee_skill.py +++ b/erpnext/hr/doctype/employee_skill/employee_skill.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class EmployeeSkill(Document): pass diff --git a/erpnext/hr/doctype/employee_skill_map/employee_skill_map.py b/erpnext/hr/doctype/employee_skill_map/employee_skill_map.py index 073f02fa258..ea7da9edf9c 100644 --- a/erpnext/hr/doctype/employee_skill_map/employee_skill_map.py +++ b/erpnext/hr/doctype/employee_skill_map/employee_skill_map.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class EmployeeSkillMap(Document): pass diff --git a/erpnext/hr/doctype/employee_training/employee_training.py b/erpnext/hr/doctype/employee_training/employee_training.py index 810796d66cf..cd92dd609f9 100644 --- a/erpnext/hr/doctype/employee_training/employee_training.py +++ b/erpnext/hr/doctype/employee_training/employee_training.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class EmployeeTraining(Document): pass diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index c2007747fb3..f927d413ae3 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee + +from erpnext.hr.utils import update_employee_work_history + class EmployeeTransfer(Document): def before_submit(self): @@ -21,7 +22,7 @@ class EmployeeTransfer(Document): new_employee = frappe.copy_doc(employee) new_employee.name = None new_employee.employee_number = None - new_employee = update_employee(new_employee, self.transfer_details, date=self.transfer_date) + new_employee = update_employee_work_history(new_employee, self.transfer_details, date=self.transfer_date) if self.new_company and self.company != self.new_company: new_employee.internal_work_history = [] new_employee.date_of_joining = self.transfer_date @@ -36,7 +37,7 @@ class EmployeeTransfer(Document): employee.db_set("relieving_date", self.transfer_date) employee.db_set("status", "Left") else: - employee = update_employee(employee, self.transfer_details, date=self.transfer_date) + employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date) if self.new_company and self.company != self.new_company: employee.company = self.new_company employee.date_of_joining = self.transfer_date @@ -53,7 +54,7 @@ class EmployeeTransfer(Document): employee.status = "Active" employee.relieving_date = '' else: - employee = update_employee(employee, self.transfer_details, cancel=True) + employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date, cancel=True) if self.new_company != self.company: employee.company = self.company employee.save() diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js deleted file mode 100644 index 05a3e1a5738..00000000000 --- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Transfer", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Transfer - () => frappe.tests.make('Employee Transfer', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py index 93fc7a27056..64eee402fec 100644 --- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py @@ -1,20 +1,24 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import getdate, add_days +from frappe.utils import add_days, getdate + from erpnext.hr.doctype.employee.test_employee import make_employee + class TestEmployeeTransfer(unittest.TestCase): def setUp(self): - make_employee("employee2@transfers.com") - make_employee("employee3@transfers.com") - frappe.db.sql("""delete from `tabEmployee Transfer`""") + create_company() + + def tearDown(self): + frappe.db.rollback() def test_submit_before_transfer_date(self): + make_employee("employee2@transfers.com") + transfer_obj = frappe.get_doc({ "doctype": "Employee Transfer", "employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"), @@ -36,6 +40,8 @@ class TestEmployeeTransfer(unittest.TestCase): self.assertEqual(transfer.docstatus, 1) def test_new_employee_creation(self): + make_employee("employee3@transfers.com") + transfer = frappe.get_doc({ "doctype": "Employee Transfer", "employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"), @@ -54,3 +60,70 @@ class TestEmployeeTransfer(unittest.TestCase): self.assertTrue(transfer.new_employee_id) self.assertEqual(frappe.get_value("Employee", transfer.new_employee_id, "status"), "Active") self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left") + + def test_employee_history(self): + employee = make_employee("employee4@transfers.com", + company="Test Company", + date_of_birth=getdate("30-09-1980"), + date_of_joining=getdate("01-10-2021"), + department="Accounts - TC", + designation="Accountant" + ) + transfer = create_employee_transfer(employee) + + count = 0 + department = ["Accounts - TC", "Management - TC"] + designation = ["Accountant", "Manager"] + dt = [getdate("01-10-2021"), getdate()] + + employee = frappe.get_doc("Employee", employee) + for data in employee.internal_work_history: + self.assertEqual(data.department, department[count]) + self.assertEqual(data.designation, designation[count]) + self.assertEqual(data.from_date, dt[count]) + count = count + 1 + + transfer.cancel() + employee.reload() + + for data in employee.internal_work_history: + self.assertEqual(data.designation, designation[0]) + self.assertEqual(data.department, department[0]) + self.assertEqual(data.from_date, dt[0]) + + +def create_company(): + if not frappe.db.exists("Company", "Test Company"): + frappe.get_doc({ + "doctype": "Company", + "company_name": "Test Company", + "default_currency": "INR", + "country": "India" + }).insert() + + +def create_employee_transfer(employee): + doc = frappe.get_doc({ + "doctype": "Employee Transfer", + "employee": employee, + "transfer_date": getdate(), + "transfer_details": [ + { + "property": "Designation", + "current": "Accountant", + "new": "Manager", + "fieldname": "designation" + }, + { + "property": "Department", + "current": "Accounts - TC", + "new": "Management - TC", + "fieldname": "department" + } + ] + }) + + doc.save() + doc.submit() + + return doc \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_transfer_property/employee_transfer_property.py b/erpnext/hr/doctype/employee_transfer_property/employee_transfer_property.py index 1a665dc1008..76e200602f5 100644 --- a/erpnext/hr/doctype/employee_transfer_property/employee_transfer_property.py +++ b/erpnext/hr/doctype/employee_transfer_property/employee_transfer_property.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class EmployeeTransferProperty(Document): pass diff --git a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js b/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js deleted file mode 100644 index 00a334a63d7..00000000000 --- a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Transfer Property", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Transfer Property - () => frappe.tests.make('Employee Transfer Property', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.py b/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.py index 39c20a6f716..981d46f57d2 100644 --- a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.py +++ b/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestEmployeeTransferProperty(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/employment_type/__init__.py b/erpnext/hr/doctype/employment_type/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/employment_type/__init__.py +++ b/erpnext/hr/doctype/employment_type/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/employment_type/employment_type.py b/erpnext/hr/doctype/employment_type/employment_type.py index 00aa6bb9bc4..b2262c0c585 100644 --- a/erpnext/hr/doctype/employment_type/employment_type.py +++ b/erpnext/hr/doctype/employment_type/employment_type.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class EmploymentType(Document): pass diff --git a/erpnext/hr/doctype/employment_type/test_employment_type.py b/erpnext/hr/doctype/employment_type/test_employment_type.py index 0297ffa01a3..c43f9636c70 100644 --- a/erpnext/hr/doctype/employment_type/test_employment_type.py +++ b/erpnext/hr/doctype/employment_type/test_employment_type.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Employment Type') diff --git a/erpnext/healthcare/doctype/diagnosis/__init__.py b/erpnext/hr/doctype/expected_skill_set/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/diagnosis/__init__.py rename to erpnext/hr/doctype/expected_skill_set/__init__.py diff --git a/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json b/erpnext/hr/doctype/expected_skill_set/expected_skill_set.json similarity index 54% rename from erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json rename to erpnext/hr/doctype/expected_skill_set/expected_skill_set.json index b37ff007cb9..899f5bd0ff4 100644 --- a/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json +++ b/erpnext/hr/doctype/expected_skill_set/expected_skill_set.json @@ -1,43 +1,39 @@ { "actions": [], - "creation": "2020-03-31 23:01:18.761967", + "creation": "2021-04-12 13:05:06.741330", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "title", - "image", + "skill", "description" ], "fields": [ { - "fieldname": "title", - "fieldtype": "Data", + "fieldname": "skill", + "fieldtype": "Link", "in_list_view": 1, - "label": "Title", + "label": "Skill", + "options": "Skill", "reqd": 1 }, { - "fieldname": "image", - "fieldtype": "Attach Image", - "label": "Image" - }, - { + "fetch_from": "skill.description", "fieldname": "description", - "fieldtype": "Long Text", + "fieldtype": "Small Text", "in_list_view": 1, "label": "Description" } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-04-02 20:39:34.258512", + "modified": "2021-04-12 14:26:33.062549", "modified_by": "Administrator", - "module": "Healthcare", - "name": "Exercise Type Step", + "module": "HR", + "name": "Expected Skill Set", "owner": "Administrator", "permissions": [], - "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/erpnext/hr/doctype/expected_skill_set/expected_skill_set.py b/erpnext/hr/doctype/expected_skill_set/expected_skill_set.py new file mode 100644 index 00000000000..0062ba91a19 --- /dev/null +++ b/erpnext/hr/doctype/expected_skill_set/expected_skill_set.py @@ -0,0 +1,10 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class ExpectedSkillSet(Document): + pass diff --git a/erpnext/hr/doctype/expense_claim/__init__.py b/erpnext/hr/doctype/expense_claim/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/expense_claim/__init__.py +++ b/erpnext/hr/doctype/expense_claim/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 3c4c672816c..665556301bb 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -10,6 +10,26 @@ frappe.ui.form.on('Expense Claim', { }, company: function(frm) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + var expenses = frm.doc.expenses; + for (var i = 0; i < expenses.length; i++) { + var expense = expenses[i]; + if (!expense.expense_type) { + continue; + } + frappe.call({ + method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center", + args: { + "expense_claim_type": expense.expense_type, + "company": frm.doc.company + }, + callback: function(r) { + if (r.message) { + expense.default_account = r.message.account; + expense.cost_center = r.message.cost_center; + } + } + }); + } }, }); @@ -369,7 +389,9 @@ frappe.ui.form.on("Expense Claim Detail", { sanctioned_amount: function(frm, cdt, cdn) { cur_frm.cscript.calculate_total(frm.doc, cdt, cdn); frm.trigger("get_taxes"); + frm.trigger("calculate_grand_total"); }, + cost_center: function(frm, cdt, cdn) { erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "expenses", "cost_center"); } diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index a268c15c70b..45b78bfb54e 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -379,11 +379,12 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-04 05:35:12.040199", + "modified": "2021-11-22 16:26:57.787838", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 95e2806aedc..7e3898b7d51 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -1,18 +1,17 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ -from frappe.utils import get_fullname, flt, cstr, get_link_to_form -from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee -from erpnext.accounts.party import get_party_account -from erpnext.accounts.general_ledger import make_gl_entries +from frappe.utils import cstr, flt, get_link_to_form + +import erpnext from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account +from erpnext.accounts.general_ledger import make_gl_entries from erpnext.controllers.accounts_controller import AccountsController -from frappe.utils.csvutils import getlink -from erpnext.accounts.utils import get_account_currency +from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee + class InvalidExpenseApproverError(frappe.ValidationError): pass class ExpenseApproverIdentityError(frappe.ValidationError): pass @@ -77,7 +76,7 @@ class ExpenseClaim(AccountsController): self.make_gl_entries() if self.is_paid: - update_reimbursed_amount(self) + update_reimbursed_amount(self, self.grand_total) self.set_status(update=True) self.update_claimed_amount_in_employee_advance() @@ -89,7 +88,7 @@ class ExpenseClaim(AccountsController): self.make_gl_entries(cancel=True) if self.is_paid: - update_reimbursed_amount(self) + update_reimbursed_amount(self, -1 * self.grand_total) self.update_claimed_amount_in_employee_advance() @@ -270,20 +269,10 @@ class ExpenseClaim(AccountsController): if not expense.default_account or not validate: expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"] -def update_reimbursed_amount(doc, jv=None): +def update_reimbursed_amount(doc, amount): - condition = "" - - if jv: - condition += "and voucher_no = '{0}'".format(jv) - - amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) - ifnull(sum(credit_in_account_currency), 0)as amt - from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s - and party = %s {condition}""".format(condition=condition), #nosec - (doc.name, doc.employee) ,as_dict=1)[0].amt - - doc.total_amount_reimbursed = amt - frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", amt) + doc.total_amount_reimbursed += amount + frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", doc.total_amount_reimbursed) doc.set_status() frappe.db.set_value("Expense Claim", doc.name , "status", doc.status) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py index fe973507019..7539c7165d0 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'reference_name', diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index c2bd1e9f9f1..ec703614c82 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -1,13 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import random_string, nowdate -from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry +from frappe.utils import flt, nowdate, random_string + from erpnext.accounts.doctype.account.test_account import create_account from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry test_records = frappe.get_test_records('Expense Claim') test_dependencies = ['Employee'] @@ -138,6 +139,31 @@ class TestExpenseClaim(unittest.TestCase): expense_claim.submit() frappe.set_user("Administrator") + def test_multiple_payment_entries_against_expense(self): + # Creating expense claim + payable_account = get_payable_account("_Test Company") + expense_claim = make_expense_claim(payable_account, 5500, 5500, "_Test Company", "Travel Expenses - _TC") + expense_claim.save() + expense_claim.submit() + + # Payment entry 1: paying 500 + make_payment_entry(expense_claim, payable_account,500) + outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim) + self.assertEqual(outstanding_amount, 5000) + self.assertEqual(total_amount_reimbursed, 500) + + # Payment entry 1: paying 2000 + make_payment_entry(expense_claim, payable_account,2000) + outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim) + self.assertEqual(outstanding_amount, 3000) + self.assertEqual(total_amount_reimbursed, 2500) + + # Payment entry 1: paying 3000 + make_payment_entry(expense_claim, payable_account,3000) + outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim) + self.assertEqual(outstanding_amount, 0) + self.assertEqual(total_amount_reimbursed, 5500) + def get_payable_account(company): return frappe.get_cached_value('Company', company, 'default_payable_account') @@ -149,7 +175,7 @@ def generate_taxes(): account = create_account(company=company_name, account_name="Output Tax CGST", account_type="Tax", parent_account=parent_account) return {'taxes':[{ "account_head": account, - "rate": 0, + "rate": 9, "description": "CGST", "tax_amount": 10, "total": 210 @@ -191,3 +217,22 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco return expense_claim expense_claim.submit() return expense_claim + +def get_outstanding_and_total_reimbursed_amounts(expense_claim): + outstanding_amount = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_sanctioned_amount")) - \ + flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed")) + total_amount_reimbursed = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed")) + + return outstanding_amount,total_amount_reimbursed + +def make_payment_entry(expense_claim, payable_account, amt): + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + + pe = get_payment_entry("Expense Claim", expense_claim.name, bank_account="_Test Bank USD - _TC", bank_amount=amt) + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.source_exchange_rate = 1 + pe.paid_to = payable_account + pe.references[0].allocated_amount = amt + pe.insert() + pe.submit() diff --git a/erpnext/hr/doctype/expense_claim_account/expense_claim_account.py b/erpnext/hr/doctype/expense_claim_account/expense_claim_account.py index f34633cf58b..0d46a226080 100644 --- a/erpnext/hr/doctype/expense_claim_account/expense_claim_account.py +++ b/erpnext/hr/doctype/expense_claim_account/expense_claim_account.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class ExpenseClaimAccount(Document): pass diff --git a/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json b/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json index 45509257c11..aa479c83084 100644 --- a/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json +++ b/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2017-10-09 16:53:26.410762", "doctype": "DocType", "document_type": "Document", @@ -50,7 +51,7 @@ "fieldname": "unclaimed_amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Unclaimed amount", + "label": "Unclaimed Amount", "no_copy": 1, "oldfieldname": "advance_amount", "oldfieldtype": "Currency", @@ -65,7 +66,7 @@ "fieldname": "allocated_amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Allocated amount", + "label": "Allocated Amount", "no_copy": 1, "oldfieldname": "allocated_amount", "oldfieldtype": "Currency", @@ -87,7 +88,7 @@ ], "istable": 1, "links": [], - "modified": "2019-12-17 13:53:22.111766", + "modified": "2021-11-22 16:33:58.515819", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim Advance", diff --git a/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.py b/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.py index c4e7b026e3b..68b2963f2bc 100644 --- a/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.py +++ b/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class ExpenseClaimAdvance(Document): pass diff --git a/erpnext/hr/doctype/expense_claim_detail/__init__.py b/erpnext/hr/doctype/expense_claim_detail/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/expense_claim_detail/__init__.py +++ b/erpnext/hr/doctype/expense_claim_detail/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json index 70a48f93b72..6edbcb5c39b 100644 --- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json +++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json @@ -94,7 +94,6 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Sanctioned Amount", - "no_copy": 1, "oldfieldname": "sanctioned_amount", "oldfieldtype": "Currency", "options": "Company:company:default_currency", @@ -120,7 +119,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-11-26 14:23:45.539922", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim Detail", diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py index 5d48990c5ce..f58f1287cbe 100644 --- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py +++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class ExpenseClaimDetail(Document): pass diff --git a/erpnext/hr/doctype/expense_claim_type/__init__.py b/erpnext/hr/doctype/expense_claim_type/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/expense_claim_type/__init__.py +++ b/erpnext/hr/doctype/expense_claim_type/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py index a637a540213..570b2c115fa 100644 --- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py +++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py @@ -1,11 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document + class ExpenseClaimType(Document): def validate(self): self.validate_accounts() diff --git a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py index 1d894308d3a..a2403b6eb8f 100644 --- a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py +++ b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Expense Claim Type') diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json index 020457d4ec6..2f7b8fcf679 100644 --- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json +++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json @@ -56,8 +56,6 @@ }, { "columns": 2, - "fetch_from": "account_head.tax_rate", - "fetch_if_empty": 1, "fieldname": "rate", "fieldtype": "Float", "in_list_view": 1, @@ -102,7 +100,7 @@ ], "istable": 1, "links": [], - "modified": "2020-09-23 20:27:36.027728", + "modified": "2021-10-26 20:27:36.027728", "modified_by": "Administrator", "module": "HR", "name": "Expense Taxes and Charges", @@ -111,4 +109,4 @@ "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py index 4103bef1ffe..a28ef57b3fe 100644 --- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py +++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class ExpenseTaxesandCharges(Document): pass diff --git a/erpnext/healthcare/doctype/dosage_form/__init__.py b/erpnext/hr/doctype/full_and_final_asset/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/dosage_form/__init__.py rename to erpnext/hr/doctype/full_and_final_asset/__init__.py diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js new file mode 100644 index 00000000000..1965b46651b --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Full and Final Asset', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json new file mode 100644 index 00000000000..3ad8335049a --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json @@ -0,0 +1,64 @@ +{ + "actions": [], + "creation": "2021-06-28 13:36:58.658985", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference", + "asset_name", + "date", + "status", + "description" + ], + "fields": [ + { + "fieldname": "reference", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reference", + "options": "Asset Movement", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Owned\nReturned", + "reqd": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "asset_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Asset Name", + "read_only": 1 + }, + { + "fieldname": "date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Date", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-07-15 15:17:31.309834", + "modified_by": "Administrator", + "module": "HR", + "name": "Full and Final Asset", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py new file mode 100644 index 00000000000..661af7da888 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class FullandFinalAsset(Document): + pass diff --git a/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py b/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py new file mode 100644 index 00000000000..9afe0f2c163 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestFullandFinalAsset(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/dosage_strength/__init__.py b/erpnext/hr/doctype/full_and_final_outstanding_statement/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/dosage_strength/__init__.py rename to erpnext/hr/doctype/full_and_final_outstanding_statement/__init__.py diff --git a/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json new file mode 100644 index 00000000000..be242e2c444 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json @@ -0,0 +1,96 @@ +{ + "actions": [], + "creation": "2021-06-28 13:32:02.167317", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "component", + "reference_document_type", + "reference_document", + "account", + "paid_via_salary_slip", + "column_break_4", + "amount", + "status", + "remark" + ], + "fields": [ + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "columns": 2, + "default": "Unsettled", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Settled\nUnsettled" + }, + { + "fieldname": "remark", + "fieldtype": "Small Text", + "label": "Remark" + }, + { + "columns": 2, + "depends_on": "reference_document_type", + "fieldname": "reference_document", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Document", + "mandatory_depends_on": "reference_document_type", + "options": "reference_document_type", + "search_index": 1 + }, + { + "columns": 2, + "fieldname": "component", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Component", + "reqd": 1 + }, + { + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account" + }, + { + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount" + }, + { + "columns": 2, + "fieldname": "reference_document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reference Document Type", + "options": "DocType" + }, + { + "default": "0", + "fieldname": "paid_via_salary_slip", + "fieldtype": "Check", + "label": "Paid via Salary Slip" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-07-20 16:59:34.447934", + "modified_by": "Administrator", + "module": "HR", + "name": "Full and Final Outstanding Statement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py new file mode 100644 index 00000000000..4b239abec38 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class FullandFinalOutstandingStatement(Document): + pass diff --git a/erpnext/healthcare/doctype/drug_prescription/__init__.py b/erpnext/hr/doctype/full_and_final_statement/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/drug_prescription/__init__.py rename to erpnext/hr/doctype/full_and_final_statement/__init__.py diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js new file mode 100644 index 00000000000..074d85b709b --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js @@ -0,0 +1,115 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Full and Final Statement', { + refresh: function(frm) { + frm.events.set_queries(frm, "payables"); + frm.events.set_queries(frm, "receivables"); + + if (frm.doc.docstatus == 1 && frm.doc.status == "Unpaid") { + frm.add_custom_button(__("Create Journal Entry"), function () { + frm.events.create_journal_entry(frm); + }); + } + }, + + set_queries: function(frm, type) { + frm.set_query("reference_document_type", type, function () { + let modules = ["HR", "Payroll", "Loan Management"]; + return { + filters: { + istable: 0, + issingle: 0, + module: ["In", modules] + } + }; + }); + + let filters = {}; + + frm.set_query('reference_document', type, function(doc, cdt, cdn) { + let fnf_doc = frappe.get_doc(cdt, cdn); + + frappe.model.with_doctype(fnf_doc.reference_document_type, function() { + if (frappe.model.is_tree(fnf_doc.reference_document_type)) { + filters['is_group'] = 0; + } + + if (frappe.meta.has_field(fnf_doc.reference_document_type, 'company')) { + filters['company'] = frm.doc.company; + } + + if (frappe.meta.has_field(fnf_doc.reference_document_type, 'employee')) { + filters['employee'] = frm.doc.employee; + } + }); + + return { + filters: filters + }; + }); + }, + + employee: function(frm) { + frm.events.get_outstanding_statements(frm); + }, + + get_outstanding_statements: function(frm) { + if (frm.doc.employee) { + frappe.call({ + method: "get_outstanding_statements", + doc: frm.doc, + callback: function() { + frm.refresh(); + } + }); + } + }, + + create_journal_entry: function(frm) { + frappe.call({ + method: "create_journal_entry", + doc: frm.doc, + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + } +}); + +frappe.ui.form.on("Full and Final Outstanding Statement", { + reference_document: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + if (child.reference_document_type && child.reference_document) { + frappe.call({ + method: "erpnext.hr.doctype.full_and_final_statement.full_and_final_statement.get_account_and_amount", + args: { + ref_doctype: child.reference_document_type, + ref_document: child.reference_document + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, "account", r.message[0]); + frappe.model.set_value(cdt, cdn, "amount", r.message[1]); + } + } + }); + } + }, + + amount: function(frm) { + var total_payable_amount = 0; + var total_receivable_amount = 0; + + frm.doc.payables.forEach(element => { + total_payable_amount = total_payable_amount + element.amount; + }); + + frm.doc.receivables.forEach(element => { + total_receivable_amount = total_receivable_amount + element.amount; + }); + frm.set_value("total_payable_amount", flt(total_payable_amount)); + frm.set_value("total_receivable_amount", flt(total_receivable_amount)); + } +}); diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json new file mode 100644 index 00000000000..ebcf36dfd9d --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json @@ -0,0 +1,231 @@ +{ + "actions": [], + "autoname": "HR-FNF-.YYYY.-.#####", + "creation": "2021-06-28 13:17:36.050459", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "transaction_date", + "column_break_12", + "company", + "status", + "amended_from", + "employee_details_section", + "date_of_joining", + "relieving_date", + "column_break_4", + "designation", + "department", + "section_break_8", + "payables", + "section_break_10", + "receivables", + "totals_section", + "total_payable_amount", + "column_break_21", + "total_receivable_amount", + "section_break_15", + "assets_allocated" + ], + "fields": [ + { + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1 + }, + { + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1 + }, + { + "fetch_from": "employee.designation", + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "default": "Unpaid", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Paid\nUnpaid", + "read_only": 1 + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Full and Final Statement", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Payables" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break", + "label": "Receivables" + }, + { + "fieldname": "assets_allocated", + "fieldtype": "Table", + "options": "Full and Final Asset" + }, + { + "fetch_from": "employee.relieving_date", + "fieldname": "relieving_date", + "fieldtype": "Date", + "label": "Relieving Date ", + "read_only": 1, + "reqd": 1 + }, + { + "fetch_from": "employee.date_of_joining", + "fieldname": "date_of_joining", + "fieldtype": "Date", + "label": "Date of Joining", + "read_only": 1 + }, + { + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "label": "Assets Allocated" + }, + { + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "read_only": 1 + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "payables", + "fieldtype": "Table", + "options": "Full and Final Outstanding Statement" + }, + { + "fieldname": "receivables", + "fieldtype": "Table", + "options": "Full and Final Outstanding Statement" + }, + { + "fieldname": "employee_details_section", + "fieldtype": "Section Break", + "label": "Employee Details" + }, + { + "fieldname": "transaction_date", + "fieldtype": "Date", + "in_standard_filter": 1, + "label": "Transaction Date", + "reqd": 1 + }, + { + "fieldname": "totals_section", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "fieldname": "total_payable_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Payable Amount", + "read_only": 1 + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_receivable_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Receivable Amount", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-08-30 21:11:09.892560", + "modified_by": "Administrator", + "module": "HR", + "name": "Full and Final Statement", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py new file mode 100644 index 00000000000..f539537fdb3 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py @@ -0,0 +1,177 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import flt, get_link_to_form, today + + +class FullandFinalStatement(Document): + def validate(self): + self.get_outstanding_statements() + if self.docstatus == 1: + self.validate_settlement("payables") + self.validate_settlement("receivables") + self.validate_asset() + + def validate_settlement(self, component_type): + for data in self.get(component_type, []): + if data.status == "Unsettled": + frappe.throw(_("Settle all Payables and Receivables before submission")) + + def validate_asset(self): + for data in self.assets_allocated: + if data.status == "Owned": + frappe.throw(_("All allocated assets should be returned before submission")) + + @frappe.whitelist() + def get_outstanding_statements(self): + if self.relieving_date: + if not len(self.get("payables", [])): + components = self.get_payable_component() + self.create_component_row(components, "payables") + if not len(self.get("receivables", [])): + components = self.get_receivable_component() + self.create_component_row(components, "receivables") + + if not len(self.get("assets_allocated", [])): + for data in self.get_assets_movement(): + self.append("assets_allocated", data) + else: + frappe.throw(_("Set Relieving Date for Employee: {0}").format(get_link_to_form("Employee", self.employee))) + + def create_component_row(self, components, component_type): + for component in components: + self.append(component_type, { + "status": "Unsettled", + "reference_document_type": component if component != "Bonus" else "Additional Salary", + "component": component + }) + + + def get_payable_component(self): + return [ + "Salary Slip", + "Gratuity", + "Expense Claim", + "Bonus", + "Leave Encashment", + ] + + def get_receivable_component(self): + return [ + "Loan", + "Employee Advance", + ] + + def get_assets_movement(self): + asset_movements = frappe.get_all("Asset Movement Item", + filters = {"docstatus": 1}, + fields = ["asset", "from_employee", "to_employee", "parent", "asset_name"], + or_filters = { + "from_employee": self.employee, + "to_employee": self.employee + } + ) + + data = [] + inward_movements = [] + outward_movements = [] + for movement in asset_movements: + if movement.to_employee and movement.to_employee == self.employee: + inward_movements.append(movement) + + if movement.from_employee and movement.from_employee == self.employee: + outward_movements.append(movement) + + for movement in inward_movements: + outwards_count = [movement.asset for movement in outward_movements].count(movement.asset) + inwards_counts = [movement.asset for movement in inward_movements].count(movement.asset) + + if inwards_counts > outwards_count: + data.append({ + "reference": movement.parent, + "asset_name": movement.asset_name, + "date": frappe.db.get_value("Asset Movement", movement.parent, "transaction_date"), + "status": "Owned" + }) + return data + + @frappe.whitelist() + def create_journal_entry(self): + precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") + jv = frappe.new_doc("Journal Entry") + jv.company = self.company + jv.voucher_type = "Bank Entry" + jv.posting_date = today() + + difference = self.total_payable_amount - self.total_receivable_amount + + for data in self.payables: + if data.amount > 0 and not data.paid_via_salary_slip: + account_dict = { + "account": data.account, + "debit_in_account_currency": flt(data.amount, precision) + } + if data.reference_document_type == "Expense Claim": + account_dict["party_type"] = "Employee" + account_dict["party"] = self.employee + + jv.append("accounts", account_dict) + + for data in self.receivables: + if data.amount > 0: + account_dict = { + "account": data.account, + "credit_in_account_currency": flt(data.amount, precision) + } + if data.reference_document_type == "Employee Advance": + account_dict["party_type"] = "Employee" + account_dict["party"] = self.employee + + jv.append("accounts", account_dict) + + jv.append("accounts", { + "credit_in_account_currency": difference if difference > 0 else 0, + "debit_in_account_currency": -(difference) if difference < 0 else 0, + "reference_type": self.doctype, + "reference_name": self.name + }) + return jv + +@frappe.whitelist() +def get_account_and_amount(ref_doctype, ref_document): + if not ref_doctype or not ref_document: + return None + + if ref_doctype == "Salary Slip": + salary_details = frappe.db.get_value("Salary Slip", ref_document, ["payroll_entry", "net_pay"], as_dict=1) + amount = salary_details.net_pay + payable_account = frappe.db.get_value("Payroll Entry", salary_details.payroll_entry, "payroll_payable_account") if salary_details.payroll_entry else None + return [payable_account, amount] + + if ref_doctype == "Gratuity": + payable_account, amount = frappe.db.get_value("Gratuity", ref_document, ["payable_account", "amount"]) + return [payable_account, amount] + + if ref_doctype == "Expense Claim": + details = frappe.db.get_value("Expense Claim", ref_document, + ["payable_account", "grand_total", "total_amount_reimbursed", "total_advance_amount"], as_dict=True) + payable_account = details.payable_account + amount = details.grand_total - (details.total_amount_reimbursed + details.total_advance_amount) + return [payable_account, amount] + + if ref_doctype == "Loan": + details = frappe.db.get_value("Loan", ref_document, + ["payment_account", "total_payment", "total_amount_paid"], as_dict=1) + payment_account = details.payment_account + amount = details.total_payment - details.total_amount_paid + return [payment_account, amount] + + if ref_doctype == "Employee Advance": + details = frappe.db.get_value("Employee Advance", ref_document, + ["advance_account","paid_amount", "claimed_amount", "return_amount"], as_dict = 1) + payment_account = details.advance_account + amount = details.paid_amount - (details.claimed_amount + details.return_amount) + return [payment_account, amount] diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js new file mode 100644 index 00000000000..4aedec7c897 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js @@ -0,0 +1,11 @@ +frappe.listview_settings["Full and Final Statement"] = { + get_indicator: function(doc) { + var colors = { + "Draft": "red", + "Unpaid": "orange", + "Paid": "green", + "Cancelled": "red" + }; + return [__(doc.status), colors[doc.status], "status,=," + doc.status]; + } +}; diff --git a/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py new file mode 100644 index 00000000000..f6c1d1545b1 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py @@ -0,0 +1,74 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import unittest + +import frappe +from frappe.utils import add_days, today + +from erpnext.assets.doctype.asset.test_asset import create_asset_data +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + + +class TestFullandFinalStatement(unittest.TestCase): + + def setUp(self): + create_asset_data() + + def tearDown(self): + frappe.db.sql("Delete from `tabFull and Final Statement`") + frappe.db.sql("Delete from `tabAsset`") + frappe.db.sql("Delete from `tabAsset Movement`") + + def test_check_bootstraped_data_asset_movement_and_jv_creation(self): + employee = make_employee("test_fnf@example.com", company="_Test Company") + movement = create_asset_movement(employee) + frappe.db.set_value("Employee", employee, "relieving_date", add_days(today(), 30)) + fnf = create_full_and_final_statement(employee) + + payables_bootstraped_component = ["Salary Slip", "Gratuity", + "Expense Claim", "Bonus", "Leave Encashment"] + + receivable_bootstraped_component = ["Loan", "Employee Advance"] + + #checking payable s and receivables bootstraped value + self.assertEqual([payable.component for payable in fnf.payables], payables_bootstraped_component) + self.assertEqual([receivable.component for receivable in fnf.receivables], receivable_bootstraped_component) + + #checking allocated asset + self.assertIn(movement, [asset.reference for asset in fnf.assets_allocated]) + +def create_full_and_final_statement(employee): + fnf = frappe.new_doc("Full and Final Statement") + fnf.employee = employee + fnf.transaction_date = today() + fnf.save() + return fnf + +def create_asset_movement(employee): + asset_name = create_asset() + movement = frappe.new_doc("Asset Movement") + movement.company = "_Test Company" + movement.purpose = "Issue" + movement.transaction_date = today() + + movement.append("assets", { + "asset": asset_name, + "to_employee": employee + }) + + movement.save() + movement.submit() + return movement.name + +def create_asset(): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name") + asset = frappe.get_doc("Asset", asset_name) + asset.calculate_depreciation = 0 + asset.available_for_use_date = today() + asset.submit() + return asset_name diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.py b/erpnext/hr/doctype/grievance_type/grievance_type.py index 618cf0a0318..5d8d41cb73d 100644 --- a/erpnext/hr/doctype/grievance_type/grievance_type.py +++ b/erpnext/hr/doctype/grievance_type/grievance_type.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class GrievanceType(Document): pass diff --git a/erpnext/hr/doctype/grievance_type/test_grievance_type.py b/erpnext/hr/doctype/grievance_type/test_grievance_type.py index a02a34d41fa..481f4e58a79 100644 --- a/erpnext/hr/doctype/grievance_type/test_grievance_type.py +++ b/erpnext/hr/doctype/grievance_type/test_grievance_type.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestGrievanceType(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/holiday/__init__.py b/erpnext/hr/doctype/holiday/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/holiday/__init__.py +++ b/erpnext/hr/doctype/holiday/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/holiday/holiday.py b/erpnext/hr/doctype/holiday/holiday.py index 78a95b9b741..85ca0b74fc7 100644 --- a/erpnext/hr/doctype/holiday/holiday.py +++ b/erpnext/hr/doctype/holiday/holiday.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class Holiday(Document): pass diff --git a/erpnext/hr/doctype/holiday_list/__init__.py b/erpnext/hr/doctype/holiday_list/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/holiday_list/__init__.py +++ b/erpnext/hr/doctype/holiday_list/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.js b/erpnext/hr/doctype/holiday_list/holiday_list.js index 462bd8bb671..ea033c7ed92 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.js +++ b/erpnext/hr/doctype/holiday_list/holiday_list.js @@ -1,10 +1,10 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Holiday List', { +frappe.ui.form.on("Holiday List", { refresh: function(frm) { if (frm.doc.holidays) { - frm.set_value('total_holidays', frm.doc.holidays.length); + frm.set_value("total_holidays", frm.doc.holidays.length); } }, from_date: function(frm) { @@ -14,3 +14,36 @@ frappe.ui.form.on('Holiday List', { } } }); + +frappe.tour["Holiday List"] = [ + { + fieldname: "holiday_list_name", + title: "Holiday List Name", + description: __("Enter a name for this Holiday List."), + }, + { + fieldname: "from_date", + title: "From Date", + description: __("Based on your HR Policy, select your leave allocation period's start date"), + }, + { + fieldname: "to_date", + title: "To Date", + description: __("Based on your HR Policy, select your leave allocation period's end date"), + }, + { + fieldname: "weekly_off", + title: "Weekly Off", + description: __("Select your weekly off day"), + }, + { + fieldname: "get_weekly_off_dates", + title: "Add Holidays", + description: __("Click on Add to Holidays. This will populate the holidays table with all the dates that fall on the selected weekly off. Repeat the process for populating the dates for all your weekly holidays"), + }, + { + fieldname: "holidays", + title: "Holidays", + description: __("Here, your weekly offs are pre-populated based on the previous selections. You can add more rows to also add public and national holidays individually.") + }, +]; diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index f65e6e12074..a8c8c16d0d2 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -1,13 +1,14 @@ - # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + import json -from frappe.utils import cint, getdate, formatdate, today -from frappe import throw, _ + +import frappe +from frappe import _, throw from frappe.model.document import Document +from frappe.utils import cint, formatdate, getdate, today + class OverlapError(frappe.ValidationError): pass @@ -44,9 +45,10 @@ class HolidayList(Document): def get_weekly_off_date_list(self, start_date, end_date): start_date, end_date = getdate(start_date), getdate(end_date) - from dateutil import relativedelta - from datetime import timedelta import calendar + from datetime import timedelta + + from dateutil import relativedelta date_list = [] existing_date_list = [] @@ -90,9 +92,11 @@ def get_events(start, end, filters=None): update={"allDay": 1}) -def is_holiday(holiday_list, date=today()): +def is_holiday(holiday_list, date=None): """Returns true if the given date is a holiday in the given holiday list """ + if date is None: + date = today() if holiday_list: return bool(frappe.get_all('Holiday List', dict(name=holiday_list, holiday_date=date))) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py index 05641c7dc26..4a540ce610d 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_data(): return { 'fieldname': 'holiday_list', diff --git a/erpnext/hr/doctype/holiday_list/test_holiday_list.py b/erpnext/hr/doctype/holiday_list/test_holiday_list.py index 64bed6637bd..c9239edb720 100644 --- a/erpnext/hr/doctype/holiday_list/test_holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/test_holiday_list.py @@ -1,11 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + +import unittest +from datetime import timedelta import frappe -import unittest from frappe.utils import getdate -from datetime import timedelta class TestHolidayList(unittest.TestCase): diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.js b/erpnext/hr/doctype/hr_settings/hr_settings.js index ec99472d9bc..6e26a1fa71d 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.js +++ b/erpnext/hr/doctype/hr_settings/hr_settings.js @@ -2,7 +2,22 @@ // For license information, please see license.txt frappe.ui.form.on('HR Settings', { - restrict_backdated_leave_application: function(frm) { - frm.toggle_reqd("role_allowed_to_create_backdated_leave_application", frm.doc.restrict_backdated_leave_application); - } }); + +frappe.tour['HR Settings'] = [ + { + fieldname: 'emp_created_by', + title: 'Employee Naming By', + description: __('Employee can be named by Employee ID if you assign one, or via Naming Series. Select your preference here.'), + }, + { + fieldname: 'standard_working_hours', + title: 'Standard Working Hours', + description: __('Enter the Standard Working Hours for a normal work day. These hours will be used in calculations of reports such as Employee Hours Utilization and Project Profitability analysis.'), + }, + { + fieldname: 'leave_and_expense_claim_settings', + title: 'Leave and Expense Clain Settings', + description: __('Review various other settings related to Employee Leaves and Expense Claim') + } +]; diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 8aa3c0ca9f1..5148435c130 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -7,30 +7,36 @@ "engine": "InnoDB", "field_order": [ "employee_settings", - "retirement_age", "emp_created_by", - "column_break_4", "standard_working_hours", - "expense_approver_mandatory_in_expense_claim", + "column_break_9", + "retirement_age", "reminders_section", "send_birthday_reminders", - "column_break_9", - "send_work_anniversary_reminders", "column_break_11", + "send_work_anniversary_reminders", + "column_break_18", "send_holiday_reminders", "frequency", - "leave_settings", + "leave_and_expense_claim_settings", "send_leave_notification", "leave_approval_notification_template", "leave_status_notification_template", - "role_allowed_to_create_backdated_leave_application", - "column_break_18", "leave_approver_mandatory_in_leave_application", + "restrict_backdated_leave_application", + "role_allowed_to_create_backdated_leave_application", + "column_break_29", + "expense_approver_mandatory_in_expense_claim", "show_leaves_of_all_department_members_in_calendar", "auto_leave_encashment", - "restrict_backdated_leave_application", - "hiring_settings", - "check_vacancies" + "hiring_settings_section", + "check_vacancies", + "send_interview_reminder", + "interview_reminder_template", + "remind_before", + "column_break_4", + "send_interview_feedback_reminder", + "feedback_reminder_notification_template" ], "fields": [ { @@ -39,17 +45,16 @@ "label": "Employee Settings" }, { - "description": "Enter retirement age in years", "fieldname": "retirement_age", "fieldtype": "Data", - "label": "Retirement Age" + "label": "Retirement Age (In Years)" }, { "default": "Naming Series", - "description": "Employee records are created using the selected field", + "description": "Employee records are created using the selected option", "fieldname": "emp_created_by", "fieldtype": "Select", - "label": "Employee Records to be created by", + "label": "Employee Naming By", "options": "Naming Series\nEmployee Number\nFull Name" }, { @@ -62,28 +67,6 @@ "fieldtype": "Check", "label": "Expense Approver Mandatory In Expense Claim" }, - { - "collapsible": 1, - "fieldname": "leave_settings", - "fieldtype": "Section Break", - "label": "Leave Settings" - }, - { - "depends_on": "eval: doc.send_leave_notification == 1", - "fieldname": "leave_approval_notification_template", - "fieldtype": "Link", - "label": "Leave Approval Notification Template", - "mandatory_depends_on": "eval: doc.send_leave_notification == 1", - "options": "Email Template" - }, - { - "depends_on": "eval: doc.send_leave_notification == 1", - "fieldname": "leave_status_notification_template", - "fieldtype": "Link", - "label": "Leave Status Notification Template", - "mandatory_depends_on": "eval: doc.send_leave_notification == 1", - "options": "Email Template" - }, { "fieldname": "column_break_18", "fieldtype": "Column Break" @@ -100,35 +83,18 @@ "fieldtype": "Check", "label": "Show Leaves Of All Department Members In Calendar" }, - { - "collapsible": 1, - "fieldname": "hiring_settings", - "fieldtype": "Section Break", - "label": "Hiring Settings" - }, - { - "default": "0", - "fieldname": "check_vacancies", - "fieldtype": "Check", - "label": "Check Vacancies On Job Offer Creation" - }, { "default": "0", "fieldname": "auto_leave_encashment", "fieldtype": "Check", "label": "Auto Leave Encashment" }, - { - "default": "0", - "fieldname": "restrict_backdated_leave_application", - "fieldtype": "Check", - "label": "Restrict Backdated Leave Application" - }, { "depends_on": "eval:doc.restrict_backdated_leave_application == 1", "fieldname": "role_allowed_to_create_backdated_leave_application", "fieldtype": "Link", "label": "Role Allowed to Create Backdated Leave Application", + "mandatory_depends_on": "eval:doc.restrict_backdated_leave_application == 1", "options": "Role" }, { @@ -137,11 +103,40 @@ "fieldtype": "Check", "label": "Send Leave Notification" }, + { + "depends_on": "eval: doc.send_leave_notification == 1", + "fieldname": "leave_approval_notification_template", + "fieldtype": "Link", + "label": "Leave Approval Notification Template", + "mandatory_depends_on": "eval: doc.send_leave_notification == 1", + "options": "Email Template" + }, + { + "depends_on": "eval: doc.send_leave_notification == 1", + "fieldname": "leave_status_notification_template", + "fieldtype": "Link", + "label": "Leave Status Notification Template", + "mandatory_depends_on": "eval: doc.send_leave_notification == 1", + "options": "Email Template" + }, { "fieldname": "standard_working_hours", "fieldtype": "Int", "label": "Standard Working Hours" }, + { + "collapsible": 1, + "fieldname": "leave_and_expense_claim_settings", + "fieldtype": "Section Break", + "label": "Leave and Expense Claim Settings" + }, + { + "default": "00:15:00", + "depends_on": "send_interview_reminder", + "fieldname": "remind_before", + "fieldtype": "Time", + "label": "Remind Before" + }, { "collapsible": 1, "fieldname": "reminders_section", @@ -166,6 +161,7 @@ "fieldname": "frequency", "fieldtype": "Select", "label": "Set the frequency for holiday reminders", + "mandatory_depends_on": "send_holiday_reminders", "options": "Weekly\nMonthly" }, { @@ -181,13 +177,62 @@ { "fieldname": "column_break_11", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "send_interview_reminder", + "fieldtype": "Check", + "label": "Send Interview Reminder" + }, + { + "default": "0", + "fieldname": "send_interview_feedback_reminder", + "fieldtype": "Check", + "label": "Send Interview Feedback Reminder" + }, + { + "fieldname": "column_break_29", + "fieldtype": "Column Break" + }, + { + "depends_on": "send_interview_feedback_reminder", + "fieldname": "feedback_reminder_notification_template", + "fieldtype": "Link", + "label": "Feedback Reminder Notification Template", + "mandatory_depends_on": "send_interview_feedback_reminder", + "options": "Email Template" + }, + { + "depends_on": "send_interview_reminder", + "fieldname": "interview_reminder_template", + "fieldtype": "Link", + "label": "Interview Reminder Notification Template", + "mandatory_depends_on": "send_interview_reminder", + "options": "Email Template" + }, + { + "default": "0", + "fieldname": "restrict_backdated_leave_application", + "fieldtype": "Check", + "label": "Restrict Backdated Leave Application" + }, + { + "fieldname": "hiring_settings_section", + "fieldtype": "Section Break", + "label": "Hiring Settings" + }, + { + "default": "0", + "fieldname": "check_vacancies", + "fieldtype": "Check", + "label": "Check Vacancies On Job Offer Creation" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-08-24 14:54:12.834162", + "modified": "2021-10-01 23:46:11.098236", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.py b/erpnext/hr/doctype/hr_settings/hr_settings.py index a47409363c7..c295bcbc0d9 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.py +++ b/erpnext/hr/doctype/hr_settings/hr_settings.py @@ -4,7 +4,6 @@ # For license information, please see license.txt import frappe - from frappe.model.document import Document from frappe.utils import format_date diff --git a/erpnext/hr/doctype/hr_settings/test_hr_settings.js b/erpnext/hr/doctype/hr_settings/test_hr_settings.js deleted file mode 100644 index f32640ba5c6..00000000000 --- a/erpnext/hr/doctype/hr_settings/test_hr_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: HR Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new HR Settings - () => frappe.tests.make('HR Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/hr_settings/test_hr_settings.py b/erpnext/hr/doctype/hr_settings/test_hr_settings.py index b0b07b0c0b3..7e13213ff32 100644 --- a/erpnext/hr/doctype/hr_settings/test_hr_settings.py +++ b/erpnext/hr/doctype/hr_settings/test_hr_settings.py @@ -1,13 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest -from erpnext.hr.doctype.employee.test_employee import make_employee -from frappe.utils import now_datetime -from datetime import timedelta + class TestHRSettings(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/identification_document_type/identification_document_type.py b/erpnext/hr/doctype/identification_document_type/identification_document_type.py index d9d81d2fa8f..3bfcfaadcc8 100644 --- a/erpnext/hr/doctype/identification_document_type/identification_document_type.py +++ b/erpnext/hr/doctype/identification_document_type/identification_document_type.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class IdentificationDocumentType(Document): pass diff --git a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js b/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js deleted file mode 100644 index 65879098e87..00000000000 --- a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Identification Document Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Identification Document Type - () => frappe.tests.make('Identification Document Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.py b/erpnext/hr/doctype/identification_document_type/test_identification_document_type.py index 1265afaf457..3e8f7ab0d6d 100644 --- a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.py +++ b/erpnext/hr/doctype/identification_document_type/test_identification_document_type.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestIdentificationDocumentType(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/interest/interest.py b/erpnext/hr/doctype/interest/interest.py index 2a9c19c09d8..3563f7f3a0a 100644 --- a/erpnext/hr/doctype/interest/interest.py +++ b/erpnext/hr/doctype/interest/interest.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class Interest(Document): pass diff --git a/erpnext/hr/doctype/interest/test_interest.py b/erpnext/hr/doctype/interest/test_interest.py index a7fe83bccc8..d4ecd9b841e 100644 --- a/erpnext/hr/doctype/interest/test_interest.py +++ b/erpnext/hr/doctype/interest/test_interest.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Interest') diff --git a/erpnext/healthcare/doctype/exercise/__init__.py b/erpnext/hr/doctype/interview/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/exercise/__init__.py rename to erpnext/hr/doctype/interview/__init__.py diff --git a/erpnext/hr/doctype/interview/interview.js b/erpnext/hr/doctype/interview/interview.js new file mode 100644 index 00000000000..6341e3a62b4 --- /dev/null +++ b/erpnext/hr/doctype/interview/interview.js @@ -0,0 +1,237 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Interview', { + onload: function (frm) { + frm.events.set_job_applicant_query(frm); + + frm.set_query('interviewer', 'interview_details', function () { + return { + query: 'erpnext.hr.doctype.interview.interview.get_interviewer_list' + }; + }); + }, + + refresh: function (frm) { + if (frm.doc.docstatus != 2 && !frm.doc.__islocal) { + if (frm.doc.status === 'Pending') { + frm.add_custom_button(__('Reschedule Interview'), function() { + frm.events.show_reschedule_dialog(frm); + frm.refresh(); + }); + } + + let allowed_interviewers = []; + frm.doc.interview_details.forEach(values => { + allowed_interviewers.push(values.interviewer); + }); + + if ((allowed_interviewers.includes(frappe.session.user))) { + frappe.db.get_value('Interview Feedback', {'interviewer': frappe.session.user, 'interview': frm.doc.name, 'docstatus': 1}, 'name', (r) => { + if (Object.keys(r).length === 0) { + frm.add_custom_button(__('Submit Feedback'), function () { + frappe.call({ + method: 'erpnext.hr.doctype.interview.interview.get_expected_skill_set', + args: { + interview_round: frm.doc.interview_round + }, + callback: function (r) { + frm.events.show_feedback_dialog(frm, r.message); + frm.refresh(); + } + }); + }).addClass('btn-primary'); + } + }); + } + } + }, + + show_reschedule_dialog: function (frm) { + let d = new frappe.ui.Dialog({ + title: 'Reschedule Interview', + fields: [ + { + label: 'Schedule On', + fieldname: 'scheduled_on', + fieldtype: 'Date', + reqd: 1 + }, + { + label: 'From Time', + fieldname: 'from_time', + fieldtype: 'Time', + reqd: 1 + }, + { + label: 'To Time', + fieldname: 'to_time', + fieldtype: 'Time', + reqd: 1 + } + ], + primary_action_label: 'Reschedule', + primary_action(values) { + frm.call({ + method: 'reschedule_interview', + doc: frm.doc, + args: { + scheduled_on: values.scheduled_on, + from_time: values.from_time, + to_time: values.to_time + } + }).then(() => { + frm.refresh(); + d.hide(); + }); + } + }); + d.show(); + }, + + show_feedback_dialog: function (frm, data) { + let fields = frm.events.get_fields_for_feedback(); + + let d = new frappe.ui.Dialog({ + title: __('Submit Feedback'), + fields: [ + { + fieldname: 'skill_set', + fieldtype: 'Table', + label: __('Skill Assessment'), + cannot_add_rows: false, + in_editable_grid: true, + reqd: 1, + fields: fields, + data: data + }, + { + fieldname: 'result', + fieldtype: 'Select', + options: ['', 'Cleared', 'Rejected'], + label: __('Result') + }, + { + fieldname: 'feedback', + fieldtype: 'Small Text', + label: __('Feedback') + } + ], + size: 'large', + minimizable: true, + primary_action: function(values) { + frappe.call({ + method: 'erpnext.hr.doctype.interview.interview.create_interview_feedback', + args: { + data: values, + interview_name: frm.doc.name, + interviewer: frappe.session.user, + job_applicant: frm.doc.job_applicant + } + }).then(() => { + frm.refresh(); + }); + d.hide(); + } + }); + d.show(); + }, + + get_fields_for_feedback: function () { + return [{ + fieldtype: 'Link', + fieldname: 'skill', + options: 'Skill', + in_list_view: 1, + label: __('Skill') + }, { + fieldtype: 'Rating', + fieldname: 'rating', + label: __('Rating'), + in_list_view: 1, + reqd: 1, + }]; + }, + + set_job_applicant_query: function (frm) { + frm.set_query('job_applicant', function () { + let job_applicant_filters = { + status: ['!=', 'Rejected'] + }; + if (frm.doc.designation) { + job_applicant_filters.designation = frm.doc.designation; + } + return { + filters: job_applicant_filters + }; + }); + }, + + interview_round: async function (frm) { + frm.events.reset_values(frm); + frm.set_value('job_applicant', ''); + + let round_data = (await frappe.db.get_value('Interview Round', frm.doc.interview_round, 'designation')).message; + frm.set_value('designation', round_data.designation); + frm.events.set_job_applicant_query(frm); + + if (frm.doc.interview_round) { + frm.events.set_interview_details(frm); + } else { + frm.set_value('interview_details', []); + } + }, + + set_interview_details: function (frm) { + frappe.call({ + method: 'erpnext.hr.doctype.interview.interview.get_interviewers', + args: { + interview_round: frm.doc.interview_round + }, + callback: function (data) { + let interview_details = data.message; + frm.set_value('interview_details', []); + if (data.message.length) { + frm.set_value('interview_details', interview_details); + } + } + }); + }, + + job_applicant: function (frm) { + if (!frm.doc.interview_round) { + frm.doc.job_applicant = ''; + frm.refresh(); + frappe.throw(__('Select Interview Round First')); + } + + if (frm.doc.job_applicant) { + frm.events.set_designation_and_job_opening(frm); + } else { + frm.events.reset_values(frm); + } + }, + + set_designation_and_job_opening: async function (frm) { + let round_data = (await frappe.db.get_value('Interview Round', frm.doc.interview_round, 'designation')).message; + frm.set_value('designation', round_data.designation); + frm.events.set_job_applicant_query(frm); + + let job_applicant_data = (await frappe.db.get_value( + 'Job Applicant', frm.doc.job_applicant, ['designation', 'job_title', 'resume_link'], + )).message; + + if (!round_data.designation) { + frm.set_value('designation', job_applicant_data.designation); + } + + frm.set_value('job_opening', job_applicant_data.job_title); + frm.set_value('resume_link', job_applicant_data.resume_link); + }, + + reset_values: function (frm) { + frm.set_value('designation', ''); + frm.set_value('job_opening', ''); + frm.set_value('resume_link', ''); + } +}); diff --git a/erpnext/hr/doctype/interview/interview.json b/erpnext/hr/doctype/interview/interview.json new file mode 100644 index 00000000000..0d393e7556f --- /dev/null +++ b/erpnext/hr/doctype/interview/interview.json @@ -0,0 +1,254 @@ +{ + "actions": [], + "autoname": "HR-INT-.YYYY.-.####", + "creation": "2021-04-12 15:03:11.524090", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "interview_details_section", + "interview_round", + "job_applicant", + "job_opening", + "designation", + "resume_link", + "column_break_4", + "status", + "scheduled_on", + "from_time", + "to_time", + "interview_feedback_section", + "interview_details", + "ratings_section", + "expected_average_rating", + "column_break_12", + "average_rating", + "section_break_13", + "interview_summary", + "reminded", + "amended_from" + ], + "fields": [ + { + "fieldname": "job_applicant", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Job Applicant", + "options": "Job Applicant", + "reqd": 1 + }, + { + "fieldname": "job_opening", + "fieldtype": "Link", + "label": "Job Opening", + "options": "Job Opening", + "read_only": 1 + }, + { + "fieldname": "interview_round", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interview Round", + "options": "Interview Round", + "reqd": 1 + }, + { + "default": "Pending", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Pending\nUnder Review\nCleared\nRejected", + "reqd": 1 + }, + { + "fieldname": "ratings_section", + "fieldtype": "Section Break", + "label": "Ratings" + }, + { + "allow_on_submit": 1, + "fieldname": "average_rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Obtained Average Rating", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "interview_summary", + "fieldtype": "Text" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "resume_link", + "fieldtype": "Data", + "label": "Resume link" + }, + { + "fieldname": "interview_details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "fetch_from": "interview_round.expected_average_rating", + "fieldname": "expected_average_rating", + "fieldtype": "Rating", + "label": "Expected Average Rating", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "label": "Interview Summary" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fetch_from": "interview_round.designation", + "fieldname": "designation", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Interview", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "scheduled_on", + "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Scheduled On", + "reqd": 1, + "set_only_once": 1 + }, + { + "default": "0", + "fieldname": "reminded", + "fieldtype": "Check", + "hidden": 1, + "label": "Reminded" + }, + { + "allow_on_submit": 1, + "fieldname": "interview_details", + "fieldtype": "Table", + "options": "Interview Detail" + }, + { + "fieldname": "interview_feedback_section", + "fieldtype": "Section Break", + "label": "Feedback" + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "From Time", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "To Time", + "reqd": 1, + "set_only_once": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [ + { + "link_doctype": "Interview Feedback", + "link_fieldname": "interview" + } + ], + "modified": "2021-09-30 13:30:05.421035", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Interviewer", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "job_applicant", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py new file mode 100644 index 00000000000..4bb003ded18 --- /dev/null +++ b/erpnext/hr/doctype/interview/interview.py @@ -0,0 +1,290 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +import datetime + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cstr, get_datetime, get_link_to_form + + +class DuplicateInterviewRoundError(frappe.ValidationError): + pass + +class Interview(Document): + def validate(self): + self.validate_duplicate_interview() + self.validate_designation() + self.validate_overlap() + + def on_submit(self): + if self.status not in ['Cleared', 'Rejected']: + frappe.throw(_('Only Interviews with Cleared or Rejected status can be submitted.'), title=_('Not Allowed')) + + def validate_duplicate_interview(self): + duplicate_interview = frappe.db.exists('Interview', { + 'job_applicant': self.job_applicant, + 'interview_round': self.interview_round, + 'docstatus': 1 + } + ) + + if duplicate_interview: + frappe.throw(_('Job Applicants are not allowed to appear twice for the same Interview round. Interview {0} already scheduled for Job Applicant {1}').format( + frappe.bold(get_link_to_form('Interview', duplicate_interview)), + frappe.bold(self.job_applicant) + )) + + def validate_designation(self): + applicant_designation = frappe.db.get_value('Job Applicant', self.job_applicant, 'designation') + if self.designation : + if self.designation != applicant_designation: + frappe.throw(_('Interview Round {0} is only for Designation {1}. Job Applicant has applied for the role {2}').format( + self.interview_round, frappe.bold(self.designation), applicant_designation), + exc=DuplicateInterviewRoundError) + else: + self.designation = applicant_designation + + def validate_overlap(self): + interviewers = [entry.interviewer for entry in self.interview_details] or [''] + + overlaps = frappe.db.sql(""" + SELECT interview.name + FROM `tabInterview` as interview + INNER JOIN `tabInterview Detail` as detail + WHERE + interview.scheduled_on = %s and interview.name != %s and interview.docstatus != 2 + and (interview.job_applicant = %s or detail.interviewer IN %s) and + ((from_time < %s and to_time > %s) or + (from_time > %s and to_time < %s) or + (from_time = %s)) + """, (self.scheduled_on, self.name, self.job_applicant, interviewers, + self.from_time, self.to_time, self.from_time, self.to_time, self.from_time)) + + if overlaps: + overlapping_details = _('Interview overlaps with {0}').format(get_link_to_form('Interview', overlaps[0][0])) + frappe.throw(overlapping_details, title=_('Overlap')) + + + @frappe.whitelist() + def reschedule_interview(self, scheduled_on, from_time, to_time): + original_date = self.scheduled_on + from_time = self.from_time + to_time = self.to_time + + self.db_set({ + 'scheduled_on': scheduled_on, + 'from_time': from_time, + 'to_time': to_time + }) + self.notify_update() + + recipients = get_recipients(self.name) + + try: + frappe.sendmail( + recipients= recipients, + subject=_('Interview: {0} Rescheduled').format(self.name), + message=_('Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}').format( + original_date, from_time, to_time, self.scheduled_on, self.from_time, self.to_time), + reference_doctype=self.doctype, + reference_name=self.name + ) + except Exception: + frappe.msgprint(_('Failed to send the Interview Reschedule notification. Please configure your email account.')) + + frappe.msgprint(_('Interview Rescheduled successfully'), indicator='green') + + +def get_recipients(name, for_feedback=0): + interview = frappe.get_doc('Interview', name) + + if for_feedback: + recipients = [d.interviewer for d in interview.interview_details if not d.interview_feedback] + else: + recipients = [d.interviewer for d in interview.interview_details] + recipients.append(frappe.db.get_value('Job Applicant', interview.job_applicant, 'email_id')) + + return recipients + + +@frappe.whitelist() +def get_interviewers(interview_round): + return frappe.get_all('Interviewer', filters={'parent': interview_round}, fields=['user as interviewer']) + + +def send_interview_reminder(): + reminder_settings = frappe.db.get_value('HR Settings', 'HR Settings', + ['send_interview_reminder', 'interview_reminder_template'], as_dict=True) + + if not reminder_settings.send_interview_reminder: + return + + remind_before = cstr(frappe.db.get_single_value('HR Settings', 'remind_before')) or '01:00:00' + remind_before = datetime.datetime.strptime(remind_before, '%H:%M:%S') + reminder_date_time = datetime.datetime.now() + datetime.timedelta( + hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second) + + interviews = frappe.get_all('Interview', filters={ + 'scheduled_on': ['between', (datetime.datetime.now(), reminder_date_time)], + 'status': 'Pending', + 'reminded': 0, + 'docstatus': ['!=', 2] + }) + + interview_template = frappe.get_doc('Email Template', reminder_settings.interview_reminder_template) + + for d in interviews: + doc = frappe.get_doc('Interview', d.name) + context = doc.as_dict() + message = frappe.render_template(interview_template.response, context) + recipients = get_recipients(doc.name) + + frappe.sendmail( + recipients= recipients, + subject=interview_template.subject, + message=message, + reference_doctype=doc.doctype, + reference_name=doc.name + ) + + doc.db_set('reminded', 1) + + +def send_daily_feedback_reminder(): + reminder_settings = frappe.db.get_value('HR Settings', 'HR Settings', + ['send_interview_feedback_reminder', 'feedback_reminder_notification_template'], as_dict=True) + + if not reminder_settings.send_interview_feedback_reminder: + return + + interview_feedback_template = frappe.get_doc('Email Template', reminder_settings.feedback_reminder_notification_template) + interviews = frappe.get_all('Interview', filters={'status': ['in', ['Under Review', 'Pending']], 'docstatus': ['!=', 2]}) + + for entry in interviews: + recipients = get_recipients(entry.name, for_feedback=1) + + doc = frappe.get_doc('Interview', entry.name) + context = doc.as_dict() + + message = frappe.render_template(interview_feedback_template.response, context) + + if len(recipients): + frappe.sendmail( + recipients= recipients, + subject=interview_feedback_template.subject, + message=message, + reference_doctype='Interview', + reference_name=entry.name + ) + + +@frappe.whitelist() +def get_expected_skill_set(interview_round): + return frappe.get_all('Expected Skill Set', filters ={'parent': interview_round}, fields=['skill']) + + +@frappe.whitelist() +def create_interview_feedback(data, interview_name, interviewer, job_applicant): + import json + + + if isinstance(data, str): + data = frappe._dict(json.loads(data)) + + if frappe.session.user != interviewer: + frappe.throw(_('Only Interviewer Are allowed to submit Interview Feedback')) + + interview_feedback = frappe.new_doc('Interview Feedback') + interview_feedback.interview = interview_name + interview_feedback.interviewer = interviewer + interview_feedback.job_applicant = job_applicant + + for d in data.skill_set: + d = frappe._dict(d) + interview_feedback.append('skill_assessment', {'skill': d.skill, 'rating': d.rating}) + + interview_feedback.feedback = data.feedback + interview_feedback.result = data.result + + interview_feedback.save() + interview_feedback.submit() + + frappe.msgprint(_('Interview Feedback {0} submitted successfully').format( + get_link_to_form('Interview Feedback', interview_feedback.name))) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_interviewer_list(doctype, txt, searchfield, start, page_len, filters): + filters = [ + ['Has Role', 'parent', 'like', '%{}%'.format(txt)], + ['Has Role', 'role', '=', 'interviewer'], + ['Has Role', 'parenttype', '=', 'User'] + ] + + if filters and isinstance(filters, list): + filters.extend(filters) + + return frappe.get_all('Has Role', limit_start=start, limit_page_length=page_len, + filters=filters, fields = ['parent'], as_list=1) + + +@frappe.whitelist() +def get_events(start, end, filters=None): + """Returns events for Gantt / Calendar view rendering. + + :param start: Start date-time. + :param end: End date-time. + :param filters: Filters (JSON). + """ + from frappe.desk.calendar import get_event_conditions + + events = [] + + event_color = { + "Pending": "#fff4f0", + "Under Review": "#d3e8fc", + "Cleared": "#eaf5ed", + "Rejected": "#fce7e7" + } + + conditions = get_event_conditions('Interview', filters) + + interviews = frappe.db.sql(""" + SELECT DISTINCT + `tabInterview`.name, `tabInterview`.job_applicant, `tabInterview`.interview_round, + `tabInterview`.scheduled_on, `tabInterview`.status, `tabInterview`.from_time as from_time, + `tabInterview`.to_time as to_time + from + `tabInterview` + where + (`tabInterview`.scheduled_on between %(start)s and %(end)s) + and docstatus != 2 + {conditions} + """.format(conditions=conditions), { + "start": start, + "end": end + }, as_dict=True, update={"allDay": 0}) + + for d in interviews: + subject_data = [] + for field in ["name", "job_applicant", "interview_round"]: + if not d.get(field): + continue + subject_data.append(d.get(field)) + + color = event_color.get(d.status) + interview_data = { + 'from': get_datetime('%s %s' % (d.scheduled_on, d.from_time or '00:00:00')), + 'to': get_datetime('%s %s' % (d.scheduled_on, d.to_time or '00:00:00')), + 'name': d.name, + 'subject': '\n'.join(subject_data), + 'color': color if color else "#89bcde" + } + + events.append(interview_data) + + return events diff --git a/erpnext/hr/doctype/interview/interview_calendar.js b/erpnext/hr/doctype/interview/interview_calendar.js new file mode 100644 index 00000000000..b46b72ecb21 --- /dev/null +++ b/erpnext/hr/doctype/interview/interview_calendar.js @@ -0,0 +1,14 @@ + +frappe.views.calendar['Interview'] = { + field_map: { + 'start': 'from', + 'end': 'to', + 'id': 'name', + 'title': 'subject', + 'allDay': 'allDay', + 'color': 'color' + }, + order_by: 'scheduled_on', + gantt: true, + get_events_method: 'erpnext.hr.doctype.interview.interview.get_events' +}; diff --git a/erpnext/hr/doctype/interview/interview_feedback_reminder_template.html b/erpnext/hr/doctype/interview/interview_feedback_reminder_template.html new file mode 100644 index 00000000000..8d39fb54ef7 --- /dev/null +++ b/erpnext/hr/doctype/interview/interview_feedback_reminder_template.html @@ -0,0 +1,5 @@ +

Interview Feedback Reminder

+ +

+ Interview Feedback for Interview {{ name }} is not submitted yet. Please submit your feedback. Thank you, good day! +

diff --git a/erpnext/hr/doctype/interview/interview_list.js b/erpnext/hr/doctype/interview/interview_list.js new file mode 100644 index 00000000000..b1f072f0d4b --- /dev/null +++ b/erpnext/hr/doctype/interview/interview_list.js @@ -0,0 +1,12 @@ +frappe.listview_settings['Interview'] = { + has_indicator_for_draft: 1, + get_indicator: function(doc) { + let status_color = { + 'Pending': 'orange', + 'Under Review': 'blue', + 'Cleared': 'green', + 'Rejected': 'red', + }; + return [__(doc.status), status_color[doc.status], 'status,=,'+doc.status]; + } +}; diff --git a/erpnext/hr/doctype/interview/interview_reminder_notification_template.html b/erpnext/hr/doctype/interview/interview_reminder_notification_template.html new file mode 100644 index 00000000000..76de46e28db --- /dev/null +++ b/erpnext/hr/doctype/interview/interview_reminder_notification_template.html @@ -0,0 +1,5 @@ +

Interview Reminder

+ +

+ Interview: {{name}} is scheduled on {{scheduled_on}} from {{from_time}} to {{to_time}} +

diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py new file mode 100644 index 00000000000..1a2257a6d90 --- /dev/null +++ b/erpnext/hr/doctype/interview/test_interview.py @@ -0,0 +1,172 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import datetime +import os +import unittest + +import frappe +from frappe import _ +from frappe.core.doctype.user_permission.test_user_permission import create_user +from frappe.utils import add_days, getdate, nowtime + +from erpnext.hr.doctype.designation.test_designation import create_designation +from erpnext.hr.doctype.interview.interview import DuplicateInterviewRoundError +from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant + + +class TestInterview(unittest.TestCase): + def test_validations_for_designation(self): + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name, designation='_Test_Sales_manager', save=0) + self.assertRaises(DuplicateInterviewRoundError, interview.save) + + def test_notification_on_rescheduling(self): + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -4)) + + previous_scheduled_date = interview.scheduled_on + frappe.db.sql("DELETE FROM `tabEmail Queue`") + + interview.reschedule_interview(add_days(getdate(previous_scheduled_date), 2), + from_time=nowtime(), to_time=nowtime()) + interview.reload() + + self.assertEqual(interview.scheduled_on, add_days(getdate(previous_scheduled_date), 2)) + + notification = frappe.get_all("Email Queue", filters={"message": ("like", "%Your Interview session is rescheduled from%")}) + self.assertIsNotNone(notification) + + def test_notification_for_scheduling(self): + from erpnext.hr.doctype.interview.interview import send_interview_reminder + + setup_reminder_settings() + + job_applicant = create_job_applicant() + scheduled_on = datetime.datetime.now() + datetime.timedelta(minutes=10) + + interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=scheduled_on) + + frappe.db.sql("DELETE FROM `tabEmail Queue`") + send_interview_reminder() + + interview.reload() + + email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) + self.assertTrue("Subject: Interview Reminder" in email_queue[0].message) + + def test_notification_for_feedback_submission(self): + from erpnext.hr.doctype.interview.interview import send_daily_feedback_reminder + + setup_reminder_settings() + + job_applicant = create_job_applicant() + scheduled_on = add_days(getdate(), -4) + create_interview_and_dependencies(job_applicant.name, scheduled_on=scheduled_on) + + frappe.db.sql("DELETE FROM `tabEmail Queue`") + send_daily_feedback_reminder() + + email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) + self.assertTrue("Subject: Interview Feedback Reminder" in email_queue[0].message) + + def tearDown(self): + frappe.db.rollback() + + +def create_interview_and_dependencies(job_applicant, scheduled_on=None, from_time=None, to_time=None, designation=None, save=1): + if designation: + designation=create_designation(designation_name = "_Test_Sales_manager").name + + interviewer_1 = create_user("test_interviewer1@example.com", "Interviewer") + interviewer_2 = create_user("test_interviewer2@example.com", "Interviewer") + + interview_round = create_interview_round( + "Technical Round", ["Python", "JS"], + designation=designation, save=True + ) + + interview = frappe.new_doc("Interview") + interview.interview_round = interview_round.name + interview.job_applicant = job_applicant + interview.scheduled_on = scheduled_on or getdate() + interview.from_time = from_time or nowtime() + interview.to_time = to_time or nowtime() + + interview.append("interview_details", {"interviewer": interviewer_1.name}) + interview.append("interview_details", {"interviewer": interviewer_2.name}) + + if save: + interview.save() + + return interview + +def create_interview_round(name, skill_set, interviewers=[], designation=None, save=True): + create_skill_set(skill_set) + interview_round = frappe.new_doc("Interview Round") + interview_round.round_name = name + interview_round.interview_type = create_interview_type() + interview_round.expected_average_rating = 4 + if designation: + interview_round.designation = designation + + for skill in skill_set: + interview_round.append("expected_skill_set", {"skill": skill}) + + for interviewer in interviewers: + interview_round.append("interviewer", { + "user": interviewer + }) + + if save: + interview_round.save() + + return interview_round + +def create_skill_set(skill_set): + for skill in skill_set: + if not frappe.db.exists("Skill", skill): + doc = frappe.new_doc("Skill") + doc.skill_name = skill + doc.save() + +def create_interview_type(name="test_interview_type"): + if frappe.db.exists("Interview Type", name): + return frappe.get_doc("Interview Type", name).name + else: + doc = frappe.new_doc("Interview Type") + doc.name = name + doc.description = "_Test_Description" + doc.save() + + return doc.name + +def setup_reminder_settings(): + if not frappe.db.exists('Email Template', _('Interview Reminder')): + base_path = frappe.get_app_path('erpnext', 'hr', 'doctype') + response = frappe.read_file(os.path.join(base_path, 'interview/interview_reminder_notification_template.html')) + + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _('Interview Reminder'), + 'response': response, + 'subject': _('Interview Reminder'), + 'owner': frappe.session.user, + }).insert(ignore_permissions=True) + + if not frappe.db.exists('Email Template', _('Interview Feedback Reminder')): + base_path = frappe.get_app_path('erpnext', 'hr', 'doctype') + response = frappe.read_file(os.path.join(base_path, 'interview/interview_feedback_reminder_template.html')) + + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _('Interview Feedback Reminder'), + 'response': response, + 'subject': _('Interview Feedback Reminder'), + 'owner': frappe.session.user, + }).insert(ignore_permissions=True) + + hr_settings = frappe.get_doc('HR Settings') + hr_settings.interview_reminder_template = _('Interview Reminder') + hr_settings.feedback_reminder_notification_template = _('Interview Feedback Reminder') + hr_settings.save() diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/__init__.py b/erpnext/hr/doctype/interview_detail/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/exercise_difficulty_level/__init__.py rename to erpnext/hr/doctype/interview_detail/__init__.py diff --git a/erpnext/hr/doctype/interview_detail/interview_detail.js b/erpnext/hr/doctype/interview_detail/interview_detail.js new file mode 100644 index 00000000000..88518ca4cc1 --- /dev/null +++ b/erpnext/hr/doctype/interview_detail/interview_detail.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Interview Detail', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/hr/doctype/interview_detail/interview_detail.json b/erpnext/hr/doctype/interview_detail/interview_detail.json new file mode 100644 index 00000000000..b5b49c0993a --- /dev/null +++ b/erpnext/hr/doctype/interview_detail/interview_detail.json @@ -0,0 +1,74 @@ +{ + "actions": [], + "creation": "2021-04-12 16:24:10.382863", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "interviewer", + "interview_feedback", + "average_rating", + "result", + "column_break_4", + "comments" + ], + "fields": [ + { + "fieldname": "interviewer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Interviewer", + "options": "User" + }, + { + "allow_on_submit": 1, + "fieldname": "interview_feedback", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Interview Feedback", + "options": "Interview Feedback", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "average_rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Average Rating", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fetch_from": "interview_feedback.feedback", + "fieldname": "comments", + "fieldtype": "Text", + "label": "Comments", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "result", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Result", + "options": "\nCleared\nRejected", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-29 13:13:25.865063", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview_detail/interview_detail.py b/erpnext/hr/doctype/interview_detail/interview_detail.py new file mode 100644 index 00000000000..d44e29a9c1d --- /dev/null +++ b/erpnext/hr/doctype/interview_detail/interview_detail.py @@ -0,0 +1,10 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class InterviewDetail(Document): + pass diff --git a/erpnext/hr/doctype/interview_detail/test_interview_detail.py b/erpnext/hr/doctype/interview_detail/test_interview_detail.py new file mode 100644 index 00000000000..68a1f724851 --- /dev/null +++ b/erpnext/hr/doctype/interview_detail/test_interview_detail.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestInterviewDetail(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/exercise_type/__init__.py b/erpnext/hr/doctype/interview_feedback/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/exercise_type/__init__.py rename to erpnext/hr/doctype/interview_feedback/__init__.py diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.js b/erpnext/hr/doctype/interview_feedback/interview_feedback.js new file mode 100644 index 00000000000..dec559fceae --- /dev/null +++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.js @@ -0,0 +1,54 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Interview Feedback', { + onload: function(frm) { + frm.ignore_doctypes_on_cancel_all = ['Interview']; + + frm.set_query('interview', function() { + return { + filters: { + docstatus: ['!=', 2] + } + }; + }); + }, + + interview_round: function(frm) { + frappe.call({ + method: 'erpnext.hr.doctype.interview.interview.get_expected_skill_set', + args: { + interview_round: frm.doc.interview_round + }, + callback: function(r) { + frm.set_value('skill_assessment', r.message); + } + }); + }, + + interview: function(frm) { + frappe.call({ + method: 'erpnext.hr.doctype.interview_feedback.interview_feedback.get_applicable_interviewers', + args: { + interview: frm.doc.interview || '' + }, + callback: function(r) { + frm.set_query('interviewer', function() { + return { + filters: { + name: ['in', r.message] + } + }; + }); + } + }); + + }, + + interviewer: function(frm) { + if (!frm.doc.interview) { + frappe.throw(__('Select Interview first')); + frm.set_value('interviewer', ''); + } + } +}); diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.json b/erpnext/hr/doctype/interview_feedback/interview_feedback.json new file mode 100644 index 00000000000..6a2f7e86969 --- /dev/null +++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.json @@ -0,0 +1,171 @@ +{ + "actions": [], + "autoname": "HR-INT-FEED-.####", + "creation": "2021-04-12 17:03:13.833285", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "details_section", + "interview", + "interview_round", + "job_applicant", + "column_break_3", + "interviewer", + "result", + "section_break_4", + "skill_assessment", + "average_rating", + "section_break_7", + "feedback", + "amended_from" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "interview", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interview", + "options": "Interview", + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fetch_from": "interview.interview_round", + "fieldname": "interview_round", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interview Round", + "options": "Interview Round", + "read_only": 1, + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "interviewer", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interviewer", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "label": "Skill Assessment" + }, + { + "allow_in_quick_entry": 1, + "fieldname": "skill_assessment", + "fieldtype": "Table", + "options": "Skill Assessment", + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "average_rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Average Rating", + "read_only": 1 + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Feedback" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Interview Feedback", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "feedback", + "fieldtype": "Text" + }, + { + "fieldname": "result", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Result", + "options": "\nCleared\nRejected", + "reqd": 1 + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "fetch_from": "interview.job_applicant", + "fieldname": "job_applicant", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Job Applicant", + "options": "Job Applicant", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-09-30 13:30:49.955352", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview Feedback", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Interviewer", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "interviewer", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.py b/erpnext/hr/doctype/interview_feedback/interview_feedback.py new file mode 100644 index 00000000000..d046458f196 --- /dev/null +++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.py @@ -0,0 +1,86 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import flt, get_link_to_form, getdate + + +class InterviewFeedback(Document): + def validate(self): + self.validate_interviewer() + self.validate_interview_date() + self.validate_duplicate() + self.calculate_average_rating() + + def on_submit(self): + self.update_interview_details() + + def on_cancel(self): + self.update_interview_details() + + def validate_interviewer(self): + applicable_interviewers = get_applicable_interviewers(self.interview) + if self.interviewer not in applicable_interviewers: + frappe.throw(_('{0} is not allowed to submit Interview Feedback for the Interview: {1}').format( + frappe.bold(self.interviewer), frappe.bold(self.interview))) + + def validate_interview_date(self): + scheduled_date = frappe.db.get_value('Interview', self.interview, 'scheduled_on') + + if getdate() < getdate(scheduled_date) and self.docstatus == 1: + frappe.throw(_('{0} submission before {1} is not allowed').format( + frappe.bold('Interview Feedback'), + frappe.bold('Interview Scheduled Date') + )) + + def validate_duplicate(self): + duplicate_feedback = frappe.db.exists('Interview Feedback', { + 'interviewer': self.interviewer, + 'interview': self.interview, + 'docstatus': 1 + }) + + if duplicate_feedback: + frappe.throw(_('Feedback already submitted for the Interview {0}. Please cancel the previous Interview Feedback {1} to continue.').format( + self.interview, get_link_to_form('Interview Feedback', duplicate_feedback))) + + def calculate_average_rating(self): + total_rating = 0 + for d in self.skill_assessment: + if d.rating: + total_rating += d.rating + + self.average_rating = flt(total_rating / len(self.skill_assessment) if len(self.skill_assessment) else 0) + + def update_interview_details(self): + doc = frappe.get_doc('Interview', self.interview) + total_rating = 0 + + if self.docstatus == 2: + for entry in doc.interview_details: + if entry.interview_feedback == self.name: + entry.average_rating = entry.interview_feedback = entry.comments = entry.result = None + break + else: + for entry in doc.interview_details: + if entry.interviewer == self.interviewer: + entry.average_rating = self.average_rating + entry.interview_feedback = self.name + entry.comments = self.feedback + entry.result = self.result + + if entry.average_rating: + total_rating += entry.average_rating + + doc.average_rating = flt(total_rating / len(doc.interview_details) if len(doc.interview_details) else 0) + doc.save() + doc.notify_update() + + +@frappe.whitelist() +def get_applicable_interviewers(interview): + data = frappe.get_all('Interview Detail', filters={'parent': interview}, fields=['interviewer']) + return [d.interviewer for d in data] diff --git a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py new file mode 100644 index 00000000000..4185f2827a5 --- /dev/null +++ b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py @@ -0,0 +1,101 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import unittest + +import frappe +from frappe.utils import add_days, flt, getdate + +from erpnext.hr.doctype.interview.test_interview import ( + create_interview_and_dependencies, + create_skill_set, +) +from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant + + +class TestInterviewFeedback(unittest.TestCase): + def test_validation_for_skill_set(self): + frappe.set_user("Administrator") + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -1)) + skill_ratings = get_skills_rating(interview.interview_round) + + interviewer = interview.interview_details[0].interviewer + create_skill_set(['Leadership']) + + interview_feedback = create_interview_feedback(interview.name, interviewer, skill_ratings) + interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 4}) + frappe.set_user(interviewer) + + self.assertRaises(frappe.ValidationError, interview_feedback.save) + + frappe.set_user("Administrator") + + def test_average_ratings_on_feedback_submission_and_cancellation(self): + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -1)) + skill_ratings = get_skills_rating(interview.interview_round) + + # For First Interviewer Feedback + interviewer = interview.interview_details[0].interviewer + frappe.set_user(interviewer) + + # calculating Average + feedback_1 = create_interview_feedback(interview.name, interviewer, skill_ratings) + + total_rating = 0 + for d in feedback_1.skill_assessment: + if d.rating: + total_rating += d.rating + + avg_rating = flt(total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0) + + self.assertEqual(flt(avg_rating, 3), feedback_1.average_rating) + + avg_on_interview_detail = frappe.db.get_value('Interview Detail', { + 'parent': feedback_1.interview, + 'interviewer': feedback_1.interviewer, + 'interview_feedback': feedback_1.name + }, 'average_rating') + + # 1. average should be reflected in Interview Detail. + self.assertEqual(avg_on_interview_detail, round(feedback_1.average_rating)) + + '''For Second Interviewer Feedback''' + interviewer = interview.interview_details[1].interviewer + frappe.set_user(interviewer) + + feedback_2 = create_interview_feedback(interview.name, interviewer, skill_ratings) + interview.reload() + + feedback_2.cancel() + interview.reload() + + frappe.set_user("Administrator") + + def tearDown(self): + frappe.db.rollback() + + +def create_interview_feedback(interview, interviewer, skills_ratings): + interview_feedback = frappe.new_doc("Interview Feedback") + interview_feedback.interview = interview + interview_feedback.interviewer = interviewer + interview_feedback.result = "Cleared" + + for rating in skills_ratings: + interview_feedback.append("skill_assessment", rating) + + interview_feedback.save() + interview_feedback.submit() + + return interview_feedback + + +def get_skills_rating(interview_round): + import random + + skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields = ["skill"]) + for d in skills: + d["rating"] = random.randint(1, 5) + return skills diff --git a/erpnext/healthcare/doctype/exercise_type_step/__init__.py b/erpnext/hr/doctype/interview_round/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/exercise_type_step/__init__.py rename to erpnext/hr/doctype/interview_round/__init__.py diff --git a/erpnext/hr/doctype/interview_round/interview_round.js b/erpnext/hr/doctype/interview_round/interview_round.js new file mode 100644 index 00000000000..6a608b03d25 --- /dev/null +++ b/erpnext/hr/doctype/interview_round/interview_round.js @@ -0,0 +1,24 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Interview Round", { + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__("Create Interview"), function() { + frm.events.create_interview(frm); + }); + } + }, + create_interview: function(frm) { + frappe.call({ + method: "erpnext.hr.doctype.interview_round.interview_round.create_interview", + args: { + doc: frm.doc + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + } +}); diff --git a/erpnext/hr/doctype/interview_round/interview_round.json b/erpnext/hr/doctype/interview_round/interview_round.json new file mode 100644 index 00000000000..9c95185e9ce --- /dev/null +++ b/erpnext/hr/doctype/interview_round/interview_round.json @@ -0,0 +1,118 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:round_name", + "creation": "2021-04-12 12:57:19.902866", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "round_name", + "interview_type", + "interviewers", + "column_break_3", + "designation", + "expected_average_rating", + "expected_skills_section", + "expected_skill_set" + ], + "fields": [ + { + "fieldname": "round_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Round Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation" + }, + { + "fieldname": "expected_skills_section", + "fieldtype": "Section Break", + "label": "Expected Skillset" + }, + { + "fieldname": "expected_skill_set", + "fieldtype": "Table", + "options": "Expected Skill Set", + "reqd": 1 + }, + { + "fieldname": "expected_average_rating", + "fieldtype": "Rating", + "label": "Expected Average Rating", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "interview_type", + "fieldtype": "Link", + "label": "Interview Type", + "options": "Interview Type", + "reqd": 1 + }, + { + "fieldname": "interviewers", + "fieldtype": "Table MultiSelect", + "label": "Interviewers", + "options": "Interviewer" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-09-30 13:01:25.666660", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview Round", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Interviewer", + "select": 1, + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview_round/interview_round.py b/erpnext/hr/doctype/interview_round/interview_round.py new file mode 100644 index 00000000000..0f442c320ad --- /dev/null +++ b/erpnext/hr/doctype/interview_round/interview_round.py @@ -0,0 +1,33 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +import json + +import frappe +from frappe.model.document import Document + + +class InterviewRound(Document): + pass + +@frappe.whitelist() +def create_interview(doc): + if isinstance(doc, str): + doc = json.loads(doc) + doc = frappe.get_doc(doc) + + interview = frappe.new_doc("Interview") + interview.interview_round = doc.name + interview.designation = doc.designation + + if doc.interviewers: + interview.interview_details = [] + for data in doc.interviewers: + interview.append("interview_details", { + "interviewer": data.user + }) + return interview + + + diff --git a/erpnext/hr/doctype/interview_round/test_interview_round.py b/erpnext/hr/doctype/interview_round/test_interview_round.py new file mode 100644 index 00000000000..dcec9419c07 --- /dev/null +++ b/erpnext/hr/doctype/interview_round/test_interview_round.py @@ -0,0 +1,11 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import unittest + +# import frappe + + +class TestInterviewRound(unittest.TestCase): + pass + diff --git a/erpnext/healthcare/doctype/fee_validity/__init__.py b/erpnext/hr/doctype/interview_type/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/fee_validity/__init__.py rename to erpnext/hr/doctype/interview_type/__init__.py diff --git a/erpnext/hr/doctype/interview_type/interview_type.js b/erpnext/hr/doctype/interview_type/interview_type.js new file mode 100644 index 00000000000..af77b527d4d --- /dev/null +++ b/erpnext/hr/doctype/interview_type/interview_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Interview Type', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/hr/doctype/interview_type/interview_type.json similarity index 61% rename from erpnext/buying/doctype/supplier_item_group/supplier_item_group.json rename to erpnext/hr/doctype/interview_type/interview_type.json index 1971458f61e..14636a18cb3 100644 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json +++ b/erpnext/hr/doctype/interview_type/interview_type.json @@ -1,37 +1,33 @@ { "actions": [], - "creation": "2021-05-07 18:16:40.621421", + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2021-04-12 14:44:40.664034", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "supplier", - "item_group" + "description" ], "fields": [ { - "fieldname": "supplier", - "fieldtype": "Link", + "fieldname": "description", + "fieldtype": "Text", "in_list_view": 1, - "label": "Supplier", - "options": "Supplier", - "reqd": 1 - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Group", - "options": "Item Group", - "reqd": 1 + "label": "Description" } ], "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-05-19 13:48:16.742303", + "links": [ + { + "link_doctype": "Interview Round", + "link_fieldname": "interview_type" + } + ], + "modified": "2021-09-30 13:00:16.471518", "modified_by": "Administrator", - "module": "Buying", - "name": "Supplier Item Group", + "module": "HR", + "name": "Interview Type", "owner": "Administrator", "permissions": [ { @@ -54,7 +50,7 @@ "print": 1, "read": 1, "report": 1, - "role": "Purchase User", + "role": "HR Manager", "share": 1, "write": 1 }, @@ -66,7 +62,7 @@ "print": 1, "read": 1, "report": 1, - "role": "Purchase Manager", + "role": "HR User", "share": 1, "write": 1 } diff --git a/erpnext/hr/doctype/interview_type/interview_type.py b/erpnext/hr/doctype/interview_type/interview_type.py new file mode 100644 index 00000000000..f5ebda427b8 --- /dev/null +++ b/erpnext/hr/doctype/interview_type/interview_type.py @@ -0,0 +1,10 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class InterviewType(Document): + pass diff --git a/erpnext/hr/doctype/interview_type/test_interview_type.py b/erpnext/hr/doctype/interview_type/test_interview_type.py new file mode 100644 index 00000000000..96fdfcad686 --- /dev/null +++ b/erpnext/hr/doctype/interview_type/test_interview_type.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestInterviewType(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/fee_validity_reference/__init__.py b/erpnext/hr/doctype/interviewer/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/fee_validity_reference/__init__.py rename to erpnext/hr/doctype/interviewer/__init__.py diff --git a/erpnext/healthcare/doctype/body_part_link/body_part_link.json b/erpnext/hr/doctype/interviewer/interviewer.json similarity index 57% rename from erpnext/healthcare/doctype/body_part_link/body_part_link.json rename to erpnext/hr/doctype/interviewer/interviewer.json index 400b7c6fe81..a37b8b0e4e5 100644 --- a/erpnext/healthcare/doctype/body_part_link/body_part_link.json +++ b/erpnext/hr/doctype/interviewer/interviewer.json @@ -1,31 +1,30 @@ { "actions": [], - "creation": "2020-04-10 12:23:15.259816", + "creation": "2021-04-12 17:38:19.354734", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "body_part" + "user" ], "fields": [ { - "fieldname": "body_part", + "fieldname": "user", "fieldtype": "Link", "in_list_view": 1, - "label": "Body Part", - "options": "Body Part", - "reqd": 1 + "label": "User", + "options": "User" } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-04-10 12:25:23.101749", + "modified": "2021-04-13 13:41:35.817568", "modified_by": "Administrator", - "module": "Healthcare", - "name": "Body Part Link", + "module": "HR", + "name": "Interviewer", "owner": "Administrator", "permissions": [], - "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/erpnext/hr/doctype/interviewer/interviewer.py b/erpnext/hr/doctype/interviewer/interviewer.py new file mode 100644 index 00000000000..2dc4a143254 --- /dev/null +++ b/erpnext/hr/doctype/interviewer/interviewer.py @@ -0,0 +1,10 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class Interviewer(Document): + pass diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js index 7658bc93539..d7b1c6c9df3 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.js +++ b/erpnext/hr/doctype/job_applicant/job_applicant.js @@ -8,6 +8,24 @@ cur_frm.email_field = "email_id"; frappe.ui.form.on("Job Applicant", { refresh: function(frm) { + frm.set_query("job_title", function() { + return { + filters: { + 'status': 'Open' + } + }; + }); + frm.events.create_custom_buttons(frm); + frm.events.make_dashboard(frm); + }, + + create_custom_buttons: function(frm) { + if (!frm.doc.__islocal && frm.doc.status !== "Rejected" && frm.doc.status !== "Accepted") { + frm.add_custom_button(__("Create Interview"), function() { + frm.events.create_dialog(frm); + }); + } + if (!frm.doc.__islocal) { if (frm.doc.__onload && frm.doc.__onload.job_offer) { $('[data-doctype="Employee Onboarding"]').find("button").show(); @@ -28,14 +46,57 @@ frappe.ui.form.on("Job Applicant", { }); } } + }, - frm.set_query("job_title", function() { - return { - filters: { - 'status': 'Open' - } - }; + make_dashboard: function(frm) { + frappe.call({ + method: "erpnext.hr.doctype.job_applicant.job_applicant.get_interview_details", + args: { + job_applicant: frm.doc.name + }, + callback: function(r) { + $("div").remove(".form-dashboard-section.custom"); + frm.dashboard.add_section( + frappe.render_template('job_applicant_dashboard', { + data: r.message + }), + __("Interview Summary") + ); + } }); + }, + create_dialog: function(frm) { + let d = new frappe.ui.Dialog({ + title: 'Enter Interview Round', + fields: [ + { + label: 'Interview Round', + fieldname: 'interview_round', + fieldtype: 'Link', + options: 'Interview Round' + }, + ], + primary_action_label: 'Create Interview', + primary_action(values) { + frm.events.create_interview(frm, values); + d.hide(); + } + }); + d.show(); + }, + + create_interview: function (frm, values) { + frappe.call({ + method: "erpnext.hr.doctype.job_applicant.job_applicant.create_interview", + args: { + doc: frm.doc, + interview_round: values.interview_round + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); } }); diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json index bcea5f50d93..200f675221b 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.json +++ b/erpnext/hr/doctype/job_applicant/job_applicant.json @@ -9,16 +9,20 @@ "email_append_to": 1, "engine": "InnoDB", "field_order": [ + "details_section", "applicant_name", "email_id", "phone_number", "country", - "status", "column_break_3", "job_title", + "designation", + "status", + "source_and_rating_section", "source", "source_name", "employee_referral", + "column_break_13", "applicant_rating", "section_break_6", "notes", @@ -84,7 +88,8 @@ }, { "fieldname": "section_break_6", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Resume" }, { "fieldname": "cover_letter", @@ -160,13 +165,34 @@ "label": "Employee Referral", "options": "Employee Referral", "read_only": 1 + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "fieldname": "source_and_rating_section", + "fieldtype": "Section Break", + "label": "Source and Rating" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fetch_from": "job_opening.designation", + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation" } ], "icon": "fa fa-user", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-03-24 15:51:11.117517", + "modified": "2021-09-29 23:06:10.904260", "modified_by": "Administrator", "module": "HR", "name": "Job Applicant", diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index 14aeb03a87e..abaa50c84ce 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -3,11 +3,14 @@ # For license information, please see license.txt -from __future__ import unicode_literals -from frappe.model.document import Document + import frappe from frappe import _ -from frappe.utils import comma_and, validate_email_address +from frappe.model.document import Document +from frappe.utils import validate_email_address + +from erpnext.hr.doctype.interview.interview import get_interviewers + class DuplicationError(frappe.ValidationError): pass @@ -24,7 +27,6 @@ class JobApplicant(Document): self.name = " - ".join(keys) def validate(self): - self.check_email_id_is_unique() if self.email_id: validate_email_address(self.email_id, True) @@ -42,11 +44,43 @@ class JobApplicant(Document): elif self.status in ["Accepted", "Rejected"]: emp_ref.db_set("status", self.status) +@frappe.whitelist() +def create_interview(doc, interview_round): + import json - def check_email_id_is_unique(self): - if self.email_id: - names = frappe.db.sql_list("""select name from `tabJob Applicant` - where email_id=%s and name!=%s and job_title=%s""", (self.email_id, self.name, self.job_title)) - if names: - frappe.throw(_("Email Address must be unique, already exists for {0}").format(comma_and(names)), frappe.DuplicateEntryError) + if isinstance(doc, str): + doc = json.loads(doc) + doc = frappe.get_doc(doc) + + round_designation = frappe.db.get_value("Interview Round", interview_round, "designation") + + if round_designation and doc.designation and round_designation != doc.designation: + frappe.throw(_("Interview Round {0} is only applicable for the Designation {1}").format(interview_round, round_designation)) + + interview = frappe.new_doc("Interview") + interview.interview_round = interview_round + interview.job_applicant = doc.name + interview.designation = doc.designation + interview.resume_link = doc.resume_link + interview.job_opening = doc.job_title + interviewer_detail = get_interviewers(interview_round) + + for d in interviewer_detail: + interview.append("interview_details", { + "interviewer": d.interviewer + }) + return interview + +@frappe.whitelist() +def get_interview_details(job_applicant): + interview_details = frappe.db.get_all("Interview", + filters={"job_applicant":job_applicant, "docstatus": ["!=", 2]}, + fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"] + ) + interview_detail_map = {} + + for detail in interview_details: + interview_detail_map[detail.name] = detail + + return interview_detail_map diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html new file mode 100644 index 00000000000..c286787a556 --- /dev/null +++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html @@ -0,0 +1,44 @@ + +{% if not jQuery.isEmptyObject(data) %} + + + + + + + + + + + + + {% for(const [key, value] of Object.entries(data)) { %} + + + + + + + + {% } %} + +
{{ __("Interview") }}{{ __("Interview Round") }}{{ __("Status") }}{{ __("Expected Rating") }}{{ __("Rating") }}
{%= key %} {%= value["interview_round"] %} {%= value["status"] %} + {% for (i = 0; i < value["expected_average_rating"]; i++) { %} + + {% } %} + {% for (i = 0; i < (5-value["expected_average_rating"]); i++) { %} + + {% } %} + + {% if(value["average_rating"]){ %} + {% for (i = 0; i < value["average_rating"]; i++) { %} + + {% } %} + {% for (i = 0; i < (5-value["average_rating"]); i++) { %} + + {% } %} + {% } %} +
+{% else %} +

No Interview has been scheduled.

+{% endif %} diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py index ed97978a8ad..56331ac132f 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py @@ -1,15 +1,15 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): - return { - 'fieldname': 'job_applicant', - 'transactions': [ - { - 'items': ['Employee', 'Employee Onboarding'] - }, - { - 'items': ['Job Offer'] - }, - ], - } + return { + 'fieldname': 'job_applicant', + 'transactions': [ + { + 'items': ['Employee', 'Employee Onboarding'] + }, + { + 'items': ['Job Offer', 'Appointment Letter'] + }, + { + 'items': ['Interview'] + } + ], + } diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py index 872834230e6..36dcf6b0740 100644 --- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest -# test_records = frappe.get_test_records('Job Applicant') +import frappe + +from erpnext.hr.doctype.designation.test_designation import create_designation + class TestJobApplicant(unittest.TestCase): pass @@ -24,7 +24,8 @@ def create_job_applicant(**args): job_applicant = frappe.get_doc({ "doctype": "Job Applicant", - "status": args.status or "Open" + "status": args.status or "Open", + "designation": create_designation().name }) job_applicant.update(filters) diff --git a/erpnext/hr/doctype/job_applicant_source/job_applicant_source.py b/erpnext/hr/doctype/job_applicant_source/job_applicant_source.py index 5f543d285cf..1f208c14c60 100644 --- a/erpnext/hr/doctype/job_applicant_source/job_applicant_source.py +++ b/erpnext/hr/doctype/job_applicant_source/job_applicant_source.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class JobApplicantSource(Document): pass diff --git a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js b/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js deleted file mode 100644 index c093928f321..00000000000 --- a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Job Applicant Source", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Job Applicant Source - () => frappe.tests.make('Job Applicant Source', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.py b/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.py index f318df20f7c..cee5daf783e 100644 --- a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.py +++ b/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestJobApplicantSource(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index 7e650f76917..39f471929b4 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.py +++ b/erpnext/hr/doctype/job_offer/job_offer.py @@ -1,14 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import cint +from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc -from frappe import _ +from frappe.utils import cint from frappe.utils.data import get_link_to_form + class JobOffer(Document): def onload(self): employee = frappe.db.get_value("Employee", {"job_applicant": self.job_applicant}, "name") or "" diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index edb21321fcc..d94e03ca63f 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -1,12 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import nowdate, add_days -from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant +from frappe.utils import add_days, nowdate + from erpnext.hr.doctype.designation.test_designation import create_designation +from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant from erpnext.hr.doctype.staffing_plan.test_staffing_plan import make_company # test_records = frappe.get_test_records('Job Offer') @@ -30,6 +31,7 @@ class TestJobOffer(unittest.TestCase): self.assertTrue(frappe.db.exists("Job Offer", job_offer.name)) def test_job_applicant_update(self): + frappe.db.set_value("HR Settings", None, "check_vacancies", 0) create_staffing_plan() job_applicant = create_job_applicant(email_id="test_job_applicants@example.com") job_offer = create_job_offer(job_applicant=job_applicant.name) @@ -41,7 +43,11 @@ class TestJobOffer(unittest.TestCase): job_offer.status = "Rejected" job_offer.submit() job_applicant.reload() - self.assertEqual(job_applicant.status, "Rejected") + self.assertEquals(job_applicant.status, "Rejected") + frappe.db.set_value("HR Settings", None, "check_vacancies", 1) + + def tearDown(self): + frappe.db.sql("DELETE FROM `tabJob Offer` WHERE 1") def create_job_offer(**args): args = frappe._dict(args) diff --git a/erpnext/hr/doctype/job_offer_term/job_offer_term.py b/erpnext/hr/doctype/job_offer_term/job_offer_term.py index 6dbe6757a70..d2eae467e41 100644 --- a/erpnext/hr/doctype/job_offer_term/job_offer_term.py +++ b/erpnext/hr/doctype/job_offer_term/job_offer_term.py @@ -1,9 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class JobOfferTerm(Document): pass diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py index 1e897671770..d53daf17d87 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.py +++ b/erpnext/hr/doctype/job_opening/job_opening.py @@ -3,12 +3,16 @@ # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from frappe.website.website_generator import WebsiteGenerator +import frappe from frappe import _ -from erpnext.hr.doctype.staffing_plan.staffing_plan import get_designation_counts, get_active_staffing_plan_details +from frappe.website.website_generator import WebsiteGenerator + +from erpnext.hr.doctype.staffing_plan.staffing_plan import ( + get_active_staffing_plan_details, + get_designation_counts, +) + class JobOpening(WebsiteGenerator): website = frappe._dict( diff --git a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py index 31ef33ef2ce..67600dc20e2 100644 --- a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py +++ b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'job_title', diff --git a/erpnext/hr/doctype/job_opening/test_job_opening.py b/erpnext/hr/doctype/job_opening/test_job_opening.py index 815ce5bdb8c..a1c3a1d49e3 100644 --- a/erpnext/hr/doctype/job_opening/test_job_opening.py +++ b/erpnext/hr/doctype/job_opening/test_job_opening.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Job Opening') diff --git a/erpnext/hr/doctype/leave_allocation/__init__.py b/erpnext/hr/doctype/leave_allocation/__init__.py index baffc488252..e69de29bb2d 100755 --- a/erpnext/hr/doctype/leave_allocation/__init__.py +++ b/erpnext/hr/doctype/leave_allocation/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index d94764104d0..9742387c16a 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -1,14 +1,14 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch('employee','employee_name','employee_name'); +cur_frm.add_fetch('employee', 'employee_name', 'employee_name'); frappe.ui.form.on("Leave Allocation", { onload: function(frm) { // Ignore cancellation of doctype on cancel all. frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"]; - if(!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today()); + if (!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today()); frm.set_query("employee", function() { return { @@ -25,9 +25,9 @@ frappe.ui.form.on("Leave Allocation", { }, refresh: function(frm) { - if(frm.doc.docstatus === 1 && frm.doc.expired) { + if (frm.doc.docstatus === 1 && frm.doc.expired) { var valid_expiry = moment(frappe.datetime.get_today()).isBetween(frm.doc.from_date, frm.doc.to_date); - if(valid_expiry) { + if (valid_expiry) { // expire current allocation frm.add_custom_button(__('Expire Allocation'), function() { frm.trigger("expire_allocation"); @@ -44,8 +44,8 @@ frappe.ui.form.on("Leave Allocation", { 'expiry_date': frappe.datetime.get_today() }, freeze: true, - callback: function(r){ - if(!r.exc){ + callback: function(r) { + if (!r.exc) { frappe.msgprint(__("Allocation Expired!")); } frm.refresh(); @@ -77,8 +77,8 @@ frappe.ui.form.on("Leave Allocation", { }, leave_policy: function(frm) { - if(frm.doc.leave_policy && frm.doc.leave_type) { - frappe.db.get_value("Leave Policy Detail",{ + if (frm.doc.leave_policy && frm.doc.leave_type) { + frappe.db.get_value("Leave Policy Detail", { 'parent': frm.doc.leave_policy, 'leave_type': frm.doc.leave_type }, 'annual_allocation', (r) => { @@ -91,13 +91,41 @@ frappe.ui.form.on("Leave Allocation", { return frappe.call({ method: "set_total_leaves_allocated", doc: frm.doc, - callback: function(r) { + callback: function() { frm.refresh_fields(); } - }) + }); } else if (cint(frm.doc.carry_forward) == 0) { frm.set_value("unused_leaves", 0); frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } } }); + +frappe.tour["Leave Allocation"] = [ + { + fieldname: "employee", + title: "Employee", + description: __("Select the Employee for which you want to allocate leaves.") + }, + { + fieldname: "leave_type", + title: "Leave Type", + description: __("Select the Leave Type like Sick leave, Privilege Leave, Casual Leave, etc.") + }, + { + fieldname: "from_date", + title: "From Date", + description: __("Select the date from which this Leave Allocation will be valid.") + }, + { + fieldname: "to_date", + title: "To Date", + description: __("Select the date after which this Leave Allocation will expire.") + }, + { + fieldname: "new_leaves_allocated", + title: "New Leaves Allocated", + description: __("Enter the number of leaves you want to allocate for the period.") + } +]; diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 3a6539ece9e..52ee463db02 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -219,7 +219,8 @@ "fieldname": "leave_policy_assignment", "fieldtype": "Link", "label": "Leave Policy Assignment", - "options": "Leave Policy Assignment" + "options": "Leave Policy Assignment", + "read_only": 1 }, { "fetch_from": "employee.company", @@ -236,7 +237,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-03 15:28:26.335104", + "modified": "2021-10-01 15:28:26.335104", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 4757cd3b192..232118fd67c 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -1,14 +1,19 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import flt, date_diff, formatdate, add_days, today, getdate from frappe import _ from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name, get_leave_period -from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry +from frappe.utils import add_days, date_diff, flt, formatdate, getdate + from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import ( + create_leave_ledger_entry, + expire_allocation, +) +from erpnext.hr.utils import get_leave_period, set_employee_name + class OverlapError(frappe.ValidationError): pass class BackDatedAllocationError(frappe.ValidationError): pass diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py index 7a063d92eac..631beef4359 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'leave_allocation', diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index fdcd533660b..46401a2dd8c 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -1,10 +1,13 @@ -from __future__ import unicode_literals -import frappe -import erpnext import unittest -from frappe.utils import nowdate, add_months, getdate, add_days + +import frappe +from frappe.utils import add_days, add_months, getdate, nowdate + +import erpnext +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type -from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation + + class TestLeaveAllocation(unittest.TestCase): @classmethod def setUpClass(cls): diff --git a/erpnext/hr/doctype/leave_application/__init__.py b/erpnext/hr/doctype/leave_application/__init__.py index baffc488252..e69de29bb2d 100755 --- a/erpnext/hr/doctype/leave_application/__init__.py +++ b/erpnext/hr/doctype/leave_application/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 9ccb915908f..9e8cb5516f3 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -1,8 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch('employee','employee_name','employee_name'); -cur_frm.add_fetch('employee','company','company'); +cur_frm.add_fetch('employee', 'employee_name', 'employee_name'); +cur_frm.add_fetch('employee', 'company', 'company'); frappe.ui.form.on("Leave Application", { setup: function(frm) { @@ -19,7 +19,6 @@ frappe.ui.form.on("Leave Application", { frm.set_query("employee", erpnext.queries.employee); }, onload: function(frm) { - // Ignore cancellation of doctype on cancel all. frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"]; @@ -42,9 +41,9 @@ frappe.ui.form.on("Leave Application", { }, validate: function(frm) { - if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1){ + if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1) { frm.doc.half_day_date = frm.doc.from_date; - }else if (frm.doc.half_day == 0){ + } else if (frm.doc.half_day == 0) { frm.doc.half_day_date = ""; } frm.toggle_reqd("half_day_date", frm.doc.half_day == 1); @@ -79,14 +78,14 @@ frappe.ui.form.on("Leave Application", { __("Allocated Leaves") ); frm.dashboard.show(); - let allowed_leave_types = Object.keys(leave_details); + let allowed_leave_types = Object.keys(leave_details); // lwps should be allowed, lwps don't have any allocation allowed_leave_types = allowed_leave_types.concat(lwps); - frm.set_query('leave_type', function(){ + frm.set_query('leave_type', function() { return { - filters : [ + filters: [ ['leave_type_name', 'in', allowed_leave_types] ] }; @@ -99,7 +98,7 @@ frappe.ui.form.on("Leave Application", { frm.trigger("calculate_total_days"); } cur_frm.set_intro(""); - if(frm.doc.__islocal && !in_list(frappe.user_roles, "Employee")) { + if (frm.doc.__islocal && !in_list(frappe.user_roles, "Employee")) { frm.set_intro(__("Fill the form and save it")); } @@ -118,7 +117,7 @@ frappe.ui.form.on("Leave Application", { }, leave_approver: function(frm) { - if(frm.doc.leave_approver){ + if (frm.doc.leave_approver) { frm.set_value("leave_approver_name", frappe.user.full_name(frm.doc.leave_approver)); } }, @@ -131,12 +130,10 @@ frappe.ui.form.on("Leave Application", { if (frm.doc.half_day) { if (frm.doc.from_date == frm.doc.to_date) { frm.set_value("half_day_date", frm.doc.from_date); - } - else { + } else { frm.trigger("half_day_datepicker"); } - } - else { + } else { frm.set_value("half_day_date", ""); } frm.trigger("calculate_total_days"); @@ -163,11 +160,11 @@ frappe.ui.form.on("Leave Application", { half_day_datepicker.update({ minDate: frappe.datetime.str_to_obj(frm.doc.from_date), maxDate: frappe.datetime.str_to_obj(frm.doc.to_date) - }) + }); }, get_leave_balance: function(frm) { - if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) { + if (frm.doc.docstatus === 0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) { return frappe.call({ method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on", args: { @@ -177,11 +174,10 @@ frappe.ui.form.on("Leave Application", { leave_type: frm.doc.leave_type, consider_all_leaves_in_the_allocation_period: true }, - callback: function(r) { + callback: function (r) { if (!r.exc && r.message) { frm.set_value('leave_balance', r.message); - } - else { + } else { frm.set_value('leave_balance', "0"); } } @@ -190,12 +186,12 @@ frappe.ui.form.on("Leave Application", { }, calculate_total_days: function(frm) { - if(frm.doc.from_date && frm.doc.to_date && frm.doc.employee && frm.doc.leave_type) { + if (frm.doc.from_date && frm.doc.to_date && frm.doc.employee && frm.doc.leave_type) { var from_date = Date.parse(frm.doc.from_date); var to_date = Date.parse(frm.doc.to_date); - if(to_date < from_date){ + if (to_date < from_date) { frappe.msgprint(__("To Date cannot be less than From Date")); frm.set_value('to_date', ''); return; @@ -222,7 +218,7 @@ frappe.ui.form.on("Leave Application", { }, set_leave_approver: function(frm) { - if(frm.doc.employee) { + if (frm.doc.employee) { // server call is done to include holidays in leave days calculations return frappe.call({ method: 'erpnext.hr.doctype.leave_application.leave_application.get_leave_approver', @@ -238,3 +234,36 @@ frappe.ui.form.on("Leave Application", { } } }); + +frappe.tour["Leave Application"] = [ + { + fieldname: "employee", + title: "Employee", + description: __("Select the Employee.") + }, + { + fieldname: "leave_type", + title: "Leave Type", + description: __("Select type of leave the employee wants to apply for, like Sick Leave, Privilege Leave, Casual Leave, etc.") + }, + { + fieldname: "from_date", + title: "From Date", + description: __("Select the start date for your Leave Application.") + }, + { + fieldname: "to_date", + title: "To Date", + description: __("Select the end date for your Leave Application.") + }, + { + fieldname: "half_day", + title: "Half Day", + description: __("To apply for a Half Day check 'Half Day' and select the Half Day Date") + }, + { + fieldname: "leave_approver", + title: "Leave Approver", + description: __("Select your Leave Approver i.e. the person who approves or rejects your leaves.") + } +]; diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 93fb19f4a19..1dc5b31461e 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -1,15 +1,33 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate -from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee -from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates -from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from frappe.utils import ( + add_days, + cint, + cstr, + date_diff, + flt, + formatdate, + get_fullname, + get_link_to_form, + getdate, + nowdate, +) + from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry +from erpnext.hr.utils import ( + get_leave_period, + set_employee_name, + share_doc_with_approver, + validate_active_employee, +) + class LeaveDayBlockedError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass @@ -17,6 +35,8 @@ class AttendanceAlreadyMarkedError(frappe.ValidationError): pass class NotAnOptionalHoliday(frappe.ValidationError): pass from frappe.model.document import Document + + class LeaveApplication(Document): def get_feed(self): return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type) @@ -55,6 +75,7 @@ class LeaveApplication(Document): # notify leave applier about approval if frappe.db.get_single_value("HR Settings", "send_leave_notification"): self.notify_employee() + self.create_leave_ledger_entry() self.reload() @@ -87,7 +108,13 @@ class LeaveApplication(Document): if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"): if self.from_date and getdate(self.from_date) < getdate(): allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application") - if allowed_role not in frappe.get_roles(): + user = frappe.get_doc("User", frappe.session.user) + user_roles = [d.role for d in user.roles] + if not allowed_role: + frappe.throw(_("Backdated Leave Application is restricted. Please set the {} in {}").format( + frappe.bold("Role Allowed to Create Backdated Leave Application"), get_link_to_form("HR Settings", "HR Settings"))) + + if (allowed_role and allowed_role not in user_roles): frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role)) if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)): @@ -662,26 +689,30 @@ def is_lwp(leave_type): @frappe.whitelist() def get_events(start, end, filters=None): + from frappe.desk.reportview import get_filters_cond events = [] - employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"], - as_dict=True) + employee = frappe.db.get_value("Employee", + filters={"user_id": frappe.session.user}, + fieldname=["name", "company"], + as_dict=True + ) + if employee: employee, company = employee.name, employee.company else: - employee='' - company=frappe.db.get_value("Global Defaults", None, "default_company") + employee = '' + company = frappe.db.get_value("Global Defaults", None, "default_company") - from frappe.desk.reportview import get_filters_cond conditions = get_filters_cond("Leave Application", filters, []) # show department leaves for employee if "Employee" in frappe.get_roles(): add_department_leaves(events, start, end, employee, company) add_leaves(events, start, end, conditions) - add_block_dates(events, start, end, employee, company) add_holidays(events, start, end, employee, company) + return events def add_department_leaves(events, start, end, employee, company): @@ -697,26 +728,37 @@ def add_department_leaves(events, start, end, employee, company): filter_conditions = " and employee in (\"%s\")" % '", "'.join(department_employees) add_leaves(events, start, end, filter_conditions=filter_conditions) + def add_leaves(events, start, end, filter_conditions=None): + from frappe.desk.reportview import build_match_conditions conditions = [] - if not cint(frappe.db.get_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar")): - from frappe.desk.reportview import build_match_conditions match_conditions = build_match_conditions("Leave Application") if match_conditions: conditions.append(match_conditions) - query = """select name, from_date, to_date, employee_name, half_day, - status, employee, docstatus - from `tabLeave Application` where - from_date <= %(end)s and to_date >= %(start)s <= to_date - and docstatus < 2 - and status!='Rejected' """ + query = """SELECT + docstatus, + name, + employee, + employee_name, + leave_type, + from_date, + to_date, + half_day, + status, + color + FROM `tabLeave Application` + WHERE + from_date <= %(end)s AND to_date >= %(start)s <= to_date + AND docstatus < 2 + AND status != 'Rejected' + """ if conditions: - query += ' and ' + ' and '.join(conditions) + query += ' AND ' + ' AND '.join(conditions) if filter_conditions: query += filter_conditions @@ -729,11 +771,13 @@ def add_leaves(events, start, end, filter_conditions=None): "to_date": d.to_date, "docstatus": d.docstatus, "color": d.color, - "title": cstr(d.employee_name) + (' ' + _('(Half Day)') if d.half_day else ''), + "all_day": int(not d.half_day), + "title": cstr(d.employee_name) + f' ({cstr(d.leave_type)})' + (' ' + _('(Half Day)') if d.half_day else ''), } if e not in events: events.append(e) + def add_block_dates(events, start, end, employee, company): # block days from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates diff --git a/erpnext/hr/doctype/leave_application/leave_application_calendar.js b/erpnext/hr/doctype/leave_application/leave_application_calendar.js index 31faadb1079..0ba0285552b 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_calendar.js +++ b/erpnext/hr/doctype/leave_application/leave_application_calendar.js @@ -7,7 +7,9 @@ frappe.views.calendar["Leave Application"] = { "end": "to_date", "id": "name", "title": "title", - "docstatus": 1 + "docstatus": 1, + "color": "color", + "allDay": "all_day" }, options: { header: { diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py index c45717f5870..8b0b98ddc06 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py +++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from frappe import _ diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 2832e2fad3b..f73d3e52da1 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -1,17 +1,24 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe import unittest -from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on +import frappe from frappe.permissions import clear_user_permissions_for_doctype -from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months -from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type -from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation -from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees +from frappe.utils import add_days, add_months, getdate, nowdate + from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation +from erpnext.hr.doctype.leave_application.leave_application import ( + LeaveDayBlockedError, + NotAnOptionalHoliday, + OverlapError, + get_leave_balance_on, +) +from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import ( + create_assignment_for_multiple_employees, +) +from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"] @@ -113,6 +120,7 @@ class TestLeaveApplication(unittest.TestCase): application = self.get_application(_test_records[0]) application.insert() + application.reload() application.status = "Approved" self.assertRaises(LeaveDayBlockedError, application.submit) diff --git a/erpnext/hr/doctype/leave_block_list/leave_block_list.py b/erpnext/hr/doctype/leave_block_list/leave_block_list.py index 9cb9fc05cee..d6b77f984cf 100644 --- a/erpnext/hr/doctype/leave_block_list/leave_block_list.py +++ b/erpnext/hr/doctype/leave_block_list/leave_block_list.py @@ -3,11 +3,12 @@ # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document + class LeaveBlockList(Document): def validate(self): diff --git a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py index 45aa4915bc6..7cca62e0f56 100644 --- a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py +++ b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_data(): return { 'fieldname': 'leave_block_list', diff --git a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py index 0eb69a55a70..afbabb66a4a 100644 --- a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py +++ b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py @@ -1,13 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe import unittest +import frappe from frappe.utils import getdate + from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates + class TestLeaveBlockList(unittest.TestCase): def tearDown(self): frappe.set_user("Administrator") diff --git a/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py b/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py index 8e5a09e01ec..50dc125650b 100644 --- a/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py +++ b/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py @@ -3,10 +3,9 @@ # For license information, please see license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class LeaveBlockListAllow(Document): pass diff --git a/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py b/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py index 54978a1e83a..36550ccd89d 100644 --- a/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py +++ b/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py @@ -3,10 +3,9 @@ # For license information, please see license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class LeaveBlockListDate(Document): pass diff --git a/erpnext/hr/doctype/leave_control_panel/__init__.py b/erpnext/hr/doctype/leave_control_panel/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/leave_control_panel/__init__.py +++ b/erpnext/hr/doctype/leave_control_panel/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py index 74014020fc6..19f97b83d47 100644 --- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py +++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import cint, cstr, flt, nowdate, comma_and, date_diff -from frappe import msgprint, _ +import frappe +from frappe import _, msgprint from frappe.model.document import Document +from frappe.utils import cint, comma_and, cstr, flt + class LeaveControlPanel(Document): def get_employees(self): @@ -51,7 +51,7 @@ class LeaveControlPanel(Document): la.docstatus = 1 la.save() leave_allocated_for.append(d[0]) - except: + except Exception: pass if leave_allocated_for: msgprint(_("Leaves Allocated Successfully for {0}").format(comma_and(leave_allocated_for))) diff --git a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.py b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.py index 9a907c885ac..d5a9bc04861 100644 --- a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.py +++ b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestLeaveControlPanel(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index d136210a043..8ef0e36fb8d 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -1,16 +1,19 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import getdate, nowdate, flt -from erpnext.hr.utils import set_employee_name, validate_active_employee -from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure -from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry +from frappe.utils import getdate, nowdate + from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry +from erpnext.hr.utils import set_employee_name, validate_active_employee +from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import ( + get_assigned_salary_structure, +) + class LeaveEncashment(Document): def validate(self): diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js deleted file mode 100644 index cafd9602cbe..00000000000 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Leave Encashment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Leave Encashment - () => frappe.tests.make('Leave Encashment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index c1da8b47ffa..99a479d3e5c 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -1,16 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import today, add_months +from frappe.utils import add_months, today + from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period -from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees -from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\ +from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy +from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import ( + create_assignment_for_multiple_employees, +) +from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure test_dependencies = ["Leave Type"] diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 33a6243e609..5c5299ea7eb 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document from frappe import _ -from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate +from frappe.model.document import Document +from frappe.utils import DATE_FORMAT, flt, getdate, today + class LeaveLedgerEntry(Document): def validate(self): diff --git a/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py index 6f7725c254e..31211091831 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLeaveLedgerEntry(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index 28a33f6fac8..b1cb6887d99 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import getdate, cstr, add_days, date_diff, getdate, ceil from frappe.model.document import Document +from frappe.utils import getdate + from erpnext.hr.utils import validate_overlap -from frappe.utils.background_jobs import enqueue + class LeavePeriod(Document): diff --git a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py index 7c2c9632d85..1adae0fda3f 100644 --- a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py +++ b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'leave_period', diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.js b/erpnext/hr/doctype/leave_period/test_leave_period.js deleted file mode 100644 index ec0a8096890..00000000000 --- a/erpnext/hr/doctype/leave_period/test_leave_period.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Leave Period", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Leave Period - () => frappe.tests.make('Leave Period', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py index cbb34371fc9..10936dddc98 100644 --- a/erpnext/hr/doctype/leave_period/test_leave_period.py +++ b/erpnext/hr/doctype/leave_period/test_leave_period.py @@ -1,11 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe, erpnext import unittest +import frappe + +import erpnext + test_dependencies = ["Employee", "Leave Type", "Leave Policy"] class TestLeavePeriod(unittest.TestCase): diff --git a/erpnext/hr/doctype/leave_policy/leave_policy.py b/erpnext/hr/doctype/leave_policy/leave_policy.py index 964a5de83ec..80450d5d6e0 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document + class LeavePolicy(Document): def validate(self): if self.leave_policy_details: diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py index 474f3a77ad0..73782d6c818 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'leave_policy', diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.js b/erpnext/hr/doctype/leave_policy/test_leave_policy.js deleted file mode 100644 index 5404a63bed1..00000000000 --- a/erpnext/hr/doctype/leave_policy/test_leave_policy.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Leave Policy", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Leave Policy - () => frappe.tests.make('Leave Policy', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py index af7567b5bc7..3dbbef857ec 100644 --- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py +++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest + class TestLeavePolicy(unittest.TestCase): def test_max_leave_allowed(self): diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index d7cb1c88c92..dca7e4895ec 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -1,15 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document -from frappe import _, bold -from frappe.utils import getdate, date_diff, comma_and, formatdate, get_datetime, flt -from math import ceil + import json -from six import string_types +from math import ceil + +import frappe +from frappe import _, bold +from frappe.model.document import Document +from frappe.utils import date_diff, flt, formatdate, get_datetime, getdate + class LeavePolicyAssignment(Document): @@ -137,10 +137,10 @@ class LeavePolicyAssignment(Document): @frappe.whitelist() def create_assignment_for_multiple_employees(employees, data): - if isinstance(employees, string_types): + if isinstance(employees, str): employees= json.loads(employees) - if isinstance(data, string_types): + if isinstance(data, str): data = frappe._dict(json.loads(data)) docs_name = [] diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py index a2f7f5866b7..4363439b7ca 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'leave_policy_assignment', diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py index 0089804f512..b1861ad4d8e 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py @@ -1,13 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from erpnext.hr.doctype.leave_application.test_leave_application import get_leave_period, get_employee -from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees + +from erpnext.hr.doctype.leave_application.test_leave_application import ( + get_employee, + get_leave_period, +) from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy +from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import ( + create_assignment_for_multiple_employees, +) test_dependencies = ["Employee"] diff --git a/erpnext/hr/doctype/leave_policy_detail/leave_policy_detail.py b/erpnext/hr/doctype/leave_policy_detail/leave_policy_detail.py index c103f08cd97..8916d3d65af 100644 --- a/erpnext/hr/doctype/leave_policy_detail/leave_policy_detail.py +++ b/erpnext/hr/doctype/leave_policy_detail/leave_policy_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class LeavePolicyDetail(Document): pass diff --git a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js b/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js deleted file mode 100644 index 1c8995b7967..00000000000 --- a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Leave Policy Detail", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Leave Policy Detail - () => frappe.tests.make('Leave Policy Detail', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.py b/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.py index 610b1fa332e..aacf64fd69a 100644 --- a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.py +++ b/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestLeavePolicyDetail(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/leave_type/__init__.py b/erpnext/hr/doctype/leave_type/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/doctype/leave_type/__init__.py +++ b/erpnext/hr/doctype/leave_type/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/doctype/leave_type/leave_type.js b/erpnext/hr/doctype/leave_type/leave_type.js index 8622309848a..b930dedaca8 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.js +++ b/erpnext/hr/doctype/leave_type/leave_type.js @@ -2,3 +2,37 @@ frappe.ui.form.on("Leave Type", { refresh: function(frm) { } }); + + +frappe.tour["Leave Type"] = [ + { + fieldname: "max_leaves_allowed", + title: "Maximum Leave Allocation Allowed", + description: __("This field allows you to set the maximum number of leaves that can be allocated annually for this Leave Type while creating the Leave Policy") + }, + { + fieldname: "max_continuous_days_allowed", + title: "Maximum Consecutive Leaves Allowed", + description: __("This field allows you to set the maximum number of consecutive leaves an Employee can apply for.") + }, + { + fieldname: "is_optional_leave", + title: "Is Optional Leave", + description: __("Optional Leaves are holidays that Employees can choose to avail from a list of holidays published by the company.") + }, + { + fieldname: "is_compensatory", + title: "Is Compensatory Leave", + description: __("Leaves you can avail against a holiday you worked on. You can claim Compensatory Off Leave using Compensatory Leave request. Click") + " here " + __('to know more') + }, + { + fieldname: "allow_encashment", + title: "Allow Encashment", + description: __("From here, you can enable encashment for the balance leaves.") + }, + { + fieldname: "is_earned_leave", + title: "Is Earned Leaves", + description: __("Earned Leaves are leaves earned by an Employee after working with the company for a certain amount of time. Enabling this will allocate leaves on pro-rata basis by automatically updating Leave Allocation for leaves of this type at intervals set by 'Earned Leave Frequency.") + } +]; \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 8f2ae6eb15d..06ca4cdedbc 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -50,7 +50,7 @@ { "fieldname": "max_leaves_allowed", "fieldtype": "Int", - "label": "Max Leaves Allowed" + "label": "Maximum Leave Allocation Allowed" }, { "fieldname": "applicable_after", @@ -61,7 +61,7 @@ "fieldname": "max_continuous_days_allowed", "fieldtype": "Int", "in_list_view": 1, - "label": "Maximum Continuous Days Applicable", + "label": "Maximum Consecutive Leaves Allowed", "oldfieldname": "max_days_allowed", "oldfieldtype": "Data" }, @@ -87,6 +87,7 @@ }, { "default": "0", + "description": "These leaves are holidays permitted by the company however, availing it is optional for an Employee.", "fieldname": "is_optional_leave", "fieldtype": "Check", "label": "Is Optional Leave" @@ -205,6 +206,7 @@ }, { "depends_on": "eval:doc.is_ppl == 1", + "description": "For a day of leave taken, if you still pay (say) 50% of the daily salary, then enter 0.50 in this field.", "fieldname": "fraction_of_daily_salary_per_leave", "fieldtype": "Float", "label": "Fraction of Daily Salary per Leave", @@ -214,7 +216,7 @@ "icon": "fa fa-flag", "idx": 1, "links": [], - "modified": "2021-08-12 16:10:36.464690", + "modified": "2021-10-02 11:59:40.503359", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index 21f180b857d..4b59c2c09b4 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -1,14 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import calendar -import frappe -from datetime import datetime -from frappe.utils import today -from frappe import _ +import frappe +from frappe import _ from frappe.model.document import Document +from frappe.utils import today + class LeaveType(Document): def validate(self): diff --git a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py index c8944fcb9e2..074d3e4e52a 100644 --- a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py +++ b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_data(): return { 'fieldname': 'leave_type', diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py index 048dddd3ef9..c1b64e99eff 100644 --- a/erpnext/hr/doctype/leave_type/test_leave_type.py +++ b/erpnext/hr/doctype/leave_type/test_leave_type.py @@ -1,9 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe -from frappe import _ test_records = frappe.get_test_records('Leave Type') diff --git a/erpnext/hr/doctype/offer_term/offer_term.py b/erpnext/hr/doctype/offer_term/offer_term.py index 6a632011060..cee6c4518bc 100644 --- a/erpnext/hr/doctype/offer_term/offer_term.py +++ b/erpnext/hr/doctype/offer_term/offer_term.py @@ -1,9 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class OfferTerm(Document): pass diff --git a/erpnext/hr/doctype/offer_term/test_offer_term.py b/erpnext/hr/doctype/offer_term/test_offer_term.py index d0dd14d1e62..2e5ed75438b 100644 --- a/erpnext/hr/doctype/offer_term/test_offer_term.py +++ b/erpnext/hr/doctype/offer_term/test_offer_term.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Offer Term') diff --git a/erpnext/hr/doctype/purpose_of_travel/purpose_of_travel.py b/erpnext/hr/doctype/purpose_of_travel/purpose_of_travel.py index 62f62a5c249..c9d6e713fe1 100644 --- a/erpnext/hr/doctype/purpose_of_travel/purpose_of_travel.py +++ b/erpnext/hr/doctype/purpose_of_travel/purpose_of_travel.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class PurposeofTravel(Document): pass diff --git a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js b/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js deleted file mode 100644 index 936c21ccf05..00000000000 --- a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Purpose of Travel", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Purpose of Travel - () => frappe.tests.make('Purpose of Travel', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.py b/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.py index ccd950dff3a..354663b78b7 100644 --- a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.py +++ b/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestPurposeofTravel(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 89ae4d535d4..517730281fc 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -1,24 +1,26 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + +from datetime import datetime, timedelta + import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate +from frappe.utils import cstr, getdate, now_datetime, nowdate + from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday from erpnext.hr.utils import validate_active_employee -from datetime import timedelta, datetime + class ShiftAssignment(Document): def validate(self): validate_active_employee(self.employee) self.validate_overlapping_dates() - if self.end_date and self.end_date <= self.start_date: - frappe.throw(_("End Date must not be lesser than Start Date")) + if self.end_date: + self.validate_from_to_dates('start_date', 'end_date') def validate_overlapping_dates(self): if not self.name: @@ -135,7 +137,7 @@ def get_shift_type_timing(shift_types): return shift_timing_map -def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=False, next_shift_direction=None): +def get_employee_shift(employee, for_date=None, consider_default_shift=False, next_shift_direction=None): """Returns a Shift Type for the given employee on the given date. (excluding the holidays) :param employee: Employee for which shift is required. @@ -143,6 +145,8 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals :param consider_default_shift: If set to true, default shift is taken when no shift assignment is found. :param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date. """ + if for_date is None: + for_date = nowdate() default_shift = frappe.db.get_value('Employee', employee, 'default_shift') shift_type_name = None shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date']) @@ -196,9 +200,11 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals return get_shift_details(shift_type_name, for_date) -def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_default_shift=False): +def get_employee_shift_timings(employee, for_timestamp=None, consider_default_shift=False): """Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee """ + if for_timestamp is None: + for_timestamp = now_datetime() # write and verify a test case for midnight shift. prev_shift = curr_shift = next_shift = None curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward') @@ -216,7 +222,7 @@ def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_ return prev_shift, curr_shift, next_shift -def get_shift_details(shift_type_name, for_date=nowdate()): +def get_shift_details(shift_type_name, for_date=None): """Returns Shift Details which contain some additional information as described below. 'shift_details' contains the following keys: 'shift_type' - Object of DocType Shift Type, @@ -230,6 +236,8 @@ def get_shift_details(shift_type_name, for_date=nowdate()): """ if not shift_type_name: return None + if not for_date: + for_date = nowdate() shift_type = frappe.get_doc('Shift Type', shift_type_name) start_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.start_time for_date = for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js deleted file mode 100644 index 77272877423..00000000000 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shift Assignment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shift Assignment - () => frappe.tests.make('Shift Assignment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py index 07d92fe61d6..d4900814ffe 100644 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import nowdate, add_days +from frappe.utils import add_days, nowdate test_dependencies = ["Shift Type"] diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 2731da125a8..d4fcf99d7d8 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate + from erpnext.hr.utils import share_doc_with_approver, validate_active_employee + class OverlapError(frappe.ValidationError): pass class ShiftRequest(Document): diff --git a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py index f70b61a20a6..531c98db5f8 100644 --- a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py +++ b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'shift_request', diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.js b/erpnext/hr/doctype/shift_request/test_shift_request.js deleted file mode 100644 index 9c8cd70020c..00000000000 --- a/erpnext/hr/doctype/shift_request/test_shift_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shift Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shift Request - () => frappe.tests.make('Shift Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py index 60b7676e251..3633c9b3003 100644 --- a/erpnext/hr/doctype/shift_request/test_shift_request.py +++ b/erpnext/hr/doctype/shift_request/test_shift_request.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import nowdate, add_days +from frappe.utils import add_days, nowdate + from erpnext.hr.doctype.employee.test_employee import make_employee test_dependencies = ["Shift Type"] diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index d5fdda80944..562a5739d67 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -1,18 +1,25 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import itertools from datetime import timedelta import frappe from frappe.model.document import Document -from frappe.utils import cint, getdate, get_datetime -from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift, get_employee_shift -from erpnext.hr.doctype.employee_checkin.employee_checkin import mark_attendance_and_link_log, calculate_working_hours +from frappe.utils import cint, get_datetime, getdate + from erpnext.hr.doctype.attendance.attendance import mark_attendance from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from erpnext.hr.doctype.employee_checkin.employee_checkin import ( + calculate_working_hours, + mark_attendance_and_link_log, +) +from erpnext.hr.doctype.shift_assignment.shift_assignment import ( + get_actual_start_end_datetime_of_shift, + get_employee_shift, +) + class ShiftType(Document): @frappe.whitelist() @@ -88,7 +95,7 @@ class ShiftType(Document): assigned_employees = [x[0] for x in assigned_employees] if consider_default_shift: - filters = {'default_shift': self.name} + filters = {'default_shift': self.name, 'status': ['!=', 'Inactive']} default_shift_employees = frappe.get_all('Employee', 'name', filters, as_list=True) default_shift_employees = [x[0] for x in default_shift_employees] return list(set(assigned_employees+default_shift_employees)) diff --git a/erpnext/hr/doctype/shift_type/shift_type_dashboard.py b/erpnext/hr/doctype/shift_type/shift_type_dashboard.py index aedd190bcb4..919da2db275 100644 --- a/erpnext/hr/doctype/shift_type/shift_type_dashboard.py +++ b/erpnext/hr/doctype/shift_type/shift_type_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'shift', diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.js b/erpnext/hr/doctype/shift_type/test_shift_type.js deleted file mode 100644 index 846f9316f58..00000000000 --- a/erpnext/hr/doctype/shift_type/test_shift_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shift Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shift Type - () => frappe.tests.make('Shift Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.py b/erpnext/hr/doctype/shift_type/test_shift_type.py index bc4f0eafcd5..7d2f29cd6cc 100644 --- a/erpnext/hr/doctype/shift_type/test_shift_type.py +++ b/erpnext/hr/doctype/shift_type/test_shift_type.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestShiftType(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/skill/skill.py b/erpnext/hr/doctype/skill/skill.py index 8d242120753..d26e7ca4909 100644 --- a/erpnext/hr/doctype/skill/skill.py +++ b/erpnext/hr/doctype/skill/skill.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class Skill(Document): pass diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/__init__.py b/erpnext/hr/doctype/skill_assessment/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/healthcare_practitioner/__init__.py rename to erpnext/hr/doctype/skill_assessment/__init__.py diff --git a/erpnext/hr/doctype/skill_assessment/skill_assessment.json b/erpnext/hr/doctype/skill_assessment/skill_assessment.json new file mode 100644 index 00000000000..8b935c4073a --- /dev/null +++ b/erpnext/hr/doctype/skill_assessment/skill_assessment.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "creation": "2021-04-12 17:07:39.656289", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "skill", + "rating" + ], + "fields": [ + { + "fieldname": "skill", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Skill", + "options": "Skill", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Rating", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-12 17:18:14.032298", + "modified_by": "Administrator", + "module": "HR", + "name": "Skill Assessment", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/skill_assessment/skill_assessment.py b/erpnext/hr/doctype/skill_assessment/skill_assessment.py new file mode 100644 index 00000000000..13775be6bd2 --- /dev/null +++ b/erpnext/hr/doctype/skill_assessment/skill_assessment.py @@ -0,0 +1,10 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class SkillAssessment(Document): + pass diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index e6c783aca22..7b2ea215ad8 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document from frappe import _ -from frappe.utils import getdate, nowdate, cint, flt +from frappe.model.document import Document +from frappe.utils import cint, flt, getdate, nowdate from frappe.utils.nestedset import get_descendants_of + class SubsidiaryCompanyError(frappe.ValidationError): pass class ParentCompanyError(frappe.ValidationError): pass @@ -153,7 +153,11 @@ def get_designation_counts(designation, company): return employee_counts @frappe.whitelist() -def get_active_staffing_plan_details(company, designation, from_date=getdate(nowdate()), to_date=getdate(nowdate())): +def get_active_staffing_plan_details(company, designation, from_date=None, to_date=None): + if from_date is None: + from_date = getdate(nowdate()) + if to_date is None: + to_date = getdate(nowdate()) if not company or not designation: frappe.throw(_("Please select Company and Designation")) diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py index 8e89d53c8e0..abde0d5a519 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'staffing_plan', diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js deleted file mode 100644 index 64320bcd92b..00000000000 --- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Staffing Plan", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Staffing Plan - () => frappe.tests.make('Staffing Plan', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py index 1c6218e9a70..8ff0dbbc28e 100644 --- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from erpnext.hr.doctype.staffing_plan.staffing_plan import SubsidiaryCompanyError -from erpnext.hr.doctype.staffing_plan.staffing_plan import ParentCompanyError -from frappe.utils import nowdate, add_days +from frappe.utils import add_days, nowdate + +from erpnext.hr.doctype.staffing_plan.staffing_plan import ( + ParentCompanyError, + SubsidiaryCompanyError, +) test_dependencies = ["Designation"] diff --git a/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.py b/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.py index 28a651e72d8..67496909348 100644 --- a/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.py +++ b/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class StaffingPlanDetail(Document): pass diff --git a/erpnext/hr/doctype/training_event/test_training_event.py b/erpnext/hr/doctype/training_event/test_training_event.py index 6a275b330c0..f4329c9fe70 100644 --- a/erpnext/hr/doctype/training_event/test_training_event.py +++ b/erpnext/hr/doctype/training_event/test_training_event.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import today, add_days +from frappe.utils import add_days, today + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee + class TestTrainingEvent(unittest.TestCase): def setUp(self): create_training_program("Basic Training") diff --git a/erpnext/hr/doctype/training_event/training_event.py b/erpnext/hr/doctype/training_event/training_event.py index e2c30cb3145..c8c8bbe7339 100644 --- a/erpnext/hr/doctype/training_event/training_event.py +++ b/erpnext/hr/doctype/training_event/training_event.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document from frappe.utils import time_diff_in_seconds + from erpnext.hr.doctype.employee.employee import get_employee_emails + class TrainingEvent(Document): def validate(self): self.set_employee_emails() diff --git a/erpnext/hr/doctype/training_event/training_event_dashboard.py b/erpnext/hr/doctype/training_event/training_event_dashboard.py index 19afd8dd6e1..141fffc2826 100644 --- a/erpnext/hr/doctype/training_event/training_event_dashboard.py +++ b/erpnext/hr/doctype/training_event/training_event_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'training_event', diff --git a/erpnext/hr/doctype/training_event_employee/training_event_employee.py b/erpnext/hr/doctype/training_event_employee/training_event_employee.py index 234e958a212..5dce6e17c06 100644 --- a/erpnext/hr/doctype/training_event_employee/training_event_employee.py +++ b/erpnext/hr/doctype/training_event_employee/training_event_employee.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class TrainingEventEmployee(Document): pass diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.py b/erpnext/hr/doctype/training_feedback/test_training_feedback.py index 4c0c18029d0..58ed6231003 100644 --- a/erpnext/hr/doctype/training_feedback/test_training_feedback.py +++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.py @@ -1,12 +1,17 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest + +from erpnext.hr.doctype.training_event.test_training_event import ( + create_training_event, + create_training_program, +) from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee -from erpnext.hr.doctype.training_event.test_training_event import create_training_program, create_training_event + + class TestTrainingFeedback(unittest.TestCase): def setUp(self): create_training_program("Basic Training") diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.py b/erpnext/hr/doctype/training_feedback/training_feedback.py index 3d4b9b3ea96..1f9ec3b0b8a 100644 --- a/erpnext/hr/doctype/training_feedback/training_feedback.py +++ b/erpnext/hr/doctype/training_feedback/training_feedback.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document + class TrainingFeedback(Document): def validate(self): diff --git a/erpnext/hr/doctype/training_program/test_training_program.js b/erpnext/hr/doctype/training_program/test_training_program.js deleted file mode 100644 index 3a62b2fa221..00000000000 --- a/erpnext/hr/doctype/training_program/test_training_program.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Training Program", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Training Program - () => frappe.tests.make('Training Program', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/training_program/test_training_program.py b/erpnext/hr/doctype/training_program/test_training_program.py index 9d5b28616b1..5000705ab24 100644 --- a/erpnext/hr/doctype/training_program/test_training_program.py +++ b/erpnext/hr/doctype/training_program/test_training_program.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestTrainingProgram(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/training_program/training_program.py b/erpnext/hr/doctype/training_program/training_program.py index 7a3720b66b6..96b2fd70023 100644 --- a/erpnext/hr/doctype/training_program/training_program.py +++ b/erpnext/hr/doctype/training_program/training_program.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class TrainingProgram(Document): pass diff --git a/erpnext/hr/doctype/training_program/training_program_dashboard.py b/erpnext/hr/doctype/training_program/training_program_dashboard.py index 0fc18a80298..374c1e8913c 100644 --- a/erpnext/hr/doctype/training_program/training_program_dashboard.py +++ b/erpnext/hr/doctype/training_program/training_program_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'training_program', diff --git a/erpnext/hr/doctype/training_result/test_training_result.js b/erpnext/hr/doctype/training_result/test_training_result.js deleted file mode 100644 index cb1d7fb27a3..00000000000 --- a/erpnext/hr/doctype/training_result/test_training_result.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Training Result", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Training Result - () => frappe.tests.make('Training Result', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/training_result/test_training_result.py b/erpnext/hr/doctype/training_result/test_training_result.py index 29ed2a0ff38..1735ff4e341 100644 --- a/erpnext/hr/doctype/training_result/test_training_result.py +++ b/erpnext/hr/doctype/training_result/test_training_result.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Training Result') diff --git a/erpnext/hr/doctype/training_result/training_result.js b/erpnext/hr/doctype/training_result/training_result.js index 5cdbcad8058..718b383e721 100644 --- a/erpnext/hr/doctype/training_result/training_result.js +++ b/erpnext/hr/doctype/training_result/training_result.js @@ -21,7 +21,7 @@ frappe.ui.form.on('Training Result', { frm.set_value("employees" ,""); if (r.message) { $.each(r.message, function(i, d) { - var row = frappe.model.add_child(cur_frm.doc, "Training Result Employee", "employees"); + var row = frappe.model.add_child(frm.doc, "Training Result Employee", "employees"); row.employee = d.employee; row.employee_name = d.employee_name; }); diff --git a/erpnext/hr/doctype/training_result/training_result.py b/erpnext/hr/doctype/training_result/training_result.py index 7cdc51f8010..bb5c71e7a15 100644 --- a/erpnext/hr/doctype/training_result/training_result.py +++ b/erpnext/hr/doctype/training_result/training_result.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document + from erpnext.hr.doctype.employee.employee import get_employee_emails + class TrainingResult(Document): def validate(self): training_event = frappe.get_doc("Training Event", self.training_event) diff --git a/erpnext/hr/doctype/training_result_employee/training_result_employee.py b/erpnext/hr/doctype/training_result_employee/training_result_employee.py index 54e2a18260a..e048ff53410 100644 --- a/erpnext/hr/doctype/training_result_employee/training_result_employee.py +++ b/erpnext/hr/doctype/training_result_employee/training_result_employee.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class TrainingResultEmployee(Document): pass diff --git a/erpnext/hr/doctype/travel_itinerary/travel_itinerary.py b/erpnext/hr/doctype/travel_itinerary/travel_itinerary.py index 0b369beb134..529909b0e00 100644 --- a/erpnext/hr/doctype/travel_itinerary/travel_itinerary.py +++ b/erpnext/hr/doctype/travel_itinerary/travel_itinerary.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class TravelItinerary(Document): pass diff --git a/erpnext/hr/doctype/travel_request/test_travel_request.js b/erpnext/hr/doctype/travel_request/test_travel_request.js deleted file mode 100644 index 7e645918239..00000000000 --- a/erpnext/hr/doctype/travel_request/test_travel_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Travel Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Travel Request - () => frappe.tests.make('Travel Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/travel_request/test_travel_request.py b/erpnext/hr/doctype/travel_request/test_travel_request.py index dac5517aab0..e29a1ca648d 100644 --- a/erpnext/hr/doctype/travel_request/test_travel_request.py +++ b/erpnext/hr/doctype/travel_request/test_travel_request.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestTravelRequest(unittest.TestCase): pass diff --git a/erpnext/hr/doctype/travel_request/travel_request.py b/erpnext/hr/doctype/travel_request/travel_request.py index 60834d3f4a6..02379c5334c 100644 --- a/erpnext/hr/doctype/travel_request/travel_request.py +++ b/erpnext/hr/doctype/travel_request/travel_request.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + from erpnext.hr.utils import validate_active_employee + class TravelRequest(Document): def validate(self): validate_active_employee(self.employee) diff --git a/erpnext/hr/doctype/travel_request_costing/travel_request_costing.py b/erpnext/hr/doctype/travel_request_costing/travel_request_costing.py index 9fa85e84c28..0d1a592c80e 100644 --- a/erpnext/hr/doctype/travel_request_costing/travel_request_costing.py +++ b/erpnext/hr/doctype/travel_request_costing/travel_request_costing.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class TravelRequestCosting(Document): pass diff --git a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py index 03b0cf3da21..4c7bd805f98 100644 --- a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -import erpnext from frappe.utils import getdate -from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data + +import erpnext from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data test_dependencies = ['Holiday List'] diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py index 9c765d73716..94eb3001009 100644 --- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py @@ -3,15 +3,17 @@ # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import cstr, add_days, date_diff, getdate from frappe import _ -from frappe.utils.csvutils import UnicodeWriter from frappe.model.document import Document +from frappe.utils import add_days, cstr, date_diff, getdate +from frappe.utils.csvutils import UnicodeWriter + from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.utils import get_holiday_dates_for_employee + class UploadAttendance(Document): pass diff --git a/erpnext/hr/doctype/vehicle/test_vehicle.js b/erpnext/hr/doctype/vehicle/test_vehicle.js deleted file mode 100644 index 4d40cce086a..00000000000 --- a/erpnext/hr/doctype/vehicle/test_vehicle.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Vehicle", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Vehicle - () => frappe.tests.make('Vehicle', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/vehicle/test_vehicle.py b/erpnext/hr/doctype/vehicle/test_vehicle.py index ff3429d0e7e..c5ea5a38c86 100644 --- a/erpnext/hr/doctype/vehicle/test_vehicle.py +++ b/erpnext/hr/doctype/vehicle/test_vehicle.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import nowdate,flt, cstr,random_string +from frappe.utils import random_string + # test_records = frappe.get_test_records('Vehicle') class TestVehicle(unittest.TestCase): diff --git a/erpnext/hr/doctype/vehicle/vehicle.py b/erpnext/hr/doctype/vehicle/vehicle.py index 1df50682683..946233b5481 100644 --- a/erpnext/hr/doctype/vehicle/vehicle.py +++ b/erpnext/hr/doctype/vehicle/vehicle.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import getdate from frappe.model.document import Document +from frappe.utils import getdate + class Vehicle(Document): def validate(self): diff --git a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py index 628c8972cec..f6e5f06d8cb 100644 --- a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py +++ b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'heatmap': True, diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py index ed02120cca3..acd50f278cd 100644 --- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py +++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import nowdate, flt, cstr, random_string +from frappe.utils import cstr, flt, nowdate, random_string + from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim + class TestVehicleLog(unittest.TestCase): def setUp(self): employee_id = frappe.db.sql("""select name from `tabEmployee` where name='testdriver@example.com'""") diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.py b/erpnext/hr/doctype/vehicle_log/vehicle_log.py index 04c94e37d5a..e414141efb5 100644 --- a/erpnext/hr/doctype/vehicle_log/vehicle_log.py +++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt, cstr -from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document +from frappe.utils import flt + class VehicleLog(Document): def validate(self): diff --git a/erpnext/hr/doctype/vehicle_service/vehicle_service.py b/erpnext/hr/doctype/vehicle_service/vehicle_service.py index 18ed7821382..fdd4e39dd85 100644 --- a/erpnext/hr/doctype/vehicle_service/vehicle_service.py +++ b/erpnext/hr/doctype/vehicle_service/vehicle_service.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class VehicleService(Document): pass diff --git a/erpnext/hr/module_onboarding/human_resource/human_resource.json b/erpnext/hr/module_onboarding/human_resource/human_resource.json index 518c002bcaa..cd11bd1102e 100644 --- a/erpnext/hr/module_onboarding/human_resource/human_resource.json +++ b/erpnext/hr/module_onboarding/human_resource/human_resource.json @@ -13,17 +13,14 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/human-resources", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:47.018799", + "modified": "2021-05-19 05:32:01.794628", "modified_by": "Administrator", "module": "HR", "name": "Human Resource", "owner": "Administrator", "steps": [ { - "step": "Create Department" - }, - { - "step": "Create Designation" + "step": "HR Settings" }, { "step": "Create Holiday list" @@ -31,6 +28,9 @@ { "step": "Create Employee" }, + { + "step": "Data import" + }, { "step": "Create Leave Type" }, @@ -39,9 +39,6 @@ }, { "step": "Create Leave Application" - }, - { - "step": "HR Settings" } ], "subtitle": "Employee, Leaves, and more.", diff --git a/erpnext/hr/notification/training_feedback/training_feedback.py b/erpnext/hr/notification/training_feedback/training_feedback.py index 2334f8b26d8..02e3e933330 100644 --- a/erpnext/hr/notification/training_feedback/training_feedback.py +++ b/erpnext/hr/notification/training_feedback/training_feedback.py @@ -1,7 +1,3 @@ -from __future__ import unicode_literals - -import frappe - def get_context(context): # do your magic here pass diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.py b/erpnext/hr/notification/training_scheduled/training_scheduled.py index 2334f8b26d8..02e3e933330 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.py +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.py @@ -1,7 +1,3 @@ -from __future__ import unicode_literals - -import frappe - def get_context(context): # do your magic here pass diff --git a/erpnext/hr/onboarding_step/create_employee/create_employee.json b/erpnext/hr/onboarding_step/create_employee/create_employee.json index 3aa33c6d862..47828186bf3 100644 --- a/erpnext/hr/onboarding_step/create_employee/create_employee.json +++ b/erpnext/hr/onboarding_step/create_employee/create_employee.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-14 11:43:25.561152", + "description": "

Employee

\n\nAn individual who works and is recognized for his rights and duties in your company is your Employee. You can manage the Employee master. It captures the demographic, personal and professional details, joining and leave details, etc.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 12:26:28.629074", + "modified": "2021-05-19 04:50:02.240321", "modified_by": "Administrator", "name": "Create Employee", "owner": "Administrator", "reference_document": "Employee", + "show_form_tour": 0, "show_full_form": 0, "title": "Create Employee", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json index 32472b4b3fa..a08e85fff01 100644 --- a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json +++ b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-28 11:47:34.700174", + "description": "

Holiday List.

\n\nHoliday List is a list which contains the dates of holidays. Most organizations have a standard Holiday List for their employees. However, some of them may have different holiday lists based on different Locations or Departments. In ERPNext, you can configure multiple Holiday Lists.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 12:25:38.068582", + "modified": "2021-05-19 04:19:52.305199", "modified_by": "Administrator", "name": "Create Holiday list", "owner": "Administrator", "reference_document": "Holiday List", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Holiday List", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json b/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json index fa9941e6b97..0b0ce3fc8bb 100644 --- a/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json +++ b/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-14 11:48:56.123718", + "description": "

Leave Allocation

\n\nLeave Allocation enables you to allocate a specific number of leaves of a particular type to an Employee so that, an employee will be able to create a Leave Application only if Leaves are allocated. ", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 11:48:56.123718", + "modified": "2021-05-19 04:22:34.220238", "modified_by": "Administrator", "name": "Create Leave Allocation", "owner": "Administrator", "reference_document": "Leave Allocation", + "show_form_tour": 0, "show_full_form": 0, "title": "Create Leave Allocation", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json b/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json index 1ed074e9a1d..af63aa59ed6 100644 --- a/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json +++ b/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-14 11:49:45.400764", + "description": "

Leave Application

\n\nLeave Application is a formal document created by an Employee to apply for Leaves for a particular time period based on there leave allocation and leave type according to there need.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 11:49:45.400764", + "modified": "2021-05-19 04:39:09.893474", "modified_by": "Administrator", "name": "Create Leave Application", "owner": "Administrator", "reference_document": "Leave Application", + "show_form_tour": 0, "show_full_form": 0, "title": "Create Leave Application", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json index 8cbfc5c81f9..397f5cde49c 100644 --- a/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json +++ b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-27 11:17:31.119312", + "description": "

Leave Type

\n\nLeave type is defined based on many factors and features like encashment, earned leaves, partially paid, without pay and, a lot more. To check other options and to define your leave type click on Show Tour.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-20 11:17:31.119312", + "modified": "2021-05-19 04:32:48.135406", "modified_by": "Administrator", "name": "Create Leave Type", "owner": "Administrator", "reference_document": "Leave Type", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Leave Type", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/data_import/data_import.json b/erpnext/hr/onboarding_step/data_import/data_import.json new file mode 100644 index 00000000000..ac343c67759 --- /dev/null +++ b/erpnext/hr/onboarding_step/data_import/data_import.json @@ -0,0 +1,21 @@ +{ + "action": "Watch Video", + "action_label": "", + "creation": "2021-05-19 05:29:16.809610", + "description": "

Data Import

\n\nData import is the tool to migrate your existing data like Employee, Customer, Supplier, and a lot more to our ERPNext system.\nGo through the video for a detailed explanation of this tool.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-05-19 05:29:16.809610", + "modified_by": "Administrator", + "name": "Data import", + "owner": "Administrator", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Data Import", + "validate_action": 1, + "video_url": "https://www.youtube.com/watch?v=DQyqeurPI64" +} \ No newline at end of file diff --git a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json index 0a1d0baf8aa..355664fbc59 100644 --- a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json +++ b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json @@ -1,18 +1,20 @@ { - "action": "Update Settings", + "action": "Show Form Tour", + "action_label": "Explore", "creation": "2020-05-28 13:13:52.427711", + "description": "

HR Settings

\n\nHr Settings consists of major settings related to Employee Lifecycle, Leave Management, etc. Click on Explore, to explore Hr Settings.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-05-20 11:16:42.430974", + "modified": "2021-05-18 07:02:05.747548", "modified_by": "Administrator", "name": "HR Settings", "owner": "Administrator", "reference_document": "HR Settings", + "show_form_tour": 0, "show_full_form": 0, "title": "HR Settings", "validate_action": 0 diff --git a/erpnext/hr/page/__init__.py b/erpnext/hr/page/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/hr/page/__init__.py +++ b/erpnext/hr/page/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.js b/erpnext/hr/page/organizational_chart/organizational_chart.js index 81162a4c9a8..b0e41e00905 100644 --- a/erpnext/hr/page/organizational_chart/organizational_chart.js +++ b/erpnext/hr/page/organizational_chart/organizational_chart.js @@ -15,6 +15,8 @@ frappe.pages['organizational-chart'].on_page_load = function(wrapper) { } else { organizational_chart = new erpnext.HierarchyChart('Employee', wrapper, method); } + + frappe.breadcrumbs.add('HR'); organizational_chart.show(); }); }); diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.py b/erpnext/hr/page/organizational_chart/organizational_chart.py index 4423d29e402..1e2d7581795 100644 --- a/erpnext/hr/page/organizational_chart/organizational_chart.py +++ b/erpnext/hr/page/organizational_chart/organizational_chart.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + @frappe.whitelist() def get_children(parent=None, company=None, exclude_node=None): filters = [['status', '!=', 'Left']] diff --git a/erpnext/hr/page/team_updates/team_updates.py b/erpnext/hr/page/team_updates/team_updates.py index 58cdc4b7e1d..0a4624c531e 100644 --- a/erpnext/hr/page/team_updates/team_updates.py +++ b/erpnext/hr/page/team_updates/team_updates.py @@ -1,8 +1,7 @@ -from __future__ import unicode_literals - import frappe from email_reply_parser import EmailReplyParser + @frappe.whitelist() def get_data(start=0): #frappe.only_for('Employee', 'System Manager') diff --git a/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py b/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py index d8691b4d025..63764bb8a9c 100644 --- a/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py +++ b/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py @@ -1,11 +1,13 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from frappe import _ + import frappe +from frappe import _ + from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_user_emails_from_group + def execute(filters=None): if not filters.group: return [], [] columns, data = get_columns(), get_data(filters) diff --git a/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py b/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py index 363e31d0960..62b83f26a61 100644 --- a/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py +++ b/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py @@ -1,9 +1,10 @@ # 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 msgprint, _ +from frappe import _, msgprint + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/hr/report/employee_analytics/employee_analytics.py b/erpnext/hr/report/employee_analytics/employee_analytics.py index fe77b6abc96..3a75276cb07 100644 --- a/erpnext/hr/report/employee_analytics/employee_analytics.py +++ b/erpnext/hr/report/employee_analytics/employee_analytics.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/hr/report/employee_birthday/employee_birthday.py b/erpnext/hr/report/employee_birthday/employee_birthday.py index e8d78449bff..cec5a48c199 100644 --- a/erpnext/hr/report/employee_birthday/employee_birthday.py +++ b/erpnext/hr/report/employee_birthday/employee_birthday.py @@ -1,10 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index b8953b3eaac..b375b18b079 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -1,13 +1,19 @@ # 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.utils import flt, add_days -from frappe import _ -from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on + from itertools import groupby +import frappe +from frappe import _ +from frappe.utils import add_days + +from erpnext.hr.doctype.leave_application.leave_application import ( + get_leave_balance_on, + get_leaves_for_period, +) + + def execute(filters=None): if filters.to_date <= filters.from_date: frappe.throw(_('"From Date" can not be greater than or equal to "To Date"')) @@ -175,10 +181,11 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type): records= frappe.db.sql(""" SELECT employee, leave_type, from_date, to_date, leaves, transaction_name, - is_carry_forward, is_expired + transaction_type, is_carry_forward, is_expired FROM `tabLeave Ledger Entry` WHERE employee=%(employee)s AND leave_type=%(leave_type)s AND docstatus=1 + AND transaction_type = 'Leave Allocation' AND (from_date between %(from_date)s AND %(to_date)s OR to_date between %(from_date)s AND %(to_date)s OR (from_date < %(from_date)s AND to_date > %(to_date)s)) diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py index e86fa2b1c42..71c18bb51ff 100644 --- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py +++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py @@ -1,15 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt -from erpnext.hr.doctype.leave_application.leave_application \ - import get_leave_details -from erpnext.hr.report.employee_leave_balance.employee_leave_balance \ - import get_department_leave_approver_map +from erpnext.hr.doctype.leave_application.leave_application import get_leave_details +from erpnext.hr.report.employee_leave_balance.employee_leave_balance import ( + get_department_leave_approver_map, +) + def execute(filters=None): leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc") @@ -65,7 +65,7 @@ def get_data(filters, leave_types): for leave_type in leave_types: remaining = 0 if leave_type in available_leave["leave_allocation"]: - # opening balance + # opening balance remaining = available_leave["leave_allocation"][leave_type]['remaining_leaves'] row += [remaining] diff --git a/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py b/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py index 59f56d7345f..00a4a7c29f5 100644 --- a/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py +++ b/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py @@ -1,7 +1,7 @@ # 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 _ diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index bcb0ee4d0da..9a993e52e27 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -1,12 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import cstr, cint, getdate -from frappe import msgprint, _ + from calendar import monthrange +import frappe +from frappe import _, msgprint +from frappe.utils import cint, cstr, getdate + status_map = { "Absent": "A", "Half Day": "HD", diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py index 303c829eb6c..6383a9bbac9 100644 --- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py index 26e0f26392e..8672e68cf4b 100644 --- a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py +++ b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py @@ -1,15 +1,18 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import unittest + import frappe from frappe.utils import getdate -from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim -from erpnext.hr.doctype.vehicle_log.test_vehicle_log import get_vehicle, make_vehicle_log -from erpnext.hr.report.vehicle_expenses.vehicle_expenses import execute + from erpnext.accounts.utils import get_fiscal_year +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.vehicle_log.test_vehicle_log import get_vehicle, make_vehicle_log +from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim +from erpnext.hr.report.vehicle_expenses.vehicle_expenses import execute + class TestVehicleExpenses(unittest.TestCase): @classmethod diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py index d847cbb5c9b..17d1e9d46a0 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py @@ -1,13 +1,14 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -import erpnext from frappe import _ from frappe.utils import flt + from erpnext.accounts.report.financial_statements import get_period_list + def execute(filters=None): filters = frappe._dict(filters or {}) @@ -96,8 +97,6 @@ def get_columns(): } ] - return columns - def get_vehicle_log_data(filters): start_date, end_date = get_period_dates(filters) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 15b237d93cd..0febce1610a 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -1,14 +1,27 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import erpnext import frappe -from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError from frappe import _ -from frappe.desk.form import assign_to -from frappe.model.document import Document -from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate, - get_datetime, getdate, nowdate, today, unique, get_link_to_form) +from frappe.utils import ( + add_days, + cstr, + flt, + format_datetime, + formatdate, + get_datetime, + get_link_to_form, + getdate, + nowdate, + today, +) + +import erpnext +from erpnext.hr.doctype.employee.employee import ( + InactiveEmployeeStatusError, + get_holiday_list_for_employee, +) + class DuplicateDeclarationError(frappe.ValidationError): pass @@ -16,10 +29,21 @@ def set_employee_name(doc): if doc.employee and not doc.employee_name: doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name") -def update_employee(employee, details, date=None, cancel=False): +def update_employee_work_history(employee, details, date=None, cancel=False): + if not employee.internal_work_history and not cancel: + employee.append("internal_work_history", { + "branch": employee.branch, + "designation": employee.designation, + "department": employee.department, + "from_date": employee.date_of_joining + }) + internal_work_history = {} for item in details: - fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype + field = frappe.get_meta("Employee").get_field(item.fieldname) + if not field: + continue + fieldtype = field.fieldtype new_data = item.new if not cancel else item.current if fieldtype == "Date" and new_data: new_data = getdate(new_data) @@ -28,11 +52,35 @@ def update_employee(employee, details, date=None, cancel=False): setattr(employee, item.fieldname, new_data) if item.fieldname in ["department", "designation", "branch"]: internal_work_history[item.fieldname] = item.new + if internal_work_history and not cancel: internal_work_history["from_date"] = date employee.append("internal_work_history", internal_work_history) + + if cancel: + delete_employee_work_history(details, employee, date) + return employee +def delete_employee_work_history(details, employee, date): + filters = {} + for d in details: + for history in employee.internal_work_history: + if d.property == "Department" and history.department == d.new: + department = d.new + filters["department"] = department + if d.property == "Designation" and history.designation == d.new: + designation = d.new + filters["designation"] = designation + if d.property == "Branch" and history.branch == d.new: + branch = d.new + filters["branch"] = branch + if date and date == history.from_date: + filters["from_date"] = date + if filters: + frappe.db.delete("Employee Internal Work History", filters) + + @frappe.whitelist() def get_employee_fields_label(): fields = [] @@ -272,6 +320,7 @@ def create_additional_leave_ledger_entry(allocation, leaves, date): def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining_date): import calendar + from dateutil import relativedelta from_date = get_datetime(from_date) @@ -337,9 +386,9 @@ def get_sal_slip_total_benefit_given(employee, payroll_period, component=False): def get_holiday_dates_for_employee(employee, start_date, end_date): """return a list of holiday dates for the given employee between start_date and end_date""" - # return only date - holidays = get_holidays_for_employee(employee, start_date, end_date) - + # return only date + holidays = get_holidays_for_employee(employee, start_date, end_date) + return [cstr(h.holiday_date) for h in holidays] @@ -352,7 +401,7 @@ def get_holidays_for_employee(employee, start_date, end_date, raise_exception=Tr `raise_exception` (bool) `only_non_weekly` (bool) - return: list of dicts with `holiday_date` and `description` + return: list of dicts with `holiday_date` and `description` """ holiday_list = get_holiday_list_for_employee(employee, raise_exception=raise_exception) @@ -368,11 +417,11 @@ def get_holidays_for_employee(employee, start_date, end_date, raise_exception=Tr filters['weekly_off'] = False holidays = frappe.get_all( - 'Holiday', + 'Holiday', fields=['description', 'holiday_date'], filters=filters ) - + return holidays @erpnext.allow_regional diff --git a/erpnext/hr/web_form/job_application/job_application.py b/erpnext/hr/web_form/job_application/job_application.py index 2334f8b26d8..02e3e933330 100644 --- a/erpnext/hr/web_form/job_application/job_application.py +++ b/erpnext/hr/web_form/job_application/job_application.py @@ -1,7 +1,3 @@ -from __future__ import unicode_literals - -import frappe - def get_context(context): # do your magic here pass diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index 575fa7be6fa..7408d63eee5 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Outgoing Salary", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Human Resource\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Employee\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Leave Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Job Applicant\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Monthly Attendance Sheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Employee\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Employee Lifecycle\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Shift Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Leaves\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Expense Claims\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Fleet Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Recruitment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loans\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Training\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Performance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:48:58.322521", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "hr", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "HR", "links": [ { @@ -223,6 +216,17 @@ "onboard": 0, "type": "Link" }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Full and Final Statement", + "link_count": 0, + "link_to": "Full and Final Statement", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -931,15 +935,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:59.842918", + "modified": "2021-08-31 12:18:59.842919", "modified_by": "Administrator", "module": "HR", "name": "HR", - "onboarding": "Human Resource", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/hub/__init__.py b/erpnext/hub/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py deleted file mode 100644 index 85ffe29d118..00000000000 --- a/erpnext/hub_node/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe - -@frappe.whitelist() -def enable_hub(): - hub_settings = frappe.get_doc('Marketplace Settings') - hub_settings.register() - frappe.db.commit() - return hub_settings - -@frappe.whitelist() -def sync(): - hub_settings = frappe.get_doc('Marketplace Settings') - hub_settings.sync() diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py deleted file mode 100644 index 42f90006f4d..00000000000 --- a/erpnext/hub_node/api.py +++ /dev/null @@ -1,233 +0,0 @@ -from __future__ import unicode_literals - -import frappe -import json - -from frappe import _ -from frappe.frappeclient import FrappeClient -from frappe.desk.form.load import get_attachments -from six import string_types - -current_user = frappe.session.user - - -@frappe.whitelist() -def register_marketplace(company, company_description): - validate_registerer() - - settings = frappe.get_single('Marketplace Settings') - message = settings.register_seller(company, company_description) - - if message.get('hub_seller_name'): - settings.registered = 1 - settings.hub_seller_name = message.get('hub_seller_name') - settings.save() - - settings.add_hub_user(frappe.session.user) - - return { 'ok': 1 } - - -@frappe.whitelist() -def register_users(user_list): - user_list = json.loads(user_list) - - settings = frappe.get_single('Marketplace Settings') - - for user in user_list: - settings.add_hub_user(user) - - return user_list - - -def validate_registerer(): - if current_user == 'Administrator': - frappe.throw(_('Please login as another user to register on Marketplace')) - - valid_roles = ['System Manager', 'Item Manager'] - - if not frappe.utils.is_subset(valid_roles, frappe.get_roles()): - frappe.throw(_('Only users with {0} role can register on Marketplace').format(', '.join(valid_roles)), - frappe.PermissionError) - - -@frappe.whitelist() -def call_hub_method(method, params=None): - connection = get_hub_connection() - - if isinstance(params, string_types): - params = json.loads(params) - - params.update({ - 'cmd': 'hub.hub.api.' + method - }) - - response = connection.post_request(params) - return response - - -def map_fields(items): - field_mappings = get_field_mappings() - table_fields = [d.fieldname for d in frappe.get_meta('Item').get_table_fields()] - - hub_seller_name = frappe.db.get_value('Marketplace Settings', 'Marketplace Settings', 'hub_seller_name') - - for item in items: - for fieldname in table_fields: - item.pop(fieldname, None) - - for mapping in field_mappings: - local_fieldname = mapping.get('local_fieldname') - remote_fieldname = mapping.get('remote_fieldname') - - value = item.get(local_fieldname) - item.pop(local_fieldname, None) - item[remote_fieldname] = value - - item['doctype'] = 'Hub Item' - item['hub_seller'] = hub_seller_name - item.pop('attachments', None) - - return items - - -@frappe.whitelist() -def get_valid_items(search_value=''): - items = frappe.get_list( - 'Item', - fields=["*"], - filters={ - 'disabled': 0, - 'item_name': ['like', '%' + search_value + '%'], - 'publish_in_hub': 0 - }, - order_by="modified desc" - ) - - valid_items = filter(lambda x: x.image and x.description, items) - - def prepare_item(item): - item.source_type = "local" - item.attachments = get_attachments('Item', item.item_code) - return item - - valid_items = map(prepare_item, valid_items) - - return valid_items - -@frappe.whitelist() -def update_item(ref_doc, data): - data = json.loads(data) - - data.update(dict(doctype='Hub Item', name=ref_doc)) - try: - connection = get_hub_connection() - connection.update(data) - except Exception as e: - frappe.log_error(message=e, title='Hub Sync Error') - -@frappe.whitelist() -def publish_selected_items(items_to_publish): - items_to_publish = json.loads(items_to_publish) - items_to_update = [] - if not len(items_to_publish): - frappe.throw(_('No items to publish')) - - for item in items_to_publish: - item_code = item.get('item_code') - frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - - hub_dict = { - 'doctype': 'Hub Tracked Item', - 'item_code': item_code, - 'published': 1, - 'hub_category': item.get('hub_category'), - 'image_list': item.get('image_list') - } - frappe.get_doc(hub_dict).insert(ignore_if_duplicate=True) - - items = map_fields(items_to_publish) - - try: - item_sync_preprocess(len(items)) - convert_relative_image_urls_to_absolute(items) - - # TODO: Publish Progress - connection = get_hub_connection() - connection.insert_many(items) - - item_sync_postprocess() - except Exception as e: - frappe.log_error(message=e, title='Hub Sync Error') - -@frappe.whitelist() -def unpublish_item(item_code, hub_item_name): - ''' Remove item listing from the marketplace ''' - - response = call_hub_method('unpublish_item', { - 'hub_item_name': hub_item_name - }) - - if response: - frappe.db.set_value('Item', item_code, 'publish_in_hub', 0) - frappe.delete_doc('Hub Tracked Item', item_code) - else: - frappe.throw(_('Unable to update remote activity')) - -@frappe.whitelist() -def get_unregistered_users(): - settings = frappe.get_single('Marketplace Settings') - registered_users = [user.user for user in settings.users] + ['Administrator', 'Guest'] - all_users = [user.name for user in frappe.db.get_all('User', filters={'enabled': 1})] - unregistered_users = [user for user in all_users if user not in registered_users] - return unregistered_users - - -def item_sync_preprocess(intended_item_publish_count): - response = call_hub_method('pre_items_publish', { - 'intended_item_publish_count': intended_item_publish_count - }) - - if response: - frappe.db.set_value("Marketplace Settings", "Marketplace Settings", "sync_in_progress", 1) - return response - else: - frappe.throw(_('Unable to update remote activity')) - - -def item_sync_postprocess(): - response = call_hub_method('post_items_publish', {}) - if response: - frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'last_sync_datetime', frappe.utils.now()) - else: - frappe.throw(_('Unable to update remote activity')) - - frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'sync_in_progress', 0) - - -def convert_relative_image_urls_to_absolute(items): - from six.moves.urllib.parse import urljoin - - for item in items: - file_path = item['image'] - - if file_path.startswith('/files/'): - item['image'] = urljoin(frappe.utils.get_url(), file_path) - - -def get_hub_connection(): - settings = frappe.get_single('Marketplace Settings') - marketplace_url = settings.marketplace_url - hub_user = settings.get_hub_user(frappe.session.user) - - if hub_user: - password = hub_user.get_password() - hub_connection = FrappeClient(marketplace_url, hub_user.user, password) - return hub_connection - else: - read_only_hub_connection = FrappeClient(marketplace_url) - return read_only_hub_connection - - -def get_field_mappings(): - return [] diff --git a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json b/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json deleted file mode 100644 index b1e421dada8..00000000000 --- a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "condition": "{'name': ('=', frappe.db.get_single_value('Hub Settings', 'company'))}", - "creation": "2017-09-07 11:38:43.169065", - "docstatus": 0, - "doctype": "Data Migration Mapping", - "fields": [ - { - "is_child_table": 0, - "local_fieldname": "name", - "remote_fieldname": "company_name" - }, - { - "is_child_table": 0, - "local_fieldname": "country", - "remote_fieldname": "country" - }, - { - "is_child_table": 0, - "local_fieldname": "\"city\"", - "remote_fieldname": "seller_city" - }, - { - "is_child_table": 0, - "local_fieldname": "eval:frappe.local.site", - "remote_fieldname": "site_name" - }, - { - "is_child_table": 0, - "local_fieldname": "eval:frappe.session.user", - "remote_fieldname": "user" - }, - { - "is_child_table": 0, - "local_fieldname": "company_logo", - "remote_fieldname": "company_logo" - } - ], - "idx": 2, - "local_doctype": "Company", - "mapping_name": "Company to Hub Company", - "mapping_type": "Push", - "migration_id_field": "hub_sync_id", - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "name": "Company to Hub Company", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Company", - "remote_primary_key": "name" -} \ No newline at end of file diff --git a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json deleted file mode 100644 index d11abeb4b38..00000000000 --- a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "condition": "{'reference_doctype': 'Lead', 'user': frappe.db.get_single_value('Hub Settings', 'user'), 'status': 'Pending'}", - "creation": "2017-09-20 15:06:40.279930", - "docstatus": 0, - "doctype": "Data Migration Mapping", - "fields": [ - { - "is_child_table": 0, - "local_fieldname": "email_id", - "remote_fieldname": "email_id" - }, - { - "is_child_table": 0, - "local_fieldname": "lead_name", - "remote_fieldname": "lead_name" - } - ], - "idx": 0, - "local_doctype": "Lead", - "local_primary_key": "email_id", - "mapping_name": "Hub Message to Lead", - "mapping_type": "Pull", - "migration_id_field": "hub_sync_id", - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "name": "Hub Message to Lead", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Message", - "remote_primary_key": "name" -} \ No newline at end of file diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json deleted file mode 100644 index bcece69b38c..00000000000 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "condition": "{\"publish_in_hub\": 1}", - "creation": "2017-09-07 13:27:52.726350", - "docstatus": 0, - "doctype": "Data Migration Mapping", - "fields": [ - { - "is_child_table": 0, - "local_fieldname": "item_code", - "remote_fieldname": "item_code" - }, - { - "is_child_table": 0, - "local_fieldname": "item_name", - "remote_fieldname": "item_name" - }, - { - "is_child_table": 0, - "local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')", - "remote_fieldname": "hub_seller" - }, - { - "is_child_table": 0, - "local_fieldname": "image", - "remote_fieldname": "image" - }, - { - "is_child_table": 0, - "local_fieldname": "image_list", - "remote_fieldname": "image_list" - }, - { - "is_child_table": 0, - "local_fieldname": "item_group", - "remote_fieldname": "item_group" - }, - { - "is_child_table": 0, - "local_fieldname": "hub_category", - "remote_fieldname": "hub_category" - } - ], - "idx": 1, - "local_doctype": "Item", - "mapping_name": "Item to Hub Item", - "mapping_type": "Push", - "migration_id_field": "hub_sync_id", - "modified": "2018-08-19 22:20:25.727581", - "modified_by": "Administrator", - "name": "Item to Hub Item", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Item", - "remote_primary_key": "item_code" -} \ No newline at end of file diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json deleted file mode 100644 index e90b1dd1e8d..00000000000 --- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "creation": "2017-09-07 11:39:38.445902", - "docstatus": 0, - "doctype": "Data Migration Plan", - "idx": 1, - "mappings": [ - { - "enabled": 1, - "mapping": "Item to Hub Item" - } - ], - "modified": "2018-08-19 22:20:25.644602", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Hub Sync", - "owner": "Administrator", - "plan_name": "Hub Sync", - "postprocess_method": "erpnext.hub_node.api.item_sync_postprocess" -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/__init__.py b/erpnext/hub_node/doctype/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/hub_node/doctype/hub_tracked_item/__init__.py b/erpnext/hub_node/doctype/hub_tracked_item/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js deleted file mode 100644 index 660532d13df..00000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hub Tracked Item', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json deleted file mode 100644 index 7d07ba40938..00000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:item_code", - "beta": 0, - "creation": "2018-03-18 09:33:50.267762", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hub_category", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hub Category", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "published", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Published", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "image_list", - "fieldtype": "Long Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Image List", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-12-10 11:37:35.951019", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Hub Tracked Item", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Item Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 1, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py deleted file mode 100644 index be2cd6b3ee9..00000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class HubTrackedItem(Document): - pass diff --git a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py deleted file mode 100644 index 92b294064fb..00000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -class TestHubTrackedItem(unittest.TestCase): - pass diff --git a/erpnext/hub_node/doctype/hub_user/__init__.py b/erpnext/hub_node/doctype/hub_user/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/hub_node/doctype/hub_user/hub_user.json b/erpnext/hub_node/doctype/hub_user/hub_user.json deleted file mode 100644 index f51ffb4387d..00000000000 --- a/erpnext/hub_node/doctype/hub_user/hub_user.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2018-08-31 12:36:45.627531", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hub_user_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Hub User", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "password", - "fieldtype": "Password", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Hub Password", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Hub User", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/hub_user/hub_user.py b/erpnext/hub_node/doctype/hub_user/hub_user.py deleted file mode 100644 index de43f4e0c04..00000000000 --- a/erpnext/hub_node/doctype/hub_user/hub_user.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class HubUser(Document): - pass diff --git a/erpnext/hub_node/doctype/hub_users/__init__.py b/erpnext/hub_node/doctype/hub_users/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.json b/erpnext/hub_node/doctype/hub_users/hub_users.json deleted file mode 100644 index d42f3fdf1b7..00000000000 --- a/erpnext/hub_node/doctype/hub_users/hub_users.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-03-06 04:38:49.891787", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Hub Users", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.py b/erpnext/hub_node/doctype/hub_users/hub_users.py deleted file mode 100644 index 440be14c0bf..00000000000 --- a/erpnext/hub_node/doctype/hub_users/hub_users.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class HubUsers(Document): - pass diff --git a/erpnext/hub_node/doctype/marketplace_settings/__init__.py b/erpnext/hub_node/doctype/marketplace_settings/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js deleted file mode 100644 index 36da832c7c0..00000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Marketplace Settings', { - refresh: function(frm) { - $('#toolbar-user .marketplace-link').toggle(!frm.doc.disable_marketplace); - }, -}); diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json deleted file mode 100644 index e784f68fcf3..00000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json +++ /dev/null @@ -1,410 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2018-08-31 15:54:38.795263", - "custom": 0, - "description": "", - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disable_marketplace", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disable Marketplace", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!doc.disable_marketplace", - "fieldname": "marketplace_settings_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Marketplace Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "https://hubmarket.org", - "fieldname": "marketplace_url", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Marketplace URL (to hide and update label)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "registered", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Registered", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sync_in_progress", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sync in Progress", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hub_seller_name", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hub Seller Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "users", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Users", - "length": 0, - "no_copy": 0, - "options": "Hub User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "last_sync_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Last Sync On", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "eval:1", - "fieldname": "custom_data", - "fieldtype": "Code", - "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": "Custom Data", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Marketplace Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py deleted file mode 100644 index 91c7bf5850a..00000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe, requests, json, time - -from frappe.model.document import Document -from frappe.utils import add_years, now, get_datetime, get_datetime_str, cint -from frappe import _ -from frappe.frappeclient import FrappeClient -from erpnext.utilities.product import get_price, get_qty_in_stock -from six import string_types - -class MarketplaceSettings(Document): - - def register_seller(self, company, company_description): - - country, currency, company_logo = frappe.db.get_value('Company', company, - ['country', 'default_currency', 'company_logo']) - - company_details = { - 'company': company, - 'country': country, - 'currency': currency, - 'company_description': company_description, - 'company_logo': company_logo, - 'site_name': frappe.utils.get_url() - } - - hub_connection = self.get_connection() - - response = hub_connection.post_request({ - 'cmd': 'hub.hub.api.add_hub_seller', - 'company_details': json.dumps(company_details) - }) - - return response - - - def add_hub_user(self, user_email): - '''Create a Hub User and User record on hub server - and if successfull append it to Hub User table - ''' - - if not self.registered: - return - - hub_connection = self.get_connection() - - first_name, last_name = frappe.db.get_value('User', user_email, ['first_name', 'last_name']) - - hub_user = hub_connection.post_request({ - 'cmd': 'hub.hub.api.add_hub_user', - 'user_email': user_email, - 'first_name': first_name, - 'last_name': last_name, - 'hub_seller': self.hub_seller_name - }) - - self.append('users', { - 'user': hub_user.get('user_email'), - 'hub_user_name': hub_user.get('hub_user_name'), - 'password': hub_user.get('password') - }) - - self.save() - - def get_hub_user(self, user): - '''Return the Hub User doc from the `users` table if password is set''' - - filtered_users = list(filter( - lambda x: x.user == user and x.password, - self.users - )) - - if filtered_users: - return filtered_users[0] - - - def get_connection(self): - return FrappeClient(self.marketplace_url) - - - def unregister(self): - """Disable the User on hubmarket.org""" - pass - -@frappe.whitelist() -def is_marketplace_enabled(): - if not hasattr(frappe.local, 'is_marketplace_enabled'): - frappe.local.is_marketplace_enabled = cint(frappe.db.get_single_value('Marketplace Settings', - 'disable_marketplace')) - - return frappe.local.is_marketplace_enabled diff --git a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js b/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js deleted file mode 100644 index fba3e098d4b..00000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Marketplace Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Marketplace Settings - () => frappe.tests.make('Marketplace Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py b/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py deleted file mode 100644 index 549b9914c34..00000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -class TestMarketplaceSettings(unittest.TestCase): - pass diff --git a/erpnext/hub_node/legacy.py b/erpnext/hub_node/legacy.py deleted file mode 100644 index b61b88bf078..00000000000 --- a/erpnext/hub_node/legacy.py +++ /dev/null @@ -1,144 +0,0 @@ -from __future__ import unicode_literals -import frappe, json -from frappe.utils import nowdate -from frappe.frappeclient import FrappeClient -from frappe.utils.nestedset import get_root_of -from frappe.contacts.doctype.contact.contact import get_default_contact - -def get_list(doctype, start, limit, fields, filters, order_by): - pass - -def get_hub_connection(): - if frappe.db.exists('Data Migration Connector', 'Hub Connector'): - hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') - hub_connection = hub_connector.get_connection() - return hub_connection.connection - - # read-only connection - hub_connection = FrappeClient(frappe.conf.hub_url) - return hub_connection - -def make_opportunity(buyer_name, email_id): - buyer_name = "HUB-" + buyer_name - - if not frappe.db.exists('Lead', {'email_id': email_id}): - lead = frappe.new_doc("Lead") - lead.lead_name = buyer_name - lead.email_id = email_id - lead.save(ignore_permissions=True) - - o = frappe.new_doc("Opportunity") - o.opportunity_from = "Lead" - o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"] - o.save(ignore_permissions=True) - -@frappe.whitelist() -def make_rfq_and_send_opportunity(item, supplier): - supplier = make_supplier(supplier) - contact = make_contact(supplier) - item = make_item(item) - rfq = make_rfq(item, supplier, contact) - status = send_opportunity(contact) - - return { - 'rfq': rfq, - 'hub_document_created': status - } - -def make_supplier(supplier): - # make supplier if not already exists - supplier = frappe._dict(json.loads(supplier)) - - if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}): - supplier_doc = frappe.get_doc({ - 'doctype': 'Supplier', - 'supplier_name': supplier.supplier_name, - 'supplier_group': supplier.supplier_group, - 'supplier_email': supplier.supplier_email - }).insert() - else: - supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name) - - return supplier_doc - -def make_contact(supplier): - contact_name = get_default_contact('Supplier', supplier.supplier_name) - # make contact if not already exists - if not contact_name: - contact = frappe.get_doc({ - 'doctype': 'Contact', - 'first_name': supplier.supplier_name, - 'is_primary_contact': 1, - 'links': [ - {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name} - ] - }) - contact.add_email(supplier.supplier_email, is_primary=True) - contact.insert() - else: - contact = frappe.get_doc('Contact', contact_name) - - return contact - -def make_item(item): - # make item if not already exists - item = frappe._dict(json.loads(item)) - - if not frappe.db.exists('Item', {'item_code': item.item_code}): - item_doc = frappe.get_doc({ - 'doctype': 'Item', - 'item_code': item.item_code, - 'item_group': item.item_group, - 'is_item_from_hub': 1 - }).insert() - else: - item_doc = frappe.get_doc('Item', item.item_code) - - return item_doc - -def make_rfq(item, supplier, contact): - # make rfq - rfq = frappe.get_doc({ - 'doctype': 'Request for Quotation', - 'transaction_date': nowdate(), - 'status': 'Draft', - 'company': frappe.db.get_single_value('Marketplace Settings', 'company'), - 'message_for_supplier': 'Please supply the specified items at the best possible rates', - 'suppliers': [ - { 'supplier': supplier.name, 'contact': contact.name } - ], - 'items': [ - { - 'item_code': item.item_code, - 'qty': 1, - 'schedule_date': nowdate(), - 'warehouse': item.default_warehouse or get_root_of("Warehouse"), - 'description': item.description, - 'uom': item.stock_uom - } - ] - }).insert() - - rfq.save() - rfq.submit() - return rfq - -def send_opportunity(contact): - # Make Hub Message on Hub with lead data - doc = { - 'doctype': 'Lead', - 'lead_name': frappe.db.get_single_value('Marketplace Settings', 'company'), - 'email_id': frappe.db.get_single_value('Marketplace Settings', 'user') - } - - args = frappe._dict(dict( - doctype='Hub Message', - reference_doctype='Lead', - data=json.dumps(doc), - user=contact.email_id - )) - - connection = get_hub_connection() - response = connection.insert('Hub Message', args) - - return response.ok diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py index 6ce2a54b190..6144d9d39a4 100644 --- a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py +++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py @@ -1,12 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.utils.dashboard import cache_source -from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \ - import get_loan_security_details -from six import iteritems + +from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure import ( + get_loan_security_details, +) + @frappe.whitelist() @cache_source @@ -50,14 +52,14 @@ def get_data(chart_name = None, chart = None, no_cache = None, filters = None, f GROUP BY p.loan_security """.format(conditions=conditions), filters, as_list=1)) - for security, qty in iteritems(pledges): + for security, qty in pledges.items(): current_pledges.setdefault(security, qty) current_pledges[security] -= unpledges.get(security, 0.0) sorted_pledges = dict(sorted(current_pledges.items(), key=lambda item: item[1], reverse=True)) count = 0 - for security, qty in iteritems(sorted_pledges): + for security, qty in sorted_pledges.items(): values.append(qty * loan_security_details.get(security, {}).get('latest_price', 0)) labels.append(security) count +=1 diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index c9f23ca4df3..5979992bbe8 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -334,7 +334,6 @@ }, { "depends_on": "eval:doc.is_secured_loan", - "fetch_from": "loan_application.maximum_loan_amount", "fieldname": "maximum_loan_amount", "fieldtype": "Currency", "label": "Maximum Loan Amount", @@ -360,7 +359,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:10:32.360818", + "modified": "2021-10-12 18:10:32.360818", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index ff7fbbdf49a..84e0f03bae9 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -1,16 +1,21 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, math, json -import erpnext + +import json +import math + +import frappe from frappe import _ -from six import string_types -from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime -from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty +from frappe.utils import add_months, flt, getdate, now_datetime, nowdate + +import erpnext from erpnext.controllers.accounts_controller import AccountsController from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts +from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import ( + get_pledged_security_qty, +) + class Loan(AccountsController): def validate(self): @@ -129,16 +134,23 @@ class Loan(AccountsController): frappe.throw(_("Loan amount is mandatory")) def link_loan_security_pledge(self): - if self.is_secured_loan: - loan_security_pledge = frappe.db.get_value('Loan Security Pledge', {'loan_application': self.loan_application}, - 'name') + if self.is_secured_loan and self.loan_application: + maximum_loan_value = frappe.db.get_value('Loan Security Pledge', + { + 'loan_application': self.loan_application, + 'status': 'Requested' + }, + 'sum(maximum_loan_value)' + ) - if loan_security_pledge: - frappe.db.set_value('Loan Security Pledge', loan_security_pledge, { - 'loan': self.name, - 'status': 'Pledged', - 'pledge_time': now_datetime() - }) + if maximum_loan_value: + frappe.db.sql(""" + UPDATE `tabLoan Security Pledge` + SET loan = %s, pledge_time = %s, status = 'Pledged' + WHERE status = 'Requested' and loan_application = %s + """, (self.name, now_datetime(), self.loan_application)) + + self.db_set('maximum_loan_amount', maximum_loan_value) def unlink_loan_security_pledge(self): pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name}) @@ -308,7 +320,7 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict @frappe.whitelist() def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0): # if no security_map is passed it will be considered as full unpledge - if security_map and isinstance(security_map, string_types): + if security_map and isinstance(security_map, str): security_map = json.loads(security_map) if loan: @@ -361,7 +373,9 @@ def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, a return unpledge_request def validate_employee_currency_with_company_currency(applicant, company): - from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency + from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import ( + get_employee_currency, + ) if not applicant: frappe.throw(_("Please select Applicant")) if not company: diff --git a/erpnext/loan_management/doctype/loan/loan_dashboard.py b/erpnext/loan_management/doctype/loan/loan_dashboard.py index 711a7829baf..c8a9e64f5ef 100644 --- a/erpnext/loan_management/doctype/loan/loan_dashboard.py +++ b/erpnext/loan_management/doctype/loan/loan_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'loan', diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 122d7236051..c0f058feae6 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -1,25 +1,40 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import erpnext -import unittest -from frappe.utils import (nowdate, add_days, getdate, now_datetime, add_to_date, get_datetime, - add_months, get_first_day, get_last_day, flt, date_diff) -from erpnext.selling.doctype.customer.test_customer import get_customer_dict -from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee -from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (process_loan_interest_accrual_for_demand_loans, - process_loan_interest_accrual_for_term_loans) -from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year -from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall -from erpnext.loan_management.doctype.loan.loan import unpledge_security, request_loan_closure, make_loan_write_off -from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty +from frappe.utils import add_days, add_months, add_to_date, date_diff, flt, get_datetime, nowdate + +from erpnext.loan_management.doctype.loan.loan import ( + make_loan_write_off, + request_loan_closure, + unpledge_security, +) from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge -from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount +from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import ( + get_disbursal_amount, +) +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import ( + days_in_year, +) from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts -from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure +from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import ( + get_pledged_security_qty, +) +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_demand_loans, + process_loan_interest_accrual_for_term_loans, +) +from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import ( + create_process_loan_security_shortfall, +) +from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( + make_employee, + make_salary_structure, +) +from erpnext.selling.doctype.customer.test_customer import get_customer_dict + class TestLoan(unittest.TestCase): def setUp(self): diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index d8f3577b2c3..24d8d68de0b 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -1,18 +1,26 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, math -from frappe import _ -from frappe.utils import flt, rounded, cint -from frappe.model.mapper import get_mapped_doc -from frappe.model.document import Document -from erpnext.loan_management.doctype.loan.loan import (get_monthly_repayment_amount, validate_repayment_method, - get_total_loan_amount, get_sanctioned_amount_limit) -from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price + import json -from six import string_types +import math + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import cint, flt, rounded + +from erpnext.loan_management.doctype.loan.loan import ( + get_monthly_repayment_amount, + get_sanctioned_amount_limit, + get_total_loan_amount, + validate_repayment_method, +) +from erpnext.loan_management.doctype.loan_security_price.loan_security_price import ( + get_loan_security_price, +) + class LoanApplication(Document): def validate(self): @@ -119,10 +127,11 @@ class LoanApplication(Document): def create_loan(source_name, target_doc=None, submit=0): def update_accounts(source_doc, target_doc, source_parent): account_details = frappe.get_all("Loan Type", - fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], - filters = {'name': source_doc.loan_type} - )[0] + fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], + filters = {'name': source_doc.loan_type})[0] + if source_doc.is_secured_loan: + target_doc.maximum_loan_amount = 0 target_doc.mode_of_payment = account_details.mode_of_payment target_doc.payment_account = account_details.payment_account @@ -180,7 +189,7 @@ def create_pledge(loan_application, loan=None): #This is a sandbox method to get the proposed pledges @frappe.whitelist() def get_proposed_pledge(securities): - if isinstance(securities, string_types): + if isinstance(securities, str): securities = json.loads(securities) proposed_pledges = { diff --git a/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py b/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py index 3975adf4431..e8e2a2a0de6 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'loan_application', diff --git a/erpnext/loan_management/doctype/loan_application/test_loan_application.py b/erpnext/loan_management/doctype/loan_application/test_loan_application.py index 2a659e9fc2e..d367e92ac49 100644 --- a/erpnext/loan_management/doctype/loan_application/test_loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/test_loan_application.py @@ -1,12 +1,16 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee, make_salary_structure -from erpnext.loan_management.doctype.loan.test_loan import create_loan_type, create_loan_accounts + +from erpnext.loan_management.doctype.loan.test_loan import create_loan_accounts, create_loan_type +from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( + make_employee, + make_salary_structure, +) + class TestLoanApplication(unittest.TestCase): def setUp(self): diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index f113c10ef71..93b4af92c76 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -1,17 +1,21 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ -from frappe.model.document import Document -from frappe.utils import nowdate, getdate, add_days, flt -from erpnext.controllers.accounts_controller import AccountsController +from frappe.utils import add_days, flt, get_datetime, nowdate + +import erpnext from erpnext.accounts.general_ledger import make_gl_entries -from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans -from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty -from frappe.utils import get_datetime +from erpnext.controllers.accounts_controller import AccountsController +from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import ( + get_pledged_security_qty, +) +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_demand_loans, +) + class LoanDisbursement(AccountsController): @@ -192,7 +196,7 @@ def get_disbursal_amount(loan, on_current_security_price=0): security_value = get_total_pledged_security_value(loan) if loan_details.is_secured_loan and not on_current_security_price: - security_value = flt(loan_details.maximum_loan_amount) + security_value = get_maximum_amount_as_per_pledged_security(loan) if not security_value and not loan_details.is_secured_loan: security_value = flt(loan_details.loan_amount) @@ -203,3 +207,6 @@ def get_disbursal_amount(loan, on_current_security_price=0): disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount return disbursal_amount + +def get_maximum_amount_as_per_pledged_security(loan): + return flt(frappe.db.get_value('Loan Security Pledge', {'loan': loan}, 'sum(maximum_loan_value)')) diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py index da56710c679..94ec84ea5db 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py @@ -1,17 +1,43 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe + import unittest -from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date) -from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_repayment_entry, create_loan_application, - make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_security_price) -from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans -from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year, get_per_day_interest -from erpnext.selling.doctype.customer.test_customer import get_customer_dict + +import frappe +from frappe.utils import ( + add_days, + add_to_date, + date_diff, + flt, + get_datetime, + get_first_day, + get_last_day, + nowdate, +) + +from erpnext.loan_management.doctype.loan.test_loan import ( + create_demand_loan, + create_loan_accounts, + create_loan_application, + create_loan_security, + create_loan_security_pledge, + create_loan_security_price, + create_loan_security_type, + create_loan_type, + create_repayment_entry, + make_loan_disbursement_entry, +) from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import ( + days_in_year, + get_per_day_interest, +) from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_demand_loans, +) +from erpnext.selling.doctype.customer.test_customer import get_customer_dict + class TestLoanDisbursement(unittest.TestCase): diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index d75213ce78d..e945d4931e3 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -1,15 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ -from frappe.model.document import Document -from frappe.utils import (nowdate, getdate, now_datetime, get_datetime, flt, date_diff, get_last_day, cint, - get_first_day, get_datetime, add_days) -from erpnext.controllers.accounts_controller import AccountsController +from frappe.utils import add_days, cint, date_diff, flt, get_datetime, getdate, nowdate + +import erpnext from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.controllers.accounts_controller import AccountsController + class LoanInterestAccrual(AccountsController): def validate(self): diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py index eb626f3eee0..46aaaad9fd2 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py @@ -1,16 +1,30 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe + import unittest -from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date) -from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_price, - make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_application) -from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans -from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year -from erpnext.selling.doctype.customer.test_customer import get_customer_dict + +import frappe +from frappe.utils import add_to_date, date_diff, flt, get_datetime, get_first_day, nowdate + +from erpnext.loan_management.doctype.loan.test_loan import ( + create_demand_loan, + create_loan_accounts, + create_loan_application, + create_loan_security, + create_loan_security_price, + create_loan_security_type, + create_loan_type, + make_loan_disbursement_entry, +) from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import ( + days_in_year, +) +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_demand_loans, +) +from erpnext.selling.doctype.customer.test_customer import get_customer_dict + class TestLoanInterestAccrual(unittest.TestCase): def setUp(self): diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 57aec2e5c9c..5922e4f902a 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -1,20 +1,25 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext -import json + +import frappe from frappe import _ -from frappe.utils import flt, getdate, cint -from six import iteritems -from frappe.model.document import Document -from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day, get_datetime -from erpnext.controllers.accounts_controller import AccountsController +from frappe.utils import add_days, cint, date_diff, flt, get_datetime, getdate + +import erpnext from erpnext.accounts.general_ledger import make_gl_entries -from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import update_shortfall_status -from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans -from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import get_per_day_interest, get_last_accrual_date +from erpnext.controllers.accounts_controller import AccountsController +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import ( + get_last_accrual_date, + get_per_day_interest, +) +from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import ( + update_shortfall_status, +) +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_demand_loans, +) + class LoanRepayment(AccountsController): @@ -107,12 +112,13 @@ class LoanRepayment(AccountsController): lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1) - self.append('repayment_details', { - 'loan_interest_accrual': lia.name, - 'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision), - 'paid_principal_amount': 0.0, - 'accrual_type': 'Repayment' - }) + if lia: + self.append('repayment_details', { + 'loan_interest_accrual': lia.name, + 'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision), + 'paid_principal_amount': 0.0, + 'accrual_type': 'Repayment' + }) def update_paid_amount(self): loan = frappe.get_doc("Loan", self.against_loan) @@ -180,7 +186,7 @@ class LoanRepayment(AccountsController): # interest_paid = self.amount_paid - self.principal_amount_paid - self.penalty_amount if interest_paid > 0: - for lia, amounts in iteritems(repayment_details.get('pending_accrual_entries', [])): + for lia, amounts in repayment_details.get('pending_accrual_entries', []).items(): if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid: interest_amount = amounts['interest_amount'] paid_principal = amounts['payable_principal_amount'] @@ -402,7 +408,7 @@ def get_amounts(amounts, against_loan, posting_date): if due_date and not final_due_date: final_due_date = add_days(due_date, loan_type_details.grace_period_in_days) - if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested', 'Closed'): + if against_loan_doc.status in ('Disbursed', 'Closed') or against_loan_doc.disbursed_amount >= against_loan_doc.loan_amount: pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid \ - against_loan_doc.total_interest_payable - against_loan_doc.written_off_amount else: diff --git a/erpnext/loan_management/doctype/loan_repayment/test_loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/test_loan_repayment.py index 73585a51592..98e5a0a6af3 100644 --- a/erpnext/loan_management/doctype/loan_repayment/test_loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/test_loan_repayment.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanRepayment(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py index a83b9b59415..495b6866615 100644 --- a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py +++ b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class LoanRepaymentDetail(Document): pass diff --git a/erpnext/loan_management/doctype/loan_security/loan_security.py b/erpnext/loan_management/doctype/loan_security/loan_security.py index 8858c818362..8087fc50959 100644 --- a/erpnext/loan_management/doctype/loan_security/loan_security.py +++ b/erpnext/loan_management/doctype/loan_security/loan_security.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class LoanSecurity(Document): def autoname(self): self.name = self.loan_security_name diff --git a/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py b/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py index 3eec5660ac1..964a1ae93a7 100644 --- a/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py +++ b/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'loan_security', diff --git a/erpnext/loan_management/doctype/loan_security/test_loan_security.py b/erpnext/loan_management/doctype/loan_security/test_loan_security.py index 24dbc680461..7e702ed2345 100644 --- a/erpnext/loan_management/doctype/loan_security/test_loan_security.py +++ b/erpnext/loan_management/doctype/loan_security/test_loan_security.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanSecurity(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py index c390b6c526d..7d02645609b 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py @@ -1,14 +1,19 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import now_datetime, cint from frappe.model.document import Document -from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import update_shortfall_status -from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price +from frappe.utils import cint, now_datetime + +from erpnext.loan_management.doctype.loan_security_price.loan_security_price import ( + get_loan_security_price, +) +from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import ( + update_shortfall_status, +) + class LoanSecurityPledge(Document): def validate(self): diff --git a/erpnext/loan_management/doctype/loan_security_pledge/test_loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/test_loan_security_pledge.py index d2347c00985..b9d05c28586 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/test_loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/test_loan_security_pledge.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanSecurityPledge(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py index 9fc1fda53f4..fca9dd6bcb9 100644 --- a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py +++ b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import getdate, now_datetime, add_to_date, get_datetime, get_timestamp, get_datetime_str -from six import iteritems +from frappe.utils import get_datetime + class LoanSecurityPrice(Document): def validate(self): diff --git a/erpnext/loan_management/doctype/loan_security_price/test_loan_security_price.py b/erpnext/loan_management/doctype/loan_security_price/test_loan_security_price.py index 2fe0bd5a24e..aa533d9cf40 100644 --- a/erpnext/loan_management/doctype/loan_security_price/test_loan_security_price.py +++ b/erpnext/loan_management/doctype/loan_security_price/test_loan_security_price.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanSecurityPrice(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index cd7694b7b17..20e451b81ea 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import get_datetime, flt from frappe.model.document import Document -from six import iteritems -from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty +from frappe.utils import flt, get_datetime + +from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import ( + get_pledged_security_qty, +) + class LoanSecurityShortfall(Document): pass @@ -71,7 +73,7 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): - flt(loan.total_principal_paid) pledged_securities = get_pledged_security_qty(loan.name) - ltv_ratio = '' + ltv_ratio = 0.0 security_value = 0.0 for security, qty in pledged_securities.items(): diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/test_loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/test_loan_security_shortfall.py index b82f3d25936..58bf9744380 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/test_loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/test_loan_security_shortfall.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanSecurityShortfall(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/loan_security_type/loan_security_type.py b/erpnext/loan_management/doctype/loan_security_type/loan_security_type.py index cb8a50a27ba..af87259daf5 100644 --- a/erpnext/loan_management/doctype/loan_security_type/loan_security_type.py +++ b/erpnext/loan_management/doctype/loan_security_type/loan_security_type.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class LoanSecurityType(Document): pass diff --git a/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py b/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py index 17de8c1da4d..2a9ceed2d8c 100644 --- a/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py +++ b/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'loan_security_type', diff --git a/erpnext/loan_management/doctype/loan_security_type/test_loan_security_type.py b/erpnext/loan_management/doctype/loan_security_type/test_loan_security_type.py index f7d845a779c..cf7a335a774 100644 --- a/erpnext/loan_management/doctype/loan_security_type/test_loan_security_type.py +++ b/erpnext/loan_management/doctype/loan_security_type/test_loan_security_type.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanSecurityType(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index 4f936dd7c11..bff9d5cf62d 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -1,15 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import get_datetime, flt, getdate -import json -from six import iteritems -from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price +from frappe.utils import flt, get_datetime, getdate + class LoanSecurityUnpledge(Document): def validate(self): @@ -30,7 +27,9 @@ class LoanSecurityUnpledge(Document): d.idx, frappe.bold(d.loan_security))) def validate_unpledge_qty(self): - from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import get_ltv_ratio + from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import ( + get_ltv_ratio, + ) pledge_qty_map = get_pledged_security_qty(self.loan) @@ -109,7 +108,7 @@ class LoanSecurityUnpledge(Document): pledged_qty = 0 current_pledges = get_pledged_security_qty(self.loan) - for security, qty in iteritems(current_pledges): + for security, qty in current_pledges.items(): pledged_qty += qty if not pledged_qty: @@ -142,7 +141,7 @@ def get_pledged_security_qty(loan): GROUP BY p.loan_security """, (loan))) - for security, qty in iteritems(pledges): + for security, qty in pledges.items(): current_pledges.setdefault(security, qty) current_pledges[security] -= unpledges.get(security, 0.0) diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/test_loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/test_loan_security_unpledge.py index 5b5c205bda0..2f124e4965f 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/test_loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/test_loan_security_unpledge.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanSecurityUnpledge(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.py b/erpnext/loan_management/doctype/loan_type/loan_type.py index 50ef930dbbe..592229cf994 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.py +++ b/erpnext/loan_management/doctype/loan_type/loan_type.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document + class LoanType(Document): def validate(self): self.validate_accounts() diff --git a/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py b/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py index 95d97fdf9b0..245e1021202 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py +++ b/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'loan_type', diff --git a/erpnext/loan_management/doctype/loan_type/test_loan_type.py b/erpnext/loan_management/doctype/loan_type/test_loan_type.py index 5877ab6f7f0..e3b51e8f7d2 100644 --- a/erpnext/loan_management/doctype/loan_type/test_loan_type.py +++ b/erpnext/loan_management/doctype/loan_type/test_loan_type.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanType(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py index 676df701cc3..35be587f87f 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ -from frappe.utils import getdate, flt, cint -from erpnext.controllers.accounts_controller import AccountsController +from frappe.utils import cint, flt, getdate + +import erpnext from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.controllers.accounts_controller import AccountsController + class LoanWriteOff(AccountsController): def validate(self): diff --git a/erpnext/loan_management/doctype/loan_write_off/test_loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/test_loan_write_off.py index 9f6700e2749..1494114afb3 100644 --- a/erpnext/loan_management/doctype/loan_write_off/test_loan_write_off.py +++ b/erpnext/loan_management/doctype/loan_write_off/test_loan_write_off.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLoanWriteOff(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/pledge/pledge.py b/erpnext/loan_management/doctype/pledge/pledge.py index 0457ad7abdb..f02ed95275d 100644 --- a/erpnext/loan_management/doctype/pledge/pledge.py +++ b/erpnext/loan_management/doctype/pledge/pledge.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class Pledge(Document): pass diff --git a/erpnext/loan_management/doctype/pledge/test_pledge.py b/erpnext/loan_management/doctype/pledge/test_pledge.py index 2e01dc114d7..2d3fd320f5b 100644 --- a/erpnext/loan_management/doctype/pledge/test_pledge.py +++ b/erpnext/loan_management/doctype/pledge/test_pledge.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestPledge(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py index 8c67c0affee..4c34ccd983e 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py @@ -1,13 +1,16 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import nowdate from frappe.model.document import Document -from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import (make_accrual_interest_entry_for_demand_loans, - make_accrual_interest_entry_for_term_loans) +from frappe.utils import nowdate + +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import ( + make_accrual_interest_entry_for_demand_loans, + make_accrual_interest_entry_for_term_loans, +) + class ProcessLoanInterestAccrual(Document): def on_submit(self): diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py index e104c6646b0..932087cb5a1 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'process_loan_interest_accrual', diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/test_process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/test_process_loan_interest_accrual.py index 6bfd3f42103..1fb8c2e9529 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/test_process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/test_process_loan_interest_accrual.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestProcessLoanInterestAccrual(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.py b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.py index b4aad25ac80..ba9fb0c449f 100644 --- a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import get_datetime -from frappe import _ from frappe.model.document import Document -from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import check_for_ltv_shortfall +from frappe.utils import get_datetime + +from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import ( + check_for_ltv_shortfall, +) + class ProcessLoanSecurityShortfall(Document): def onload(self): diff --git a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py index e67e4d4738f..1f5d8438ff3 100644 --- a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py +++ b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'process_loan_security_shortfall', diff --git a/erpnext/loan_management/doctype/process_loan_security_shortfall/test_process_loan_security_shortfall.py b/erpnext/loan_management/doctype/process_loan_security_shortfall/test_process_loan_security_shortfall.py index cd379a1bea2..c743cf05007 100644 --- a/erpnext/loan_management/doctype/process_loan_security_shortfall/test_process_loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/process_loan_security_shortfall/test_process_loan_security_shortfall.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestProcessLoanSecurityShortfall(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.py b/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.py index dfa5c7965a0..ac2b7d24012 100644 --- a/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.py +++ b/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class ProposedPledge(Document): pass diff --git a/erpnext/loan_management/doctype/repayment_schedule/repayment_schedule.py b/erpnext/loan_management/doctype/repayment_schedule/repayment_schedule.py index 2aa27b09687..dc407e7d99f 100644 --- a/erpnext/loan_management/doctype/repayment_schedule/repayment_schedule.py +++ b/erpnext/loan_management/doctype/repayment_schedule/repayment_schedule.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class RepaymentSchedule(Document): pass diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.py b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.py index 9ee0b96dc1d..91267b80ba1 100644 --- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.py +++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class SalarySlipLoan(Document): pass diff --git a/erpnext/loan_management/doctype/sanctioned_loan_amount/sanctioned_loan_amount.py b/erpnext/loan_management/doctype/sanctioned_loan_amount/sanctioned_loan_amount.py index 74a131015b5..6063b7bad8b 100644 --- a/erpnext/loan_management/doctype/sanctioned_loan_amount/sanctioned_loan_amount.py +++ b/erpnext/loan_management/doctype/sanctioned_loan_amount/sanctioned_loan_amount.py @@ -1,11 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe +from frappe import _ from frappe.model.document import Document + class SanctionedLoanAmount(Document): def validate(self): sanctioned_doc = frappe.db.exists('Sanctioned Loan Amount', {'applicant': self.applicant, 'company': self.company}) diff --git a/erpnext/loan_management/doctype/sanctioned_loan_amount/test_sanctioned_loan_amount.py b/erpnext/loan_management/doctype/sanctioned_loan_amount/test_sanctioned_loan_amount.py index ba1372f175d..4d99086d612 100644 --- a/erpnext/loan_management/doctype/sanctioned_loan_amount/test_sanctioned_loan_amount.py +++ b/erpnext/loan_management/doctype/sanctioned_loan_amount/test_sanctioned_loan_amount.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestSanctionedLoanAmount(unittest.TestCase): pass diff --git a/erpnext/loan_management/doctype/unpledge/unpledge.py b/erpnext/loan_management/doctype/unpledge/unpledge.py index 205230a308f..403749ba646 100644 --- a/erpnext/loan_management/doctype/unpledge/unpledge.py +++ b/erpnext/loan_management/doctype/unpledge/unpledge.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class Unpledge(Document): pass diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py index f2cbbb469f0..512b47f7991 100644 --- a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py +++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py @@ -1,12 +1,13 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -import erpnext from frappe import _ -from frappe.utils import get_datetime, flt -from six import iteritems +from frappe.utils import flt + +import erpnext + def execute(filters=None): columns = get_columns(filters) @@ -42,7 +43,7 @@ def get_data(filters): currency = erpnext.get_company_currency(filters.get('company')) - for key, qty in iteritems(pledge_values): + for key, qty in pledge_values.items(): if qty: row = {} current_value = flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0)) diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py index a505e72c4d9..7c512679567 100644 --- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py +++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py @@ -1,13 +1,15 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -import erpnext from frappe import _ -from frappe.utils import flt, getdate, add_days -from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \ - import get_loan_security_details +from frappe.utils import add_days, flt, getdate + +import erpnext +from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure import ( + get_loan_security_details, +) def execute(filters=None): diff --git a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py index 65910770881..68fd3d8e8b5 100644 --- a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py +++ b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): columns = get_columns() data = get_data(filters) diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py index 34bbe5a4503..7dbb9662339 100644 --- a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py +++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py @@ -1,13 +1,16 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import erpnext + from frappe import _ from frappe.utils import flt -from six import iteritems -from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \ - import get_loan_security_details, get_applicant_wise_total_loan_security_qty + +import erpnext +from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure import ( + get_applicant_wise_total_loan_security_qty, + get_loan_security_details, +) + def execute(filters=None): columns = get_columns(filters) @@ -39,7 +42,7 @@ def get_data(filters): current_pledges, total_portfolio_value = get_company_wise_loan_security_details(filters, loan_security_details) currency = erpnext.get_company_currency(filters.get('company')) - for security, value in iteritems(current_pledges): + for security, value in current_pledges.items(): if value.get('qty'): row = {} current_value = flt(value.get('qty', 0) * loan_security_details.get(security, {}).get('latest_price', 0)) @@ -66,7 +69,7 @@ def get_company_wise_loan_security_details(filters, loan_security_details): total_portfolio_value = 0 security_wise_map = {} - for key, qty in iteritems(pledge_values): + for key, qty in pledge_values.items(): security_wise_map.setdefault(key[1], { 'qty': 0.0, 'applicant_count': 0.0 diff --git a/erpnext/loan_management/report/loan_security_status/loan_security_status.py b/erpnext/loan_management/report/loan_security_status/loan_security_status.py index 19518554759..b7e716880e9 100644 --- a/erpnext/loan_management/report/loan_security_status/loan_security_status.py +++ b/erpnext/loan_management/report/loan_security_status/loan_security_status.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): columns = get_columns(filters) data = get_data(filters) diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json index ca528ec6bd9..7deee0d4612 100644 --- a/erpnext/loan_management/workspace/loan_management/loan_management.json +++ b/erpnext/loan_management/workspace/loan_management/loan_management.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Processes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Disbursement and Repayment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Security\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-12 16:35:55.299820", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "loan", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Loans", "links": [ { @@ -245,15 +238,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:18:13.350904", + "modified": "2021-08-05 12:18:13.350905", "modified_by": "Administrator", "module": "Loan Management", "name": "Loans", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index d1a8c8de27c..035290d8f19 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -18,7 +18,7 @@ frappe.ui.form.on('Maintenance Schedule', { }, refresh: function (frm) { setTimeout(() => { - frm.toggle_display('generate_schedule', !(frm.is_new())); + frm.toggle_display('generate_schedule', !(frm.is_new() || frm.doc.docstatus)); frm.toggle_display('schedule', !(frm.is_new())); }, 10); }, diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 97289032d70..2ffae1a4f2a 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -1,23 +1,23 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe +from frappe import _, throw +from frappe.utils import add_days, cint, cstr, date_diff, formatdate, getdate -from frappe.utils import add_days, getdate, cint, cstr, date_diff, formatdate - -from frappe import throw, _ -from erpnext.utilities.transaction_base import TransactionBase, delete_events -from erpnext.stock.utils import get_valid_serial_nos from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.utils import get_valid_serial_nos +from erpnext.utilities.transaction_base import TransactionBase, delete_events + class MaintenanceSchedule(TransactionBase): @frappe.whitelist() def generate_schedule(self): + if self.docstatus != 0: + return self.set('schedules', []) - frappe.db.sql("""delete from `tabMaintenance Schedule Detail` - where parent=%s""", (self.name)) count = 1 for d in self.get('items'): self.validate_maintenance_detail() @@ -46,7 +46,7 @@ class MaintenanceSchedule(TransactionBase): "Yearly": 365 } for item in self.items: - if item.periodicity and item.start_date: + if item.periodicity and item.periodicity != "Random" and item.start_date: if not item.end_date: if item.no_of_visits: item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) @@ -198,12 +198,16 @@ class MaintenanceSchedule(TransactionBase): if chk: throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order)) + def validate_no_of_visits(self): + return len(self.schedules) != sum(d.no_of_visits for d in self.items) + def validate(self): self.validate_end_date_visits() self.validate_maintenance_detail() self.validate_dates_with_periodicity() self.validate_sales_order() - self.generate_schedule() + if not self.schedules or self.validate_no_of_visits(): + self.generate_schedule() def on_update(self): frappe.db.set(self, 'status', 'Draft') @@ -319,10 +323,14 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No target.maintenance_schedule = source.name target.maintenance_schedule_detail = s_id - def update_sales(source, target, parent): + def update_sales_and_serial(source, target, parent): sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') target.service_person = sales_person - target.serial_no = '' + serial_nos = get_serial_nos(target.serial_no) + if len(serial_nos) == 1: + target.serial_no = serial_nos[0] + else: + target.serial_no = '' doclist = get_mapped_doc("Maintenance Schedule", source_name, { "Maintenance Schedule": { @@ -338,7 +346,7 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No "Maintenance Schedule Item": { "doctype": "Maintenance Visit Purpose", "condition": lambda doc: doc.item_name == item_name, - "postprocess": update_sales + "postprocess": update_sales_and_serial } }, target_doc) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js deleted file mode 100644 index e0f05b1abbd..00000000000 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Maintenance Schedule", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Maintenance Schedule - () => frappe.tests.make('Maintenance Schedule', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index c733dd0c92c..37ea3fdac30 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -1,12 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -from frappe.utils.data import add_days, today, formatdate -from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import make_maintenance_visit + +import unittest import frappe -import unittest +from frappe.utils.data import add_days, formatdate, today + +from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import ( + make_maintenance_visit, +) # test_records = frappe.get_test_records('Maintenance Schedule') diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 8ccef6a8172..afe273f3102 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -89,13 +89,14 @@ "width": "160px" }, { + "allow_on_submit": 1, "columns": 2, + "default": "Pending", "fieldname": "completion_status", "fieldtype": "Select", "in_list_view": 1, "label": "Completion Status", - "options": "Pending\nPartially Completed\nFully Completed", - "read_only": 1 + "options": "Pending\nPartially Completed\nFully Completed" }, { "fieldname": "column_break_3", @@ -125,10 +126,11 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-27 16:07:25.905015", + "modified": "2021-09-16 21:25:22.506485", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.py b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.py index e69b4fb65e0..cb20066da04 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.py +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class MaintenanceScheduleDetail(Document): pass diff --git a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.py b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.py index 1dd47fea24f..b6ce0a50f17 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.py +++ b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class MaintenanceScheduleItem(Document): pass diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 53ecdf5a61f..6f6ca61ebc7 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -31,8 +31,8 @@ frappe.ui.form.on('Maintenance Visit', { }, onload: function (frm, cdt, cdn) { let item = locals[cdt][cdn]; - if (frm.maintenance_type == 'Scheduled') { - let schedule_id = item.purposes[0].prevdoc_detail_docname; + if (frm.doc.maintenance_type === "Scheduled") { + const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail; frappe.call({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", args: { @@ -43,11 +43,11 @@ frappe.ui.form.on('Maintenance Visit', { } }); } - if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { + frm.clear_table("purposes"); frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, @@ -73,12 +73,16 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui. if (this.frm.doc.docstatus === 0) { this.frm.add_custom_button(__('Maintenance Schedule'), function () { + if (!me.frm.doc.customer) { + frappe.msgprint(__('Please select Customer first')); + return; + } erpnext.utils.map_current_doc({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", source_doctype: "Maintenance Schedule", target: me.frm, setters: { - customer: me.frm.doc.customer || undefined, + customer: me.frm.doc.customer, }, get_query_filters: { docstatus: 1, @@ -104,12 +108,16 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui. }, __("Get Items From")); this.frm.add_custom_button(__('Sales Order'), function () { + if (!me.frm.doc.customer) { + frappe.msgprint(__('Please select Customer first')); + return; + } erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit", source_doctype: "Sales Order", target: me.frm, setters: { - customer: me.frm.doc.customer || undefined, + customer: me.frm.doc.customer, }, get_query_filters: { docstatus: 1, diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index d63c7003870..5a87b162af6 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -1,13 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.utils import get_datetime from erpnext.utilities.transaction_base import TransactionBase + class MaintenanceVisit(TransactionBase): def get_feed(self): return _("To {0}").format(self.customer_name) diff --git a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py index 2bea8d1e2f6..6a9e70a1a92 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py @@ -1,12 +1,41 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest +from frappe.utils.data import today # test_records = frappe.get_test_records('Maintenance Visit') class TestMaintenanceVisit(unittest.TestCase): pass + +def make_maintenance_visit(): + mv = frappe.new_doc("Maintenance Visit") + mv.company = "_Test Company" + mv.customer = "_Test Customer" + mv.mntc_date = today() + mv.completion_status = "Partially Completed" + + sales_person = make_sales_person("Dwight Schrute") + + mv.append("purposes", { + "item_code": "_Test Item", + "sales_person": "Sales Team", + "description": "Test Item", + "work_done": "Test Work Done", + "service_person": sales_person.name + }) + mv.insert(ignore_permissions=True) + + return mv + +def make_sales_person(name): + sales_person = frappe.get_doc({ + 'doctype': "Sales Person", + 'sales_person_name': name + }) + sales_person.insert(ignore_if_duplicate = True) + + return sales_person diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py index a7f0f5b3bb9..50d9a4e08af 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class MaintenanceVisitPurpose(Document): pass diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py index 7ba43d6471e..1bc12ff35eb 100644 --- a/erpnext/manufacturing/dashboard_fixtures.py +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -1,9 +1,14 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import frappe, erpnext, json +import json + +import frappe from frappe import _ -from frappe.utils import nowdate, get_first_day, get_last_day, add_months +from frappe.utils import add_months, nowdate + +import erpnext + def get_data(): return frappe._dict({ diff --git a/erpnext/manufacturing/doctype/__init__.py b/erpnext/manufacturing/doctype/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/__init__.py +++ b/erpnext/manufacturing/doctype/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index 1aedb1e590f..5340c51131f 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt, getdate from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc +from frappe.utils import flt, getdate + from erpnext.stock.doctype.item.item import get_item_defaults diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py index d9aa0ca49d0..c6745c877ca 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'blanket_order', diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js deleted file mode 100644 index 51a0d948419..00000000000 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Blanket Order", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Blanket Order - () => frappe.tests.make('Blanket Order', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index 9a0a72fb475..d5db3fc1ccf 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -1,14 +1,16 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest from frappe.utils import add_months, today + from erpnext import get_company_currency + from .blanket_order import make_order + class TestBlanketOrder(unittest.TestCase): def setUp(self): frappe.flags.args = frappe._dict() diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py index f07f3c8e046..ebce209fbc2 100644 --- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py +++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class BlanketOrderItem(Document): pass diff --git a/erpnext/manufacturing/doctype/bom/__init__.py b/erpnext/manufacturing/doctype/bom/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/bom/__init__.py +++ b/erpnext/manufacturing/doctype/bom/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index e72c8eb4088..6d35d65bea9 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -215,7 +215,32 @@ frappe.ui.form.on("BOM", { label: __('Qty To Manufacture'), fieldname: 'qty', reqd: 1, - default: 1 + default: 1, + onchange: () => { + const { quantity, items: rm } = frm.doc; + const variant_items_map = rm.reduce((acc, item) => { + acc[item.item_code] = item.qty; + return acc; + }, {}); + const mf_qty = cur_dialog.fields_list.filter( + (f) => f.df.fieldname === "qty" + )[0]?.value; + const items = cur_dialog.fields.filter( + (f) => f.fieldname === "items" + )[0]?.data; + + if (!items) { + return; + } + + items.forEach((item) => { + item.qty = + (variant_items_map[item.item_code] * mf_qty) / + quantity; + }); + + cur_dialog.refresh(); + } }); } @@ -446,6 +471,11 @@ var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) { }, callback: function(r) { d = locals[cdt][cdn]; + if (d.is_process_loss) { + r.message.rate = 0; + r.message.base_rate = 0; + } + $.extend(d, r.message); refresh_field("items"); refresh_field("scrap_items"); @@ -650,8 +680,57 @@ frappe.ui.form.on("BOM Item", "items_remove", function(frm) { erpnext.bom.calculate_total(frm.doc); }); -frappe.ui.form.on("BOM", "with_operations", function(frm) { - if(!cint(frm.doc.with_operations)) { - frm.set_value("operations", []); +frappe.tour['BOM'] = [ + { + fieldname: "item", + title: "Item", + description: __("Select the Item to be manufactured. The Item name, UoM, Company, and Currency will be fetched automatically.") + }, + { + fieldname: "quantity", + title: "Quantity", + description: __("Enter the quantity of the Item that will be manufactured from this Bill of Materials.") + }, + { + fieldname: "with_operations", + title: "With Operations", + description: __("To add Operations tick the 'With Operations' checkbox.") + }, + { + fieldname: "items", + title: "Raw Materials", + description: __("Select the raw materials (Items) required to manufacture the Item") } +]; + +frappe.ui.form.on("BOM Scrap Item", { + item_code(frm, cdt, cdn) { + const { item_code } = locals[cdt][cdn]; + if (item_code === frm.doc.item) { + locals[cdt][cdn].is_process_loss = 1; + trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code); + } + }, }); + +function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) { + frappe.prompt( + { + fieldname: "percent", + fieldtype: "Percent", + label: __("% Finished Item Quantity"), + description: + __("Set quantity of process loss item:") + + ` ${item_code} ` + + __("as a percentage of finished item quantity"), + }, + (data) => { + const row = locals[cdt][cdn]; + row.stock_qty = (frm.doc.quantity * data.percent) / 100; + row.qty = row.stock_qty / (row.conversion_factor || 1); + refresh_field("scrap_items"); + }, + __("Set Process Loss Item Quantity"), + __("Set Quantity") + ); +} diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 7e539183b0c..218ac64d8da 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -237,6 +237,7 @@ "options": "Price List" }, { + "depends_on": "with_operations", "fieldname": "operations_section", "fieldtype": "Section Break", "hide_border": 1, @@ -436,7 +437,7 @@ "description": "Item Image (if not slideshow)", "fieldname": "website_image", "fieldtype": "Attach Image", - "label": "Image" + "label": "Website Image" }, { "allow_on_submit": 1, @@ -539,7 +540,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-05-16 12:25:09.081968", + "modified": "2021-11-18 13:04:16.271975", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index eb1dfc8cae8..2cd8f8c15af 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1,23 +1,22 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from typing import List -from collections import deque -import frappe, erpnext -from frappe.utils import cint, cstr, flt, today -from frappe import _ -from erpnext.setup.utils import get_exchange_rate -from frappe.website.website_generator import WebsiteGenerator -from erpnext.stock.get_item_details import get_conversion_factor -from erpnext.stock.get_item_details import get_price_list_rate -from frappe.core.doctype.version.version import get_diff -from erpnext.controllers.queries import get_match_cond -from erpnext.stock.doctype.item.item import get_item_details -from frappe.model.mapper import get_mapped_doc - import functools - +from collections import deque from operator import itemgetter +from typing import List + +import frappe +from frappe import _ +from frappe.core.doctype.version.version import get_diff +from frappe.model.mapper import get_mapped_doc +from frappe.utils import cint, cstr, flt, today +from frappe.website.website_generator import WebsiteGenerator + +import erpnext +from erpnext.setup.utils import get_exchange_rate +from erpnext.stock.doctype.item.item import get_item_details +from erpnext.stock.get_item_details import get_conversion_factor, get_price_list_rate form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -156,6 +155,7 @@ class BOM(WebsiteGenerator): self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False) self.set_bom_level() + self.validate_scrap_items() def get_context(self, context): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] @@ -230,7 +230,7 @@ class BOM(WebsiteGenerator): } ret = self.get_bom_material_detail(args) for key, value in ret.items(): - if not item.get(key): + if item.get(key) is None: item.set(key, value) @frappe.whitelist() @@ -307,6 +307,9 @@ class BOM(WebsiteGenerator): existing_bom_cost = self.total_cost for d in self.get("items"): + if not d.item_code: + continue + rate = self.get_rm_rate({ "company": self.company, "item_code": d.item_code, @@ -446,25 +449,29 @@ class BOM(WebsiteGenerator): frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx)) check_list.append(m) - def check_recursion(self, bom_list=[]): + def check_recursion(self, bom_list=None): """ Check whether recursion occurs in any bom""" + def _throw_error(bom_name): + frappe.throw(_("BOM recursion: {0} cannot be parent or child of {0}").format(bom_name)) + bom_list = self.traverse_tree() - bom_nos = frappe.get_all('BOM Item', fields=["bom_no"], - filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) + child_items = frappe.get_all('BOM Item', fields=["bom_no", "item_code"], + filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) or [] - raise_exception = False - if bom_nos and self.name in [d.bom_no for d in bom_nos]: - raise_exception = True + child_bom = {d.bom_no for d in child_items} + child_items_codes = {d.item_code for d in child_items} - if not raise_exception: - bom_nos = frappe.get_all('BOM Item', fields=["parent"], - filters={'bom_no': self.name, 'parenttype': 'BOM'}) + if self.name in child_bom: + _throw_error(self.name) - if self.name in [d.parent for d in bom_nos]: - raise_exception = True + if self.item in child_items_codes: + _throw_error(self.item) - if raise_exception: - frappe.throw(_("BOM recursion: {0} cannot be parent or child of {1}").format(self.name, self.name)) + bom_nos = frappe.get_all('BOM Item', fields=["parent"], + filters={'bom_no': self.name, 'parenttype': 'BOM'}) or [] + + if self.name in {d.parent for d in bom_nos}: + _throw_error(self.name) def traverse_tree(self, bom_list=None): def _get_children(bom_no): @@ -506,27 +513,39 @@ class BOM(WebsiteGenerator): if d.workstation: self.update_rate_and_time(d, update_hour_rate) - self.operating_cost += flt(d.operating_cost) - self.base_operating_cost += flt(d.base_operating_cost) + operating_cost = d.operating_cost + base_operating_cost = d.base_operating_cost + if d.set_cost_based_on_bom_qty: + operating_cost = flt(d.cost_per_unit) * flt(self.quantity) + base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity) + + self.operating_cost += flt(operating_cost) + self.base_operating_cost += flt(base_operating_cost) def update_rate_and_time(self, row, update_hour_rate = False): if not row.hour_rate or update_hour_rate: hour_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate")) - row.hour_rate = (hour_rate / flt(self.conversion_rate) - if self.conversion_rate and hour_rate else hour_rate) + + if hour_rate: + row.hour_rate = (hour_rate / flt(self.conversion_rate) + if self.conversion_rate and hour_rate else hour_rate) if self.routing: - row.time_in_mins = flt(frappe.db.get_value("BOM Operation", { + time_in_mins = flt(frappe.db.get_value("BOM Operation", { "workstation": row.workstation, "operation": row.operation, - "sequence_id": row.sequence_id, "parent": self.routing }, ["time_in_mins"])) + if time_in_mins: + row.time_in_mins = time_in_mins + if row.hour_rate and row.time_in_mins: row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate) row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0 row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate) + row.cost_per_unit = row.operating_cost / (row.batch_size or 1.0) + row.base_cost_per_unit = row.base_operating_cost / (row.batch_size or 1.0) if update_hour_rate: row.db_update() @@ -583,7 +602,7 @@ class BOM(WebsiteGenerator): for d in self.get('items'): if d.bom_no: self.get_child_exploded_items(d.bom_no, d.stock_qty) - else: + elif d.item_code: self.add_to_cur_exploded_items(frappe._dict({ 'item_code' : d.item_code, 'item_name' : d.item_name, @@ -705,6 +724,32 @@ class BOM(WebsiteGenerator): if update: self.db_set("bom_level", self.bom_level) + def validate_scrap_items(self): + for item in self.scrap_items: + msg = "" + if item.item_code == self.item and not item.is_process_loss: + msg = _('Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked.') \ + .format(frappe.bold(item.item_code)) + elif item.item_code != self.item and item.is_process_loss: + msg = _('Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from the item to be manufactured or repacked') \ + .format(frappe.bold(item.item_code)) + + must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number") + if item.is_process_loss and must_be_whole_number: + msg = _("Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM.") \ + .format(frappe.bold(item.item_code), frappe.bold(item.stock_uom)) + + if item.is_process_loss and (item.stock_qty >= self.quantity): + msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.") \ + .format(frappe.bold(item.item_code)) + + if item.is_process_loss and (item.rate > 0): + msg = _("Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked.") \ + .format(frappe.bold(item.item_code)) + + if msg: + frappe.throw(msg, title=_("Note")) + def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1) @@ -822,8 +867,11 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True) elif fetch_scrap_items: - query = query.format(table="BOM Scrap Item", where_conditions="", - select_columns=", bom_item.idx, item.description", is_stock_item=is_stock_item, qty_field="stock_qty") + query = query.format( + table="BOM Scrap Item", where_conditions="", + select_columns=", bom_item.idx, item.description, is_process_loss", + is_stock_item=is_stock_item, qty_field="stock_qty" + ) items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True) else: diff --git a/erpnext/manufacturing/doctype/bom/bom_dashboard.py b/erpnext/manufacturing/doctype/bom/bom_dashboard.py index 361826e2d0c..0699f74aad9 100644 --- a/erpnext/manufacturing/doctype/bom/bom_dashboard.py +++ b/erpnext/manufacturing/doctype/bom/bom_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'bom_no', diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index e614a7ebaa1..eb4135e03ac 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -16,26 +16,15 @@

- {% if data.value %} - + {% if data.value && data.value != "BOM" %} + {{ __("Open BOM {0}", [data.value.bold()]) }} {% endif %} {% if data.item_code %} - + {{ __("Open Item {0}", [data.item_code.bold()]) }} {% endif %}

-
-

- {% if data.value %} - - {{ __("Open BOM {0}", [data.value.bold()]) }} - {% endif %} - {% if data.item_code %} - - {{ __("Open Item {0}", [data.item_code.bold()]) }} - {% endif %} -

diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js index 6e2599e41bc..fb99add12cf 100644 --- a/erpnext/manufacturing/doctype/bom/bom_tree.js +++ b/erpnext/manufacturing/doctype/bom/bom_tree.js @@ -66,6 +66,7 @@ frappe.treeview_settings["BOM"] = { var bom = frappe.model.get_doc("BOM", node.data.value); node.data.image = escape(bom.image) || ""; node.data.description = bom.description || ""; + node.data.item_code = bom.item || ""; }); } }, diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index c89f7d66fdf..4c032307d80 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -2,16 +2,21 @@ # License: GNU General Public License v3. See license.txt -from collections import deque import unittest +from collections import deque +from functools import partial + import frappe -from frappe.utils import cstr, flt from frappe.test_runner import make_test_records -from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation -from erpnext.manufacturing.doctype.bom.bom import make_variant_bom +from frappe.utils import cstr, flt + +from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.manufacturing.doctype.bom.bom import item_query, make_variant_bom from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.stock.doctype.item.test_item import make_item -from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_stock_reconciliation, +) from erpnext.tests.test_subcontracting import set_backflush_based_on test_records = frappe.get_test_records('BOM') @@ -104,6 +109,24 @@ class TestBOM(unittest.TestCase): self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) + def test_bom_cost_with_batch_size(self): + bom = frappe.copy_doc(test_records[2]) + bom.docstatus = 0 + op_cost = 0.0 + for op_row in bom.operations: + op_row.docstatus = 0 + op_row.batch_size = 2 + op_row.set_cost_based_on_bom_qty = 1 + op_cost += op_row.operating_cost + + bom.save() + + for op_row in bom.operations: + self.assertAlmostEqual(op_row.cost_per_unit, op_row.operating_cost / 2) + + self.assertAlmostEqual(bom.operating_cost, op_cost/2) + bom.delete() + def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)): @@ -280,13 +303,92 @@ class TestBOM(unittest.TestCase): self.assertEqual(reqd_item.qty, created_item.qty) self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty) + def test_bom_recursion_1st_level(self): + """BOM should not allow BOM item again in child""" + item_code = "_Test BOM Recursion" + make_item(item_code, {'is_stock_item': 1}) + + bom = frappe.new_doc("BOM") + bom.item = item_code + bom.append("items", frappe._dict(item_code=item_code)) + with self.assertRaises(frappe.ValidationError) as err: + bom.save() + + self.assertTrue("recursion" in str(err.exception).lower()) + frappe.delete_doc("BOM", bom.name, ignore_missing=True) + + def test_bom_recursion_transitive(self): + item1 = "_Test BOM Recursion" + item2 = "_Test BOM Recursion 2" + make_item(item1, {'is_stock_item': 1}) + make_item(item2, {'is_stock_item': 1}) + + bom1 = frappe.new_doc("BOM") + bom1.item = item1 + bom1.append("items", frappe._dict(item_code=item2)) + bom1.save() + bom1.submit() + + bom2 = frappe.new_doc("BOM") + bom2.item = item2 + bom2.append("items", frappe._dict(item_code=item1)) + + with self.assertRaises(frappe.ValidationError) as err: + bom2.save() + bom2.submit() + + self.assertTrue("recursion" in str(err.exception).lower()) + + bom1.cancel() + frappe.delete_doc("BOM", bom1.name, ignore_missing=True, force=True) + frappe.delete_doc("BOM", bom2.name, ignore_missing=True, force=True) + + def test_bom_with_process_loss_item(self): + fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items() + + if not frappe.db.exists("BOM", f"BOM-{fg_item_non_whole.item_code}-001"): + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, fg_qty=1 + ) + bom_doc.submit() + + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, scrap_qty=2, scrap_rate=0 + ) + # PL Item qty can't be >= FG Item qty + self.assertRaises(frappe.ValidationError, bom_doc.submit) + + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, scrap_qty=1, scrap_rate=100 + ) + # PL Item rate has to be 0 + self.assertRaises(frappe.ValidationError, bom_doc.submit) + + bom_doc = create_bom_with_process_loss_item( + fg_item_whole, bom_item, scrap_qty=0.25, scrap_rate=0 + ) + # Items with whole UOMs can't be PL Items + self.assertRaises(frappe.ValidationError, bom_doc.submit) + + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, is_process_loss=0 + ) + # FG Items in Scrap/Loss Table should have Is Process Loss set + self.assertRaises(frappe.ValidationError, bom_doc.submit) + + def test_bom_item_query(self): + query = partial(item_query, doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters={"is_stock_item": 1}) + + test_items = query(txt="_Test") + filtered = query(txt="_Test Item 2") + + self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results") + self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results") + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) - - - def level_order_traversal(node): traversal = [] q = deque() @@ -332,6 +434,7 @@ def create_nested_bom(tree, prefix="_Test bom "): bom = frappe.get_doc(doctype="BOM", item=bom_item_code) for child_item in child_items.keys(): bom.append("items", {"item_code": prefix + child_item}) + bom.currency = "INR" bom.insert() bom.submit() @@ -353,3 +456,45 @@ def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=Non for warehouse in warehouse_list: create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=qty, rate=rate) + +def create_bom_with_process_loss_item( + fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1): + bom_doc = frappe.new_doc("BOM") + bom_doc.item = fg_item.item_code + bom_doc.quantity = fg_qty + bom_doc.append("items", { + "item_code": bom_item.item_code, + "qty": 1, + "uom": bom_item.stock_uom, + "stock_uom": bom_item.stock_uom, + "rate": 100.0 + }) + bom_doc.append("scrap_items", { + "item_code": fg_item.item_code, + "qty": scrap_qty, + "stock_qty": scrap_qty, + "uom": fg_item.stock_uom, + "stock_uom": fg_item.stock_uom, + "rate": scrap_rate, + "is_process_loss": is_process_loss + }) + bom_doc.currency = "INR" + return bom_doc + +def create_process_loss_bom_items(): + item_list = [ + ("_Test Item - Non Whole UOM", "Kg"), + ("_Test Item - Whole UOM", "Unit"), + ("_Test PL BOM Item", "Unit") + ] + return [create_process_loss_bom_item(it) for it in item_list] + +def create_process_loss_bom_item(item_tuple): + item_code, stock_uom = item_tuple + if frappe.db.exists("Item", item_code) is None: + return make_item( + item_code, + {'stock_uom':stock_uom, 'valuation_rate':100} + ) + else: + return frappe.get_doc("Item", item_code) diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/__init__.py b/erpnext/manufacturing/doctype/bom_explosion_item/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/bom_explosion_item/__init__.py +++ b/erpnext/manufacturing/doctype/bom_explosion_item/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.py b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.py index 39ccbddbea2..cbcba345bca 100644 --- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.py +++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class BOMExplosionItem(Document): pass diff --git a/erpnext/manufacturing/doctype/bom_item/__init__.py b/erpnext/manufacturing/doctype/bom_item/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/bom_item/__init__.py +++ b/erpnext/manufacturing/doctype/bom_item/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.py b/erpnext/manufacturing/doctype/bom_item/bom_item.py index 220c73e1493..28a4b201444 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.py +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class BOMItem(Document): pass diff --git a/erpnext/manufacturing/doctype/bom_operation/__init__.py b/erpnext/manufacturing/doctype/bom_operation/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/bom_operation/__init__.py +++ b/erpnext/manufacturing/doctype/bom_operation/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 4458e6db234..ec617f3aaa9 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -8,15 +8,23 @@ "field_order": [ "sequence_id", "operation", - "workstation", - "description", "col_break1", - "hour_rate", + "workstation", "time_in_mins", - "operating_cost", + "costing_section", + "hour_rate", "base_hour_rate", + "column_break_9", + "operating_cost", "base_operating_cost", + "column_break_11", "batch_size", + "set_cost_based_on_bom_qty", + "cost_per_unit", + "base_cost_per_unit", + "more_information_section", + "description", + "column_break_18", "image" ], "fields": [ @@ -117,13 +125,59 @@ "fieldname": "sequence_id", "fieldtype": "Int", "label": "Sequence ID" + }, + { + "depends_on": "eval:doc.batch_size > 0 && doc.set_cost_based_on_bom_qty", + "fieldname": "cost_per_unit", + "fieldtype": "Float", + "label": "Cost Per Unit", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_cost_per_unit", + "fieldtype": "Float", + "hidden": 1, + "label": "Base Cost Per Unit", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "costing_section", + "fieldtype": "Section Break", + "label": "Costing" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "more_information_section", + "fieldtype": "Section Break", + "label": "More Information" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "set_cost_based_on_bom_qty", + "fieldtype": "Check", + "label": "Set Operating Cost Based On BOM Quantity" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-12 14:48:09.596843", + "modified": "2021-09-13 16:45:01.092868", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.py b/erpnext/manufacturing/doctype/bom_operation/bom_operation.py index e3501eb9cf6..0ddc280ac0e 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.py +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class BOMOperation(Document): pass diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json index 9f7091dd8d7..7018082e402 100644 --- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +++ b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json @@ -1,345 +1,112 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-09-26 02:19:21.642081", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-09-26 02:19:21.642081", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "column_break_2", + "item_name", + "is_process_loss", + "quantity_and_rate", + "stock_qty", + "rate", + "amount", + "column_break_6", + "stock_uom", + "base_rate", + "base_amount" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "quantity_and_rate", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quantity and Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "quantity_and_rate", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "stock_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "currency" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "options": "currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Basic Rate (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "base_rate", + "fieldtype": "Currency", + "label": "Basic Rate (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Basic Amount (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "base_amount", + "fieldtype": "Currency", + "label": "Basic Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_process_loss", + "fieldtype": "Check", + "label": "Is Process Loss" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-07-04 16:04:32.442287", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "BOM Scrap Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2021-06-22 16:46:12.153311", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "BOM Scrap Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.py b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.py index b6d423f09f2..f400303b95e 100644 --- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.py +++ b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class BOMScrapItem(Document): pass diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 8fbcd4ea1db..0e3955f5a73 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -1,15 +1,17 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, json -from frappe.utils import cstr, flt -from frappe import _ -from six import string_types -from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order -from frappe.model.document import Document + +import json + import click +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cstr, flt + +from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order + class BOMUpdateTool(Document): def replace_bom(self): @@ -76,7 +78,7 @@ def get_new_bom_unit_cost(bom): @frappe.whitelist() def enqueue_replace_bom(args): - if isinstance(args, string_types): + if isinstance(args, str): args = json.loads(args) frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.replace_bom", args=args, timeout=40000) diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js deleted file mode 100644 index d220df2824f..00000000000 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: BOM Update Tool", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('BOM Update Tool', [ - // insert a new BOM Update Tool - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index 80d1cdfc8f2..526c243ed78 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -2,12 +2,14 @@ # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import unittest + import frappe -from erpnext.stock.doctype.item.test_item import create_item -from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.stock.doctype.item.test_item import create_item test_records = frappe.get_test_records('BOM') diff --git a/erpnext/manufacturing/doctype/bom_website_item/bom_website_item.py b/erpnext/manufacturing/doctype/bom_website_item/bom_website_item.py index 4088a7fc540..33256a3b31d 100644 --- a/erpnext/manufacturing/doctype/bom_website_item/bom_website_item.py +++ b/erpnext/manufacturing/doctype/bom_website_item/bom_website_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class BOMWebsiteItem(Document): pass diff --git a/erpnext/manufacturing/doctype/bom_website_operation/bom_website_operation.py b/erpnext/manufacturing/doctype/bom_website_operation/bom_website_operation.py index bcc5ddab089..f8e279287a6 100644 --- a/erpnext/manufacturing/doctype/bom_website_operation/bom_website_operation.py +++ b/erpnext/manufacturing/doctype/bom_website_operation/bom_website_operation.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class BOMWebsiteOperation(Document): pass diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py index 56ec4356af4..460281658a8 100644 --- a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import time_diff_in_hours + from frappe.model.document import Document +from frappe.utils import time_diff_in_hours + class DowntimeEntry(Document): def validate(self): diff --git a/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py index 8b2a8d36c12..2f99a5cd112 100644 --- a/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py +++ b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestDowntimeEntry(unittest.TestCase): pass diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 91eb4a0fa90..453ad50e8e6 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -26,15 +26,28 @@ frappe.ui.form.on('Job Card', { refresh: function(frm) { frappe.flags.pause_job = 0; frappe.flags.resume_job = 0; + let has_items = frm.doc.items && frm.doc.items.length; - if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) { - if (frm.doc.for_quantity != frm.doc.transferred_qty) { + if (frm.doc.__onload.work_order_closed) { + frm.disable_save(); + return; + } + + if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) { + let to_request = frm.doc.for_quantity > frm.doc.transferred_qty; + let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; + + if (to_request || excess_transfer_allowed) { frm.add_custom_button(__("Material Request"), () => { frm.trigger("make_material_request"); }); } - if (frm.doc.for_quantity != frm.doc.transferred_qty) { + // check if any row has untransferred materials + // in case of multiple items in JC + let to_transfer = frm.doc.items.some((row) => row.transferred_qty < row.required_qty); + + if (to_transfer || excess_transfer_allowed) { frm.add_custom_button(__("Material Transfer"), () => { frm.trigger("make_stock_entry"); }).addClass("btn-primary"); diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 046e2fd1825..6528199d82a 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -38,6 +38,8 @@ "total_time_in_mins", "section_break_8", "items", + "scrap_items_section", + "scrap_items", "corrective_operation_section", "for_job_card", "is_corrective_job_card", @@ -185,7 +187,7 @@ "default": "0", "fieldname": "transferred_qty", "fieldtype": "Float", - "label": "Transferred Qty", + "label": "FG Qty from Transferred Raw Materials", "read_only": 1 }, { @@ -392,14 +394,29 @@ "fieldtype": "Link", "label": "Batch No", "options": "Batch" + }, + { + "collapsible": 1, + "fieldname": "scrap_items_section", + "fieldtype": "Section Break", + "label": "Scrap Items" + }, + { + "fieldname": "scrap_items", + "fieldtype": "Table", + "label": "Scrap Items", + "no_copy": 1, + "options": "Job Card Scrap Item", + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2021-03-16 15:59:32.766484", + "modified": "2021-11-12 10:15:03.572401", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 3efbe88adaf..8d00019b7d6 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -1,18 +1,30 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - -from __future__ import unicode_literals -import frappe import datetime import json -from frappe import _, bold -from frappe.model.mapper import get_mapped_doc -from frappe.model.document import Document -from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate, - get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form, time_diff_in_seconds) -from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations +import frappe +from frappe import _, bold +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import ( + add_days, + add_to_date, + cint, + flt, + get_datetime, + get_link_to_form, + get_time, + getdate, + time_diff, + time_diff_in_hours, + time_diff_in_seconds, +) + +from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import ( + get_mins_between_operations, +) + class OverlapError(frappe.ValidationError): pass @@ -21,6 +33,11 @@ class OperationSequenceError(frappe.ValidationError): pass class JobCardCancelError(frappe.ValidationError): pass class JobCard(Document): + def onload(self): + excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") + self.set_onload("job_card_excess_transfer", excess_transfer) + self.set_onload("work_order_closed", self.is_work_order_closed()) + def validate(self): self.validate_time_logs() self.set_status() @@ -28,6 +45,7 @@ class JobCard(Document): self.validate_sequence_id() self.set_sub_operations() self.update_sub_operation_status() + self.validate_work_order() def set_sub_operations(self): if self.operation: @@ -75,7 +93,7 @@ class JobCard(Document): if args.get("employee"): # override capacity for employee production_capacity = 1 - validate_overlap_for = " and jc.employee = %(employee)s " + validate_overlap_for = " and jctl.employee = %(employee)s " extra_cond = '' if check_next_available_slot: @@ -433,6 +451,7 @@ class JobCard(Document): frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty)) def set_transferred_qty(self, update_status=False): + "Set total FG Qty for which RM was transferred." if not self.items: self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0 @@ -441,6 +460,7 @@ class JobCard(Document): return if self.items: + # sum of 'For Quantity' of Stock Entries against JC self.transferred_qty = frappe.db.get_value('Stock Entry', { 'job_card': self.name, 'work_order': self.work_order, @@ -484,11 +504,11 @@ class JobCard(Document): self.status = 'Work In Progress' if (self.docstatus == 1 and - (self.for_quantity == self.transferred_qty or not self.items)): + (self.for_quantity <= self.total_completed_qty or not self.items)): self.status = 'Completed' if self.status != 'Completed': - if self.for_quantity == self.transferred_qty: + if self.for_quantity <= self.transferred_qty: self.status = 'Material Transferred' if update_status: @@ -528,6 +548,18 @@ class JobCard(Document): frappe.throw(_("{0}, complete the operation {1} before the operation {2}.") .format(message, bold(row.operation), bold(self.operation)), OperationSequenceError) + def validate_work_order(self): + if self.is_work_order_closed(): + frappe.throw(_("You can't make any changes to Job Card since Work Order is closed.")) + + def is_work_order_closed(self): + if self.work_order: + status = frappe.get_value('Work Order', self.work_order) + + if status == "Closed": + return True + + return False @frappe.whitelist() def make_time_log(args): @@ -584,7 +616,8 @@ def make_material_request(source_name, target_doc=None): "doctype": "Material Request Item", "field_map": { "required_qty": "qty", - "uom": "stock_uom" + "uom": "stock_uom", + "name": "job_card_item" }, "postprocess": update_item, } @@ -594,15 +627,24 @@ def make_material_request(source_name, target_doc=None): @frappe.whitelist() def make_stock_entry(source_name, target_doc=None): - def update_item(obj, target, source_parent): + def update_item(source, target, source_parent): target.t_warehouse = source_parent.wip_warehouse + if not target.conversion_factor: target.conversion_factor = 1 + pending_rm_qty = flt(source.required_qty) - flt(source.transferred_qty) + if pending_rm_qty > 0: + target.qty = pending_rm_qty + def set_missing_values(source, target): target.purpose = "Material Transfer for Manufacture" target.from_bom = 1 - target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0) + + # avoid negative 'For Quantity' + pending_fg_qty = flt(source.get('for_quantity', 0)) - flt(source.get('transferred_qty', 0)) + target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0 + target.set_transfer_qty() target.calculate_rate_and_amount() target.set_missing_values() @@ -652,7 +694,7 @@ def get_job_details(start, end, filters=None): conditions = get_filters_cond("Job Card", filters, []) job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order, - `tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''), + `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''), min(`tabJob Card Time Log`.from_time) as from_time, max(`tabJob Card Time Log`.to_time) as to_time FROM `tabJob Card` , `tabJob Card Time Log` @@ -662,7 +704,7 @@ def get_job_details(start, end, filters=None): for d in job_cards: subject_data = [] - for field in ["name", "work_order", "remarks", "employee_name"]: + for field in ["name", "work_order", "remarks"]: if not d.get(field): continue subject_data.append(d.get(field)) diff --git a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py index c2aa2bd968a..2c488721b0c 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py +++ b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py @@ -1,13 +1,20 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'job_card', + 'non_standard_fieldnames': { + 'Quality Inspection': 'reference_name' + }, 'transactions': [ { 'label': _('Transactions'), 'items': ['Material Request', 'Stock Entry'] + }, + { + 'label': _('Reference'), + 'items': ['Quality Inspection'] } ] } diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.js b/erpnext/manufacturing/doctype/job_card/test_job_card.js deleted file mode 100644 index 5dc7805d226..00000000000 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Job Card", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Job Card - () => frappe.tests.make('Job Card', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 8fa0b27fcb8..9b4fc8b8b7a 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -1,75 +1,332 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import unittest + import frappe from frappe.utils import random_string -from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation + +from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError +from erpnext.manufacturing.doctype.job_card.job_card import ( + make_stock_entry as make_stock_entry_from_jc, +) from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record -from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError +from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + class TestJobCard(unittest.TestCase): + def setUp(self): + make_bom_for_jc_tests() + + transfer_material_against, source_warehouse = None, None + + tests_that_skip_setup = ( + "test_job_card_material_transfer_correctness", + ) + tests_that_transfer_against_jc = ( + "test_job_card_multiple_materials_transfer", + "test_job_card_excess_material_transfer", + "test_job_card_partial_material_transfer" + ) + + if self._testMethodName in tests_that_skip_setup: + return + + if self._testMethodName in tests_that_transfer_against_jc: + transfer_material_against = "Job Card" + source_warehouse = "Stores - _TC" + + self.work_order = make_wo_order_test_record( + item="_Test FG Item 2", + qty=2, + transfer_material_against=transfer_material_against, + source_warehouse=source_warehouse + ) + + def tearDown(self): + frappe.db.rollback() + def test_job_card(self): - data = frappe.get_cached_value('BOM', - {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) - if data: - bom, bom_item = data + job_cards = frappe.get_all('Job Card', + filters = {'work_order': self.work_order.name}, fields = ["operation_id", "name"]) - work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + if job_cards: + job_card = job_cards[0] + frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id) - job_cards = frappe.get_all('Job Card', - filters = {'work_order': work_order.name}, fields = ["operation_id", "name"]) + doc = frappe.get_doc("Job Card", job_card.name) + doc.operation_id = "Test Data" + self.assertRaises(OperationMismatchError, doc.save) - if job_cards: - job_card = job_cards[0] - frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id) - - doc = frappe.get_doc("Job Card", job_card.name) - doc.operation_id = "Test Data" - self.assertRaises(OperationMismatchError, doc.save) - - for d in job_cards: - frappe.delete_doc("Job Card", d.name) + for d in job_cards: + frappe.delete_doc("Job Card", d.name) def test_job_card_with_different_work_station(self): - data = frappe.get_cached_value('BOM', - {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) + job_cards = frappe.get_all('Job Card', + filters = {'work_order': self.work_order.name}, + fields = ["operation_id", "workstation", "name", "for_quantity"]) - if data: - bom, bom_item = data + job_card = job_cards[0] - work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + if job_card: + workstation = frappe.db.get_value("Workstation", + {"name": ("not in", [job_card.workstation])}, "name") - job_cards = frappe.get_all('Job Card', - filters = {'work_order': work_order.name}, - fields = ["operation_id", "workstation", "name", "for_quantity"]) + if not workstation or job_card.workstation == workstation: + workstation = make_workstation(workstation_name=random_string(5)).name - job_card = job_cards[0] + doc = frappe.get_doc("Job Card", job_card.name) + doc.workstation = workstation + doc.append("time_logs", { + "from_time": "2009-01-01 12:06:25", + "to_time": "2009-01-01 12:37:25", + "time_in_mins": "31.00002", + "completed_qty": job_card.for_quantity + }) + doc.submit() - if job_card: - workstation = frappe.db.get_value("Workstation", - {"name": ("not in", [job_card.workstation])}, "name") + completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty") + self.assertEqual(completed_qty, job_card.for_quantity) - if not workstation or job_card.workstation == workstation: - workstation = make_workstation(workstation_name=random_string(5)).name - - doc = frappe.get_doc("Job Card", job_card.name) - doc.workstation = workstation - doc.append("time_logs", { - "from_time": "2009-01-01 12:06:25", - "to_time": "2009-01-01 12:37:25", - "time_in_mins": "31.00002", - "completed_qty": job_card.for_quantity - }) - doc.submit() - - completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty") - self.assertEqual(completed_qty, job_card.for_quantity) - - doc.cancel() + doc.cancel() for d in job_cards: frappe.delete_doc("Job Card", d.name) + + def test_job_card_overlap(self): + wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + + jc1_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + jc2_name = frappe.db.get_value("Job Card", {'work_order': wo2.name}) + + jc1 = frappe.get_doc("Job Card", jc1_name) + jc2 = frappe.get_doc("Job Card", jc2_name) + + employee = "_T-Employee-00001" # from test records + + jc1.append("time_logs", { + "from_time": "2021-01-01 00:00:00", + "to_time": "2021-01-01 08:00:00", + "completed_qty": 1, + "employee": employee, + }) + jc1.save() + + # add a new entry in same time slice + jc2.append("time_logs", { + "from_time": "2021-01-01 00:01:00", + "to_time": "2021-01-01 06:00:00", + "completed_qty": 1, + "employee": employee, + }) + self.assertRaises(OverlapError, jc2.save) + + def test_job_card_multiple_materials_transfer(self): + "Test transferring RMs separately against Job Card with multiple RMs." + make_stock_entry( + item_code="_Test Item", + target="Stores - _TC", + qty=10, + basic_rate=100 + ) + make_stock_entry( + item_code="_Test Item Home Desktop Manufactured", + target="Stores - _TC", + qty=6, + basic_rate=100 + ) + + job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + job_card = frappe.get_doc("Job Card", job_card_name) + + transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + del transfer_entry_1.items[1] # transfer only 1 of 2 RMs + transfer_entry_1.insert() + transfer_entry_1.submit() + + job_card.reload() + + self.assertEqual(transfer_entry_1.fg_completed_qty, 2) + self.assertEqual(job_card.transferred_qty, 2) + + # transfer second RM + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + del transfer_entry_2.items[0] + transfer_entry_2.insert() + transfer_entry_2.submit() + + # 'For Quantity' here will be 0 since + # transfer was made for 2 fg qty in first transfer Stock Entry + self.assertEqual(transfer_entry_2.fg_completed_qty, 0) + + def test_job_card_excess_material_transfer(self): + "Test transferring more than required RM against Job Card." + make_stock_entry(item_code="_Test Item", target="Stores - _TC", + qty=25, basic_rate=100) + make_stock_entry(item_code="_Test Item Home Desktop Manufactured", + target="Stores - _TC", qty=15, basic_rate=100) + + job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + job_card = frappe.get_doc("Job Card", job_card_name) + + # fully transfer both RMs + transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + transfer_entry_1.insert() + transfer_entry_1.submit() + + # transfer extra qty of both RM due to previously damaged RM + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + # deliberately change 'For Quantity' + transfer_entry_2.fg_completed_qty = 1 + transfer_entry_2.items[0].qty = 5 + transfer_entry_2.items[1].qty = 3 + transfer_entry_2.insert() + transfer_entry_2.submit() + + job_card.reload() + self.assertGreater(job_card.transferred_qty, job_card.for_quantity) + + # Check if 'For Quantity' is negative + # as 'transferred_qty' > Qty to Manufacture + transfer_entry_3 = make_stock_entry_from_jc(job_card_name) + self.assertEqual(transfer_entry_3.fg_completed_qty, 0) + + job_card.append("time_logs", { + "from_time": "2021-01-01 00:01:00", + "to_time": "2021-01-01 06:00:00", + "completed_qty": 2 + }) + job_card.save() + job_card.submit() + + # JC is Completed with excess transfer + self.assertEqual(job_card.status, "Completed") + + def test_job_card_partial_material_transfer(self): + "Test partial material transfer against Job Card" + + make_stock_entry(item_code="_Test Item", target="Stores - _TC", + qty=25, basic_rate=100) + make_stock_entry(item_code="_Test Item Home Desktop Manufactured", + target="Stores - _TC", qty=15, basic_rate=100) + + job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + job_card = frappe.get_doc("Job Card", job_card_name) + + # partially transfer + transfer_entry = make_stock_entry_from_jc(job_card_name) + transfer_entry.fg_completed_qty = 1 + transfer_entry.get_items() + transfer_entry.insert() + transfer_entry.submit() + + job_card.reload() + self.assertEqual(job_card.transferred_qty, 1) + self.assertEqual(transfer_entry.items[0].qty, 5) + self.assertEqual(transfer_entry.items[1].qty, 3) + + # transfer remaining + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + + self.assertEqual(transfer_entry_2.fg_completed_qty, 1) + self.assertEqual(transfer_entry_2.items[0].qty, 5) + self.assertEqual(transfer_entry_2.items[1].qty, 3) + + transfer_entry_2.insert() + transfer_entry_2.submit() + + job_card.reload() + self.assertEqual(job_card.transferred_qty, 2) + + def test_job_card_material_transfer_correctness(self): + """ + 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card + 2. Test impact of changing 'For Qty' in such a Stock Entry + """ + create_bom_with_multiple_operations() + work_order = make_wo_with_transfer_against_jc() + + job_card_name = frappe.db.get_value( + "Job Card", + {"work_order": work_order.name,"operation": "Test Operation A"} + ) + job_card = frappe.get_doc("Job Card", job_card_name) + + self.assertEqual(len(job_card.items), 1) + self.assertEqual(job_card.items[0].item_code, "_Test Item") + + # check if right items are mapped in transfer entry + transfer_entry = make_stock_entry_from_jc(job_card_name) + transfer_entry.insert() + + self.assertEqual(len(transfer_entry.items), 1) + self.assertEqual(transfer_entry.items[0].item_code, "_Test Item") + self.assertEqual(transfer_entry.items[0].qty, 4) + + # change 'For Qty' and check impact on items table + # no.of items should be the same with qty change + transfer_entry.fg_completed_qty = 2 + transfer_entry.get_items() + + self.assertEqual(len(transfer_entry.items), 1) + self.assertEqual(transfer_entry.items[0].item_code, "_Test Item") + self.assertEqual(transfer_entry.items[0].qty, 2) + + # rollback via tearDown method + +def create_bom_with_multiple_operations(): + "Create a BOM with multiple operations and Material Transfer against Job Card" + from erpnext.manufacturing.doctype.operation.test_operation import make_operation + + test_record = frappe.get_test_records("BOM")[2] + bom_doc = frappe.get_doc(test_record) + + row = { + "operation": "Test Operation A", + "workstation": "_Test Workstation A", + "hour_rate_rent": 300, + "time_in_mins": 60 + } + make_workstation(row) + make_operation(row) + + bom_doc.append("operations", { + "operation": "Test Operation A", + "description": "Test Operation A", + "workstation": "_Test Workstation A", + "hour_rate": 300, + "time_in_mins": 60, + "operating_cost": 100 + }) + + bom_doc.transfer_material_against = "Job Card" + bom_doc.save() + bom_doc.submit() + + return bom_doc + +def make_wo_with_transfer_against_jc(): + "Create a WO with multiple operations and Material Transfer against Job Card" + + work_order = make_wo_order_test_record( + item="_Test FG Item 2", + qty=4, + transfer_material_against="Job Card", + source_warehouse="Stores - _TC", + do_not_submit=True + ) + work_order.required_items[0].operation = "Test Operation A" + work_order.required_items[1].operation = "_Test Operation 1" + work_order.submit() + + return work_order + +def make_bom_for_jc_tests(): + test_records = frappe.get_test_records('BOM') + bom = frappe.copy_doc(test_records[2]) + bom.set_rate_of_sub_assembly_item_based_on_bom = 0 + bom.rm_cost_as_per = "Valuation Rate" + bom.items[0].uom = "_Test UOM 1" + bom.items[0].conversion_factor = 5 + bom.insert() \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.py b/erpnext/manufacturing/doctype/job_card_item/job_card_item.py index 373cba293e3..51a7b41f0d9 100644 --- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.py +++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class JobCardItem(Document): pass diff --git a/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py index 85d72982ed3..de44071c6ad 100644 --- a/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py +++ b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class JobCardOperation(Document): pass diff --git a/erpnext/healthcare/doctype/healthcare_schedule_time_slot/__init__.py b/erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/healthcare_schedule_time_slot/__init__.py rename to erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json new file mode 100644 index 00000000000..9e9f1c4c89f --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json @@ -0,0 +1,82 @@ +{ + "actions": [], + "creation": "2021-09-14 00:30:28.533884", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "column_break_3", + "description", + "quantity_and_rate", + "stock_qty", + "column_break_6", + "stock_uom" + ], + "fields": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Scrap Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Scrap Item Name" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "item_code.description", + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "read_only": 1 + }, + { + "fieldname": "quantity_and_rate", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, + { + "fieldname": "stock_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "reqd": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fetch_from": "item_code.stock_uom", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-14 01:20:48.588052", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Scrap Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py new file mode 100644 index 00000000000..372df1b0fad --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from frappe.model.document import Document + + +class JobCardScrapItem(Document): + pass diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py index 3dc66891216..2b3ead383a3 100644 --- a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class JobCardTimeLog(Document): pass diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 024f7847259..01647d56c91 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -25,9 +25,12 @@ "overproduction_percentage_for_sales_order", "column_break_16", "overproduction_percentage_for_work_order", + "job_card_section", + "add_corrective_operation_cost_in_finished_good_valuation", + "column_break_24", + "job_card_excess_transfer", "other_settings_section", "update_bom_costs_automatically", - "add_corrective_operation_cost_in_finished_good_valuation", "column_break_23", "make_serial_no_batch_from_work_order" ], @@ -96,10 +99,10 @@ }, { "default": "0", - "description": "Allow multiple material consumptions against a Work Order", + "description": "Allow material consumptions without immediately manufacturing finished goods against a Work Order", "fieldname": "material_consumption", "fieldtype": "Check", - "label": "Allow Multiple Material Consumption" + "label": "Allow Continuous Material Consumption" }, { "default": "0", @@ -175,13 +178,29 @@ "fieldname": "add_corrective_operation_cost_in_finished_good_valuation", "fieldtype": "Check", "label": "Add Corrective Operation Cost in Finished Good Valuation" + }, + { + "fieldname": "job_card_section", + "fieldtype": "Section Break", + "label": "Job Card" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Allow transferring raw materials even after the Required Quantity is fulfilled", + "fieldname": "job_card_excess_transfer", + "fieldtype": "Check", + "label": "Allow Excess Material Transfer" } ], "icon": "icon-wrench", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-16 15:54:38.967341", + "modified": "2021-09-13 22:09:09.401559", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py index 149fe3e22b8..c919e8bef1d 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py @@ -1,11 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe +from dateutil.relativedelta import relativedelta from frappe.model.document import Document from frappe.utils import cint -from dateutil.relativedelta import relativedelta + class ManufacturingSettings(Document): pass diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js deleted file mode 100644 index 2b2589eddd8..00000000000 --- a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Manufacturing Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Manufacturing Settings', [ - // insert a new Manufacturing Settings - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.py index 7391f65dec9..1b2f18fc7ba 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.py +++ b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestManufacturingSettings(unittest.TestCase): pass diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 6c60bbde86c..27d7c4175e5 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -6,17 +6,17 @@ "engine": "InnoDB", "field_order": [ "item_code", - "item_name", - "material_request_type", "from_warehouse", "warehouse", - "column_break_4", + "item_name", + "material_request_type", + "actual_qty", + "ordered_qty", "required_bom_qty", + "column_break_4", "quantity", "uom", "projected_qty", - "actual_qty", - "ordered_qty", "reserved_qty_for_production", "safety_stock", "item_details", @@ -28,6 +28,7 @@ ], "fields": [ { + "columns": 2, "fieldname": "item_code", "fieldtype": "Link", "in_list_view": 1, @@ -41,6 +42,7 @@ "label": "Item Name" }, { + "columns": 2, "fieldname": "warehouse", "fieldtype": "Link", "in_list_view": 1, @@ -50,10 +52,11 @@ "reqd": 1 }, { + "columns": 1, "fieldname": "material_request_type", "fieldtype": "Select", "in_list_view": 1, - "label": "Material Request Type", + "label": "Type", "options": "\nPurchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided" }, { @@ -61,10 +64,11 @@ "fieldtype": "Column Break" }, { + "columns": 1, "fieldname": "quantity", "fieldtype": "Float", "in_list_view": 1, - "label": "Required Quantity", + "label": "Plan to Request Qty", "no_copy": 1, "reqd": 1 }, @@ -75,11 +79,12 @@ "read_only": 1 }, { + "columns": 2, "default": "0", "fieldname": "actual_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Actual Qty", + "label": "Available Qty", "no_copy": 1, "read_only": 1 }, @@ -157,16 +162,18 @@ "read_only": 1 }, { + "columns": 2, "fieldname": "required_bom_qty", "fieldtype": "Float", - "label": "Required Qty as per BOM", + "in_list_view": 1, + "label": "Qty As Per BOM", "no_copy": 1, "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-03-26 12:41:13.013149", + "modified": "2021-08-23 18:17:58.400462", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.py b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.py index 73e369c149e..3d5a7ce3fe0 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.py +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class MaterialRequestPlanItem(Document): pass diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js b/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js deleted file mode 100644 index 14c6e393847..00000000000 --- a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Material Request Plan Item", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Material Request Plan Item - () => frappe.tests.make('Material Request Plan Item', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.py b/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.py index dc43b69ef24..0654c1ebbbc 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.py +++ b/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestMaterialRequestPlanItem(unittest.TestCase): pass diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js index 2936e33b118..ea73fd6e273 100644 --- a/erpnext/manufacturing/doctype/operation/operation.js +++ b/erpnext/manufacturing/doctype/operation/operation.js @@ -12,3 +12,21 @@ frappe.ui.form.on('Operation', { }); } }); + +frappe.tour['Operation'] = [ + { + fieldname: "__newname", + title: "Operation Name", + description: __("Enter a name for the Operation, for example, Cutting.") + }, + { + fieldname: "workstation", + title: "Default Workstation", + description: __("Select the Default Workstation where the Operation will be performed. This will be fetched in BOMs and Work Orders.") + }, + { + fieldname: "sub_operations", + title: "Sub Operations", + description: __("If an operation is divided into sub operations, they can be added here.") + } +]; diff --git a/erpnext/manufacturing/doctype/operation/operation.py b/erpnext/manufacturing/doctype/operation/operation.py index 374f32019bd..41726f30cf5 100644 --- a/erpnext/manufacturing/doctype/operation/operation.py +++ b/erpnext/manufacturing/doctype/operation/operation.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document + class Operation(Document): def validate(self): if not self.description: diff --git a/erpnext/manufacturing/doctype/operation/operation_dashboard.py b/erpnext/manufacturing/doctype/operation/operation_dashboard.py index 8deb9ec6e0b..4fbcf4954ec 100644 --- a/erpnext/manufacturing/doctype/operation/operation_dashboard.py +++ b/erpnext/manufacturing/doctype/operation/operation_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'operation', diff --git a/erpnext/manufacturing/doctype/operation/test_operation.py b/erpnext/manufacturing/doctype/operation/test_operation.py index 8e7e7237263..e511084e7d0 100644 --- a/erpnext/manufacturing/doctype/operation/test_operation.py +++ b/erpnext/manufacturing/doctype/operation/test_operation.py @@ -1,9 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest test_records = frappe.get_test_records('Operation') @@ -17,15 +17,13 @@ def make_operation(*args, **kwargs): args = frappe._dict(args) - try: + if not frappe.db.exists("Operation", args.operation): doc = frappe.get_doc({ "doctype": "Operation", "name": args.operation, "workstation": args.workstation }) - doc.insert() - return doc - except frappe.DuplicateEntryError: - return frappe.get_doc("Operation", args.operation) + + return frappe.get_doc("Operation", args.operation) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index d198a6962a5..0babf875e75 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -105,7 +105,7 @@ frappe.ui.form.on('Production Plan', { } frm.trigger("material_requirement"); - const projected_qty_formula = ` + const projected_qty_formula = `

@@ -238,10 +238,18 @@ frappe.ui.form.on('Production Plan', { method: "get_items", freeze: true, doc: frm.doc, + callback: function() { + frm.refresh_field("po_items"); + if (frm.doc.sub_assembly_items.length > 0) { + frm.trigger("get_sub_assembly_items"); + } + } }); }, get_sub_assembly_items: function(frm) { + frm.dirty(); + frappe.call({ method: "get_sub_assembly_items", freeze: true, @@ -254,7 +262,7 @@ frappe.ui.form.on('Production Plan', { get_items_for_mr: function(frm) { if (!frm.doc.for_warehouse) { - frappe.throw(__("Select warehouse for material requests")); + frappe.throw(__("To make material requests, 'Make Material Request for Warehouse' field is mandatory")); } if (frm.doc.ignore_existing_ordered_qty) { @@ -265,9 +273,18 @@ frappe.ui.form.on('Production Plan', { title: title, fields: [ { - "fieldtype": "Table MultiSelect", "label": __("Source Warehouses (Optional)"), - "fieldname": "warehouses", "options": "Production Plan Material Request Warehouse", - "description": __("System will pickup the materials from the selected warehouses. If not specified, system will create material request for purchase."), + 'label': __('Target Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'target_warehouse', + 'read_only': true, + 'default': frm.doc.for_warehouse + }, + { + 'label': __('Source Warehouses (Optional)'), + 'fieldtype': 'Table MultiSelect', + 'fieldname': 'warehouses', + 'options': 'Production Plan Material Request Warehouse', + 'description': __('If source warehouse selected then system will create the material request with type Material Transfer from Source to Target warehouse. If not selected then will create the material request with type Purchase for the target warehouse.'), get_query: function () { return { filters: { @@ -342,7 +359,11 @@ frappe.ui.form.on('Production Plan', { frappe.prompt(fields, (row) => { let get_template_url = 'erpnext.manufacturing.doctype.production_plan.production_plan.download_raw_materials'; - open_url_post(frappe.request.url, { cmd: get_template_url, doc: frm.doc, warehouses: row.warehouses }); + open_url_post(frappe.request.url, { + cmd: get_template_url, + doc: frm.doc, + warehouses: row.warehouses + }); }, __('Select Warehouses to get Stock for Materials Planning'), __('Get Stock')); }, @@ -421,6 +442,25 @@ frappe.ui.form.on("Material Request Plan Item", { } }); +frappe.ui.form.on("Production Plan Sales Order", { + sales_order(frm, cdt, cdn) { + const { sales_order } = locals[cdt][cdn]; + if (!sales_order) { + return; + } + frappe.call({ + method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details", + args: { sales_order }, + callback(r) { + const {transaction_date, customer, grand_total} = r.message; + frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date); + frappe.model.set_value(cdt, cdn, 'customer', customer); + frappe.model.set_value(cdt, cdn, 'grand_total', grand_total); + } + }); + } +}); + cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = function() { return{ filters: [ @@ -428,3 +468,36 @@ cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = fu ] } }; + +frappe.tour['Production Plan'] = [ + { + fieldname: "get_items_from", + title: "Get Items From", + description: __("Select whether to get items from a Sales Order or a Material Request. For now select Sales Order.\n A Production Plan can also be created manually where you can select the Items to manufacture.") + }, + { + fieldname: "get_sales_orders", + title: "Get Sales Orders", + description: __("Click on Get Sales Orders to fetch sales orders based on the above filters.") + }, + { + fieldname: "get_items", + title: "Get Finished Goods for Manufacture", + description: __("Click on 'Get Finished Goods for Manufacture' to fetch the items from the above Sales Orders. Items only for which a BOM is present will be fetched.") + }, + { + fieldname: "po_items", + title: "Finished Goods", + description: __("On expanding a row in the Items to Manufacture table, you'll see an option to 'Include Exploded Items'. Ticking this includes raw materials of the sub-assembly items in the production process.") + }, + { + fieldname: "include_non_stock_items", + title: "Include Non Stock Items", + description: __("To include non-stock items in the material request planning. i.e. Items for which 'Maintain Stock' checkbox is unticked.") + }, + { + fieldname: "include_subcontracted_items", + title: "Include Subcontracted Items", + description: __("To add subcontracted Item's raw materials if include exploded items is disabled.") + } +]; diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 84378956c61..56cf2b4f08a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -16,10 +16,12 @@ "customer", "warehouse", "project", + "sales_order_status", "column_break2", "from_date", "to_date", - "sales_order_status", + "from_delivery_date", + "to_delivery_date", "sales_orders_detail", "get_sales_orders", "sales_orders", @@ -300,7 +302,7 @@ { "fieldname": "for_warehouse", "fieldtype": "Link", - "label": "Material Request Warehouse", + "label": "Make Material Request for Warehouse", "options": "Warehouse" }, { @@ -358,13 +360,23 @@ "fieldname": "get_sub_assembly_items", "fieldtype": "Button", "label": "Get Sub Assembly Items" + }, + { + "fieldname": "from_delivery_date", + "fieldtype": "Date", + "label": "From Delivery Date" + }, + { + "fieldname": "to_delivery_date", + "fieldtype": "Date", + "label": "To Delivery Date" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-28 20:00:33.905114", + "modified": "2021-09-06 18:35:59.642232", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index b4c663507ce..7cec7f515a3 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1,20 +1,31 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, json, copy -from frappe import msgprint, _ -from six import iteritems +import copy +import json + +import frappe +from frappe import _, msgprint from frappe.model.document import Document -from frappe.utils import (flt, cint, nowdate, add_days, comma_and, now_datetime, - ceil, get_link_to_form, getdate) +from frappe.utils import ( + add_days, + ceil, + cint, + comma_and, + flt, + get_link_to_form, + getdate, + now_datetime, + nowdate, +) from frappe.utils.csvutils import build_csv_response -from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_children + +from erpnext.manufacturing.doctype.bom.bom import get_children, validate_bom_no from erpnext.manufacturing.doctype.work_order.work_order import get_item_details from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults + class ProductionPlan(Document): def validate(self): self.calculate_total_planned_qty() @@ -213,7 +224,6 @@ class ProductionPlan(Document): }) pi = self.append('po_items', { - 'include_exploded_items': 1, 'warehouse': data.warehouse, 'item_code': data.item_code, 'description': data.description or item_details.description, @@ -224,6 +234,7 @@ class ProductionPlan(Document): 'planned_start_date': now_datetime(), 'product_bundle_item': data.parent_item }) + pi._set_defaults() if self.get_items_from == "Sales Order": pi.sales_order = data.parent @@ -297,7 +308,7 @@ class ProductionPlan(Document): if self.total_produced_qty > 0: self.status = "In Process" - if self.total_produced_qty >= self.total_planned_qty: + if self.check_have_work_orders_completed(): self.status = "Completed" if self.status != 'Completed': @@ -331,7 +342,7 @@ class ProductionPlan(Document): def get_production_items(self): item_dict = {} for d in self.po_items: - item_details= { + item_details = { "production_item" : d.item_code, "use_multi_level_bom" : d.include_exploded_items, "sales_order" : d.sales_order, @@ -346,8 +357,7 @@ class ProductionPlan(Document): "production_plan" : self.name, "production_plan_item" : d.name, "product_bundle_item" : d.product_bundle_item, - "planned_start_date" : d.planned_start_date, - "make_work_order_for_sub_assembly_items": d.get("make_work_order_for_sub_assembly_items", 0) + "planned_start_date" : d.planned_start_date } item_details.update({ @@ -411,7 +421,7 @@ class ProductionPlan(Document): po = frappe.new_doc('Purchase Order') po.supplier = supplier po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate() - po.is_subcontracted_item = 'Yes' + po.is_subcontracted = 'Yes' for row in po_list: args = { 'item_code': row.production_item, @@ -444,7 +454,8 @@ class ProductionPlan(Document): def prepare_args_for_sub_assembly_items(self, row, args): for field in ["production_item", "item_name", "qty", "fg_warehouse", - "description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]: + "description", "bom_no", "stock_uom", "bom_level", + "production_plan_item", "schedule_date"]: args[field] = row.get(field) args.update({ @@ -454,10 +465,14 @@ class ProductionPlan(Document): }) def create_work_order(self, item): - from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse + from erpnext.manufacturing.doctype.work_order.work_order import ( + OverProductionError, + get_default_warehouse, + ) warehouse = get_default_warehouse() wo = frappe.new_doc("Work Order") wo.update(item) + wo.planned_start_date = item.get('planned_start_date') or item.get('schedule_date') if item.get("warehouse"): wo.fg_warehouse = item.get("warehouse") @@ -544,8 +559,6 @@ class ProductionPlan(Document): get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) - self.save() - def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): bom_data = sorted(bom_data, key = lambda i: i.bom_level) @@ -559,6 +572,15 @@ class ProductionPlan(Document): self.append("sub_assembly_items", data) + def check_have_work_orders_completed(self): + wo_status = frappe.db.get_list( + "Work Order", + filters={"production_plan": self.name}, + fields="status", + pluck="status" + ) + return all(s == "Completed" for s in wo_status) + @frappe.whitelist() def download_raw_materials(doc, warehouses=None): if isinstance(doc, str): @@ -569,7 +591,10 @@ def download_raw_materials(doc, warehouses=None): 'Reserved Qty for Production', 'Safety Stock', 'Required Qty']] doc.warehouse = None - for d in get_items_for_material_requests(doc, warehouses=warehouses, get_parent_warehouse_data=True): + frappe.flags.show_qty_in_stock_uom = 1 + items = get_items_for_material_requests(doc, warehouses=warehouses, get_parent_warehouse_data=True) + + for d in items: item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('warehouse'), d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'), d.get('planned_qty'), d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')]) @@ -605,9 +630,16 @@ def get_exploded_items(item_details, company, bom_no, include_non_stock_items, p and bom.name=%s and item.is_stock_item in (1, {0}) group by bei.item_code, bei.stock_uom""".format(0 if include_non_stock_items else 1), (planned_qty, company, bom_no), as_dict=1): - item_details.setdefault(d.get('item_code'), d) + if not d.conversion_factor and d.purchase_uom: + d.conversion_factor = get_uom_conversion_factor(d.item_code, d.purchase_uom) + item_details.setdefault(d.get('item_code'), d) + return item_details +def get_uom_conversion_factor(item_code, uom): + return frappe.db.get_value('UOM Conversion Detail', + {'parent': item_code, 'uom': uom}, 'conversion_factor') + def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, parent_qty, planned_qty=1): items = frappe.db.sql(""" @@ -642,6 +674,9 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite if d.item_code in item_details: item_details[d.item_code].qty = item_details[d.item_code].qty + d.qty else: + if not d.conversion_factor and d.purchase_uom: + d.conversion_factor = get_uom_conversion_factor(d.item_code, d.purchase_uom) + item_details[d.item_code] = d if data.get('include_exploded_items') and d.default_bom: @@ -669,10 +704,11 @@ def get_material_request_items(row, sales_order, company, row['purchase_uom'] = row['stock_uom'] if row['purchase_uom'] != row['stock_uom']: - if not row['conversion_factor']: + if not (row['conversion_factor'] or frappe.flags.show_qty_in_stock_uom): frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}") .format(row['purchase_uom'], row['stock_uom'], row.item_code)) - required_qty = required_qty / row['conversion_factor'] + + required_qty = required_qty / row['conversion_factor'] if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"): required_qty = ceil(required_qty) @@ -704,43 +740,42 @@ def get_material_request_items(row, sales_order, company, def get_sales_orders(self): so_filter = item_filter = "" bom_item = "bom.item = so_item.item_code" - if self.from_date: - so_filter += " and so.transaction_date >= %(from_date)s" - if self.to_date: - so_filter += " and so.transaction_date <= %(to_date)s" - if self.customer: - so_filter += " and so.customer = %(customer)s" - if self.project: - so_filter += " and so.project = %(project)s" - if self.sales_order_status: - so_filter += "and so.status = %(sales_order_status)s" + + date_field_mapper = { + 'from_date': ('>=', 'so.transaction_date'), + 'to_date': ('<=', 'so.transaction_date'), + 'from_delivery_date': ('>=', 'so_item.delivery_date'), + 'to_delivery_date': ('<=', 'so_item.delivery_date') + } + + for field, value in date_field_mapper.items(): + if self.get(field): + so_filter += f" and {value[1]} {value[0]} %({field})s" + + for field in ['customer', 'project', 'sales_order_status']: + if self.get(field): + so_field = 'status' if field == 'sales_order_status' else field + so_filter += f" and so.{so_field} = %({field})s" if self.item_code and frappe.db.exists('Item', self.item_code): bom_item = self.get_bom_item() or bom_item - item_filter += " and so_item.item_code = %(item)s" + item_filter += " and so_item.item_code = %(item_code)s" - open_so = frappe.db.sql(""" + open_so = frappe.db.sql(f""" select distinct so.name, so.transaction_date, so.customer, so.base_grand_total from `tabSales Order` so, `tabSales Order Item` so_item where so_item.parent = so.name and so.docstatus = 1 and so.status not in ("Stopped", "Closed") and so.company = %(company)s - and so_item.qty > so_item.work_order_qty {0} {1} - and (exists (select name from `tabBOM` bom where {2} + and so_item.qty > so_item.work_order_qty {so_filter} {item_filter} + and (exists (select name from `tabBOM` bom where {bom_item} and bom.is_active = 1) or exists (select name from `tabPacked Item` pi where pi.parent = so.name and pi.parent_item = so_item.item_code and exists (select name from `tabBOM` bom where bom.item=pi.item_code and bom.is_active = 1))) - """.format(so_filter, item_filter, bom_item), { - "from_date": self.from_date, - "to_date": self.to_date, - "customer": self.customer, - "project": self.project, - "item": self.item_code, - "company": self.company, - "sales_order_status": self.sales_order_status - }, as_dict=1) + """, self.as_dict(), as_dict=1) + return open_so @frappe.whitelist() @@ -769,6 +804,12 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) +@frappe.whitelist() +def get_so_details(sales_order): + return frappe.db.get_value("Sales Order", sales_order, + ['transaction_date', 'customer', 'grand_total'], as_dict=1 + ) + def get_warehouse_list(warehouses): warehouse_list = [] @@ -841,10 +882,8 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d elif data.get('item_code'): item_master = frappe.get_doc('Item', data['item_code']).as_dict() purchase_uom = item_master.purchase_uom or item_master.stock_uom - conversion_factor = 0 - for d in item_master.get("uoms"): - if d.uom == purchase_uom: - conversion_factor = d.conversion_factor + conversion_factor = (get_uom_conversion_factor(item_master.name, purchase_uom) + if item_master.purchase_uom else 1.0) item_details[item_master.name] = frappe._dict( { @@ -866,7 +905,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d sales_order = doc.get("sales_order") - for item_code, details in iteritems(item_details): + for item_code, details in item_details.items(): so_item_details.setdefault(sales_order, frappe._dict()) if item_code in so_item_details.get(sales_order, {}): so_item_details[sales_order][item_code]['qty'] = so_item_details[sales_order][item_code].get("qty", 0) + flt(details.qty) @@ -874,7 +913,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d so_item_details[sales_order][item_code] = details mr_items = [] - for sales_order, item_code in iteritems(so_item_details): + for sales_order, item_code in so_item_details.items(): item_dict = so_item_details[sales_order] for details in item_dict.values(): bin_dict = get_bin_details(details, doc.company, warehouse) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py index 52a56af7bce..e13f042e435 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'production_plan', diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js index c2e3e6d7124..8f946866247 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js @@ -1,4 +1,5 @@ frappe.listview_settings['Production Plan'] = { + hide_name_column: true, add_fields: ["status"], filters: [["status", "!=", "Closed"]], get_indicator: function (doc) { diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.js b/erpnext/manufacturing/doctype/production_plan/test_production_plan.js deleted file mode 100644 index ef7d64c92da..00000000000 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Production Plan", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Production Plan - () => frappe.tests.make('Production Plan', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index a5b9ff845fc..a2980a732ed 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1,17 +1,23 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from frappe.utils import nowdate, now_datetime, flt, add_to_date -from erpnext.stock.doctype.item.test_item import create_item -from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders -from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation -from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list +from frappe.utils import add_to_date, flt, now_datetime, nowdate + from erpnext.controllers.item_variant import create_variant +from erpnext.manufacturing.doctype.production_plan.production_plan import ( + get_items_for_material_requests, + get_sales_orders, + get_warehouse_list, +) +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_stock_reconciliation, +) + class TestProductionPlan(unittest.TestCase): def setUp(self): @@ -288,6 +294,7 @@ class TestProductionPlan(unittest.TestCase): self.assertEqual(warehouses, expected_warehouses) def test_get_sales_order_with_variant(self): + rm_item = create_item('PIV_RM', valuation_rate = 100) if not frappe.db.exists('Item', {"item_code": 'PIV'}): item = create_item('PIV', valuation_rate = 100) variant_settings = { @@ -300,20 +307,20 @@ class TestProductionPlan(unittest.TestCase): } item.update(variant_settings) item.save() - parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV']) + parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code]) if not frappe.db.exists('BOM', {"item": 'PIV'}): - parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV']) + parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code]) else: parent_bom = frappe.get_doc('BOM', {"item": 'PIV'}) if not frappe.db.exists('Item', {"item_code": 'PIV-RED'}): variant = create_variant("PIV", {"Colour": "Red"}) variant.save() - variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code]) + variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code]) else: variant = frappe.get_doc('Item', 'PIV-RED') if not frappe.db.exists('BOM', {"item": 'PIV-RED'}): - variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code]) + variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code]) """Testing when item variant has a BOM""" so = make_sales_order(item_code="PIV-RED", qty=5) @@ -395,6 +402,7 @@ def make_bom(**args): 'uom': item_doc.stock_uom, 'stock_uom': item_doc.stock_uom, 'rate': item_doc.valuation_rate or args.rate, + 'source_warehouse': args.source_warehouse }) if not args.do_not_save: diff --git a/erpnext/manufacturing/doctype/production_plan_item/__init__.py b/erpnext/manufacturing/doctype/production_plan_item/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/__init__.py +++ b/erpnext/manufacturing/doctype/production_plan_item/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.py b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.py index 37cf5a49dc9..cc79ac327db 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.py +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class ProductionPlanItem(Document): pass diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py index 51fbc3633b1..81d2ecad544 100644 --- a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class ProductionPlanItemReference(Document): pass diff --git a/erpnext/manufacturing/doctype/production_plan_material_request/production_plan_material_request.py b/erpnext/manufacturing/doctype/production_plan_material_request/production_plan_material_request.py index 44786f8388d..83b17893c04 100644 --- a/erpnext/manufacturing/doctype/production_plan_material_request/production_plan_material_request.py +++ b/erpnext/manufacturing/doctype/production_plan_material_request/production_plan_material_request.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class ProductionPlanMaterialRequest(Document): pass diff --git a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.py b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.py index f605985ae96..a66ff44f558 100644 --- a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.py +++ b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class ProductionPlanMaterialRequestWarehouse(Document): pass diff --git a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/test_production_plan_material_request_warehouse.py b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/test_production_plan_material_request_warehouse.py index ecab5fbae12..4394c142d49 100644 --- a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/test_production_plan_material_request_warehouse.py +++ b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/test_production_plan_material_request_warehouse.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestProductionPlanMaterialRequestWarehouse(unittest.TestCase): pass diff --git a/erpnext/manufacturing/doctype/production_plan_sales_order/__init__.py b/erpnext/manufacturing/doctype/production_plan_sales_order/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/production_plan_sales_order/__init__.py +++ b/erpnext/manufacturing/doctype/production_plan_sales_order/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.py b/erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.py index 99c7273a640..3f3852983ce 100644 --- a/erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.py +++ b/erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class ProductionPlanSalesOrder(Document): pass diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py index 6850a2eb4ed..069667a6410 100644 --- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class ProductionPlanSubAssemblyItem(Document): pass diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js index 032c9cd9a21..33a313e32f5 100644 --- a/erpnext/manufacturing/doctype/routing/routing.js +++ b/erpnext/manufacturing/doctype/routing/routing.js @@ -69,3 +69,16 @@ frappe.ui.form.on('BOM Operation', { frm.events.calculate_operating_cost(frm, d); } }); + +frappe.tour['Routing'] = [ + { + fieldname: "routing_name", + title: "Routing Name", + description: __("Enter a name for Routing.") + }, + { + fieldname: "operations", + title: "BOM Operations", + description: __("Enter the Operation, the table will fetch the Operation details like Hourly Rate, Workstation automatically.\n\n After that, set the Operation Time in minutes and the table will calculate the Operation Costs based on the Hourly Rate and Operation Time.") + } +]; diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py index ece0db717a2..1c76634646d 100644 --- a/erpnext/manufacturing/doctype/routing/routing.py +++ b/erpnext/manufacturing/doctype/routing/routing.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import cint, flt from frappe import _ from frappe.model.document import Document +from frappe.utils import cint, flt + class Routing(Document): def validate(self): diff --git a/erpnext/manufacturing/doctype/routing/routing_dashboard.py b/erpnext/manufacturing/doctype/routing/routing_dashboard.py index 50a3fe62da5..d051e38a34c 100644 --- a/erpnext/manufacturing/doctype/routing/routing_dashboard.py +++ b/erpnext/manufacturing/doctype/routing/routing_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'routing', diff --git a/erpnext/manufacturing/doctype/routing/test_routing.js b/erpnext/manufacturing/doctype/routing/test_routing.js deleted file mode 100644 index 6cb65494af2..00000000000 --- a/erpnext/manufacturing/doctype/routing/test_routing.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Routing", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Routing - () => frappe.tests.make('Routing', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 92f26946ab7..68d9dec04b1 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + import frappe from frappe.test_runner import make_test_records -from erpnext.stock.doctype.item.test_item import make_item + from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record +from erpnext.stock.doctype.item.test_item import make_item + class TestRouting(unittest.TestCase): @classmethod @@ -91,8 +92,8 @@ class TestRouting(unittest.TestCase): def setup_operations(rows): - from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation from erpnext.manufacturing.doctype.operation.test_operation import make_operation + from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation for row in rows: make_workstation(row) make_operation(row) diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.py b/erpnext/manufacturing/doctype/sub_operation/sub_operation.py index f4b27758e9d..c86058eb586 100644 --- a/erpnext/manufacturing/doctype/sub_operation/sub_operation.py +++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class SubOperation(Document): pass diff --git a/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py b/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py index d3410ca3120..189fdaee085 100644 --- a/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py +++ b/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestSubOperation(unittest.TestCase): pass diff --git a/erpnext/manufacturing/doctype/work_order/__init__.py b/erpnext/manufacturing/doctype/work_order/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/work_order/__init__.py +++ b/erpnext/manufacturing/doctype/work_order/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index bf1ccb71594..f590d680d35 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1,22 +1,30 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - - -from __future__ import unicode_literals import unittest -import frappe -from frappe.utils import flt, now, add_months, cint, today, add_to_date -from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry, - ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError) -from erpnext.stock.doctype.stock_entry import test_stock_entry -from erpnext.stock.utils import get_bin -from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.stock.doctype.item.test_item import make_item -from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom -from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse -from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError -class TestWorkOrder(unittest.TestCase): +import frappe +from frappe.utils import add_months, cint, flt, now, today + +from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.manufacturing.doctype.work_order.work_order import ( + CapacityError, + ItemHasVariantError, + OverProductionError, + StockOverProductionError, + close_work_order, + make_stock_entry, + stop_unstop, +) +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.stock.doctype.item.test_item import create_item, make_item +from erpnext.stock.doctype.stock_entry import test_stock_entry +from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse +from erpnext.stock.utils import get_bin +from erpnext.tests.utils import ERPNextTestCase, timeout + + +class TestWorkOrder(ERPNextTestCase): def setUp(self): self.warehouse = '_Test Warehouse 2 - _TC' self.item = '_Test Item' @@ -369,6 +377,7 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(len(ste.additional_costs), 1) self.assertEqual(ste.total_additional_costs, 1000) + @timeout(seconds=60) def test_job_card(self): stock_entries = [] bom = frappe.get_doc('BOM', { @@ -675,13 +684,18 @@ class TestWorkOrder(unittest.TestCase): def test_valuation_rate_missing_on_make_stock_entry(self): item_name = 'Test Valuation Rate Missing' + rm_item = '_Test raw material item' make_item(item_name, { "is_stock_item": 1, "include_item_in_manufacturing": 1, }) + make_item('_Test raw material item', { + "is_stock_item": 1, + "include_item_in_manufacturing": 1, + }) if not frappe.db.get_value('BOM', {'item': item_name}): - make_bom(item=item_name, raw_materials=[item_name], rm_qty=1) + make_bom(item=item_name, raw_materials=[rm_item], rm_qty=1) company = "_Test Company with perpetual inventory" source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company) @@ -690,6 +704,168 @@ class TestWorkOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, make_stock_entry, wo.name, 'Material Transfer for Manufacture') + def test_wo_completion_with_pl_bom(self): + from erpnext.manufacturing.doctype.bom.test_bom import ( + create_bom_with_process_loss_item, + create_process_loss_bom_items, + ) + + qty = 4 + scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG + source_warehouse = "Stores - _TC" + wip_warehouse = "_Test Warehouse - _TC" + fg_item_non_whole, _, bom_item = create_process_loss_bom_items() + + test_stock_entry.make_stock_entry(item_code=bom_item.item_code, + target=source_warehouse, qty=4, basic_rate=100) + + bom_no = f"BOM-{fg_item_non_whole.item_code}-001" + if not frappe.db.exists("BOM", bom_no): + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, scrap_qty=scrap_qty, + scrap_rate=0, fg_qty=1, is_process_loss=1 + ) + bom_doc.submit() + + wo = make_wo_order_test_record( + production_item=fg_item_non_whole.item_code, + bom_no=bom_no, + wip_warehouse=wip_warehouse, + qty=qty, + skip_transfer=1, + stock_uom=fg_item_non_whole.stock_uom, + ) + + se = frappe.get_doc( + make_stock_entry(wo.name, "Material Transfer for Manufacture", qty) + ) + se.get("items")[0].s_warehouse = "Stores - _TC" + se.insert() + se.submit() + + se = frappe.get_doc( + make_stock_entry(wo.name, "Manufacture", qty) + ) + se.insert() + se.submit() + + # Testing stock entry values + items = se.get("items") + self.assertEqual(len(items), 3, "There should be 3 items including process loss.") + + source_item, fg_item, pl_item = items + + total_pl_qty = qty * scrap_qty + actual_fg_qty = qty - total_pl_qty + + self.assertEqual(pl_item.qty, total_pl_qty) + self.assertEqual(fg_item.qty, actual_fg_qty) + + # Testing Work Order values + self.assertEqual( + frappe.db.get_value("Work Order", wo.name, "produced_qty"), + qty + ) + self.assertEqual( + frappe.db.get_value("Work Order", wo.name, "process_loss_qty"), + total_pl_qty + ) + + @timeout(seconds=60) + def test_job_card_scrap_item(self): + items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test', + 'Test RM Item 2 for Scrap Item Test'] + + company = '_Test Company with perpetual inventory' + for item_code in items: + create_item(item_code = item_code, is_stock_item = 1, + is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1') + + item = 'Test FG Item for Scrap Item Test' + raw_materials = ['Test RM Item 1 for Scrap Item Test', 'Test RM Item 2 for Scrap Item Test'] + if not frappe.db.get_value('BOM', {'item': item}): + bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True) + bom.with_operations = 1 + bom.append('operations', { + 'operation': '_Test Operation 1', + 'workstation': '_Test Workstation 1', + 'hour_rate': 20, + 'time_in_mins': 60 + }) + + bom.submit() + + wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) + job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + update_job_card(job_card) + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + for row in stock_entry.items: + if row.is_scrap_item: + self.assertEqual(row.qty, 1) + + def test_close_work_order(self): + items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO', + 'Test RM Item 2 for Closed WO'] + + company = '_Test Company with perpetual inventory' + for item_code in items: + create_item(item_code = item_code, is_stock_item = 1, + is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1') + + item = 'Test FG Item for Closed WO' + raw_materials = ['Test RM Item 1 for Closed WO', 'Test RM Item 2 for Closed WO'] + if not frappe.db.get_value('BOM', {'item': item}): + bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True) + bom.with_operations = 1 + bom.append('operations', { + 'operation': '_Test Operation 1', + 'workstation': '_Test Workstation 1', + 'hour_rate': 20, + 'time_in_mins': 60 + }) + + bom.submit() + + wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) + job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + + if len(job_cards) == len(bom.operations): + for jc in job_cards: + job_card_doc = frappe.get_doc('Job Card', jc) + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) + + job_card_doc.submit() + + close_work_order(wo_order, "Closed") + self.assertEqual(wo_order.get('status'), "Closed") + +def update_job_card(job_card): + job_card_doc = frappe.get_doc('Job Card', job_card) + job_card_doc.set('scrap_items', [ + { + 'item_code': 'Test RM Item 1 for Scrap Item Test', + 'stock_qty': 2 + }, + { + 'item_code': 'Test RM Item 2 for Scrap Item Test', + 'stock_qty': 2 + }, + ]) + + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) + + job_card_doc.submit() + + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` @@ -732,6 +908,7 @@ def make_wo_order_test_record(**args): wo_order.get_items_and_operations_from_bom() wo_order.sales_order = args.sales_order or None wo_order.planned_start_date = args.planned_start_date or now() + wo_order.transfer_material_against = args.transfer_material_against or "Work Order" if args.source_warehouse: for item in wo_order.get("required_items"): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 512048512ed..5ffbb0374e9 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -135,24 +135,26 @@ frappe.ui.form.on("Work Order", { frm.set_intro(__("Submit this Work Order for further processing.")); } - if (frm.doc.docstatus===1) { - frm.trigger('show_progress_for_items'); - frm.trigger('show_progress_for_operations'); - } + if (frm.doc.status != "Closed") { + if (frm.doc.docstatus===1) { + frm.trigger('show_progress_for_items'); + frm.trigger('show_progress_for_operations'); + } - if (frm.doc.docstatus === 1 - && frm.doc.operations && frm.doc.operations.length) { + if (frm.doc.docstatus === 1 + && frm.doc.operations && frm.doc.operations.length) { - const not_completed = frm.doc.operations.filter(d => { - if(d.status != 'Completed') { - return true; + const not_completed = frm.doc.operations.filter(d => { + if (d.status != 'Completed') { + return true; + } + }); + + if (not_completed && not_completed.length) { + frm.add_custom_button(__('Create Job Card'), () => { + frm.trigger("make_job_card"); + }).addClass('btn-primary'); } - }); - - if(not_completed && not_completed.length) { - frm.add_custom_button(__('Create Job Card'), () => { - frm.trigger("make_job_card"); - }).addClass('btn-primary'); } } @@ -440,7 +442,7 @@ frappe.ui.form.on("Work Order", { additional_operating_cost: function(frm) { erpnext.work_order.calculate_cost(frm.doc); erpnext.work_order.calculate_total_cost(frm); - } + }, }); frappe.ui.form.on("Work Order Item", { @@ -517,14 +519,22 @@ frappe.ui.form.on("Work Order Operation", { erpnext.work_order = { set_custom_buttons: function(frm) { var doc = frm.doc; - if (doc.docstatus === 1) { + if (doc.docstatus === 1 && doc.status != "Closed") { + frm.add_custom_button(__('Close'), function() { + frappe.confirm(__("Once the Work Order is Closed. It can't be resumed."), + () => { + erpnext.work_order.change_work_order_status(frm, "Closed"); + } + ); + }, __("Status")); + if (doc.status != 'Stopped' && doc.status != 'Completed') { frm.add_custom_button(__('Stop'), function() { - erpnext.work_order.stop_work_order(frm, "Stopped"); + erpnext.work_order.change_work_order_status(frm, "Stopped"); }, __("Status")); } else if (doc.status == 'Stopped') { frm.add_custom_button(__('Re-open'), function() { - erpnext.work_order.stop_work_order(frm, "Resumed"); + erpnext.work_order.change_work_order_status(frm, "Resumed"); }, __("Status")); } @@ -713,9 +723,10 @@ erpnext.work_order = { }); }, - stop_work_order: function(frm, status) { + change_work_order_status: function(frm, status) { + let method_name = status=="Closed" ? "close_work_order" : "stop_unstop"; frappe.call({ - method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop", + method: `erpnext.manufacturing.doctype.work_order.work_order.${method_name}`, freeze: true, freeze_message: __("Updating Work Order status"), args: { @@ -731,3 +742,63 @@ erpnext.work_order = { }); } }; + +frappe.tour['Work Order'] = [ + { + fieldname: "production_item", + title: "Item to Manufacture", + description: __("Select the Item to be manufactured.") + }, + { + fieldname: "bom_no", + title: "BOM No", + description: __("The default BOM for that item will be fetched by the system. You can also change the BOM.") + }, + { + fieldname: "qty", + title: "Qty to Manufacture", + description: __("Enter the quantity to manufacture. Raw material Items will be fetched only when this is set.") + }, + { + fieldname: "use_multi_level_bom", + title: "Use Multi-Level BOM", + description: __("This is enabled by default. If you want to plan materials for sub-assemblies of the Item you're manufacturing leave this enabled. If you plan and manufacture the sub-assemblies separately, you can disable this checkbox.") + }, + { + fieldname: "source_warehouse", + title: "Source Warehouse", + description: __("The warehouse where you store your raw materials. Each required item can have a separate source warehouse. Group warehouse also can be selected as source warehouse. On submission of the Work Order, the raw materials will be reserved in these warehouses for production usage.") + }, + { + fieldname: "fg_warehouse", + title: "Target Warehouse", + description: __("The warehouse where you store finished Items before they are shipped.") + }, + { + fieldname: "wip_warehouse", + title: "Work-in-Progress Warehouse", + description: __("The warehouse where your Items will be transferred when you begin production. Group Warehouse can also be selected as a Work in Progress warehouse.") + }, + { + fieldname: "scrap_warehouse", + title: "Scrap Warehouse", + description: __("If the BOM results in Scrap material, the Scrap Warehouse needs to be selected.") + }, + { + fieldname: "required_items", + title: "Required Items", + description: __("All the required items (raw materials) will be fetched from BOM and populated in this table. Here you can also change the Source Warehouse for any item. And during the production, you can track transferred raw materials from this table.") + }, + { + fieldname: "planned_start_date", + title: "Planned Start Date", + description: __("Set the Planned Start Date (an Estimated Date at which you want the Production to begin)") + }, + { + fieldname: "operations", + title: "Operations", + description: __("If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed.") + }, + + +]; diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 3b56854aaf3..12cd58f418b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -19,6 +19,7 @@ "qty", "material_transferred_for_manufacturing", "produced_qty", + "process_loss_qty", "sales_order", "project", "serial_no_and_batch_for_finished_good_section", @@ -64,16 +65,12 @@ "description", "stock_uom", "column_break2", - "references_section", "material_request", "material_request_item", "sales_order_item", - "column_break_61", "production_plan", "production_plan_item", "production_plan_sub_assembly_item", - "parent_work_order", - "bom_level", "product_bundle_item", "amended_from" ], @@ -102,7 +99,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled", + "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nClosed\nCancelled", "read_only": 1, "reqd": 1, "search_index": 1 @@ -185,6 +182,7 @@ "reqd": 1 }, { + "default": "1.0", "fieldname": "qty", "fieldtype": "Float", "label": "Qty To Manufacture", @@ -328,6 +326,7 @@ "label": "Expected Delivery Date" }, { + "collapsible": 1, "fieldname": "operations_section", "fieldtype": "Section Break", "label": "Operations", @@ -339,7 +338,7 @@ "fieldname": "transfer_material_against", "fieldtype": "Select", "label": "Transfer Material Against", - "options": "\nWork Order\nJob Card" + "options": "Work Order\nJob Card" }, { "fieldname": "operations", @@ -553,23 +552,33 @@ "read_only": 1 }, { - "fieldname": "production_plan_sub_assembly_item", - "fieldtype": "Data", - "label": "Production Plan Sub-assembly Item", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - } + "fieldname": "production_plan_sub_assembly_item", + "fieldtype": "Data", + "label": "Production Plan Sub-assembly Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval: doc.process_loss_qty", + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "label": "Process Loss Qty", + "no_copy": 1, + "non_negative": 1, + "read_only": 1 + } ], "icon": "fa fa-cogs", "idx": 1, "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-06-28 16:19:14.902699", + "modified": "2021-11-08 17:36:07.016300", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", + "naming_rule": "By \"Naming Series\" field", "nsm_parent_field": "parent_work_order", "owner": "Administrator", "permissions": [ diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 5fe9fec2af1..0090f4d04ee 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1,25 +1,43 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import frappe import json -import math -from frappe import _ -from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form, time_diff_in_hours -from frappe.model.document import Document -from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict, get_bom_item_rate + +import frappe from dateutil.relativedelta import relativedelta -from erpnext.stock.doctype.item.item import validate_end_of_life, get_item_defaults -from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError -from erpnext.projects.doctype.timesheet.timesheet import OverlapError -from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations -from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty -from frappe.utils.csvutils import getlink -from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty -from erpnext.utilities.transaction_base import validate_uom_is_integer +from frappe import _ +from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc +from frappe.utils import ( + cint, + date_diff, + flt, + get_datetime, + get_link_to_form, + getdate, + nowdate, + time_diff_in_hours, +) + +from erpnext.manufacturing.doctype.bom.bom import ( + get_bom_item_rate, + get_bom_items_as_dict, + validate_bom_no, +) +from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import ( + get_mins_between_operations, +) from erpnext.stock.doctype.batch.batch import make_batch -from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_auto_serial_nos, auto_make_serial_nos +from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life +from erpnext.stock.doctype.serial_no.serial_no import ( + auto_make_serial_nos, + get_auto_serial_nos, + get_serial_nos, +) +from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty +from erpnext.stock.utils import get_bin, get_latest_stock_qty, validate_warehouse_company +from erpnext.utilities.transaction_base import validate_uom_is_integer + class OverProductionError(frappe.ValidationError): pass class CapacityError(frappe.ValidationError): pass @@ -157,7 +175,7 @@ class WorkOrder(Document): def update_status(self, status=None): '''Update status of work order if unknown''' - if status != "Stopped": + if status != "Stopped" and status != "Closed": status = self.get_status(status) if status != self.status: @@ -214,6 +232,7 @@ class WorkOrder(Document): self.meta.get_label(fieldname), qty, completed_qty, self.name), StockOverProductionError) self.db_set(fieldname, qty) + self.set_process_loss_qty() from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item @@ -223,6 +242,22 @@ class WorkOrder(Document): if self.production_plan: self.update_production_plan_status() + def set_process_loss_qty(self): + process_loss_qty = flt(frappe.db.sql(""" + SELECT sum(qty) FROM `tabStock Entry Detail` + WHERE + is_process_loss=1 + AND parent IN ( + SELECT name FROM `tabStock Entry` + WHERE + work_order=%s + AND purpose='Manufacture' + AND docstatus=1 + ) + """, (self.name, ))[0][0]) + if process_loss_qty is not None: + self.db_set('process_loss_qty', process_loss_qty) + def update_production_plan_status(self): production_plan = frappe.get_doc('Production Plan', self.production_plan) produced_qty = 0 @@ -589,7 +624,6 @@ class WorkOrder(Document): def validate_operation_time(self): for d in self.operations: if not d.time_in_mins > 0: - print(self.bom_no, self.production_item) frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation)) def update_required_items(self): @@ -650,9 +684,7 @@ class WorkOrder(Document): if not d.operation: d.operation = operation else: - # Attribute a big number (999) to idx for sorting putpose in case idx is NULL - # For instance in BOM Explosion Item child table, the items coming from sub assembly items - for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999): + for item in sorted(item_dict.values(), key=lambda d: d['idx'] or float('inf')): self.append('required_items', { 'rate': item.rate, 'amount': item.rate * item.qty, @@ -934,6 +966,10 @@ def stop_unstop(work_order, status): frappe.throw(_("Not permitted"), frappe.PermissionError) pro_order = frappe.get_doc("Work Order", work_order) + + if pro_order.status == "Closed": + frappe.throw(_("Closed Work Order can not be stopped or Re-opened")) + pro_order.update_status(status) pro_order.update_planned_qty() frappe.msgprint(_("Work Order has been {0}").format(status)) @@ -968,6 +1004,29 @@ def make_job_card(work_order, operations): if row.job_card_qty > 0: create_job_card(work_order, row, auto_create=True) +@frappe.whitelist() +def close_work_order(work_order, status): + if not frappe.has_permission("Work Order", "write"): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + work_order = frappe.get_doc("Work Order", work_order) + if work_order.get("operations"): + job_cards = frappe.get_list("Job Card", + filters={ + "work_order": work_order.name, + "status": "Work In Progress" + }, pluck='name') + + if job_cards: + job_cards = ", ".join(job_cards) + frappe.throw(_("Can not close Work Order. Since {0} Job Cards are in Work In Progress state.").format(job_cards)) + + work_order.update_status(status) + work_order.update_planned_qty() + frappe.msgprint(_("Work Order has been {0}").format(status)) + work_order.notify_update() + return work_order.status + def split_qty_based_on_batch_size(wo_doc, row, qty): if not cint(frappe.db.get_value("Operation", row.operation, "create_job_card_based_on_batch_size")): diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py index 403d46d8d42..37dd11aab4f 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py +++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'work_order', diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.py b/erpnext/manufacturing/doctype/work_order_item/work_order_item.py index 9aa53b5e3c3..4311d3bf17f 100644 --- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.py +++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class WorkOrderItem(Document): pass diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index f7b8787a0b3..4e1a464cb05 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -6,27 +6,27 @@ "field_order": [ "details", "operation", - "bom", - "column_break_4", - "description", - "sequence_id", - "col_break1", - "completed_qty", "status", + "completed_qty", + "column_break_4", + "bom", "workstation", + "sequence_id", + "section_break_10", + "description", "estimated_time_and_cost", "planned_start_time", - "planned_end_time", - "column_break_10", - "time_in_mins", "hour_rate", + "time_in_mins", + "column_break_10", + "planned_end_time", "batch_size", "planned_operating_cost", "section_break_9", "actual_start_time", - "actual_end_time", - "column_break_11", "actual_operation_time", + "column_break_11", + "actual_end_time", "actual_operating_cost" ], "fields": [ @@ -42,7 +42,6 @@ "oldfieldname": "operation_no", "oldfieldtype": "Data", "options": "Operation", - "read_only": 1, "reqd": 1 }, { @@ -52,20 +51,14 @@ "label": "BOM", "no_copy": 1, "options": "BOM", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "description", "fieldtype": "Text Editor", "label": "Operation Description", "oldfieldname": "opn_description", - "oldfieldtype": "Text", - "read_only": 1 - }, - { - "fieldname": "col_break1", - "fieldtype": "Column Break" + "oldfieldtype": "Text" }, { "columns": 1, @@ -74,19 +67,16 @@ "fieldtype": "Float", "in_list_view": 1, "label": "Completed Qty", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "columns": 1, "default": "Pending", "fieldname": "status", "fieldtype": "Select", - "in_list_view": 1, "label": "Status", "no_copy": 1, - "options": "Pending\nWork in Progress\nCompleted", - "read_only": 1 + "options": "Pending\nWork in Progress\nCompleted" }, { "fieldname": "workstation", @@ -106,15 +96,13 @@ "fieldname": "planned_start_time", "fieldtype": "Datetime", "label": "Planned Start Time", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "fieldname": "planned_end_time", "fieldtype": "Datetime", "label": "Planned End Time", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "fieldname": "column_break_10", @@ -122,7 +110,7 @@ }, { "columns": 1, - "description": "in Minutes", + "description": "In Minutes", "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, @@ -152,6 +140,7 @@ "label": "Actual Time and Cost" }, { + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_start_time", "fieldtype": "Datetime", "label": "Actual Start Time", @@ -159,7 +148,7 @@ "read_only": 1 }, { - "description": "Updated via 'Time Log'", + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_end_time", "fieldtype": "Datetime", "label": "Actual End Time", @@ -171,7 +160,7 @@ "fieldtype": "Column Break" }, { - "description": "in Minutes\nUpdated via 'Time Log'", + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_operation_time", "fieldtype": "Float", "label": "Actual Operation Time", @@ -189,26 +178,30 @@ }, { "fieldname": "batch_size", - "fieldtype": "Int", + "fieldtype": "Float", "label": "Batch Size", "read_only": 1 }, { "fieldname": "sequence_id", "fieldtype": "Int", + "hidden": 1, "label": "Sequence ID", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "column_break_4", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-24 14:36:12.835543", + "modified": "2021-11-29 16:37:18.824489", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", @@ -217,4 +210,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py index 3c20d8e88a0..6bda58ea77e 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py @@ -1,9 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class WorkOrderOperation(Document): pass diff --git a/erpnext/manufacturing/doctype/workstation/__init__.py b/erpnext/manufacturing/doctype/workstation/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/manufacturing/doctype/workstation/__init__.py +++ b/erpnext/manufacturing/doctype/workstation/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py index 9b73aca6010..5ed51535282 100644 --- a/erpnext/manufacturing/doctype/workstation/test_workstation.py +++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py @@ -1,14 +1,19 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -from erpnext.manufacturing.doctype.operation.test_operation import make_operation + +import unittest import frappe -import unittest -from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours, NotInWorkingHoursError, WorkstationHolidayError -from erpnext.manufacturing.doctype.routing.test_routing import setup_bom, create_routing from frappe.test_runner import make_test_records +from erpnext.manufacturing.doctype.operation.test_operation import make_operation +from erpnext.manufacturing.doctype.routing.test_routing import create_routing, setup_bom +from erpnext.manufacturing.doctype.workstation.workstation import ( + NotInWorkingHoursError, + WorkstationHolidayError, + check_if_within_operating_hours, +) + test_dependencies = ["Warehouse"] test_records = frappe.get_test_records('Workstation') make_test_records('Workstation') @@ -84,7 +89,7 @@ def make_workstation(*args, **kwargs): args = frappe._dict(args) workstation_name = args.workstation_name or args.workstation - try: + if not frappe.db.exists("Workstation", workstation_name): doc = frappe.get_doc({ "doctype": "Workstation", "workstation_name": workstation_name @@ -94,5 +99,5 @@ def make_workstation(*args, **kwargs): doc.insert() return doc - except frappe.DuplicateEntryError: - return frappe.get_doc("Workstation", workstation_name) + + return frappe.get_doc("Workstation", workstation_name) diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js index d8d25fc6f83..5b9cedb6f98 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.js +++ b/erpnext/manufacturing/doctype/workstation/workstation.js @@ -16,4 +16,29 @@ frappe.ui.form.on("Workstation", { }) } } -}) +}); + +frappe.tour['Workstation'] = [ + { + fieldname: "workstation_name", + title: "Workstation Name", + description: __("You can set it as a machine name or operation type. For example, stiching machine 12") + }, + { + fieldname: "production_capacity", + title: "Production Capacity", + description: __("No. of parallel job cards which can be allowed on this workstation. Example: 2 would mean this workstation can process production for two Work Orders at a time.") + }, + { + fieldname: "holiday_list", + title: "Holiday List", + description: __("A Holiday List can be added to exclude counting these days for the Workstation.") + }, + { + fieldname: "working_hours", + title: "Working Hours", + description: __("Under Working Hours table, you can add start and end times for a Workstation. For example, a Workstation may be active from 9 am to 1 pm, then 2 pm to 5 pm. You can also specify the working hours based on shifts. While scheduling a Work Order, the system will check for the availability of the Workstation based on the working hours specified.") + }, + + +]; diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index f4483f75472..4cfd410ac72 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -1,14 +1,23 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from erpnext.support.doctype.issue.issue import get_holidays -from frappe.utils import (flt, cint, getdate, formatdate, - comma_and, time_diff_in_seconds, to_timedelta, add_days) from frappe.model.document import Document -from dateutil.parser import parse +from frappe.utils import ( + add_days, + cint, + comma_and, + flt, + formatdate, + getdate, + time_diff_in_seconds, + to_timedelta, +) + +from erpnext.support.doctype.issue.issue import get_holidays + class WorkstationHolidayError(frappe.ValidationError): pass class NotInWorkingHoursError(frappe.ValidationError): pass diff --git a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py index 3ddbe731700..bc481ca1926 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py +++ b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'workstation', diff --git a/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.py b/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.py index 215df4c9b5d..99fb5524a90 100644 --- a/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.py +++ b/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.py @@ -1,9 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class WorkstationWorkingHour(Document): pass diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json index 7317152565a..032091f67e2 100644 --- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json @@ -19,14 +19,14 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing", "idx": 0, "is_complete": 0, - "modified": "2020-06-29 20:25:36.899106", + "modified": "2021-08-13 16:04:34.333812", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", "owner": "Administrator", "steps": [ { - "step": "Warehouse" + "step": "Explore Manufacturing Settings" }, { "step": "Workstation" @@ -35,22 +35,22 @@ "step": "Operation" }, { - "step": "Create Product" + "step": "Routing" }, { - "step": "Create Raw Materials" + "step": "Create Product" }, { "step": "Create BOM" }, { - "step": "Work Order" + "step": "Production Planning" }, { - "step": "Explore Manufacturing Settings" + "step": "Work Order" } ], "subtitle": "Products, Raw Materials, BOM, Work Order, and more.", "success_message": "Manufacturing module is all set up!", "title": "Let's Set Up the Manufacturing Module." -} +} \ No newline at end of file diff --git a/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.py b/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.py index 2334f8b26d8..02e3e933330 100644 --- a/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.py +++ b/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.py @@ -1,7 +1,3 @@ -from __future__ import unicode_literals - -import frappe - def get_context(context): # do your magic here pass diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json index 84b4088f233..9d7a58c1214 100644 --- a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json +++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Create your first Bill of Materials", "creation": "2020-05-05 16:41:20.239696", + "description": "# Create a Bill of Materials\n\nA Bill of Materials (BOM) is a list of items and sub-assemblies with quantities required to manufacture an Item.\n\nBOM also provides cost estimation for the production of the item. It takes raw-materials cost based on valuation and operations to cost based on routing, which gives total costing for a BOM.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:51:31.315686", + "modified": "2021-08-13 16:00:51.092671", "modified_by": "Administrator", "name": "Create BOM", "owner": "Administrator", "reference_document": "BOM", + "show_form_tour": 1, "show_full_form": 1, - "title": "Create a BOM (Bill of Material)", + "title": "Bill of Materials", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json index 0ffa30158b0..ad7345234d1 100644 --- a/erpnext/manufacturing/onboarding_step/create_product/create_product.json +++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Create an Item", "creation": "2020-05-05 16:42:31.476275", + "description": "# Create Items for Bill of Materials\n\nOne of the prerequisites of a BOM is the creation of raw materials, sub-assembly, and finished items. Once these items are created, you will be able to proceed to the Bill of Materials master, which is composed of items and routing.\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:50:59.010439", + "modified": "2021-08-13 16:00:22.407811", "modified_by": "Administrator", "name": "Create Product", "owner": "Administrator", "reference_document": "Item", - "show_full_form": 0, - "title": "Create a Finished Good", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Finished Items", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json index 0764f2e44a8..3f94764f089 100644 --- a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json +++ b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-05-19 11:53:25.147837", @@ -13,6 +12,7 @@ "name": "Create Raw Materials", "owner": "Administrator", "reference_document": "Item", + "show_form_tour": 0, "show_full_form": 0, "title": "Create Raw Materials", "validate_action": 1 diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json index 7ef202ee4e7..1d2c27e3796 100644 --- a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json +++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json @@ -1,20 +1,22 @@ { "action": "Show Form Tour", + "action_label": "Take a walk-through of Manufacturing Settings", "creation": "2020-05-19 11:55:11.378374", + "description": "# Review Manufacturing Settings\n\nIn ERPNext, the Manufacturing module\u2019s features are configurable as per your business needs. Manufacturing Settings is the place where you can set your preferences for:\n\n- Capacity planning for allocating jobs to workstations\n- Raw-material consumption based on BOM or actual\n- Default values and over-production allowance\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-05-26 20:28:03.558199", + "modified": "2021-08-13 15:59:32.145655", "modified_by": "Administrator", "name": "Explore Manufacturing Settings", "owner": "Administrator", "reference_document": "Manufacturing Settings", + "show_form_tour": 0, "show_full_form": 0, - "title": "Explore Manufacturing Settings", + "title": "Manufacturing Settings", "validate_action": 0, "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4" } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json index b532e6778c4..2e9592185ef 100644 --- a/erpnext/manufacturing/onboarding_step/operation/operation.json +++ b/erpnext/manufacturing/onboarding_step/operation/operation.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Let\u2019s create an Operation", "creation": "2020-05-12 16:15:31.706756", + "description": "# Create Operations\n\nAn Operation refers to any manufacturing operation performed on the raw materials to process it further in the manufacturing path. As an example, if you are into garments manufacturing, you will create Operations like fabric cutting, stitching, and washing as some of the operations.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:50:41.642754", + "modified": "2021-08-13 15:59:53.313532", "modified_by": "Administrator", "name": "Operation", "owner": "Administrator", "reference_document": "Operation", - "show_full_form": 0, - "title": "Create a Operation", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Operation", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json b/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json new file mode 100644 index 00000000000..ff9fdb9fd0c --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Learn more about Production Planning", + "creation": "2021-08-04 17:33:06.551077", + "description": "# How Production Planning Works\n\nProduction Plan helps in production and material planning for the Items planned for manufacturing. These production items can be committed via Sales Order (to Customers) or Material Requests (internally).\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-13 16:04:15.491414", + "modified_by": "Administrator", + "name": "Production Planning", + "owner": "Administrator", + "reference_document": "Production Plan", + "show_form_tour": 0, + "show_full_form": 1, + "title": "Production Planning", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/routing/routing.json b/erpnext/manufacturing/onboarding_step/routing/routing.json new file mode 100644 index 00000000000..be4fbff171f --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/routing/routing.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Check help to setup Routing", + "creation": "2021-08-04 11:56:42.455758", + "description": "# Setup Routing\n\nA Routing stores all Operations along with the description, hourly rate, operation time, batch size, etc. Click below to learn how the Routing template can be created, for quick selection in the BOM.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-13 16:00:07.391563", + "modified_by": "Administrator", + "name": "Routing", + "owner": "Administrator", + "reference_document": "Routing", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Routing", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json index c63363e7cb2..d2756c92758 100644 --- a/erpnext/manufacturing/onboarding_step/work_order/work_order.json +++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Create your first Work Order", "creation": "2020-05-12 16:15:56.084682", + "description": "# Create a Work Order\n\nA Work Order or a Job order is given to the manufacturing shop floor by the Production Manager to initiate the manufacturing of a certain quantity of an item. Work Order carriers details of production Item, its BOM, quantities to be manufactured, and operations.\n\nThrough Work Order, you can track various production status like:\n\n- Issue of raw-material to shop material\n- Progress on each Workstation via Job Card\n- Manufactured Quantity against Work Order\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:51:38.133150", + "modified": "2021-08-13 16:02:04.223536", "modified_by": "Administrator", "name": "Work Order", "owner": "Administrator", "reference_document": "Work Order", + "show_form_tour": 1, "show_full_form": 1, - "title": "Create a Work Order", + "title": "Work Order", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json index df244bb494a..67dd52eba2c 100644 --- a/erpnext/manufacturing/onboarding_step/workstation/workstation.json +++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Let\u2019s create a Workstation", "creation": "2020-05-12 16:14:14.930214", + "description": "# Create Workstations\n\nA Workstation stores information regarding the place where the workstation operations are performed. As an example, if you have ten sewing machines doing stitching jobs, each machine will be added as a workstation.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:50:33.938176", + "modified": "2021-08-13 15:59:59.634802", "modified_by": "Administrator", "name": "Workstation", "owner": "Administrator", "reference_document": "Workstation", - "show_full_form": 0, - "title": "Create a Workstation / Machine", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Workstation", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py index 858b5546b02..25de2e03797 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py @@ -1,9 +1,9 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from pprint import pprint + def execute(filters=None): data = [] diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py index 8778d9ba557..e7a818abd5d 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): data = get_data(filters) columns = get_columns(filters) diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index ffd9242e1b8..090a3e74fc8 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -1,13 +1,14 @@ # 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 frappe.utils.data import comma_and + def execute(filters=None): -# if not filters: filters = {} + # if not filters: filters = {} columns = get_columns() summ_data = [] @@ -88,7 +89,7 @@ def get_bom_stock(filters): GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1) def get_manufacturer_records(): - details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) + details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) manufacture_details = frappe._dict() for detail in details: dic = manufacture_details.setdefault(detail.get('parent'), {}) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index ed8b93929a1..fa943912617 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py index dc424b7605c..a5ae43e9add 100644 --- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py +++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): columns, data = [], [] columns = get_columns(filters) diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index b4db98c3d7e..77418235b07 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -1,11 +1,11 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt + def execute(filters=None): columns, data = [], [] @@ -29,7 +29,6 @@ def get_data(report_filters): for row in job_cards: row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) - update_raw_material_cost(row, report_filters) data.append(row) return data @@ -45,12 +44,6 @@ def get_filters(report_filters, operations): return filters -def update_raw_material_cost(row, filters): - row.rm_cost = 0.0 - for data in frappe.get_all("Job Card Item", fields = ["amount"], - filters={"parent": row.name, "docstatus": 1}): - row.rm_cost += data.amount - def get_columns(filters): return [ { @@ -58,7 +51,7 @@ def get_columns(filters): "fieldtype": "Link", "fieldname": "name", "options": "Job Card", - "width": "100" + "width": "120" }, { "label": _("Work Order"), @@ -110,18 +103,12 @@ def get_columns(filters): "label": _("Operating Cost"), "fieldtype": "Currency", "fieldname": "operating_cost", - "width": "100" - }, - { - "label": _("Raw Material Cost"), - "fieldtype": "Currency", - "fieldname": "rm_cost", - "width": "100" + "width": "150" }, { "label": _("Total Time (in Mins)"), "fieldtype": "Float", "fieldname": "total_time_in_mins", - "width": "100" + "width": "150" } ] diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py index 74c794b5dd0..2c515d1b36f 100644 --- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py @@ -1,10 +1,11 @@ # 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.utils import flt from frappe import _ +from frappe.utils import flt + def execute(filters=None): columns, data = [], [] diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 9a6c764c609..26b3359dee1 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -1,13 +1,16 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ -from frappe.utils import flt, nowdate, add_years, cint, getdate +from frappe.utils import add_years, cint, flt, getdate + +import erpnext from erpnext.accounts.report.financial_statements import get_period_list from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + def execute(filters=None): return ForecastingReport(filters).execute_report() diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index a8939051523..4046bb12b86 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -1,11 +1,13 @@ # 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 frappe.utils import getdate, flt -from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period) +from frappe.utils import getdate + +from erpnext.stock.report.stock_analytics.stock_analytics import get_period, get_period_date_ranges + def execute(filters=None): columns, data = [], [] @@ -21,7 +23,7 @@ def get_data(filters): } fields = ["name", "status", "work_order", "production_item", "item_name", "posting_date", - "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"] + "total_completed_qty", "workstation", "operation", "total_time_in_mins"] for field in ["work_order", "workstation", "operation", "company"]: if filters.get(field): @@ -42,7 +44,7 @@ def get_data(filters): job_card_time_details = {} for job_card_data in frappe.get_all("Job Card Time Log", fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"], - filters=job_card_time_filter, group_by="parent", debug=1): + filters=job_card_time_filter, group_by="parent"): job_card_time_details[job_card_data.parent] = job_card_data res = [] @@ -169,12 +171,6 @@ def get_columns(filters): "options": "Operation", "width": 110 }, - { - "label": _("Employee Name"), - "fieldname": "employee_name", - "fieldtype": "Data", - "width": 110 - }, { "label": _("Total Completed Qty"), "fieldname": "total_completed_qty", diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/__init__.py b/erpnext/manufacturing/report/process_loss_report/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/healthcare_service_unit/__init__.py rename to erpnext/manufacturing/report/process_loss_report/__init__.py diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.js b/erpnext/manufacturing/report/process_loss_report/process_loss_report.js new file mode 100644 index 00000000000..b0c2b94a254 --- /dev/null +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.js @@ -0,0 +1,44 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Process Loss Report"] = { + filters: [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + mandatory: true, + default: frappe.defaults.get_user_default("Company"), + }, + { + label: __("Item"), + fieldname: "item", + fieldtype: "Link", + options: "Item", + mandatory: false, + }, + { + label: __("Work Order"), + fieldname: "work_order", + fieldtype: "Link", + options: "Work Order", + mandatory: false, + }, + { + label: __("From Date"), + fieldname: "from_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.year_start(), + }, + { + label: __("To Date"), + fieldname: "to_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.get_today(), + }, + ] +}; diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.json b/erpnext/manufacturing/report/process_loss_report/process_loss_report.json new file mode 100644 index 00000000000..7d3d13d98cf --- /dev/null +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.json @@ -0,0 +1,26 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-08-24 16:38:15.233395", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-10-20 22:03:57.606612", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Process Loss Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Work Order", + "report_name": "Process Loss Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py new file mode 100644 index 00000000000..9b544dafa5f --- /dev/null +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py @@ -0,0 +1,133 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from typing import Dict, List, Tuple + +import frappe +from frappe import _ + +Filters = frappe._dict +Row = frappe._dict +Data = List[Row] +Columns = List[Dict[str, str]] +QueryArgs = Dict[str, str] + +def execute(filters: Filters) -> Tuple[Columns, Data]: + columns = get_columns() + data = get_data(filters) + return columns, data + +def get_data(filters: Filters) -> Data: + query_args = get_query_args(filters) + data = run_query(query_args) + update_data_with_total_pl_value(data) + return data + +def get_columns() -> Columns: + return [ + { + 'label': _('Work Order'), + 'fieldname': 'name', + 'fieldtype': 'Link', + 'options': 'Work Order', + 'width': '200' + }, + { + 'label': _('Item'), + 'fieldname': 'production_item', + 'fieldtype': 'Link', + 'options': 'Item', + 'width': '100' + }, + { + 'label': _('Status'), + 'fieldname': 'status', + 'fieldtype': 'Data', + 'width': '100' + }, + { + 'label': _('Manufactured Qty'), + 'fieldname': 'produced_qty', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': _('Loss Qty'), + 'fieldname': 'process_loss_qty', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': _('Actual Manufactured Qty'), + 'fieldname': 'actual_produced_qty', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': _('Loss Value'), + 'fieldname': 'total_pl_value', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': _('FG Value'), + 'fieldname': 'total_fg_value', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': _('Raw Material Value'), + 'fieldname': 'total_rm_value', + 'fieldtype': 'Float', + 'width': '150' + } + ] + +def get_query_args(filters: Filters) -> QueryArgs: + query_args = {} + query_args.update(filters) + query_args.update( + get_filter_conditions(filters) + ) + return query_args + +def run_query(query_args: QueryArgs) -> Data: + return frappe.db.sql(""" + SELECT + wo.name, wo.status, wo.production_item, wo.qty, + wo.produced_qty, wo.process_loss_qty, + (wo.produced_qty - wo.process_loss_qty) as actual_produced_qty, + sum(se.total_incoming_value) as total_fg_value, + sum(se.total_outgoing_value) as total_rm_value + FROM + `tabWork Order` wo INNER JOIN `tabStock Entry` se + ON wo.name=se.work_order + WHERE + process_loss_qty > 0 + AND wo.company = %(company)s + AND se.docstatus = 1 + AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s + {item_filter} + {work_order_filter} + GROUP BY + se.work_order + """.format(**query_args), query_args, as_dict=1) + +def update_data_with_total_pl_value(data: Data) -> None: + for row in data: + value_per_unit_fg = row['total_fg_value'] / row['actual_produced_qty'] + row['total_pl_value'] = row['process_loss_qty'] * value_per_unit_fg + +def get_filter_conditions(filters: Filters) -> QueryArgs: + filter_conditions = dict(item_filter="", work_order_filter="") + if "item" in filters: + production_item = filters.get("item") + filter_conditions.update( + {"item_filter": f"AND wo.production_item='{production_item}'"} + ) + if "work_order" in filters: + work_order_name = filters.get("work_order") + filter_conditions.update( + {"work_order_filter": f"AND wo.name='{work_order_name}'"} + ) + return filter_conditions diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index 42c9d97cb5e..d4743d3a8ef 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -1,11 +1,13 @@ # 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 _, scrub from frappe.utils import getdate -from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period) + +from erpnext.stock.report.stock_analytics.stock_analytics import get_period, get_period_date_ranges + def execute(filters=None): columns = get_columns(filters) diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py index 81b1791ae81..55b1a3f2f9a 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py @@ -1,10 +1,11 @@ # 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.utils import flt + def execute(filters=None): columns, data = [], [] data = get_data(filters) @@ -27,8 +28,15 @@ def get_production_plan_item_details(filters, data, order_details): production_plan_doc = frappe.get_cached_doc("Production Plan", filters.get("production_plan")) for row in production_plan_doc.po_items: - work_order = frappe.get_cached_value("Work Order", {"production_plan_item": row.name, - "bom_no": row.bom_no, "production_item": row.item_code}, "name") + work_order = frappe.get_value( + "Work Order", + { + "production_plan_item": row.name, + "bom_no": row.bom_no, + "production_item": row.item_code + }, + "name" + ) if row.item_code not in itemwise_indent: itemwise_indent.setdefault(row.item_code, {}) @@ -39,10 +47,10 @@ def get_production_plan_item_details(filters, data, order_details): "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"), "qty": row.planned_qty, "document_type": "Work Order", - "document_name": work_order, + "document_name": work_order or "", "bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"), - "produced_qty": order_details.get((work_order, row.item_code)).get("produced_qty"), - "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code)).get("produced_qty")) + "produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0), + "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0)) }) get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details) @@ -53,11 +61,23 @@ def get_production_plan_sub_assembly_item_details(filters, row, production_plan_ subcontracted_item = (item.type_of_manufacturing == 'Subcontract') if subcontracted_item: - docname = frappe.get_cached_value("Purchase Order Item", - {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "parent") + docname = frappe.get_value( + "Purchase Order Item", + { + "production_plan_sub_assembly_item": item.name, + "docstatus": ("<", 2) + }, + "parent" + ) else: - docname = frappe.get_cached_value("Work Order", - {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "name") + docname = frappe.get_value( + "Work Order", + { + "production_plan_sub_assembly_item": item.name, + "docstatus": ("<", 2) + }, + "name" + ) data.append({ "indent": 1, @@ -65,10 +85,10 @@ def get_production_plan_sub_assembly_item_details(filters, row, production_plan_ "item_name": item.item_name, "qty": item.qty, "document_type": "Work Order" if not subcontracted_item else "Purchase Order", - "document_name": docname, + "document_name": docname or "", "bom_level": item.bom_level, - "produced_qty": order_details.get((docname, item.production_item)).get("produced_qty"), - "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item)).get("produced_qty")) + "produced_qty": order_details.get((docname, item.production_item), {}).get("produced_qty", 0), + "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item), {}).get("produced_qty", 0)) }) def get_work_order_details(filters, order_details): diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index 806d268ffde..8368db6374b 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -1,9 +1,10 @@ # 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 erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses # and bom_no is not null and bom_no !='' diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py index a12ac7f9d91..a0c4a43e90f 100644 --- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): columns, data = [], [] data = get_data(filters) diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py new file mode 100644 index 00000000000..1de472659eb --- /dev/null +++ b/erpnext/manufacturing/report/test_reports.py @@ -0,0 +1,64 @@ +import unittest +from typing import List, Tuple + +import frappe + +from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report + +DEFAULT_FILTERS = { + "company": "_Test Company", + "from_date": "2010-01-01", + "to_date": "2030-01-01", + "warehouse": "_Test Warehouse - _TC", +} + + +REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ + ("BOM Explorer", {"bom": frappe.get_last_doc("BOM").name}), + ("BOM Operations Time", {}), + ("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}), + ("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}), + ("Cost of Poor Quality Report", {}), + ("Downtime Analysis", {}), + ( + "Exponential Smoothing Forecasting", + { + "based_on_document": "Sales Order", + "based_on_field": "Qty", + "no_of_years": 3, + "periodicity": "Yearly", + "smoothing_constant": 0.3, + }, + ), + ("Job Card Summary", {"fiscal_year": "2021-2022"}), + ("Production Analytics", {"range": "Monthly"}), + ("Quality Inspection Summary", {}), + ("Process Loss Report", {}), + ("Work Order Stock Report", {}), + ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}), +] + + +if frappe.db.a_row_exists("Production Plan"): + REPORT_FILTER_TEST_CASES.append( + ("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name}) + ) + +OPTIONAL_FILTERS = { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item", + "item_group": "_Test Item Group", +} + + +class TestManufacturingReports(unittest.TestCase): + def test_execute_all_manufacturing_reports(self): + """Test that all script report in manufacturing modules are executable with supported filters""" + for report, filter in REPORT_FILTER_TEST_CASES: + execute_script_report( + report_name=report, + module="Manufacturing", + filters=filter, + default_filters=DEFAULT_FILTERS, + optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, + ) diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/__init__.py b/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/healthcare_service_unit_type/__init__.py rename to erpnext/manufacturing/report/work_order_consumed_materials/__init__.py diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js new file mode 100644 index 00000000000..b2428e85b74 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js @@ -0,0 +1,70 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Work Order Consumed Materials"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + reqd: 1 + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1 + }, + { + label: __("Work Order"), + fieldname: "name", + fieldtype: "Link", + options: "Work Order", + get_query: function() { + return { + filters: { + status: ["in", ["In Process", "Completed", "Stopped"]] + } + } + } + }, + { + label: __("Production Item"), + fieldname: "production_item", + fieldtype: "Link", + depends_on: "eval: !doc.name", + options: "Item" + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: ["In Process", "Completed", "Stopped"] + }, + { + label: __("Excess Materials Consumed"), + fieldname: "show_extra_consumed_materials", + fieldtype: "Check" + } + ], + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "raw_material_name" && data && data.extra_consumed_qty > 0 ) { + value = `
${value}
`; + } + + return value; + }, +}; diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json new file mode 100644 index 00000000000..2fc986aa36a --- /dev/null +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json @@ -0,0 +1,30 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-11-22 17:36:11.886939", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letter_head": "Gadgets International", + "modified": "2021-11-22 17:36:14.999091", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order Consumed Materials", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Work Order", + "report_name": "Work Order Consumed Materials", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Stock User" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py new file mode 100644 index 00000000000..052834807e1 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py @@ -0,0 +1,131 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ + + +def execute(filters=None): + columns, data = [], [] + columns = get_columns() + data = get_data(filters) + + return columns, data + +def get_data(report_filters): + fields = get_fields() + filters = get_filter_condition(report_filters) + + wo_items = {} + for d in frappe.get_all("Work Order", filters = filters, fields=fields): + d.extra_consumed_qty = 0.0 + if d.consumed_qty and d.consumed_qty > d.required_qty: + d.extra_consumed_qty = d.consumed_qty - d.required_qty + + if d.extra_consumed_qty or not report_filters.show_extra_consumed_materials: + wo_items.setdefault((d.name, d.production_item), []).append(d) + + data = [] + for key, wo_data in wo_items.items(): + for index, row in enumerate(wo_data): + if index != 0: + #If one work order has multiple raw materials then show parent data in the first row only + for field in ["name", "status", "production_item", "qty", "produced_qty"]: + row[field] = "" + + data.append(row) + + return data + +def get_fields(): + return ["`tabWork Order Item`.`parent`", "`tabWork Order Item`.`item_code` as raw_material_item_code", + "`tabWork Order Item`.`item_name` as raw_material_name", "`tabWork Order Item`.`required_qty`", + "`tabWork Order Item`.`transferred_qty`", "`tabWork Order Item`.`consumed_qty`", "`tabWork Order`.`status`", + "`tabWork Order`.`name`", "`tabWork Order`.`production_item`", "`tabWork Order`.`qty`", + "`tabWork Order`.`produced_qty`"] + +def get_filter_condition(report_filters): + filters = { + "docstatus": 1, "status": ("in", ["In Process", "Completed", "Stopped"]), + "creation": ("between", [report_filters.from_date, report_filters.to_date]) + } + + for field in ["name", "production_item", "company", "status"]: + value = report_filters.get(field) + if value: + key = f"`{field}`" + filters.update({key: value}) + + return filters + +def get_columns(): + return [ + { + "label": _("Id"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Work Order", + "width": 80 + }, + { + "label": _("Status"), + "fieldname": "status", + "fieldtype": "Data", + "width": 80 + }, + { + "label": _("Production Item"), + "fieldname": "production_item", + "fieldtype": "Link", + "options": "Item", + "width": 130 + }, + { + "label": _("Qty to Produce"), + "fieldname": "qty", + "fieldtype": "Float", + "width": 120 + }, + { + "label": _("Produced Qty"), + "fieldname": "produced_qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Raw Material Item"), + "fieldname": "raw_material_item_code", + "fieldtype": "Link", + "options": "Item", + "width": 150 + }, + { + "label": _("Item Name"), + "fieldname": "raw_material_name", + "width": 130 + }, + { + "label": _("Required Qty"), + "fieldname": "required_qty", + "fieldtype": "Float", + "width": 100 + }, + { + "label": _("Transferred Qty"), + "fieldname": "transferred_qty", + "fieldtype": "Float", + "width": 100 + }, + { + "label": _("Consumed Qty"), + "fieldname": "consumed_qty", + "fieldtype": "Float", + "width": 100 + }, + { + "label": _("Extra Consumed Qty"), + "fieldname": "extra_consumed_qty", + "fieldtype": "Float", + "width": 100 + } + ] diff --git a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py index 599a738f6f6..db0b239ae20 100644 --- a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py +++ b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py @@ -1,9 +1,10 @@ # Copyright (c) 2017, Velometro Mobility Inc and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from frappe.utils import cint + import frappe +from frappe.utils import cint + def execute(filters=None): wo_list = get_work_orders() diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js index eb23f17c477..832be2301c1 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -51,7 +51,7 @@ frappe.query_reports["Work Order Summary"] = { label: __("Status"), fieldname: "status", fieldtype: "Select", - options: ["", "Not Started", "In Process", "Completed", "Stopped"] + options: ["", "Not Started", "In Process", "Completed", "Stopped", "Closed"] }, { label: __("Sales Orders"), diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index d0766f9abe5..d7469ddfdd6 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -1,11 +1,14 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals +from collections import defaultdict + import frappe -from frappe.utils import date_diff, today, getdate, flt from frappe import _ -from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period) +from frappe.utils import date_diff, flt, getdate, today + +from erpnext.stock.report.stock_analytics.stock_analytics import get_period, get_period_date_ranges + def execute(filters=None): columns, data = [], [] @@ -56,21 +59,16 @@ def get_chart_data(data, filters): return get_chart_based_on_qty(data, filters) def get_chart_based_on_status(data): - labels = ["Completed", "In Process", "Stopped", "Not Started"] + labels = frappe.get_meta("Work Order").get_options("status").split("\n") + if "" in labels: + labels.remove("") - status_wise_data = { - "Not Started": 0, - "In Process": 0, - "Stopped": 0, - "Completed": 0, - "Draft": 0 - } + status_wise_data = defaultdict(int) for d in data: status_wise_data[d.status] += 1 - values = [status_wise_data["Completed"], status_wise_data["In Process"], - status_wise_data["Stopped"], status_wise_data["Not Started"]] + values = [status_wise_data[label] for label in labels] chart = { "data": { diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json index 84eabcd2bdb..65b4d026395 100644 --- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -1,24 +1,13 @@ { - "category": "", - "charts": [ - { - "chart_name": "Produced Quantity" - } - ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Manufacturing\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Plan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Forecasting\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM Stock Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Planning Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Production\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bill of Materials\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", + "charts": [], + "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", "creation": "2020-03-02 17:11:37.032604", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "organization", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Manufacturing", "links": [ { @@ -147,14 +136,6 @@ "onboard": 0, "type": "Link" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Reports", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, { "dependencies": "Work Order", "hidden": 0, @@ -302,17 +283,131 @@ "link_type": "DocType", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "link_count": 10, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Production Planning Report", + "link_count": 0, + "link_to": "Production Planning Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Work Order Summary", + "link_count": 0, + "link_to": "Work Order Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Quality Inspection", + "hidden": 0, + "is_query_report": 1, + "label": "Quality Inspection Summary", + "link_count": 0, + "link_to": "Quality Inspection Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Downtime Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Downtime Analysis", + "link_count": 0, + "link_to": "Downtime Analysis", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Job Card", + "hidden": 0, + "is_query_report": 1, + "label": "Job Card Summary", + "link_count": 0, + "link_to": "Job Card Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Search", + "link_count": 0, + "link_to": "BOM Search", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Stock Report", + "link_count": 0, + "link_to": "BOM Stock Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Production Analytics", + "link_count": 0, + "link_to": "Production Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Operations Time", + "link_count": 0, + "link_to": "BOM Operations Time", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Work Order Consumed Materials", + "link_count": 0, + "link_to": "Work Order Consumed Materials", + "link_type": "Report", + "onboard": 0, + "type": "Link" } ], - "modified": "2021-08-05 12:16:00.825741", + "modified": "2021-11-22 17:55:03.524496", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", - "onboarding": "Manufacturing", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "Manufacturing", "roles": [], diff --git a/erpnext/modules.txt b/erpnext/modules.txt index 62f5dce8460..15a24a746f7 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -15,13 +15,11 @@ Portal Maintenance Education Regional -Healthcare Restaurant Agriculture ERPNext Integrations Non Profit Hotels -Hub Node Quality Management Communication Loan Management diff --git a/erpnext/non_profit/doctype/certification_application/certification_application.py b/erpnext/non_profit/doctype/certification_application/certification_application.py index d4fc76bbfa5..cbbe191fbac 100644 --- a/erpnext/non_profit/doctype/certification_application/certification_application.py +++ b/erpnext/non_profit/doctype/certification_application/certification_application.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class CertificationApplication(Document): pass diff --git a/erpnext/non_profit/doctype/certification_application/test_certification_application.js b/erpnext/non_profit/doctype/certification_application/test_certification_application.js deleted file mode 100644 index 40e94864d49..00000000000 --- a/erpnext/non_profit/doctype/certification_application/test_certification_application.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Certification Application", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Certification Application - () => frappe.tests.make('Certification Application', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/certification_application/test_certification_application.py b/erpnext/non_profit/doctype/certification_application/test_certification_application.py index 30cb8c0acdf..8687b4daf4b 100644 --- a/erpnext/non_profit/doctype/certification_application/test_certification_application.py +++ b/erpnext/non_profit/doctype/certification_application/test_certification_application.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestCertificationApplication(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/certified_consultant/certified_consultant.py b/erpnext/non_profit/doctype/certified_consultant/certified_consultant.py index 3bc6ed74c26..47361cc39ea 100644 --- a/erpnext/non_profit/doctype/certified_consultant/certified_consultant.py +++ b/erpnext/non_profit/doctype/certified_consultant/certified_consultant.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class CertifiedConsultant(Document): pass diff --git a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js b/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js deleted file mode 100644 index f6a72a43271..00000000000 --- a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Certified Consultant", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Certified Consultant - () => frappe.tests.make('Certified Consultant', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.py b/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.py index 19b485db1fb..d10353c1e47 100644 --- a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.py +++ b/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestCertifiedConsultant(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/chapter/chapter.py b/erpnext/non_profit/doctype/chapter/chapter.py index e9554b1f55c..c01b1ef3e42 100644 --- a/erpnext/non_profit/doctype/chapter/chapter.py +++ b/erpnext/non_profit/doctype/chapter/chapter.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.website.website_generator import WebsiteGenerator + class Chapter(WebsiteGenerator): _website = frappe._dict( condition_field = "published", diff --git a/erpnext/non_profit/doctype/chapter/test_chapter.js b/erpnext/non_profit/doctype/chapter/test_chapter.js deleted file mode 100644 index e30d6a5bf9b..00000000000 --- a/erpnext/non_profit/doctype/chapter/test_chapter.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Chapter", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Chapter - () => frappe.tests.make('Chapter', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/chapter/test_chapter.py b/erpnext/non_profit/doctype/chapter/test_chapter.py index d757a1f9159..98601efcf23 100644 --- a/erpnext/non_profit/doctype/chapter/test_chapter.py +++ b/erpnext/non_profit/doctype/chapter/test_chapter.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestChapter(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/chapter_member/chapter_member.py b/erpnext/non_profit/doctype/chapter_member/chapter_member.py index a1b25f2d4e2..80c0446ee5a 100644 --- a/erpnext/non_profit/doctype/chapter_member/chapter_member.py +++ b/erpnext/non_profit/doctype/chapter_member/chapter_member.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class ChapterMember(Document): pass diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index 9aa7e13433c..54bc94b755c 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -1,17 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -import six + import json -from frappe.model.document import Document + +import frappe from frappe import _ -from frappe.utils import getdate, flt, get_link_to_form from frappe.email import sendmail_to_system_managers +from frappe.model.document import Document +from frappe.utils import flt, get_link_to_form, getdate + from erpnext.non_profit.doctype.membership.membership import verify_signature + class Donation(Document): def validate(self): if not self.donor or not frappe.db.exists('Donor', self.donor): @@ -81,7 +82,7 @@ def capture_razorpay_donations(*args, **kwargs): notify_failure(log) return { 'status': 'Failed', 'reason': e } - if isinstance(data, six.string_types): + if isinstance(data, str): data = json.loads(data) data = frappe._dict(data) @@ -167,7 +168,7 @@ def create_donor(payment): def get_company_for_donations(): company = frappe.db.get_single_value('Non Profit Settings', 'donation_company') if not company: - from erpnext.healthcare.setup import get_company + from erpnext.non_profit.utils import get_company company = get_company() return company diff --git a/erpnext/non_profit/doctype/donation/donation_dashboard.py b/erpnext/non_profit/doctype/donation/donation_dashboard.py index 3da89423d37..492ad621718 100644 --- a/erpnext/non_profit/doctype/donation/donation_dashboard.py +++ b/erpnext/non_profit/doctype/donation/donation_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'donation', diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py index b206f54523e..5fa731a6aa3 100644 --- a/erpnext/non_profit/doctype/donation/test_donation.py +++ b/erpnext/non_profit/doctype/donation/test_donation.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest + from erpnext.non_profit.doctype.donation.donation import create_donation + class TestDonation(unittest.TestCase): def setUp(self): create_donor_type() diff --git a/erpnext/non_profit/doctype/donor/donor.py b/erpnext/non_profit/doctype/donor/donor.py index ab6a197ed51..058321b1591 100644 --- a/erpnext/non_profit/doctype/donor/donor.py +++ b/erpnext/non_profit/doctype/donor/donor.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from frappe.model.document import Document + from frappe.contacts.address_and_contact import load_address_and_contact +from frappe.model.document import Document + class Donor(Document): def onload(self): diff --git a/erpnext/non_profit/doctype/donor/test_donor.py b/erpnext/non_profit/doctype/donor/test_donor.py index 3b6724eb63a..fe591c8e72c 100644 --- a/erpnext/non_profit/doctype/donor/test_donor.py +++ b/erpnext/non_profit/doctype/donor/test_donor.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestDonor(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/donor_type/donor_type.py b/erpnext/non_profit/doctype/donor_type/donor_type.py index e9262ac2a52..17dca899d56 100644 --- a/erpnext/non_profit/doctype/donor_type/donor_type.py +++ b/erpnext/non_profit/doctype/donor_type/donor_type.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class DonorType(Document): pass diff --git a/erpnext/non_profit/doctype/donor_type/test_donor_type.js b/erpnext/non_profit/doctype/donor_type/test_donor_type.js deleted file mode 100644 index 22dc18ed76a..00000000000 --- a/erpnext/non_profit/doctype/donor_type/test_donor_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Donor Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Member - () => frappe.tests.make('Donor Type', [ - // values to be set - {donor_type: 'Test Organization'}, - ]), - () => { - assert.equal(cur_frm.doc.donor_type, 'Test Organization'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/donor_type/test_donor_type.py b/erpnext/non_profit/doctype/donor_type/test_donor_type.py index e7939136b71..d433733ee25 100644 --- a/erpnext/non_profit/doctype/donor_type/test_donor_type.py +++ b/erpnext/non_profit/doctype/donor_type/test_donor_type.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import unittest + class TestDonorType(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/grant_application/grant_application.py b/erpnext/non_profit/doctype/grant_application/grant_application.py index b810fd027af..cc5e1b1442d 100644 --- a/erpnext/non_profit/doctype/grant_application/grant_application.py +++ b/erpnext/non_profit/doctype/grant_application/grant_application.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.website.website_generator import WebsiteGenerator from frappe.contacts.address_and_contact import load_address_and_contact from frappe.utils import get_url +from frappe.website.website_generator import WebsiteGenerator + class GrantApplication(WebsiteGenerator): _website = frappe._dict( diff --git a/erpnext/non_profit/doctype/grant_application/test_grant_application.py b/erpnext/non_profit/doctype/grant_application/test_grant_application.py index da16acfaacb..ef267d7af86 100644 --- a/erpnext/non_profit/doctype/grant_application/test_grant_application.py +++ b/erpnext/non_profit/doctype/grant_application/test_grant_application.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestGrantApplication(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 67828d6efc8..4d80e57eccf 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -1,16 +1,17 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.model.document import Document from frappe.contacts.address_and_contact import load_address_and_contact -from frappe.utils import cint, get_link_to_form from frappe.integrations.utils import get_payment_gateway_controller +from frappe.model.document import Document +from frappe.utils import cint, get_link_to_form + from erpnext.non_profit.doctype.membership_type.membership_type import get_membership_type + class Member(Document): def onload(self): """Load address and contacts in `__onload`""" diff --git a/erpnext/non_profit/doctype/member/member_dashboard.py b/erpnext/non_profit/doctype/member/member_dashboard.py index 743db2513af..0e31e3ceb83 100644 --- a/erpnext/non_profit/doctype/member/member_dashboard.py +++ b/erpnext/non_profit/doctype/member/member_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'heatmap': True, diff --git a/erpnext/non_profit/doctype/member/test_member.py b/erpnext/non_profit/doctype/member/test_member.py index 748a500deec..46f14ed1312 100644 --- a/erpnext/non_profit/doctype/member/test_member.py +++ b/erpnext/non_profit/doctype/member/test_member.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestMember(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 88284f8bf3b..7151a3cfdbb 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -1,20 +1,20 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import json -import frappe -import six -import os from datetime import datetime -from frappe.model.document import Document -from frappe.email import sendmail_to_system_managers -from frappe.utils import add_days, add_years, nowdate, getdate, add_months, get_link_to_form -from erpnext.non_profit.doctype.member.member import create_member + +import frappe from frappe import _ +from frappe.email import sendmail_to_system_managers +from frappe.model.document import Document +from frappe.utils import add_days, add_months, add_years, get_link_to_form, getdate, nowdate + import erpnext from erpnext import get_company_currency +from erpnext.non_profit.doctype.member.member import create_member + class Membership(Document): def validate(self): @@ -208,7 +208,7 @@ def get_member_based_on_subscription(subscription_id, email=None, customer_id=No try: return frappe.get_doc("Member", members[0]["name"]) - except: + except Exception: return None @@ -343,7 +343,7 @@ def process_request_data(data): notify_failure(log) return {"status": "Failed", "reason": e} - if isinstance(data, six.string_types): + if isinstance(data, str): data = json.loads(data) data = frappe._dict(data) @@ -353,7 +353,7 @@ def process_request_data(data): def get_company_for_memberships(): company = frappe.db.get_single_value("Non Profit Settings", "company") if not company: - from erpnext.healthcare.setup import get_company + from erpnext.non_profit.utils import get_company company = get_company() return company @@ -394,7 +394,7 @@ def notify_failure(log): """.format(get_link_to_form("Error Log", log.name)) sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content) - except: + except Exception: pass @@ -403,7 +403,7 @@ def get_plan_from_razorpay_id(plan_id): try: return plan[0]["name"] - except: + except Exception: return None diff --git a/erpnext/non_profit/doctype/membership/test_membership.js b/erpnext/non_profit/doctype/membership/test_membership.js deleted file mode 100644 index 24c85c61576..00000000000 --- a/erpnext/non_profit/doctype/membership/test_membership.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Membership", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Membership - () => frappe.tests.make('Membership', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py index 5c876dfedfb..38a33a7ec68 100644 --- a/erpnext/non_profit/doctype/membership/test_membership.py +++ b/erpnext/non_profit/doctype/membership/test_membership.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + import unittest + import frappe +from frappe.utils import add_months, nowdate + import erpnext from erpnext.non_profit.doctype.member.member import create_member from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription -from frappe.utils import nowdate, add_months + class TestMembership(unittest.TestCase): def setUp(self): diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.py b/erpnext/non_profit/doctype/membership_type/membership_type.py index c712b99c3b8..b4464215715 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.py +++ b/erpnext/non_profit/doctype/membership_type/membership_type.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from frappe.model.document import Document + import frappe from frappe import _ +from frappe.model.document import Document + class MembershipType(Document): def validate(self): diff --git a/erpnext/non_profit/doctype/membership_type/test_membership_type.py b/erpnext/non_profit/doctype/membership_type/test_membership_type.py index d2c9beed0df..98bc087acde 100644 --- a/erpnext/non_profit/doctype/membership_type/test_membership_type.py +++ b/erpnext/non_profit/doctype/membership_type/test_membership_type.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestMembershipType(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py index 50c93516adc..ace66055427 100644 --- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py +++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.integrations.utils import get_payment_gateway_controller from frappe.model.document import Document + class NonProfitSettings(Document): @frappe.whitelist() def generate_webhook_secret(self, field="membership_webhook_secret"): diff --git a/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py b/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py index 3f0ede32e59..51d1ba02eba 100644 --- a/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py +++ b/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestNonProfitSettings(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/volunteer/test_volunteer.py b/erpnext/non_profit/doctype/volunteer/test_volunteer.py index 6f3bee0edd9..0a0ab2cf342 100644 --- a/erpnext/non_profit/doctype/volunteer/test_volunteer.py +++ b/erpnext/non_profit/doctype/volunteer/test_volunteer.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestVolunteer(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/volunteer/volunteer.py b/erpnext/non_profit/doctype/volunteer/volunteer.py index 699868aeb79..b44d67dae39 100644 --- a/erpnext/non_profit/doctype/volunteer/volunteer.py +++ b/erpnext/non_profit/doctype/volunteer/volunteer.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from frappe.model.document import Document + from frappe.contacts.address_and_contact import load_address_and_contact +from frappe.model.document import Document + class Volunteer(Document): def onload(self): diff --git a/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.py b/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.py index dc9f8231944..fe7251876d2 100644 --- a/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.py +++ b/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class VolunteerSkill(Document): pass diff --git a/erpnext/non_profit/doctype/volunteer_type/test_volunteer_type.py b/erpnext/non_profit/doctype/volunteer_type/test_volunteer_type.py index 78f65c731a2..cef27c83a56 100644 --- a/erpnext/non_profit/doctype/volunteer_type/test_volunteer_type.py +++ b/erpnext/non_profit/doctype/volunteer_type/test_volunteer_type.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestVolunteerType(unittest.TestCase): pass diff --git a/erpnext/non_profit/doctype/volunteer_type/volunteer_type.py b/erpnext/non_profit/doctype/volunteer_type/volunteer_type.py index 9776402a43f..3b1ae1a88e0 100644 --- a/erpnext/non_profit/doctype/volunteer_type/volunteer_type.py +++ b/erpnext/non_profit/doctype/volunteer_type/volunteer_type.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class VolunteerType(Document): pass diff --git a/erpnext/non_profit/report/expiring_memberships/expiring_memberships.py b/erpnext/non_profit/report/expiring_memberships/expiring_memberships.py index 122db45ea4e..3ddbfdc3b0d 100644 --- a/erpnext/non_profit/report/expiring_memberships/expiring_memberships.py +++ b/erpnext/non_profit/report/expiring_memberships/expiring_memberships.py @@ -1,9 +1,10 @@ # 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 _,msgprint +from frappe import _ + def execute(filters=None): columns = get_columns(filters) diff --git a/erpnext/non_profit/utils.py b/erpnext/non_profit/utils.py new file mode 100644 index 00000000000..47ea5f57832 --- /dev/null +++ b/erpnext/non_profit/utils.py @@ -0,0 +1,12 @@ +import frappe + + +def get_company(): + company = frappe.defaults.get_defaults().company + if company: + return company + else: + company = frappe.get_list("Company", limit=1) + if company: + return company[0].name + return None diff --git a/erpnext/non_profit/web_form/certification_application/certification_application.py b/erpnext/non_profit/web_form/certification_application/certification_application.py index 2334f8b26d8..02e3e933330 100644 --- a/erpnext/non_profit/web_form/certification_application/certification_application.py +++ b/erpnext/non_profit/web_form/certification_application/certification_application.py @@ -1,7 +1,3 @@ -from __future__ import unicode_literals - -import frappe - def get_context(context): # do your magic here pass diff --git a/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.py b/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.py index 2334f8b26d8..02e3e933330 100644 --- a/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.py +++ b/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.py @@ -1,7 +1,3 @@ -from __future__ import unicode_literals - -import frappe - def get_context(context): # do your magic here pass diff --git a/erpnext/non_profit/web_form/grant_application/grant_application.py b/erpnext/non_profit/web_form/grant_application/grant_application.py index 186722a8bf0..3dfb381f657 100644 --- a/erpnext/non_profit/web_form/grant_application/grant_application.py +++ b/erpnext/non_profit/web_form/grant_application/grant_application.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_context(context): context.no_cache = True context.parents = [dict(label='View All ', diff --git a/erpnext/non_profit/workspace/non_profit/non_profit.json b/erpnext/non_profit/workspace/non_profit/non_profit.json index e6d4445945e..ba2f919d016 100644 --- a/erpnext/non_profit/workspace/non_profit/non_profit.json +++ b/erpnext/non_profit/workspace/non_profit/non_profit.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Member\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Profit Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Membership\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter Member\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Grant Application\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Membership\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Volunteer\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Donation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tax Exemption Certification (India)\", \"col\": 4}}]", "creation": "2020-03-02 17:23:47.811421", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "non-profit", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Non Profit", "links": [ { @@ -238,15 +231,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.146206", + "modified": "2021-08-05 12:16:01.146207", "modified_by": "Administrator", "module": "Non Profit", "name": "Non Profit", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "Non Profit", "roles": [], diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0a6a8bdbdca..26fb859e639 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -30,15 +30,12 @@ erpnext.patches.v11_0.add_default_email_template_for_leave erpnext.patches.v11_0.set_default_email_template_in_hr #08-06-2018 erpnext.patches.v11_0.uom_conversion_data #30-06-2018 erpnext.patches.v11_0.update_account_type_in_party_type -erpnext.patches.v11_0.rename_healthcare_doctype_and_fields erpnext.patches.v11_0.rename_supplier_type_to_supplier_group erpnext.patches.v10_1.transfer_subscription_to_auto_repeat erpnext.patches.v11_0.update_brand_in_item_price erpnext.patches.v11_0.create_default_success_action -erpnext.patches.v11_0.add_healthcare_service_unit_tree_root erpnext.patches.v11_0.rename_field_max_days_allowed erpnext.patches.v11_0.create_salary_structure_assignments -erpnext.patches.v11_0.rename_health_insurance erpnext.patches.v11_0.rebuild_tree_for_company erpnext.patches.v11_0.create_department_records_for_each_company erpnext.patches.v11_0.make_location_from_warehouse @@ -61,13 +58,7 @@ erpnext.patches.v11_0.set_department_for_doctypes erpnext.patches.v11_0.update_allow_transfer_for_manufacture erpnext.patches.v11_0.add_item_group_defaults erpnext.patches.v11_0.add_expense_claim_default_account -execute:frappe.delete_doc("Page", "hub") -erpnext.patches.v11_0.reset_publish_in_hub_for_all_items -erpnext.patches.v11_0.update_hub_url # 2018-08-31 # 2018-09-03 erpnext.patches.v11_0.make_job_card -erpnext.patches.v11_0.redesign_healthcare_billing_work_flow -erpnext.patches.v10_0.delete_hub_documents # 12-08-2018 -erpnext.patches.v11_0.rename_healthcare_fields erpnext.patches.v11_0.add_default_dispatch_notification_template erpnext.patches.v11_0.add_market_segments erpnext.patches.v11_0.add_sales_stages @@ -158,7 +149,6 @@ erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim erpnext.patches.v12_0.add_eway_bill_in_delivery_note erpnext.patches.v12_0.set_lead_title_field erpnext.patches.v12_0.set_permission_einvoicing -erpnext.patches.v12_0.set_published_in_hub_tracked_item erpnext.patches.v12_0.set_job_offer_applicant_email erpnext.patches.v12_0.create_irs_1099_field_united_states erpnext.patches.v12_0.move_bank_account_swift_number_to_bank @@ -169,7 +159,6 @@ erpnext.patches.v12_0.add_permission_in_lower_deduction erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom erpnext.patches.v12_0.rename_account_type_doctype erpnext.patches.v12_0.recalculate_requested_qty_in_bin -erpnext.patches.v12_0.update_healthcare_refactored_changes erpnext.patches.v12_0.set_total_batch_quantity erpnext.patches.v12_0.rename_mws_settings_fields erpnext.patches.v12_0.set_updated_purpose_in_pick_list @@ -178,7 +167,6 @@ erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123 erpnext.patches.v12_0.fix_quotation_expired_status -erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.rename_pos_closing_doctype erpnext.patches.v13_0.replace_pos_payment_mode_table #2020-12-29 erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22 @@ -196,7 +184,6 @@ execute:frappe.reload_doctype('Dashboard') execute:frappe.reload_doc('desk', 'doctype', 'number_card_link') execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo -erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2021-04-16 erpnext.patches.v12_0.update_bom_in_so_mr execute:frappe.delete_doc("Report", "Department Analytics") execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True) @@ -214,6 +201,7 @@ erpnext.patches.v13_0.delete_old_sales_reports execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 +erpnext.patches.v12_0.create_itc_reversal_custom_fields erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020 erpnext.patches.v12_0.add_taxjar_integration_field @@ -221,7 +209,6 @@ erpnext.patches.v12_0.fix_percent_complete_for_projects erpnext.patches.v13_0.delete_report_requested_items_to_order erpnext.patches.v12_0.update_item_tax_template_company erpnext.patches.v13_0.move_branch_code_to_bank_account -erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes erpnext.patches.v13_0.add_standard_navbar_items #2021-03-24 erpnext.patches.v13_0.stock_entry_enhancements erpnext.patches.v12_0.update_state_code_for_daman_and_diu @@ -235,7 +222,6 @@ erpnext.patches.v13_0.set_youtube_video_id erpnext.patches.v13_0.set_app_name erpnext.patches.v13_0.print_uom_after_quantity_patch erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account -erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.update_reason_for_resignation_in_employee execute:frappe.delete_doc("Report", "Quoted Item Comparison") @@ -250,23 +236,20 @@ erpnext.patches.v13_0.create_uae_pos_invoice_fields erpnext.patches.v13_0.update_project_template_tasks erpnext.patches.v13_0.set_company_in_leave_ledger_entry erpnext.patches.v13_0.convert_qi_parameter_to_link_field -erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes -erpnext.patches.v12_0.update_vehicle_no_reqd_condition +erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') -erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status -erpnext.patches.v12_0.create_itc_reversal_custom_fields erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 @@ -295,6 +278,41 @@ erpnext.patches.v13_0.update_tds_check_field #3 erpnext.patches.v13_0.add_custom_field_for_south_africa #2 erpnext.patches.v13_0.update_recipient_email_digest erpnext.patches.v13_0.shopify_deprecation_warning +erpnext.patches.v13_0.remove_bad_selling_defaults +erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.einvoicing_deprecation_warning +execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings") +execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category") erpnext.patches.v14_0.delete_einvoicing_doctypes +erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021 +erpnext.patches.v13_0.set_operation_time_based_on_operating_cost +erpnext.patches.v13_0.validate_options_for_data_field +erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021 +erpnext.patches.v14_0.delete_shopify_doctypes +erpnext.patches.v13_0.fix_invoice_statuses +erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item +erpnext.patches.v13_0.update_dates_in_tax_withholding_category +erpnext.patches.v14_0.update_opportunity_currency_fields +erpnext.patches.v13_0.gst_fields_for_pos_invoice +erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes +erpnext.patches.v13_0.trim_sales_invoice_custom_field_length +erpnext.patches.v13_0.create_custom_field_for_finance_book +erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries #2 +erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry +erpnext.patches.v13_0.set_status_in_maintenance_schedule_table +erpnext.patches.v13_0.add_default_interview_notification_templates +erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting +erpnext.patches.v13_0.requeue_failed_reposts +erpnext.patches.v13_0.update_job_card_status +erpnext.patches.v12_0.update_production_plan_status +erpnext.patches.v13_0.healthcare_deprecation_warning +erpnext.patches.v13_0.item_naming_series_not_mandatory +erpnext.patches.v14_0.delete_healthcare_doctypes +erpnext.patches.v13_0.update_category_in_ltds_certificate +erpnext.patches.v13_0.create_pan_field_for_india #2 +erpnext.patches.v14_0.delete_hub_doctypes +erpnext.patches.v13_0.create_ksa_vat_custom_fields +erpnext.patches.v14_0.migrate_crm_settings +erpnext.patches.v13_0.rename_ksa_qr_field +erpnext.patches.v13_0.disable_ksa_print_format_for_others diff --git a/erpnext/patches/__init__.py b/erpnext/patches/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/patches/__init__.py +++ b/erpnext/patches/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v10_0/add_default_cash_flow_mappers.py b/erpnext/patches/v10_0/add_default_cash_flow_mappers.py index d607b2f745e..165ca0243bf 100644 --- a/erpnext/patches/v10_0/add_default_cash_flow_mappers.py +++ b/erpnext/patches/v10_0/add_default_cash_flow_mappers.py @@ -1,9 +1,9 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + from erpnext.setup.install import create_default_cash_flow_mapper_templates diff --git a/erpnext/patches/v10_0/delete_hub_documents.py b/erpnext/patches/v10_0/delete_hub_documents.py deleted file mode 100644 index f6a14998956..00000000000 --- a/erpnext/patches/v10_0/delete_hub_documents.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - for dt, dn in (("Page", "Hub"), ("DocType", "Hub Settings"), ("DocType", "Hub Category")): - frappe.delete_doc(dt, dn, ignore_missing=True) - - if frappe.db.exists("DocType", "Data Migration Plan"): - data_migration_plans = frappe.get_all("Data Migration Plan", filters={"module": 'Hub Node'}) - for plan in data_migration_plans: - plan_doc = frappe.get_doc("Data Migration Plan", plan.name) - for m in plan_doc.get("mappings"): - frappe.delete_doc("Data Migration Mapping", m.mapping, force=True) - docs = frappe.get_all("Data Migration Run", filters={"data_migration_plan": plan.name}) - for doc in docs: - frappe.delete_doc("Data Migration Run", doc.name) - frappe.delete_doc("Data Migration Plan", plan.name) diff --git a/erpnext/patches/v10_0/fichier_des_ecritures_comptables_for_france.py b/erpnext/patches/v10_0/fichier_des_ecritures_comptables_for_france.py index 0315ae72a7d..cdf5ba29141 100644 --- a/erpnext/patches/v10_0/fichier_des_ecritures_comptables_for_france.py +++ b/erpnext/patches/v10_0/fichier_des_ecritures_comptables_for_france.py @@ -1,10 +1,12 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + from erpnext.setup.doctype.company.company import install_country_fixtures + def execute(): frappe.reload_doc('regional', 'report', 'fichier_des_ecritures_comptables_[fec]') for d in frappe.get_all('Company', filters = {'country': 'France'}): diff --git a/erpnext/patches/v10_0/item_barcode_childtable_migrate.py b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py index ec9c6c3b760..ffff95d223c 100644 --- a/erpnext/patches/v10_0/item_barcode_childtable_migrate.py +++ b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py @@ -1,7 +1,6 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe diff --git a/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py b/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py index daa258e8825..fd511849b2b 100644 --- a/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py +++ b/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py @@ -1,7 +1,7 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe diff --git a/erpnext/patches/v10_0/rename_offer_letter_to_job_offer.py b/erpnext/patches/v10_0/rename_offer_letter_to_job_offer.py index f832936b10a..a2deab62258 100644 --- a/erpnext/patches/v10_0/rename_offer_letter_to_job_offer.py +++ b/erpnext/patches/v10_0/rename_offer_letter_to_job_offer.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): if frappe.db.table_exists("Offer Letter") and not frappe.db.table_exists("Job Offer"): frappe.rename_doc("DocType", "Offer Letter", "Job Offer", force=True) diff --git a/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py b/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py index a9dd3103100..525d1ff2042 100644 --- a/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py +++ b/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc("accounts", "doctype", "pricing_rule") diff --git a/erpnext/patches/v10_0/set_currency_in_pricing_rule.py b/erpnext/patches/v10_0/set_currency_in_pricing_rule.py index c4139312d9f..3f3d42400ab 100644 --- a/erpnext/patches/v10_0/set_currency_in_pricing_rule.py +++ b/erpnext/patches/v10_0/set_currency_in_pricing_rule.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doctype("Pricing Rule") diff --git a/erpnext/patches/v10_0/update_translatable_fields.py b/erpnext/patches/v10_0/update_translatable_fields.py index 9d6bda71685..471f53704de 100644 --- a/erpnext/patches/v10_0/update_translatable_fields.py +++ b/erpnext/patches/v10_0/update_translatable_fields.py @@ -1,9 +1,6 @@ -#-*- coding: utf-8 -*- - -from __future__ import unicode_literals - import frappe + def execute(): ''' Enable translatable in these fields diff --git a/erpnext/patches/v10_1/transfer_subscription_to_auto_repeat.py b/erpnext/patches/v10_1/transfer_subscription_to_auto_repeat.py index 3d1a88e800c..6530b815cc1 100644 --- a/erpnext/patches/v10_1/transfer_subscription_to_auto_repeat.py +++ b/erpnext/patches/v10_1/transfer_subscription_to_auto_repeat.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field diff --git a/erpnext/patches/v11_0/add_default_dispatch_notification_template.py b/erpnext/patches/v11_0/add_default_dispatch_notification_template.py index f4c18955390..08006ad01b1 100644 --- a/erpnext/patches/v11_0/add_default_dispatch_notification_template.py +++ b/erpnext/patches/v11_0/add_default_dispatch_notification_template.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import os import frappe diff --git a/erpnext/patches/v11_0/add_default_email_template_for_leave.py b/erpnext/patches/v11_0/add_default_email_template_for_leave.py index 0f1e4966231..fdf30469bf0 100644 --- a/erpnext/patches/v11_0/add_default_email_template_for_leave.py +++ b/erpnext/patches/v11_0/add_default_email_template_for_leave.py @@ -1,7 +1,9 @@ -from __future__ import unicode_literals -import frappe, os +import os + +import frappe from frappe import _ + def execute(): frappe.reload_doc("email", "doctype", "email_template") diff --git a/erpnext/patches/v11_0/add_expense_claim_default_account.py b/erpnext/patches/v11_0/add_expense_claim_default_account.py index a613bd88497..f5658c5b931 100644 --- a/erpnext/patches/v11_0/add_expense_claim_default_account.py +++ b/erpnext/patches/v11_0/add_expense_claim_default_account.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("setup", "doctype", "company") diff --git a/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py b/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py deleted file mode 100644 index a45f39d4340..00000000000 --- a/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ - -def execute(): - """ assign lft and rgt appropriately """ - if "Healthcare" not in frappe.get_active_domains(): - return - - frappe.reload_doc("healthcare", "doctype", "healthcare_service_unit") - frappe.reload_doc("healthcare", "doctype", "healthcare_service_unit_type") - company = frappe.get_value("Company", {"domain": "Healthcare"}, "name") - - if company: - frappe.get_doc({ - 'doctype': 'Healthcare Service Unit', - 'healthcare_service_unit_name': _('All Healthcare Service Units'), - 'is_group': 1, - 'company': company - }).insert(ignore_permissions=True) diff --git a/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py b/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py index 0243dfb38ed..7c99f580f7c 100644 --- a/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py +++ b/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("assets", "doctype", "Location") for dt in ("Account", "Cost Center", "File", "Employee", "Location", "Task", "Customer Group", "Sales Person", "Territory"): diff --git a/erpnext/patches/v11_0/add_item_group_defaults.py b/erpnext/patches/v11_0/add_item_group_defaults.py index 2a15ad1e2a3..026047a9612 100644 --- a/erpnext/patches/v11_0/add_item_group_defaults.py +++ b/erpnext/patches/v11_0/add_item_group_defaults.py @@ -1,9 +1,10 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): ''' diff --git a/erpnext/patches/v11_0/add_market_segments.py b/erpnext/patches/v11_0/add_market_segments.py index a8841ef3a44..6dcbf99e16a 100644 --- a/erpnext/patches/v11_0/add_market_segments.py +++ b/erpnext/patches/v11_0/add_market_segments.py @@ -1,9 +1,8 @@ -from __future__ import unicode_literals - import frappe -from frappe import _ + from erpnext.setup.setup_wizard.operations.install_fixtures import add_market_segments + def execute(): frappe.reload_doc('crm', 'doctype', 'market_segment') diff --git a/erpnext/patches/v11_0/add_permissions_in_gst_settings.py b/erpnext/patches/v11_0/add_permissions_in_gst_settings.py index 83b2a4cc09e..9df1b586e30 100644 --- a/erpnext/patches/v11_0/add_permissions_in_gst_settings.py +++ b/erpnext/patches/v11_0/add_permissions_in_gst_settings.py @@ -1,6 +1,8 @@ import frappe + from erpnext.regional.india.setup import add_permissions + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: diff --git a/erpnext/patches/v11_0/add_sales_stages.py b/erpnext/patches/v11_0/add_sales_stages.py index d06c6889ff7..064b72195f6 100644 --- a/erpnext/patches/v11_0/add_sales_stages.py +++ b/erpnext/patches/v11_0/add_sales_stages.py @@ -1,8 +1,8 @@ -from __future__ import unicode_literals import frappe -from frappe import _ + from erpnext.setup.setup_wizard.operations.install_fixtures import add_sale_stages + def execute(): frappe.reload_doc('crm', 'doctype', 'sales_stage') diff --git a/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py b/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py index 0a1a36007e5..573021270df 100644 --- a/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py +++ b/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('setup', 'doctype', 'currency_exchange') frappe.db.sql("""update `tabCurrency Exchange` set for_buying = 1, for_selling = 1""") diff --git a/erpnext/patches/v11_0/create_default_success_action.py b/erpnext/patches/v11_0/create_default_success_action.py index 31feff25b93..e7b412cc5f2 100644 --- a/erpnext/patches/v11_0/create_default_success_action.py +++ b/erpnext/patches/v11_0/create_default_success_action.py @@ -1,7 +1,8 @@ -from __future__ import unicode_literals import frappe + from erpnext.setup.install import create_default_success_action + def execute(): frappe.reload_doc("core", "doctype", "success_action") create_default_success_action() diff --git a/erpnext/patches/v11_0/create_department_records_for_each_company.py b/erpnext/patches/v11_0/create_department_records_for_each_company.py index e9b5950a133..034418c1371 100644 --- a/erpnext/patches/v11_0/create_department_records_for_each_company.py +++ b/erpnext/patches/v11_0/create_department_records_for_each_company.py @@ -1,8 +1,8 @@ -from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils.nestedset import rebuild_tree + def execute(): frappe.local.lang = frappe.db.get_default("lang") or 'en' diff --git a/erpnext/patches/v11_0/create_salary_structure_assignments.py b/erpnext/patches/v11_0/create_salary_structure_assignments.py index d3ea7a3c1c0..823eca19b07 100644 --- a/erpnext/patches/v11_0/create_salary_structure_assignments.py +++ b/erpnext/patches/v11_0/create_salary_structure_assignments.py @@ -1,11 +1,16 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + from datetime import datetime + +import frappe from frappe.utils import getdate -from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import DuplicateAssignment + +from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import ( + DuplicateAssignment, +) + def execute(): frappe.reload_doc('Payroll', 'doctype', 'Salary Structure') diff --git a/erpnext/patches/v11_0/drop_column_max_days_allowed.py b/erpnext/patches/v11_0/drop_column_max_days_allowed.py index 029f75a2258..f0803cb5c7f 100644 --- a/erpnext/patches/v11_0/drop_column_max_days_allowed.py +++ b/erpnext/patches/v11_0/drop_column_max_days_allowed.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): if frappe.db.exists("DocType", "Leave Type"): if 'max_days_allowed' in frappe.db.get_table_columns("Leave Type"): diff --git a/erpnext/patches/v11_0/ewaybill_fields_gst_india.py b/erpnext/patches/v11_0/ewaybill_fields_gst_india.py index 4247c788e33..5974e27059e 100644 --- a/erpnext/patches/v11_0/ewaybill_fields_gst_india.py +++ b/erpnext/patches/v11_0/ewaybill_fields_gst_india.py @@ -1,7 +1,8 @@ -from __future__ import unicode_literals import frappe + from erpnext.regional.india.setup import make_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: diff --git a/erpnext/patches/v11_0/hr_ux_cleanups.py b/erpnext/patches/v11_0/hr_ux_cleanups.py index 8d187965011..43c85042942 100644 --- a/erpnext/patches/v11_0/hr_ux_cleanups.py +++ b/erpnext/patches/v11_0/hr_ux_cleanups.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doctype('Employee') frappe.db.sql('update tabEmployee set first_name = employee_name') diff --git a/erpnext/patches/v11_0/inter_state_field_for_gst.py b/erpnext/patches/v11_0/inter_state_field_for_gst.py index 730eebc01ce..a1f159483bd 100644 --- a/erpnext/patches/v11_0/inter_state_field_for_gst.py +++ b/erpnext/patches/v11_0/inter_state_field_for_gst.py @@ -1,6 +1,7 @@ -from __future__ import unicode_literals import frappe -from erpnext.regional.india.setup import make_custom_fields + +from erpnext.regional.india.setup import make_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py index dfcf5ab2886..cd3869b3600 100644 --- a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py +++ b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py @@ -1,9 +1,9 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils.nestedset import rebuild_tree + def execute(): frappe.reload_doc('assets', 'doctype', 'asset_finance_book') diff --git a/erpnext/patches/v11_0/make_italian_localization_fields.py b/erpnext/patches/v11_0/make_italian_localization_fields.py index 29d25c9a2d4..8ff23a50d4b 100644 --- a/erpnext/patches/v11_0/make_italian_localization_fields.py +++ b/erpnext/patches/v11_0/make_italian_localization_fields.py @@ -1,11 +1,13 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -from erpnext.regional.italy.setup import make_custom_fields, setup_report -from erpnext.regional.italy import state_codes + import frappe +from erpnext.regional.italy import state_codes +from erpnext.regional.italy.setup import make_custom_fields, setup_report + + def execute(): company = frappe.get_all('Company', filters = {'country': 'Italy'}) if not company: diff --git a/erpnext/patches/v11_0/make_job_card.py b/erpnext/patches/v11_0/make_job_card.py index 9c41c0b991f..120e018805a 100644 --- a/erpnext/patches/v11_0/make_job_card.py +++ b/erpnext/patches/v11_0/make_job_card.py @@ -1,10 +1,12 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + from erpnext.manufacturing.doctype.work_order.work_order import create_job_card + def execute(): frappe.reload_doc('manufacturing', 'doctype', 'work_order') frappe.reload_doc('manufacturing', 'doctype', 'work_order_item') diff --git a/erpnext/patches/v11_0/make_location_from_warehouse.py b/erpnext/patches/v11_0/make_location_from_warehouse.py index 8c92b5180d9..ef6262be15e 100644 --- a/erpnext/patches/v11_0/make_location_from_warehouse.py +++ b/erpnext/patches/v11_0/make_location_from_warehouse.py @@ -1,10 +1,11 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.utils.nestedset import rebuild_tree + def execute(): if not frappe.db.get_value('Asset', {'docstatus': ('<', 2) }, 'name'): return frappe.reload_doc('assets', 'doctype', 'location') diff --git a/erpnext/patches/v11_0/make_quality_inspection_template.py b/erpnext/patches/v11_0/make_quality_inspection_template.py index 9720af41219..58c9fb9239f 100644 --- a/erpnext/patches/v11_0/make_quality_inspection_template.py +++ b/erpnext/patches/v11_0/make_quality_inspection_template.py @@ -1,9 +1,10 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('stock', 'doctype', 'quality_inspection_template') frappe.reload_doc('stock', 'doctype', 'item') diff --git a/erpnext/patches/v11_0/merge_land_unit_with_location.py b/erpnext/patches/v11_0/merge_land_unit_with_location.py index 7845da255a8..e1d0b127b9d 100644 --- a/erpnext/patches/v11_0/merge_land_unit_with_location.py +++ b/erpnext/patches/v11_0/merge_land_unit_with_location.py @@ -1,7 +1,6 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field diff --git a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py index 6da70b4ce38..bfc3fbc6084 100644 --- a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py +++ b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py @@ -1,9 +1,10 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): ''' @@ -30,7 +31,7 @@ def execute(): buying_cost_center, selling_cost_center, expense_account, income_account, default_supplier FROM `tabItem`; ''', companies[0].name) - except: + except Exception: pass else: item_details = frappe.db.sql(""" SELECT name, default_warehouse, diff --git a/erpnext/patches/v11_0/move_leave_approvers_from_employee.py b/erpnext/patches/v11_0/move_leave_approvers_from_employee.py index ef703d0ea71..80e5ef7f37a 100644 --- a/erpnext/patches/v11_0/move_leave_approvers_from_employee.py +++ b/erpnext/patches/v11_0/move_leave_approvers_from_employee.py @@ -1,8 +1,7 @@ -from __future__ import unicode_literals import frappe -from frappe import _ from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc("hr", "doctype", "department_approver") frappe.reload_doc("hr", "doctype", "employee") diff --git a/erpnext/patches/v11_0/rebuild_tree_for_company.py b/erpnext/patches/v11_0/rebuild_tree_for_company.py index 4cb74c7256c..cad9c6cd788 100644 --- a/erpnext/patches/v11_0/rebuild_tree_for_company.py +++ b/erpnext/patches/v11_0/rebuild_tree_for_company.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.utils.nestedset import rebuild_tree + def execute(): frappe.reload_doc("setup", "doctype", "company") rebuild_tree('Company', 'parent_company') diff --git a/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py b/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py deleted file mode 100644 index 7c8a822fa22..00000000000 --- a/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from erpnext.domains.healthcare import data -from frappe.modules import scrub, get_doctype_module - -sales_invoice_referenced_doc = { - "Patient Appointment": "sales_invoice", - "Patient Encounter": "invoice", - "Lab Test": "invoice", - "Lab Prescription": "invoice", - "Sample Collection": "invoice" -} - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'loyalty_program') - frappe.reload_doc('accounts', 'doctype', 'sales_invoice_item') - - if "Healthcare" not in frappe.get_active_domains(): - return - - healthcare_custom_field_in_sales_invoice() - for si_ref_doc in sales_invoice_referenced_doc: - if frappe.db.exists('DocType', si_ref_doc): - frappe.reload_doc(get_doctype_module(si_ref_doc), 'doctype', scrub(si_ref_doc)) - - if frappe.db.has_column(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc]) \ - and frappe.db.has_column(si_ref_doc, 'invoiced'): - # Set Reference DocType and Reference Docname - doc_list = frappe.db.sql(""" - select name from `tab{0}` - where {1} is not null - """.format(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc])) - if doc_list: - frappe.reload_doc(get_doctype_module("Sales Invoice"), 'doctype', 'sales_invoice') - for doc_id in doc_list: - invoice_id = frappe.db.get_value(si_ref_doc, doc_id[0], sales_invoice_referenced_doc[si_ref_doc]) - if frappe.db.exists("Sales Invoice", invoice_id): - if si_ref_doc == "Lab Test": - template = frappe.db.get_value("Lab Test", doc_id[0], "template") - if template: - item = frappe.db.get_value("Lab Test Template", template, "item") - if item: - frappe.db.sql("""update `tabSales Invoice Item` set reference_dt = '{0}', - reference_dn = '{1}' where parent = '{2}' and item_code='{3}'""".format\ - (si_ref_doc, doc_id[0], invoice_id, item)) - else: - invoice = frappe.get_doc("Sales Invoice", invoice_id) - for item_line in invoice.items: - if not item_line.reference_dn: - item_line.db_set({"reference_dt":si_ref_doc, "reference_dn": doc_id[0]}) - break - # Documents mark invoiced for submitted sales invoice - frappe.db.sql("""update `tab{0}` doc, `tabSales Invoice` si - set doc.invoiced = 1 where si.docstatus = 1 and doc.{1} = si.name - """.format(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc])) - -def healthcare_custom_field_in_sales_invoice(): - frappe.reload_doc('healthcare', 'doctype', 'patient') - frappe.reload_doc('healthcare', 'doctype', 'healthcare_practitioner') - if data['custom_fields']: - create_custom_fields(data['custom_fields']) - - frappe.db.sql(""" - delete from `tabCustom Field` - where fieldname = 'appointment' and options = 'Patient Appointment' - """) diff --git a/erpnext/patches/v11_0/refactor_autoname_naming.py b/erpnext/patches/v11_0/refactor_autoname_naming.py index dd5cb639b1b..1c4d8f1f79f 100644 --- a/erpnext/patches/v11_0/refactor_autoname_naming.py +++ b/erpnext/patches/v11_0/refactor_autoname_naming.py @@ -1,7 +1,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import print_function, unicode_literals import frappe from frappe.custom.doctype.property_setter.property_setter import make_property_setter diff --git a/erpnext/patches/v11_0/refactor_naming_series.py b/erpnext/patches/v11_0/refactor_naming_series.py index 9f231edea73..e0aa004e476 100644 --- a/erpnext/patches/v11_0/refactor_naming_series.py +++ b/erpnext/patches/v11_0/refactor_naming_series.py @@ -1,7 +1,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import print_function, unicode_literals import frappe from frappe.custom.doctype.property_setter.property_setter import make_property_setter @@ -15,7 +14,6 @@ doctype_series_map = { 'Blanket Order': 'MFG-BLR-.YYYY.-', 'C-Form': 'ACC-CF-.YYYY.-', 'Campaign': 'SAL-CAM-.YYYY.-', - 'Clinical Procedure': 'HLC-CPR-.YYYY.-', 'Course Schedule': 'EDU-CSH-.YYYY.-', 'Customer': 'CUST-.YYYY.-', 'Delivery Note': 'MAT-DN-.YYYY.-', @@ -27,12 +25,10 @@ doctype_series_map = { 'Fee Schedule': 'EDU-FSH-.YYYY.-', 'Fee Structure': 'EDU-FST-.YYYY.-', 'Fees': 'EDU-FEE-.YYYY.-', - 'Inpatient Record': 'HLC-INP-.YYYY.-', 'Installation Note': 'MAT-INS-.YYYY.-', 'Instructor': 'EDU-INS-.YYYY.-', 'Issue': 'ISS-.YYYY.-', 'Journal Entry': 'ACC-JV-.YYYY.-', - 'Lab Test': 'HLC-LT-.YYYY.-', 'Landed Cost Voucher': 'MAT-LCV-.YYYY.-', 'Lead': 'CRM-LEAD-.YYYY.-', 'Leave Allocation': 'HR-LAL-.YYYY.-', @@ -43,9 +39,6 @@ doctype_series_map = { 'Member': 'NPO-MEM-.YYYY.-', 'Opportunity': 'CRM-OPP-.YYYY.-', 'Packing Slip': 'MAT-PAC-.YYYY.-', - 'Patient': 'HLC-PAT-.YYYY.-', - 'Patient Encounter': 'HLC-ENC-.YYYY.-', - 'Patient Medical Record': 'HLC-PMR-.YYYY.-', 'Payment Entry': 'ACC-PAY-.YYYY.-', 'Payment Request': 'ACC-PRQ-.YYYY.-', 'Production Plan': 'MFG-PP-.YYYY.-', diff --git a/erpnext/patches/v11_0/remove_barcodes_field_from_copy_fields_to_variants.py b/erpnext/patches/v11_0/remove_barcodes_field_from_copy_fields_to_variants.py index 97ddd41ddb6..caf74f578de 100644 --- a/erpnext/patches/v11_0/remove_barcodes_field_from_copy_fields_to_variants.py +++ b/erpnext/patches/v11_0/remove_barcodes_field_from_copy_fields_to_variants.py @@ -1,5 +1,6 @@ import frappe + def execute(): '''Remove barcodes field from "Copy Fields to Variants" table because barcodes must be unique''' diff --git a/erpnext/patches/v11_0/remove_modules_setup_page.py b/erpnext/patches/v11_0/remove_modules_setup_page.py index bb0bdf59da1..91f4bc5d0e2 100644 --- a/erpnext/patches/v11_0/remove_modules_setup_page.py +++ b/erpnext/patches/v11_0/remove_modules_setup_page.py @@ -1,8 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.delete_doc("Page", "modules_setup") diff --git a/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py b/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py index 8eb70167447..8fa876dd743 100644 --- a/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py +++ b/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import frappe # this patch should have been included with this PR https://github.com/frappe/erpnext/pull/14302 diff --git a/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py b/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py index 923b23048df..c7a3aa2abd4 100644 --- a/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py +++ b/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe diff --git a/erpnext/patches/v11_0/rename_bom_wo_fields.py b/erpnext/patches/v11_0/rename_bom_wo_fields.py index 0e6036b0740..cab7d0a673f 100644 --- a/erpnext/patches/v11_0/rename_bom_wo_fields.py +++ b/erpnext/patches/v11_0/rename_bom_wo_fields.py @@ -1,10 +1,11 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.model.utils.rename_field import rename_field + def execute(): # updating column value to handle field change from Data to Currency changed_field = "base_scrap_material_cost" diff --git a/erpnext/patches/v11_0/rename_duplicate_item_code_values.py b/erpnext/patches/v11_0/rename_duplicate_item_code_values.py index 00ab562c353..61f3856e8eb 100644 --- a/erpnext/patches/v11_0/rename_duplicate_item_code_values.py +++ b/erpnext/patches/v11_0/rename_duplicate_item_code_values.py @@ -1,5 +1,6 @@ import frappe + def execute(): items = [] items = frappe.db.sql("""select item_code from `tabItem` group by item_code having count(*) > 1""", as_dict=True) diff --git a/erpnext/patches/v11_0/rename_field_max_days_allowed.py b/erpnext/patches/v11_0/rename_field_max_days_allowed.py index 4e99fac8224..4b55aa06bbf 100644 --- a/erpnext/patches/v11_0/rename_field_max_days_allowed.py +++ b/erpnext/patches/v11_0/rename_field_max_days_allowed.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.db.sql(""" UPDATE `tabLeave Type` diff --git a/erpnext/patches/v11_0/rename_health_insurance.py b/erpnext/patches/v11_0/rename_health_insurance.py deleted file mode 100644 index 06fc6151675..00000000000 --- a/erpnext/patches/v11_0/rename_health_insurance.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2018, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.rename_doc('DocType', 'Health Insurance', 'Employee Health Insurance', force=True) - frappe.reload_doc('hr', 'doctype', 'employee_health_insurance') diff --git a/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py b/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py deleted file mode 100644 index 9705681b33f..00000000000 --- a/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field -from frappe.modules import scrub, get_doctype_module - -field_rename_map = { - "Patient Encounter": [ - ["consultation_time", "encounter_time"], - ["consultation_date", "encounter_date"], - ["consultation_comment", "encounter_comment"], - ["physician", "practitioner"] - ], - "Fee Validity": [ - ["physician", "practitioner"] - ], - "Lab Test": [ - ["physician", "practitioner"] - ], - "Patient Appointment": [ - ["physician", "practitioner"], - ["referring_physician", "referring_practitioner"] - ], - "Procedure Prescription": [ - ["physician", "practitioner"] - ] -} - -doc_rename_map = { - "Physician Schedule Time Slot": "Healthcare Schedule Time Slot", - "Physician Schedule": "Practitioner Schedule", - "Physician Service Unit Schedule": "Practitioner Service Unit Schedule", - "Consultation": "Patient Encounter", - "Physician": "Healthcare Practitioner" -} - -def execute(): - for dt in doc_rename_map: - if frappe.db.exists('DocType', dt): - frappe.rename_doc('DocType', dt, doc_rename_map[dt], force=True) - - for dn in field_rename_map: - if frappe.db.exists('DocType', dn): - frappe.reload_doc(get_doctype_module(dn), "doctype", scrub(dn)) - - for dt, field_list in field_rename_map.items(): - if frappe.db.exists('DocType', dt): - for field in field_list: - if frappe.db.has_column(dt, field[0]): - rename_field(dt, field[0], field[1]) - - if frappe.db.exists('DocType', 'Practitioner Service Unit Schedule'): - if frappe.db.has_column('Practitioner Service Unit Schedule', 'parentfield'): - frappe.db.sql(""" - update `tabPractitioner Service Unit Schedule` set parentfield = 'practitioner_schedules' - where parentfield = 'physician_schedules' and parenttype = 'Healthcare Practitioner' - """) - - if frappe.db.exists("DocType", "Healthcare Practitioner"): - frappe.reload_doc("healthcare", "doctype", "healthcare_practitioner") - frappe.reload_doc("healthcare", "doctype", "practitioner_service_unit_schedule") - if frappe.db.has_column('Healthcare Practitioner', 'physician_schedule'): - for doc in frappe.get_all('Healthcare Practitioner'): - _doc = frappe.get_doc('Healthcare Practitioner', doc.name) - if _doc.physician_schedule: - _doc.append('practitioner_schedules', {'schedule': _doc.physician_schedule}) - _doc.save() diff --git a/erpnext/patches/v11_0/rename_healthcare_fields.py b/erpnext/patches/v11_0/rename_healthcare_fields.py deleted file mode 100644 index 9aeb433cff8..00000000000 --- a/erpnext/patches/v11_0/rename_healthcare_fields.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field -from frappe.modules import scrub, get_doctype_module - -lab_test_name = ["test_name", "lab_test_name"] -lab_test_code = ["test_code", "lab_test_code"] -lab_test_comment = ["test_comment", "lab_test_comment"] -lab_test_created = ["test_created", "lab_test_created"] -lab_test_template = ["test_template", "lab_test_template"] -lab_test_rate = ["test_rate", "lab_test_rate"] -lab_test_description = ["test_description", "lab_test_description"] -lab_test_group = ["test_group", "lab_test_group"] -lab_test_template_type = ["test_template_type", "lab_test_template_type"] -lab_test_uom = ["test_uom", "lab_test_uom"] -lab_test_normal_range = ["test_normal_range", "lab_test_normal_range"] -lab_test_event = ["test_event", "lab_test_event"] -lab_test_particulars = ["test_particulars", "lab_test_particulars"] - -field_rename_map = { - "Lab Test Template": [lab_test_name, lab_test_code, lab_test_rate, lab_test_description, - lab_test_group, lab_test_template_type, lab_test_uom, lab_test_normal_range], - "Normal Test Items": [lab_test_name, lab_test_comment, lab_test_uom, lab_test_event], - "Lab Test": [lab_test_name, lab_test_comment, lab_test_group], - "Lab Prescription": [lab_test_name, lab_test_code, lab_test_comment, lab_test_created], - "Lab Test Groups": [lab_test_template, lab_test_rate, lab_test_description], - "Lab Test UOM": [lab_test_uom], - "Normal Test Template": [lab_test_uom, lab_test_event], - "Special Test Items": [lab_test_particulars] -} - - -def execute(): - for dt, field_list in field_rename_map.items(): - if frappe.db.exists('DocType', dt): - frappe.reload_doc(get_doctype_module(dt), "doctype", scrub(dt)) - for field in field_list: - if frappe.db.has_column(dt, field[0]): - rename_field(dt, field[0], field[1]) - - if frappe.db.exists('DocType', 'Lab Prescription'): - if frappe.db.has_column('Lab Prescription', 'parentfield'): - frappe.db.sql(""" - update `tabLab Prescription` set parentfield = 'lab_test_prescription' - where parentfield = 'test_prescription' - """) - - if frappe.db.exists('DocType', 'Lab Test Groups'): - if frappe.db.has_column('Lab Test Groups', 'parentfield'): - frappe.db.sql(""" - update `tabLab Test Groups` set parentfield = 'lab_test_groups' - where parentfield = 'test_groups' - """) diff --git a/erpnext/patches/v11_0/rename_members_with_naming_series.py b/erpnext/patches/v11_0/rename_members_with_naming_series.py index 84f5518926f..95fb55d9b45 100644 --- a/erpnext/patches/v11_0/rename_members_with_naming_series.py +++ b/erpnext/patches/v11_0/rename_members_with_naming_series.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("non_profit", "doctype", "member") old_named_members = frappe.get_all("Member", filters = {"name": ("not like", "MEM-%")}) diff --git a/erpnext/patches/v11_0/rename_overproduction_percent_field.py b/erpnext/patches/v11_0/rename_overproduction_percent_field.py index fbf925d955c..c78ec5d0128 100644 --- a/erpnext/patches/v11_0/rename_overproduction_percent_field.py +++ b/erpnext/patches/v11_0/rename_overproduction_percent_field.py @@ -1,9 +1,10 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -from frappe.model.utils.rename_field import rename_field + import frappe +from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings') diff --git a/erpnext/patches/v11_0/rename_production_order_to_work_order.py b/erpnext/patches/v11_0/rename_production_order_to_work_order.py index 2f620f413ba..453a5710a1d 100644 --- a/erpnext/patches/v11_0/rename_production_order_to_work_order.py +++ b/erpnext/patches/v11_0/rename_production_order_to_work_order.py @@ -1,10 +1,11 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.rename_doc('DocType', 'Production Order', 'Work Order', force=True) frappe.reload_doc('manufacturing', 'doctype', 'work_order') diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py index c4b3838c71d..3f87550224d 100644 --- a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py +++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py @@ -1,9 +1,9 @@ -from __future__ import unicode_literals import frappe -from frappe.model.utils.rename_field import rename_field from frappe import _ +from frappe.model.utils.rename_field import rename_field from frappe.utils.nestedset import rebuild_tree + def execute(): if frappe.db.table_exists("Supplier Group"): frappe.reload_doc('setup', 'doctype', 'supplier_group') diff --git a/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py b/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py index d5ca4cc5749..f23a81494be 100644 --- a/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py +++ b/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc('projects', 'doctype', 'project') diff --git a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py b/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py deleted file mode 100644 index 56e95e03286..00000000000 --- a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('stock', 'doctype', 'item') - frappe.db.sql("""update `tabItem` set publish_in_hub = 0""") diff --git a/erpnext/patches/v11_0/set_default_email_template_in_hr.py b/erpnext/patches/v11_0/set_default_email_template_in_hr.py index 46223761095..ee083ca4b80 100644 --- a/erpnext/patches/v11_0/set_default_email_template_in_hr.py +++ b/erpnext/patches/v11_0/set_default_email_template_in_hr.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals -from frappe import _ import frappe +from frappe import _ + def execute(): hr_settings = frappe.get_single("HR Settings") diff --git a/erpnext/patches/v11_0/set_department_for_doctypes.py b/erpnext/patches/v11_0/set_department_for_doctypes.py index 175d2a189f3..b1098abb57a 100644 --- a/erpnext/patches/v11_0/set_department_for_doctypes.py +++ b/erpnext/patches/v11_0/set_department_for_doctypes.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import frappe # Set department value based on employee value diff --git a/erpnext/patches/v11_0/set_missing_gst_hsn_code.py b/erpnext/patches/v11_0/set_missing_gst_hsn_code.py index 8f8a545c410..ec75d454aa8 100644 --- a/erpnext/patches/v11_0/set_missing_gst_hsn_code.py +++ b/erpnext/patches/v11_0/set_missing_gst_hsn_code.py @@ -1,7 +1,8 @@ -from __future__ import unicode_literals import frappe + from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_html + def execute(): company = frappe.db.sql_list("select name from tabCompany where country = 'India'") if not company: diff --git a/erpnext/patches/v11_0/set_salary_component_properties.py b/erpnext/patches/v11_0/set_salary_component_properties.py index d8ce31f3076..99c3b0b7c46 100644 --- a/erpnext/patches/v11_0/set_salary_component_properties.py +++ b/erpnext/patches/v11_0/set_salary_component_properties.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('Payroll', 'doctype', 'salary_detail') frappe.reload_doc('Payroll', 'doctype', 'salary_component') diff --git a/erpnext/patches/v11_0/set_update_field_and_value_in_workflow_state.py b/erpnext/patches/v11_0/set_update_field_and_value_in_workflow_state.py index d0cabb38359..a44daaa1979 100644 --- a/erpnext/patches/v11_0/set_update_field_and_value_in_workflow_state.py +++ b/erpnext/patches/v11_0/set_update_field_and_value_in_workflow_state.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.model.workflow import get_workflow_name + def execute(): for doctype in ['Expense Claim', 'Leave Application']: diff --git a/erpnext/patches/v11_0/set_user_permissions_for_department.py b/erpnext/patches/v11_0/set_user_permissions_for_department.py index 2f90f14db3e..bb7ef877476 100644 --- a/erpnext/patches/v11_0/set_user_permissions_for_department.py +++ b/erpnext/patches/v11_0/set_user_permissions_for_department.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): user_permissions = frappe.db.sql("""select name, for_value from `tabUser Permission` where allow='Department'""", as_dict=1) diff --git a/erpnext/patches/v11_0/skip_user_permission_check_for_department.py b/erpnext/patches/v11_0/skip_user_permission_check_for_department.py index 4e72917547b..d3875773108 100644 --- a/erpnext/patches/v11_0/skip_user_permission_check_for_department.py +++ b/erpnext/patches/v11_0/skip_user_permission_check_for_department.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import frappe from frappe.desk.form.linked_with import get_linked_doctypes diff --git a/erpnext/patches/v11_0/uom_conversion_data.py b/erpnext/patches/v11_0/uom_conversion_data.py index 91470b3558a..81e547b16a8 100644 --- a/erpnext/patches/v11_0/uom_conversion_data.py +++ b/erpnext/patches/v11_0/uom_conversion_data.py @@ -1,5 +1,5 @@ -from __future__ import unicode_literals -import frappe, json +import frappe + def execute(): from erpnext.setup.setup_wizard.operations.install_fixtures import add_uom_data diff --git a/erpnext/patches/v11_0/update_account_type_in_party_type.py b/erpnext/patches/v11_0/update_account_type_in_party_type.py index dabaeffc94a..c66cef042d9 100644 --- a/erpnext/patches/v11_0/update_account_type_in_party_type.py +++ b/erpnext/patches/v11_0/update_account_type_in_party_type.py @@ -1,9 +1,10 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('setup', 'doctype', 'party_type') party_types = {'Customer': 'Receivable', 'Supplier': 'Payable', diff --git a/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py b/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py index 799e91a3e22..3e36a4bb90d 100644 --- a/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py +++ b/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py @@ -1,9 +1,10 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('stock', 'doctype', 'item') frappe.db.sql(""" update `tabItem` set include_item_in_manufacturing = 1 diff --git a/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py b/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py index 37a616c7021..f3a2ac6a655 100644 --- a/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py +++ b/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py @@ -1,9 +1,10 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('buying', 'doctype', 'buying_settings') frappe.db.set_value('Buying Settings', None, 'backflush_raw_materials_of_subcontract_based_on', 'BOM') diff --git a/erpnext/patches/v11_0/update_brand_in_item_price.py b/erpnext/patches/v11_0/update_brand_in_item_price.py index 977d84fefe8..ce1df78083f 100644 --- a/erpnext/patches/v11_0/update_brand_in_item_price.py +++ b/erpnext/patches/v11_0/update_brand_in_item_price.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('stock', 'doctype', 'item_price') diff --git a/erpnext/patches/v11_0/update_delivery_trip_status.py b/erpnext/patches/v11_0/update_delivery_trip_status.py index 42f017e04d2..35b95353b12 100755 --- a/erpnext/patches/v11_0/update_delivery_trip_status.py +++ b/erpnext/patches/v11_0/update_delivery_trip_status.py @@ -1,9 +1,10 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('setup', 'doctype', 'global_defaults', force=True) frappe.reload_doc('stock', 'doctype', 'delivery_trip') diff --git a/erpnext/patches/v11_0/update_department_lft_rgt.py b/erpnext/patches/v11_0/update_department_lft_rgt.py index 2b382037109..aff8e1556b9 100644 --- a/erpnext/patches/v11_0/update_department_lft_rgt.py +++ b/erpnext/patches/v11_0/update_department_lft_rgt.py @@ -1,9 +1,8 @@ -from __future__ import unicode_literals - import frappe from frappe import _ from frappe.utils.nestedset import rebuild_tree + def execute(): """ assign lft and rgt appropriately """ frappe.reload_doc("hr", "doctype", "department") diff --git a/erpnext/patches/v11_0/update_hub_url.py b/erpnext/patches/v11_0/update_hub_url.py deleted file mode 100644 index 6c6ca3c5c28..00000000000 --- a/erpnext/patches/v11_0/update_hub_url.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('hub_node', 'doctype', 'Marketplace Settings') - frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'marketplace_url', 'https://hubmarket.org') diff --git a/erpnext/patches/v11_0/update_sales_partner_type.py b/erpnext/patches/v11_0/update_sales_partner_type.py index b393926b237..ef58499f0f1 100644 --- a/erpnext/patches/v11_0/update_sales_partner_type.py +++ b/erpnext/patches/v11_0/update_sales_partner_type.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe import _ + def execute(): from erpnext.setup.setup_wizard.operations.install_fixtures import default_sales_partner_type diff --git a/erpnext/patches/v11_0/update_total_qty_field.py b/erpnext/patches/v11_0/update_total_qty_field.py index 9407256acfa..4e807b4f136 100644 --- a/erpnext/patches/v11_0/update_total_qty_field.py +++ b/erpnext/patches/v11_0/update_total_qty_field.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('buying', 'doctype', 'purchase_order') frappe.reload_doc('buying', 'doctype', 'supplier_quotation') diff --git a/erpnext/patches/v11_1/delete_bom_browser.py b/erpnext/patches/v11_1/delete_bom_browser.py index 2892674d374..9b5c169717a 100644 --- a/erpnext/patches/v11_1/delete_bom_browser.py +++ b/erpnext/patches/v11_1/delete_bom_browser.py @@ -1,8 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.delete_doc_if_exists('Page', 'bom-browser') diff --git a/erpnext/patches/v11_1/delete_scheduling_tool.py b/erpnext/patches/v11_1/delete_scheduling_tool.py index b7ad28a3fd6..6f3da92ee74 100644 --- a/erpnext/patches/v11_1/delete_scheduling_tool.py +++ b/erpnext/patches/v11_1/delete_scheduling_tool.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): if frappe.db.exists("DocType", "Scheduling Tool"): frappe.delete_doc("DocType", "Scheduling Tool", ignore_permissions=True) diff --git a/erpnext/patches/v11_1/make_job_card_time_logs.py b/erpnext/patches/v11_1/make_job_card_time_logs.py index b706e5c1ffb..100cd64f8fe 100644 --- a/erpnext/patches/v11_1/make_job_card_time_logs.py +++ b/erpnext/patches/v11_1/make_job_card_time_logs.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('manufacturing', 'doctype', 'job_card_time_log') diff --git a/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py b/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py index fc3ec74083a..d292d7ae432 100644 --- a/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py +++ b/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doctype("Quotation") frappe.db.sql(""" UPDATE `tabQuotation` set party_name = lead WHERE quotation_to = 'Lead' """) diff --git a/erpnext/patches/v11_1/rename_depends_on_lwp.py b/erpnext/patches/v11_1/rename_depends_on_lwp.py index 4c4b14fd4e7..4e71838aa06 100644 --- a/erpnext/patches/v11_1/rename_depends_on_lwp.py +++ b/erpnext/patches/v11_1/rename_depends_on_lwp.py @@ -1,11 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import scrub from frappe.model.utils.rename_field import rename_field + def execute(): for doctype in ("Salary Component", "Salary Detail"): if "depends_on_lwp" in frappe.db.get_table_columns(doctype): diff --git a/erpnext/patches/v11_1/renamed_delayed_item_report.py b/erpnext/patches/v11_1/renamed_delayed_item_report.py index 8e8725c8af6..c160b79d0e9 100644 --- a/erpnext/patches/v11_1/renamed_delayed_item_report.py +++ b/erpnext/patches/v11_1/renamed_delayed_item_report.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): for report in ["Delayed Order Item Summary", "Delayed Order Summary"]: if frappe.db.exists("Report", report): diff --git a/erpnext/patches/v11_1/set_default_action_for_quality_inspection.py b/erpnext/patches/v11_1/set_default_action_for_quality_inspection.py index b13239f7d12..672b7628bb4 100644 --- a/erpnext/patches/v11_1/set_default_action_for_quality_inspection.py +++ b/erpnext/patches/v11_1/set_default_action_for_quality_inspection.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): stock_settings = frappe.get_doc('Stock Settings') if stock_settings.default_warehouse and not frappe.db.exists("Warehouse", stock_settings.default_warehouse): diff --git a/erpnext/patches/v11_1/set_missing_opportunity_from.py b/erpnext/patches/v11_1/set_missing_opportunity_from.py index cb444b2e5dd..beec63af4b4 100644 --- a/erpnext/patches/v11_1/set_missing_opportunity_from.py +++ b/erpnext/patches/v11_1/set_missing_opportunity_from.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doctype("Opportunity") diff --git a/erpnext/patches/v11_1/set_missing_title_for_quotation.py b/erpnext/patches/v11_1/set_missing_title_for_quotation.py index e2ef3433d3e..93d9f0e7d89 100644 --- a/erpnext/patches/v11_1/set_missing_title_for_quotation.py +++ b/erpnext/patches/v11_1/set_missing_title_for_quotation.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doctype("Quotation") # update customer_name from Customer document if quotation_to is set to Customer diff --git a/erpnext/patches/v11_1/set_salary_details_submittable.py b/erpnext/patches/v11_1/set_salary_details_submittable.py index 6d847ec3d05..ac082b1ae22 100644 --- a/erpnext/patches/v11_1/set_salary_details_submittable.py +++ b/erpnext/patches/v11_1/set_salary_details_submittable.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.db.sql(""" update `tabSalary Structure` ss, `tabSalary Detail` sd diff --git a/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py b/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py index ec01fbb642e..0d4f3d2ce11 100644 --- a/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py +++ b/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.db.sql(""" update `tabMaterial Request` diff --git a/erpnext/patches/v11_1/set_variant_based_on.py b/erpnext/patches/v11_1/set_variant_based_on.py index 49a9a177246..2e06e63a8aa 100644 --- a/erpnext/patches/v11_1/set_variant_based_on.py +++ b/erpnext/patches/v11_1/set_variant_based_on.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.db.sql("""update tabItem set variant_based_on = 'Item Attribute' where ifnull(variant_based_on, '') = '' diff --git a/erpnext/patches/v11_1/setup_guardian_role.py b/erpnext/patches/v11_1/setup_guardian_role.py index 6ccfed9617c..dd9c1d28871 100644 --- a/erpnext/patches/v11_1/setup_guardian_role.py +++ b/erpnext/patches/v11_1/setup_guardian_role.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): if 'Education' in frappe.get_active_domains() and not frappe.db.exists("Role", "Guardian"): doc = frappe.new_doc("Role") diff --git a/erpnext/patches/v11_1/update_bank_transaction_status.py b/erpnext/patches/v11_1/update_bank_transaction_status.py index 354e636c9b0..9b8be3de1b0 100644 --- a/erpnext/patches/v11_1/update_bank_transaction_status.py +++ b/erpnext/patches/v11_1/update_bank_transaction_status.py @@ -1,9 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("accounts", "doctype", "bank_transaction") diff --git a/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py b/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py index 8c360ad9353..902df201a42 100644 --- a/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py +++ b/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py @@ -1,9 +1,10 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): ''' default supplier was not set in the item defaults for multi company instance, diff --git a/erpnext/patches/v11_1/woocommerce_set_creation_user.py b/erpnext/patches/v11_1/woocommerce_set_creation_user.py index 074b904002c..19958eef14a 100644 --- a/erpnext/patches/v11_1/woocommerce_set_creation_user.py +++ b/erpnext/patches/v11_1/woocommerce_set_creation_user.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.utils import cint + def execute(): frappe.reload_doc("erpnext_integrations", "doctype","woocommerce_settings") doc = frappe.get_doc("Woocommerce Settings") diff --git a/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py b/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py index 855d21dd992..80187d834a4 100644 --- a/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py +++ b/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py @@ -1,11 +1,11 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc("setup", "doctype", "company") if frappe.db.has_column('Company', 'default_terms'): diff --git a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py index 6fe578dbd95..f860cb46935 100644 --- a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py +++ b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'Italy'}) diff --git a/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py b/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py index cf1ed3676bf..973da895623 100644 --- a/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py +++ b/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py @@ -1,6 +1,7 @@ import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_field + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py index a0b1f87d61b..dc9e8849189 100644 --- a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py +++ b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py @@ -1,7 +1,8 @@ -from __future__ import unicode_literals import frappe + from erpnext.regional.india.setup import make_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py index c90819238c8..6316bb3da94 100644 --- a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py +++ b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: diff --git a/erpnext/patches/v12_0/add_permission_in_lower_deduction.py b/erpnext/patches/v12_0/add_permission_in_lower_deduction.py index 2e42368b152..1d77e5ad3d1 100644 --- a/erpnext/patches/v12_0/add_permission_in_lower_deduction.py +++ b/erpnext/patches/v12_0/add_permission_in_lower_deduction.py @@ -1,6 +1,7 @@ import frappe from frappe.permissions import add_permission, update_permission_property + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: diff --git a/erpnext/patches/v12_0/add_state_code_for_ladakh.py b/erpnext/patches/v12_0/add_state_code_for_ladakh.py index 29a7b4bd602..6722b7bcef0 100644 --- a/erpnext/patches/v12_0/add_state_code_for_ladakh.py +++ b/erpnext/patches/v12_0/add_state_code_for_ladakh.py @@ -1,6 +1,8 @@ import frappe + from erpnext.regional.india import states + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v12_0/add_taxjar_integration_field.py b/erpnext/patches/v12_0/add_taxjar_integration_field.py index 4c823e13bdf..b0ddf001a65 100644 --- a/erpnext/patches/v12_0/add_taxjar_integration_field.py +++ b/erpnext/patches/v12_0/add_taxjar_integration_field.py @@ -1,6 +1,5 @@ -from __future__ import unicode_literals - import frappe + from erpnext.regional.united_states.setup import make_custom_fields diff --git a/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py b/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py index 893f7a4909e..c3a422c49e5 100644 --- a/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py +++ b/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doc('stock', 'doctype', 'item_variant_attribute') frappe.db.sql(''' diff --git a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py index f171542df16..aec9cb8b26b 100644 --- a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py +++ b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_field + def execute(): frappe.reload_doc('accounts', 'doctype', 'accounting_dimension') diff --git a/erpnext/patches/v12_0/create_default_energy_point_rules.py b/erpnext/patches/v12_0/create_default_energy_point_rules.py index 93d2576bb6d..35eaca7f400 100644 --- a/erpnext/patches/v12_0/create_default_energy_point_rules.py +++ b/erpnext/patches/v12_0/create_default_energy_point_rules.py @@ -1,6 +1,8 @@ import frappe + from erpnext.setup.install import create_default_energy_point_rules + def execute(): frappe.reload_doc('social', 'doctype', 'energy_point_rule') create_default_energy_point_rules() diff --git a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py index 23a8f24d780..efcc7cf0f7d 100644 --- a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py +++ b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py @@ -1,7 +1,8 @@ -from __future__ import unicode_literals import frappe + from erpnext.regional.united_states.setup import make_custom_fields + def execute(): frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True) diff --git a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py index a6230f42771..d157aad8f2d 100644 --- a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py +++ b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py @@ -1,9 +1,10 @@ -from __future__ import unicode_literals import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.custom.doctype.property_setter.property_setter import make_property_setter + from erpnext.regional.india.utils import get_gst_accounts + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name']) if not company: diff --git a/erpnext/patches/v12_0/create_taxable_value_field.py b/erpnext/patches/v12_0/create_taxable_value_field.py index b9ee81df50e..55717ccab2e 100644 --- a/erpnext/patches/v12_0/create_taxable_value_field.py +++ b/erpnext/patches/v12_0/create_taxable_value_field.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: diff --git a/erpnext/patches/v12_0/delete_priority_property_setter.py b/erpnext/patches/v12_0/delete_priority_property_setter.py index 163855729df..cacc463d4b4 100644 --- a/erpnext/patches/v12_0/delete_priority_property_setter.py +++ b/erpnext/patches/v12_0/delete_priority_property_setter.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.db.sql(""" DELETE FROM `tabProperty Setter` diff --git a/erpnext/patches/v12_0/fix_percent_complete_for_projects.py b/erpnext/patches/v12_0/fix_percent_complete_for_projects.py index 3622df6bc81..36f51bca60d 100644 --- a/erpnext/patches/v12_0/fix_percent_complete_for_projects.py +++ b/erpnext/patches/v12_0/fix_percent_complete_for_projects.py @@ -1,6 +1,7 @@ import frappe from frappe.utils import flt + def execute(): for project in frappe.get_all("Project", fields=["name", "percent_complete_method"]): total = frappe.db.count('Task', dict(project=project.name)) diff --git a/erpnext/patches/v12_0/fix_quotation_expired_status.py b/erpnext/patches/v12_0/fix_quotation_expired_status.py index ac7e82d2d0d..e5c4b8c524f 100644 --- a/erpnext/patches/v12_0/fix_quotation_expired_status.py +++ b/erpnext/patches/v12_0/fix_quotation_expired_status.py @@ -1,5 +1,6 @@ import frappe + def execute(): # fixes status of quotations which have status 'Expired' despite having valid sales order created diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index fe072d7eb96..90e46d07e40 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -1,10 +1,11 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.utils import getdate, today + def execute(): """ Generates leave ledger entries for leave allocation/application/encashment for last allocation """ diff --git a/erpnext/patches/v12_0/make_item_manufacturer.py b/erpnext/patches/v12_0/make_item_manufacturer.py index ebc28320aea..d66f429de3c 100644 --- a/erpnext/patches/v12_0/make_item_manufacturer.py +++ b/erpnext/patches/v12_0/make_item_manufacturer.py @@ -1,9 +1,10 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("stock", "doctype", "item_manufacturer") diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py index a670adebfd6..b3ee3404642 100644 --- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py +++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('accounts', 'doctype', 'bank', force=1) diff --git a/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py b/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py index c9293b9b63c..82dfba52c9f 100644 --- a/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py +++ b/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py @@ -1,9 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): ''' Move credit limit and bypass credit limit to the child table of customer credit limit ''' frappe.reload_doc("Selling", "doctype", "Customer Credit Limit") diff --git a/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py index 6013eaa29c6..5de7e69620b 100644 --- a/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py +++ b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py @@ -1,9 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): ''' Move from due_advance_amount to pending_amount ''' diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index a6471eb53cd..905aebee151 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -1,8 +1,9 @@ -import frappe import json -from six import iteritems + +import frappe from frappe.model.naming import make_autoname + def execute(): if "tax_type" not in frappe.db.get_table_columns("Item Tax"): return @@ -82,7 +83,7 @@ def execute(): def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None): # search for previously created item tax template by comparing tax maps - for template, item_tax_template_map in iteritems(item_tax_templates): + for template, item_tax_template_map in item_tax_templates.items(): if item_tax_map == item_tax_template_map: return template @@ -90,9 +91,10 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp item_tax_template = frappe.new_doc("Item Tax Template") item_tax_template.title = make_autoname("Item Tax Template-.####") - for tax_type, tax_rate in iteritems(item_tax_map): - account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1) + for tax_type, tax_rate in item_tax_map.items(): + account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type', 'company'], as_dict=1) if account_details: + item_tax_template.company = account_details.company if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'): frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable') else: diff --git a/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py b/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py index d2bcb12070c..c396891b59e 100644 --- a/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py +++ b/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py @@ -1,7 +1,7 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe diff --git a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py index 97badf355d9..36fe18d8b71 100644 --- a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py +++ b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py @@ -1,11 +1,13 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("setup", "doctype", "target_detail") + frappe.reload_doc("core", "doctype", "prepared_report") for d in ['Sales Person', 'Sales Partner', 'Territory']: frappe.db.sql(""" diff --git a/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py b/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py index 46794bebe70..9b083cafb3a 100644 --- a/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py +++ b/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py @@ -1,6 +1,7 @@ -from __future__ import unicode_literals import frappe -from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty + +from erpnext.stock.stock_balance import get_indented_qty, update_bin_qty + def execute(): bin_details = frappe.db.sql(""" diff --git a/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py b/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py index be884f94d15..12768a6f959 100644 --- a/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py +++ b/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py @@ -1,6 +1,5 @@ -from __future__ import unicode_literals import frappe -from erpnext.regional.india.setup import make_custom_fields + def execute(): frappe.reload_doc("accounts", "doctype", "tax_category") diff --git a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py index 4fcffb702a4..d1d4bcc140f 100644 --- a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py +++ b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py @@ -1,9 +1,9 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import getdate, today + def execute(): ''' Delete leave ledger entry created diff --git a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py index 6b1b601db19..6ad68ccc6e4 100644 --- a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py @@ -1,9 +1,10 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): """Delete duplicate leave ledger entries of type allocation created.""" frappe.reload_doc('hr', 'doctype', 'leave_ledger_entry') diff --git a/erpnext/patches/v12_0/remove_patient_medical_record_page.py b/erpnext/patches/v12_0/remove_patient_medical_record_page.py index 904bfe4bf19..e02bf62dfbc 100644 --- a/erpnext/patches/v12_0/remove_patient_medical_record_page.py +++ b/erpnext/patches/v12_0/remove_patient_medical_record_page.py @@ -1,7 +1,8 @@ # Copyright (c) 2019 -from __future__ import unicode_literals + import frappe + def execute(): frappe.delete_doc("Page", "medical_record") diff --git a/erpnext/patches/v12_0/rename_account_type_doctype.py b/erpnext/patches/v12_0/rename_account_type_doctype.py index 9a08ad45213..e33a1d010d1 100644 --- a/erpnext/patches/v12_0/rename_account_type_doctype.py +++ b/erpnext/patches/v12_0/rename_account_type_doctype.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.rename_doc('DocType', 'Account Type', 'Bank Account Type', force=True) frappe.rename_doc('DocType', 'Account Subtype', 'Bank Account Subtype', force=True) diff --git a/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py b/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py index 7489ea30a09..a5d986a0a16 100644 --- a/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py +++ b/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py @@ -1,10 +1,11 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.model.utils.rename_field import rename_field + def execute(): ''' Change the fieldname from bank_account_no to bank_account ''' if not frappe.get_meta("Journal Entry Account").has_field("bank_account"): diff --git a/erpnext/patches/v12_0/rename_bank_reconciliation.py b/erpnext/patches/v12_0/rename_bank_reconciliation.py index 2efa854fba9..51ff0c8c949 100644 --- a/erpnext/patches/v12_0/rename_bank_reconciliation.py +++ b/erpnext/patches/v12_0/rename_bank_reconciliation.py @@ -1,9 +1,10 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): if frappe.db.table_exists("Bank Reconciliation"): frappe.rename_doc('DocType', 'Bank Reconciliation', 'Bank Clearance', force=True) diff --git a/erpnext/patches/v12_0/rename_bank_reconciliation_fields.py b/erpnext/patches/v12_0/rename_bank_reconciliation_fields.py index 978b1c92b96..629cd5bda66 100644 --- a/erpnext/patches/v12_0/rename_bank_reconciliation_fields.py +++ b/erpnext/patches/v12_0/rename_bank_reconciliation_fields.py @@ -3,6 +3,7 @@ import frappe + def _rename_single_field(**kwargs): count = frappe.db.sql("SELECT COUNT(*) FROM tabSingles WHERE doctype='{doctype}' AND field='{new_name}';".format(**kwargs))[0][0] #nosec if count == 0: diff --git a/erpnext/patches/v12_0/rename_lost_reason_detail.py b/erpnext/patches/v12_0/rename_lost_reason_detail.py index c71b91c9256..96ae9798c65 100644 --- a/erpnext/patches/v12_0/rename_lost_reason_detail.py +++ b/erpnext/patches/v12_0/rename_lost_reason_detail.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): if frappe.db.exists("DocType", "Lost Reason Detail"): frappe.reload_doc("crm", "doctype", "opportunity_lost_reason") diff --git a/erpnext/patches/v12_0/rename_mws_settings_fields.py b/erpnext/patches/v12_0/rename_mws_settings_fields.py index e08e3769153..d5bf38d204d 100644 --- a/erpnext/patches/v12_0/rename_mws_settings_fields.py +++ b/erpnext/patches/v12_0/rename_mws_settings_fields.py @@ -3,6 +3,7 @@ import frappe + def execute(): count = frappe.db.sql("SELECT COUNT(*) FROM `tabSingles` WHERE doctype='Amazon MWS Settings' AND field='enable_sync';")[0][0] if count == 0: diff --git a/erpnext/patches/v12_0/rename_pos_closing_doctype.py b/erpnext/patches/v12_0/rename_pos_closing_doctype.py index 9d8626b8527..f5f0112e036 100644 --- a/erpnext/patches/v12_0/rename_pos_closing_doctype.py +++ b/erpnext/patches/v12_0/rename_pos_closing_doctype.py @@ -1,8 +1,9 @@ # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): if frappe.db.table_exists("POS Closing Voucher"): if not frappe.db.exists("DocType", "POS Closing Entry"): diff --git a/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py b/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py index b9ad622b0ea..87630fbcaf9 100644 --- a/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py +++ b/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py @@ -1,7 +1,7 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe doctypes = { diff --git a/erpnext/patches/v12_0/rename_tolerance_fields.py b/erpnext/patches/v12_0/rename_tolerance_fields.py index 20b096331ed..ca2427bc3dd 100644 --- a/erpnext/patches/v12_0/rename_tolerance_fields.py +++ b/erpnext/patches/v12_0/rename_tolerance_fields.py @@ -1,6 +1,7 @@ import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc("stock", "doctype", "item") frappe.reload_doc("stock", "doctype", "stock_settings") diff --git a/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py b/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py index f88a22f6c9d..ff332f771d3 100644 --- a/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py +++ b/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.db.sql("""UPDATE `tabUser` SET `home_settings` = REPLACE(`home_settings`, 'Accounting', 'Accounts')""") frappe.cache().delete_key('home_settings') diff --git a/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py b/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py index c52f380d8c2..198963df711 100644 --- a/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py +++ b/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py @@ -1,9 +1,10 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): warehouse_perm = frappe.get_all("User Permission", fields=["count(*) as p_count", "is_default", "user"], filters={"allow": "Warehouse"}, group_by="user") diff --git a/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py b/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py index 85202bff4d6..b76e34abe13 100644 --- a/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py +++ b/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doc('selling', 'doctype', 'sales_order_item', force=True) diff --git a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py index b5d7e3dcb9e..8f29fc888e0 100644 --- a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py +++ b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("accounts", "doctype", "accounts_settings") diff --git a/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py index 4415cfeaba9..d3045a1a576 100644 --- a/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py +++ b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py @@ -1,4 +1,6 @@ import frappe + + def execute(): frappe.reload_doc('hr', 'doctype', 'expense_claim_detail') frappe.db.sql(""" diff --git a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py index 13110dfe03f..d1e0e4550eb 100644 --- a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py +++ b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import frappe from frappe.utils import cint diff --git a/erpnext/patches/v12_0/set_default_homepage_type.py b/erpnext/patches/v12_0/set_default_homepage_type.py index a290e31cf24..1e4333aa466 100644 --- a/erpnext/patches/v12_0/set_default_homepage_type.py +++ b/erpnext/patches/v12_0/set_default_homepage_type.py @@ -1,4 +1,5 @@ import frappe + def execute(): frappe.db.set_value('Homepage', 'Homepage', 'hero_section_based_on', 'Default') diff --git a/erpnext/patches/v12_0/set_default_payroll_based_on.py b/erpnext/patches/v12_0/set_default_payroll_based_on.py index 038bd6d21ae..de641c65a1c 100644 --- a/erpnext/patches/v12_0/set_default_payroll_based_on.py +++ b/erpnext/patches/v12_0/set_default_payroll_based_on.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("hr", "doctype", "hr_settings") frappe.db.set_value("HR Settings", None, "payroll_based_on", "Leave") diff --git a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py index a27c7b24a8c..50d9fee0992 100644 --- a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py +++ b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py @@ -1,6 +1,5 @@ -from __future__ import unicode_literals import frappe -from six import iteritems + def execute(): frappe.reload_doctype('Landed Cost Taxes and Charges') @@ -9,7 +8,7 @@ def execute(): SELECT name, expenses_included_in_valuation from `tabCompany` """)) - for company, account in iteritems(company_account_map): + for company, account in company_account_map.items(): frappe.db.sql(""" UPDATE `tabLanded Cost Taxes and Charges` t, `tabLanded Cost Voucher` l diff --git a/erpnext/patches/v12_0/set_gst_category.py b/erpnext/patches/v12_0/set_gst_category.py index cc093953bf4..094e2a3134b 100644 --- a/erpnext/patches/v12_0/set_gst_category.py +++ b/erpnext/patches/v12_0/set_gst_category.py @@ -1,6 +1,8 @@ import frappe + from erpnext.regional.india.setup import make_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py b/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py index 8fdc73b8ff1..b942fa4365f 100644 --- a/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py +++ b/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py @@ -1,10 +1,12 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + from erpnext.regional.italy.setup import add_permissions + def execute(): countries = frappe.get_all("Company", fields="country") countries = [country["country"] for country in countries] diff --git a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py index a5c8f7524a7..a8e0ec1f816 100644 --- a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py +++ b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py @@ -1,10 +1,9 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import flt -from erpnext.stock.get_item_details import get_conversion_factor + def execute(): frappe.reload_doc('buying', 'doctype', 'request_for_quotation_item') diff --git a/erpnext/patches/v12_0/set_payment_entry_status.py b/erpnext/patches/v12_0/set_payment_entry_status.py index 84645a38639..f8792952d8b 100644 --- a/erpnext/patches/v12_0/set_payment_entry_status.py +++ b/erpnext/patches/v12_0/set_payment_entry_status.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doctype("Payment Entry") frappe.db.sql("""update `tabPayment Entry` set status = CASE diff --git a/erpnext/patches/v12_0/set_permission_einvoicing.py b/erpnext/patches/v12_0/set_permission_einvoicing.py index e2235105f94..01cab14db9d 100644 --- a/erpnext/patches/v12_0/set_permission_einvoicing.py +++ b/erpnext/patches/v12_0/set_permission_einvoicing.py @@ -1,7 +1,9 @@ import frappe -from erpnext.regional.italy.setup import make_custom_fields from frappe.permissions import add_permission, update_permission_property +from erpnext.regional.italy.setup import make_custom_fields + + def execute(): company = frappe.get_all('Company', filters = {'country': 'Italy'}) diff --git a/erpnext/patches/v12_0/set_priority_for_support.py b/erpnext/patches/v12_0/set_priority_for_support.py index 66696bee541..6d7d0993460 100644 --- a/erpnext/patches/v12_0/set_priority_for_support.py +++ b/erpnext/patches/v12_0/set_priority_for_support.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doc("support", "doctype", "issue_priority") frappe.reload_doc("support", "doctype", "service_level_priority") diff --git a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py index 6c11cb415f9..9c851ddcee1 100644 --- a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py +++ b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py @@ -1,7 +1,8 @@ import frappe -from frappe.utils import flt + from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item + def execute(): frappe.reload_doctype('Sales Order Item') frappe.reload_doctype('Sales Order') diff --git a/erpnext/patches/v12_0/set_production_capacity_in_workstation.py b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py index babaebeaefc..bd2f7e2decd 100644 --- a/erpnext/patches/v12_0/set_production_capacity_in_workstation.py +++ b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("manufacturing", "doctype", "workstation") diff --git a/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py b/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py deleted file mode 100644 index e54c7f315c6..00000000000 --- a/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("Hub Node", "doctype", "Hub Tracked Item") - if not frappe.db.a_row_exists("Hub Tracked Item"): - return - - frappe.db.sql(''' - Update `tabHub Tracked Item` - SET published = 1 - ''') diff --git a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py index 52c9a2d7b3c..a15166ed5f6 100644 --- a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py +++ b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py @@ -1,7 +1,8 @@ -from __future__ import unicode_literals -import frappe from collections import defaultdict +import frappe + + def execute(): frappe.reload_doc('stock', 'doctype', 'delivery_note_item', force=True) diff --git a/erpnext/patches/v12_0/set_quotation_status.py b/erpnext/patches/v12_0/set_quotation_status.py index 87643a23545..91e77e4e0d4 100644 --- a/erpnext/patches/v12_0/set_quotation_status.py +++ b/erpnext/patches/v12_0/set_quotation_status.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.db.sql(""" UPDATE `tabQuotation` set status = 'Open' diff --git a/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py b/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py index 88c3e2e3024..d41134d4db3 100644 --- a/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py +++ b/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): purchase_receipts = frappe.db.sql(""" SELECT diff --git a/erpnext/patches/v12_0/set_serial_no_status.py b/erpnext/patches/v12_0/set_serial_no_status.py index 3b5f5ef3407..8c136e66632 100644 --- a/erpnext/patches/v12_0/set_serial_no_status.py +++ b/erpnext/patches/v12_0/set_serial_no_status.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.utils import getdate, nowdate + def execute(): frappe.reload_doc('stock', 'doctype', 'serial_no') diff --git a/erpnext/patches/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py index dbd7e5a8181..1b4955a75be 100644 --- a/erpnext/patches/v12_0/set_task_status.py +++ b/erpnext/patches/v12_0/set_task_status.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doctype('Task') diff --git a/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py b/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py index 1cc37caba42..300d0f2ba47 100644 --- a/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py +++ b/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("stock", "doctype", "pick_list") frappe.db.sql("""UPDATE `tabPick List` set purpose = 'Delivery' diff --git a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py index 4a6e2288564..154d7ba1767 100644 --- a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py +++ b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("buying", "doctype", "supplier_quotation") frappe.db.sql("""UPDATE `tabSupplier Quotation` diff --git a/erpnext/patches/v12_0/stock_entry_enhancements.py b/erpnext/patches/v12_0/stock_entry_enhancements.py index 17fdcd9395a..94d8ff9cde3 100644 --- a/erpnext/patches/v12_0/stock_entry_enhancements.py +++ b/erpnext/patches/v12_0/stock_entry_enhancements.py @@ -2,10 +2,11 @@ # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + def execute(): create_stock_entry_types() diff --git a/erpnext/patches/v12_0/unhide_cost_center_field.py b/erpnext/patches/v12_0/unhide_cost_center_field.py index 3474a34af4b..72450212872 100644 --- a/erpnext/patches/v12_0/unhide_cost_center_field.py +++ b/erpnext/patches/v12_0/unhide_cost_center_field.py @@ -1,9 +1,10 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.db.sql(""" DELETE FROM `tabProperty Setter` diff --git a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py index b8efb210a03..5b5f623b48c 100644 --- a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py +++ b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import frappe diff --git a/erpnext/patches/v12_0/update_address_template_for_india.py b/erpnext/patches/v12_0/update_address_template_for_india.py index 0d582da4b5c..64a2e41587f 100644 --- a/erpnext/patches/v12_0/update_address_template_for_india.py +++ b/erpnext/patches/v12_0/update_address_template_for_india.py @@ -1,10 +1,12 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + from erpnext.regional.address_template.setup import set_up_address_templates + def execute(): if frappe.db.get_value('Company', {'country': 'India'}, 'name'): address_template = frappe.db.get_value('Address Template', 'India', 'template') diff --git a/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py b/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py deleted file mode 100644 index f4516649610..00000000000 --- a/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py +++ /dev/null @@ -1,7 +0,0 @@ -import frappe - -def execute(): - job = frappe.db.exists('Scheduled Job Type', 'patient_appointment.send_appointment_reminder') - if job: - method = 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder' - frappe.db.set_value('Scheduled Job Type', job, 'method', method) diff --git a/erpnext/patches/v12_0/update_bom_in_so_mr.py b/erpnext/patches/v12_0/update_bom_in_so_mr.py index 8a871718133..37d850fab44 100644 --- a/erpnext/patches/v12_0/update_bom_in_so_mr.py +++ b/erpnext/patches/v12_0/update_bom_in_so_mr.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("stock", "doctype", "material_request_item") frappe.reload_doc("selling", "doctype", "sales_order_item") diff --git a/erpnext/patches/v12_0/update_due_date_in_gle.py b/erpnext/patches/v12_0/update_due_date_in_gle.py index 34848725cec..e4418b01036 100644 --- a/erpnext/patches/v12_0/update_due_date_in_gle.py +++ b/erpnext/patches/v12_0/update_due_date_in_gle.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("accounts", "doctype", "gl_entry") diff --git a/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py b/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py index c45f6221f93..ef4204fc9c3 100644 --- a/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py +++ b/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.utils import add_days, getdate, today + def execute(): if frappe.db.exists('DocType', 'Email Campaign'): email_campaign = frappe.get_all('Email Campaign') diff --git a/erpnext/patches/v12_0/update_ewaybill_field_position.py b/erpnext/patches/v12_0/update_ewaybill_field_position.py index 9e5f599d2c8..132fd900bd1 100644 --- a/erpnext/patches/v12_0/update_ewaybill_field_position.py +++ b/erpnext/patches/v12_0/update_ewaybill_field_position.py @@ -1,6 +1,5 @@ -from __future__ import unicode_literals import frappe -from erpnext.regional.india.setup import make_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v12_0/update_gst_category.py b/erpnext/patches/v12_0/update_gst_category.py index 1a54216b885..8b15370b09d 100644 --- a/erpnext/patches/v12_0/update_gst_category.py +++ b/erpnext/patches/v12_0/update_gst_category.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v12_0/update_healthcare_refactored_changes.py b/erpnext/patches/v12_0/update_healthcare_refactored_changes.py index d0b04433979..f553ee9d855 100644 --- a/erpnext/patches/v12_0/update_healthcare_refactored_changes.py +++ b/erpnext/patches/v12_0/update_healthcare_refactored_changes.py @@ -1,7 +1,6 @@ -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field -from frappe.modules import scrub, get_doctype_module +from frappe.modules import get_doctype_module, scrub field_rename_map = { 'Healthcare Settings': [ diff --git a/erpnext/patches/v12_0/update_is_cancelled_field.py b/erpnext/patches/v12_0/update_is_cancelled_field.py index 4bbec44aa42..df7875079bc 100644 --- a/erpnext/patches/v12_0/update_is_cancelled_field.py +++ b/erpnext/patches/v12_0/update_is_cancelled_field.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): try: frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')") @@ -11,5 +11,5 @@ def execute(): frappe.reload_doc("stock", "doctype", "stock_ledger_entry") frappe.reload_doc("stock", "doctype", "serial_no") - except: + except Exception: pass diff --git a/erpnext/patches/v12_0/update_item_tax_template_company.py b/erpnext/patches/v12_0/update_item_tax_template_company.py index e15894df890..abd4f6fb180 100644 --- a/erpnext/patches/v12_0/update_item_tax_template_company.py +++ b/erpnext/patches/v12_0/update_item_tax_template_company.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('accounts', 'doctype', 'item_tax_template') diff --git a/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py b/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py index 6ebaf48e0e8..e5f24d4b888 100644 --- a/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py +++ b/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py @@ -1,6 +1,9 @@ -from __future__ import unicode_literals import frappe -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_doctypes_with_dimensions + +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_doctypes_with_dimensions, +) + def execute(): accounting_dimensions = frappe.db.sql("""select fieldname from diff --git a/erpnext/patches/v12_0/update_price_list_currency_in_bom.py b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py index 09f07074299..ea3e002e5d2 100644 --- a/erpnext/patches/v12_0/update_price_list_currency_in_bom.py +++ b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py @@ -1,8 +1,9 @@ -from __future__ import unicode_literals import frappe -from frappe.utils import getdate, flt +from frappe.utils import getdate + from erpnext.setup.utils import get_exchange_rate + def execute(): frappe.reload_doc("manufacturing", "doctype", "bom") frappe.reload_doc("manufacturing", "doctype", "bom_item") diff --git a/erpnext/patches/v12_0/update_price_or_product_discount.py b/erpnext/patches/v12_0/update_price_or_product_discount.py index 3a8cd43e302..53c0ba525b2 100644 --- a/erpnext/patches/v12_0/update_price_or_product_discount.py +++ b/erpnext/patches/v12_0/update_price_or_product_discount.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("accounts", "doctype", "pricing_rule") diff --git a/erpnext/patches/v12_0/update_pricing_rule_fields.py b/erpnext/patches/v12_0/update_pricing_rule_fields.py index 985613a9739..b7c36ae7780 100644 --- a/erpnext/patches/v12_0/update_pricing_rule_fields.py +++ b/erpnext/patches/v12_0/update_pricing_rule_fields.py @@ -1,7 +1,7 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe parentfield = { diff --git a/erpnext/patches/v12_0/update_production_plan_status.py b/erpnext/patches/v12_0/update_production_plan_status.py new file mode 100644 index 00000000000..06fc503a33f --- /dev/null +++ b/erpnext/patches/v12_0/update_production_plan_status.py @@ -0,0 +1,31 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + frappe.reload_doc("manufacturing", "doctype", "production_plan") + frappe.db.sql(""" + UPDATE `tabProduction Plan` ppl + SET status = "Completed" + WHERE ppl.name IN ( + SELECT ss.name FROM ( + SELECT + ( + count(wo.status = "Completed") = + count(pp.name) + ) = + ( + pp.status != "Completed" + AND pp.total_produced_qty >= pp.total_planned_qty + ) AS should_set, + pp.name AS name + FROM + `tabWork Order` wo INNER JOIN`tabProduction Plan` pp + ON wo.production_plan = pp.name + GROUP BY pp.name + HAVING should_set = 1 + ) ss + ) + """) diff --git a/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py b/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py index 8dbfa1866d3..25cf6b97e3b 100644 --- a/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py +++ b/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py @@ -1,6 +1,8 @@ import frappe + from erpnext.regional.india import states + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v12_0/update_uom_conversion_factor.py b/erpnext/patches/v12_0/update_uom_conversion_factor.py index 24914fd13bc..a09ac190e2e 100644 --- a/erpnext/patches/v12_0/update_uom_conversion_factor.py +++ b/erpnext/patches/v12_0/update_uom_conversion_factor.py @@ -1,5 +1,5 @@ -from __future__ import unicode_literals -import frappe, json +import frappe + def execute(): from erpnext.setup.setup_wizard.operations.install_fixtures import add_uom_data diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py index 73ff1cad5b6..b34b5c1801f 100644 --- a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py +++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py @@ -1,14 +1,19 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe -from erpnext.regional.south_africa.setup import make_custom_fields, add_permissions + +from erpnext.regional.south_africa.setup import add_permissions, make_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'South Africa'}) if not company: return + frappe.reload_doc('regional', 'doctype', 'south_africa_vat_settings') + frappe.reload_doc('regional', 'report', 'vat_audit_report') + frappe.reload_doc('accounts', 'doctype', 'south_africa_vat_account') + make_custom_fields() add_permissions() diff --git a/erpnext/patches/v13_0/add_default_interview_notification_templates.py b/erpnext/patches/v13_0/add_default_interview_notification_templates.py new file mode 100644 index 00000000000..0208ca914eb --- /dev/null +++ b/erpnext/patches/v13_0/add_default_interview_notification_templates.py @@ -0,0 +1,35 @@ +import os + +import frappe +from frappe import _ + + +def execute(): + if not frappe.db.exists('Email Template', _('Interview Reminder')): + base_path = frappe.get_app_path('erpnext', 'hr', 'doctype') + response = frappe.read_file(os.path.join(base_path, 'interview/interview_reminder_notification_template.html')) + + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _('Interview Reminder'), + 'response': response, + 'subject': _('Interview Reminder'), + 'owner': frappe.session.user, + }).insert(ignore_permissions=True) + + if not frappe.db.exists('Email Template', _('Interview Feedback Reminder')): + base_path = frappe.get_app_path('erpnext', 'hr', 'doctype') + response = frappe.read_file(os.path.join(base_path, 'interview/interview_feedback_reminder_template.html')) + + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _('Interview Feedback Reminder'), + 'response': response, + 'subject': _('Interview Feedback Reminder'), + 'owner': frappe.session.user, + }).insert(ignore_permissions=True) + + hr_settings = frappe.get_doc('HR Settings') + hr_settings.interview_reminder_template = _('Interview Reminder') + hr_settings.feedback_reminder_notification_template = _('Interview Feedback Reminder') + hr_settings.save() diff --git a/erpnext/patches/v13_0/add_doctype_to_sla.py b/erpnext/patches/v13_0/add_doctype_to_sla.py index cdc5a1eabb5..8cee378d900 100644 --- a/erpnext/patches/v13_0/add_doctype_to_sla.py +++ b/erpnext/patches/v13_0/add_doctype_to_sla.py @@ -1,11 +1,11 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc('support', 'doctype', 'sla_fulfilled_on_status') frappe.reload_doc('support', 'doctype', 'service_level_agreement') diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py index 0d8109c41ad..bd18b9bd173 100644 --- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py +++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py @@ -2,9 +2,11 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.utils import cstr, flt, cint -from erpnext.stock.stock_ledger import make_sl_entries +from frappe.utils import cint, cstr, flt + from erpnext.controllers.stock_controller import create_repost_item_valuation_entry +from erpnext.stock.stock_ledger import make_sl_entries + def execute(): if not frappe.db.has_column('Work Order', 'has_batch_no'): diff --git a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py index a7b66f0d2bb..71abe2e98e0 100644 --- a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py +++ b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py @@ -1,6 +1,5 @@ -from __future__ import unicode_literals import frappe -from frappe.custom.doctype.property_setter.property_setter import make_property_setter, delete_property_setter + def execute(): frappe.reload_doc("projects", "doctype", "project") diff --git a/erpnext/patches/v13_0/add_po_to_global_search.py b/erpnext/patches/v13_0/add_po_to_global_search.py index 1c60b18e5b2..7fbaffb23c7 100644 --- a/erpnext/patches/v13_0/add_po_to_global_search.py +++ b/erpnext/patches/v13_0/add_po_to_global_search.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import frappe diff --git a/erpnext/patches/v13_0/add_standard_navbar_items.py b/erpnext/patches/v13_0/add_standard_navbar_items.py index d05b258db0c..24141b7862d 100644 --- a/erpnext/patches/v13_0/add_standard_navbar_items.py +++ b/erpnext/patches/v13_0/add_standard_navbar_items.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals # import frappe from erpnext.setup.install import add_standard_navbar_items + def execute(): # Add standard navbar items for ERPNext in Navbar Settings add_standard_navbar_items() diff --git a/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py index 7de9fa1e23e..ee23747cc04 100644 --- a/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py +++ b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doctype("Buying Settings") buying_settings = frappe.get_single("Buying Settings") diff --git a/erpnext/patches/v13_0/change_default_pos_print_format.py b/erpnext/patches/v13_0/change_default_pos_print_format.py index 1e4f383dda6..23cad31f596 100644 --- a/erpnext/patches/v13_0/change_default_pos_print_format.py +++ b/erpnext/patches/v13_0/change_default_pos_print_format.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.db.sql( """UPDATE `tabPOS Profile` profile diff --git a/erpnext/patches/v13_0/check_is_income_tax_component.py b/erpnext/patches/v13_0/check_is_income_tax_component.py index ebae3ad7157..5e1df14d4e0 100644 --- a/erpnext/patches/v13_0/check_is_income_tax_component.py +++ b/erpnext/patches/v13_0/check_is_income_tax_component.py @@ -1,10 +1,12 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from erpnext.regional.india.setup import setup +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field + +import erpnext + def execute(): @@ -19,16 +21,23 @@ def execute(): ] for doctype in doctypes: - frappe.reload_doc('Payroll', 'doctype', doctype) + frappe.reload_doc('Payroll', 'doctype', doctype, force=True) - reports = ['Professional Tax Deductions', 'Provident Fund Deductions'] + reports = ['Professional Tax Deductions', 'Provident Fund Deductions', 'E-Invoice Summary'] for report in reports: frappe.reload_doc('Regional', 'Report', report) frappe.reload_doc('Regional', 'Report', report) if erpnext.get_region() == "India": - setup(patch=True) + create_custom_field('Salary Component', + dict(fieldname='component_type', + label='Component Type', + fieldtype='Select', + insert_after='description', + options='\nProvident Fund\nAdditional Provident Fund\nProvident Fund Loan\nProfessional Tax', + depends_on='eval:doc.type == "Deduction"') + ) if frappe.db.exists("Salary Component", "Income Tax"): frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1) diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py index 341955aa35f..bc64c637722 100644 --- a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py +++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('stock', 'doctype', 'quality_inspection_parameter') diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py b/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py new file mode 100644 index 00000000000..44501088102 --- /dev/null +++ b/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py @@ -0,0 +1,42 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'accounting_dimension') + accounting_dimensions = frappe.db.sql("""select fieldname, label, document_type, disabled from + `tabAccounting Dimension`""", as_dict=1) + + if not accounting_dimensions: + return + + count = 1 + for d in accounting_dimensions: + + if count % 2 == 0: + insert_after_field = 'dimension_col_break' + else: + insert_after_field = 'accounting_dimensions_section' + + for doctype in ["POS Invoice", "POS Invoice Item"]: + + field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname}) + + if field: + continue + meta = frappe.get_meta(doctype, cached=False) + fieldnames = [d.fieldname for d in meta.get("fields")] + + df = { + "fieldname": d.fieldname, + "label": d.label, + "fieldtype": "Link", + "options": d.document_type, + "insert_after": insert_after_field + } + + if df['fieldname'] not in fieldnames: + create_custom_field(doctype, df) + frappe.clear_cache(doctype=doctype) + + count += 1 diff --git a/erpnext/patches/v13_0/create_custom_field_for_finance_book.py b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py new file mode 100644 index 00000000000..313b0e9a2eb --- /dev/null +++ b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py @@ -0,0 +1,21 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_field = { + 'Finance Book': [ + { + 'fieldname': 'for_income_tax', + 'label': 'For Income Tax', + 'fieldtype': 'Check', + 'insert_after': 'finance_book_name', + 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.' + } + ] + } + create_custom_fields(custom_field, update=1) diff --git a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py new file mode 100644 index 00000000000..416694559cb --- /dev/null +++ b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py @@ -0,0 +1,37 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges') + frappe.reload_doc('accounts', 'doctype', 'payment_entry') + + if frappe.db.exists('Company', {'country': 'India'}): + custom_fields = { + 'Payment Entry': [ + dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions', + print_hide=1, collapsible=1), + dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section', + print_hide=1, options='Address'), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + fetch_from='company_address.gstin', print_hide=1, read_only=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='company_gstin', + print_hide=1, read_only=1), + dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply', + print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'), + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='customer_address', + fetch_from='customer_address.gstin', print_hide=1, read_only=1) + ] + } + + create_custom_fields(custom_fields, update=True) + else: + fields = ['gst_section', 'company_address', 'company_gstin', 'place_of_supply', 'customer_address', 'customer_gstin'] + for field in fields: + frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}") \ No newline at end of file diff --git a/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py b/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py deleted file mode 100644 index 08d4876c0d1..00000000000 --- a/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py +++ /dev/null @@ -1,10 +0,0 @@ -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from erpnext.domains.healthcare import data - -def execute(): - if 'Healthcare' not in frappe.get_active_domains(): - return - - if data['custom_fields']: - create_custom_fields(data['custom_fields']) diff --git a/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py b/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py new file mode 100644 index 00000000000..f33b4b3ea0d --- /dev/null +++ b/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py @@ -0,0 +1,12 @@ +import frappe + +from erpnext.regional.saudi_arabia.setup import make_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'}) + if not company: + return + + make_custom_fields() + diff --git a/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py index 9a354537f7d..55125431b52 100644 --- a/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py +++ b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): if "leave_policy" in frappe.db.get_table_columns("Employee"): employees_with_leave_policy = frappe.db.sql("SELECT name, leave_policy FROM `tabEmployee` WHERE leave_policy IS NOT NULL and leave_policy != ''", as_dict = 1) diff --git a/erpnext/patches/v13_0/create_pan_field_for_india.py b/erpnext/patches/v13_0/create_pan_field_for_india.py new file mode 100644 index 00000000000..6df6e1eb329 --- /dev/null +++ b/erpnext/patches/v13_0/create_pan_field_for_india.py @@ -0,0 +1,29 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + frappe.reload_doc('buying', 'doctype', 'supplier', force=True) + frappe.reload_doc('selling', 'doctype', 'customer', force=True) + frappe.reload_doc('core', 'doctype', 'doctype', force=True) + + custom_fields = { + 'Supplier': [ + { + 'fieldname': 'pan', + 'label': 'PAN', + 'fieldtype': 'Data', + 'insert_after': 'supplier_type' + } + ], + 'Customer': [ + { + 'fieldname': 'pan', + 'label': 'PAN', + 'fieldtype': 'Data', + 'insert_after': 'customer_type' + } + ] + } + + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py index 6ad3402ba02..87c9cf1ebd5 100644 --- a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py +++ b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py @@ -1,11 +1,12 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + from erpnext.regional.united_arab_emirates.setup import make_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': ['in', ['Saudi Arabia', 'United Arab Emirates']]}) if not company: diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py new file mode 100644 index 00000000000..078c558d885 --- /dev/null +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -0,0 +1,39 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +from erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings import add_permissions + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name']) + if not company: + return + + TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") + TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") + TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") + + if (not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE): + return + + custom_fields = { + 'Sales Invoice Item': [ + dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category', + label='Product Tax Category', fetch_from='item_code.product_tax_category'), + dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', + label='Tax Collectable', read_only=1, options='currency'), + dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', + label='Taxable Amount', read_only=1, options='currency') + ], + 'Item': [ + dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category', + label='Product Tax Category') + ], + 'TaxJar Settings': [ + dict(fieldname='company', fieldtype='Link', insert_after='configuration', options='Company', + label='Company') + ] + } + create_custom_fields(custom_fields, update=True) + add_permissions() + frappe.enqueue('erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings.add_product_tax_categories', now=True) diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py index 77a23cfc3f8..2c5c577978e 100644 --- a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py +++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py @@ -1,11 +1,11 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field + def execute(): doctypes = [ "Bank Statement Settings", diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py index c17aad06c7f..e57d6d0d3e2 100644 --- a/erpnext/patches/v13_0/delete_old_purchase_reports.py +++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py @@ -1,10 +1,12 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe +from erpnext.accounts.utils import check_and_delete_linked_reports + + def execute(): reports_to_delete = ["Requested Items To Be Ordered", "Purchase Order Items To Be Received or Billed","Purchase Order Items To Be Received", @@ -13,6 +15,7 @@ def execute(): for report in reports_to_delete: if frappe.db.exists("Report", report): delete_auto_email_reports(report) + check_and_delete_linked_reports(report) frappe.delete_doc("Report", report) diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py index 671c012c8a0..c597fe86457 100644 --- a/erpnext/patches/v13_0/delete_old_sales_reports.py +++ b/erpnext/patches/v13_0/delete_old_sales_reports.py @@ -1,16 +1,19 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe +from erpnext.accounts.utils import check_and_delete_linked_reports + + def execute(): reports_to_delete = ["Ordered Items To Be Delivered", "Ordered Items To Be Billed"] for report in reports_to_delete: if frappe.db.exists("Report", report): delete_auto_email_reports(report) + check_and_delete_linked_reports(report) frappe.delete_doc("Report", report) diff --git a/erpnext/patches/v13_0/delete_orphaned_tables.py b/erpnext/patches/v13_0/delete_orphaned_tables.py index 50a4a0efcbe..c32f83067bc 100644 --- a/erpnext/patches/v13_0/delete_orphaned_tables.py +++ b/erpnext/patches/v13_0/delete_orphaned_tables.py @@ -1,11 +1,11 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe.utils import getdate + def execute(): frappe.reload_doc('setup', 'doctype', 'transaction_deletion_record') diff --git a/erpnext/patches/v13_0/delete_report_requested_items_to_order.py b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py index 8d6340d44ef..87565f0fe42 100644 --- a/erpnext/patches/v13_0/delete_report_requested_items_to_order.py +++ b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py @@ -1,5 +1,6 @@ import frappe + def execute(): """ Check for one or multiple Auto Email Reports and delete """ auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": "Requested Items to Order"}, ["name"]) diff --git a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py new file mode 100644 index 00000000000..c815b3bb3c9 --- /dev/null +++ b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py @@ -0,0 +1,16 @@ +# Copyright (c) 2020, Wahni Green Technologies and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'}) + if company: + return + + if frappe.db.exists('DocType', 'Print Format'): + frappe.reload_doc("regional", "print_format", "ksa_vat_invoice", force=True) + frappe.reload_doc("regional", "print_format", "ksa_pos_invoice", force=True) + for d in ('KSA VAT Invoice', 'KSA POS Invoice'): + frappe.db.set_value("Print Format", d, "disabled", 1) diff --git a/erpnext/patches/v13_0/drop_razorpay_payload_column.py b/erpnext/patches/v13_0/drop_razorpay_payload_column.py index 76b8041cd94..611ba7e3243 100644 --- a/erpnext/patches/v13_0/drop_razorpay_payload_column.py +++ b/erpnext/patches/v13_0/drop_razorpay_payload_column.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): if frappe.db.exists("DocType", "Membership"): if 'webhook_payload' in frappe.db.get_table_columns("Membership"): diff --git a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py new file mode 100644 index 00000000000..7a51b432117 --- /dev/null +++ b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py @@ -0,0 +1,8 @@ +import frappe + + +def execute(): + frappe.reload_doc('core', 'doctype', 'scheduled_job_type') + if frappe.db.exists('Scheduled Job Type', 'repost_item_valuation.repost_entries'): + frappe.db.set_value('Scheduled Job Type', + 'repost_item_valuation.repost_entries', 'stopped', 0) diff --git a/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py new file mode 100644 index 00000000000..aeb8d8eb588 --- /dev/null +++ b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py @@ -0,0 +1,76 @@ +from typing import List, NewType + +import frappe + +StockEntryCode = NewType("StockEntryCode", str) + + +def execute(): + stock_entry_codes = find_broken_stock_entries() + + for stock_entry_code in stock_entry_codes: + patched_stock_entry = patch_additional_cost(stock_entry_code) + create_repost_item_valuation(patched_stock_entry) + + +def find_broken_stock_entries() -> List[StockEntryCode]: + period_closing_date = frappe.db.get_value( + "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc" + ) + + stock_entries_to_patch = frappe.db.sql( + """ + select se.name, sum(sed.additional_cost) as item_additional_cost, se.total_additional_costs + from `tabStock Entry` se + join `tabStock Entry Detail` sed + on sed.parent = se.name + where + se.docstatus = 1 and + se.posting_date > %s + group by + sed.parent + having + item_additional_cost != se.total_additional_costs + """, + period_closing_date, + as_dict=True, + ) + + return [d.name for d in stock_entries_to_patch] + + +def patch_additional_cost(code: StockEntryCode): + stock_entry = frappe.get_doc("Stock Entry", code) + stock_entry.distribute_additional_costs() + stock_entry.update_valuation_rate() + stock_entry.set_total_incoming_outgoing_value() + stock_entry.set_total_amount() + stock_entry.db_update() + for item in stock_entry.items: + item.db_update() + return stock_entry + + +def create_repost_item_valuation(stock_entry): + from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + + # turn on recalculate flag so reposting corrects the incoming/outgoing rates. + frappe.db.set_value( + "Stock Ledger Entry", + {"voucher_no": stock_entry.name, "actual_qty": (">", 0)}, + "recalculate_rate", + 1, + update_modified=False, + ) + + create_repost_item_valuation_entry( + args=frappe._dict( + { + "posting_date": stock_entry.posting_date, + "posting_time": stock_entry.posting_time, + "voucher_type": stock_entry.doctype, + "voucher_no": stock_entry.name, + "company": stock_entry.company, + } + ) + ) diff --git a/erpnext/patches/v13_0/fix_invoice_statuses.py b/erpnext/patches/v13_0/fix_invoice_statuses.py new file mode 100644 index 00000000000..4395757159f --- /dev/null +++ b/erpnext/patches/v13_0/fix_invoice_statuses.py @@ -0,0 +1,113 @@ +import frappe +from frappe.utils import flt, getdate + +from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + get_total_in_party_account_currency, + is_overdue, +) + +TODAY = getdate() + +def execute(): + # This fix is not related to Party Specific Item, + # but it is needed for code introduced after Party Specific Item was + # If your DB doesn't have this doctype yet, you should be fine + if not frappe.db.exists("DocType", "Party Specific Item"): + return + + for doctype in ("Purchase Invoice", "Sales Invoice"): + fields = [ + "name", + "status", + "due_date", + "outstanding_amount", + "grand_total", + "base_grand_total", + "rounded_total", + "base_rounded_total", + "disable_rounded_total", + ] + if doctype == "Sales Invoice": + fields.append("is_pos") + + invoices_to_update = frappe.get_all( + doctype, + fields=fields, + filters={ + "docstatus": 1, + "status": ("in", ( + "Overdue", + "Overdue and Discounted", + "Partly Paid", + "Partly Paid and Discounted" + )), + "outstanding_amount": (">", 0), + "modified": (">", "2021-01-01") + # an assumption is being made that only invoices modified + # after 2021 got affected as incorrectly overdue. + # required for performance reasons. + } + ) + + invoices_to_update = { + invoice.name: invoice for invoice in invoices_to_update + } + + payment_schedule_items = frappe.get_all( + "Payment Schedule", + fields=( + "due_date", + "payment_amount", + "base_payment_amount", + "parent" + ), + filters={"parent": ("in", invoices_to_update)} + ) + + for item in payment_schedule_items: + invoices_to_update[item.parent].setdefault( + "payment_schedule", [] + ).append(item) + + status_map = {} + + for invoice in invoices_to_update.values(): + invoice.doctype = doctype + doc = frappe.get_doc(invoice) + correct_status = get_correct_status(doc) + if not correct_status or doc.status == correct_status: + continue + + status_map.setdefault(correct_status, []).append(doc.name) + + for status, docs in status_map.items(): + frappe.db.set_value( + doctype, {"name": ("in", docs)}, + "status", + status, + update_modified=False + ) + + + +def get_correct_status(doc): + outstanding_amount = flt( + doc.outstanding_amount, doc.precision("outstanding_amount") + ) + total = get_total_in_party_account_currency(doc) + + status = "" + if is_overdue(doc, total): + status = "Overdue" + elif 0 < outstanding_amount < total: + status = "Partly Paid" + elif outstanding_amount > 0 and getdate(doc.due_date) >= TODAY: + status = "Unpaid" + + if not status: + return + + if doc.status.endswith(" and Discounted"): + status += " and Discounted" + + return status diff --git a/erpnext/patches/v13_0/fix_non_unique_represents_company.py b/erpnext/patches/v13_0/fix_non_unique_represents_company.py index f20c73ae102..e91c1db4dd4 100644 --- a/erpnext/patches/v13_0/fix_non_unique_represents_company.py +++ b/erpnext/patches/v13_0/fix_non_unique_represents_company.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.db.sql(""" update tabCustomer diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py index dca43b4193d..72cda751e6c 100644 --- a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py +++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py @@ -1,7 +1,6 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py index 41ab945eb12..80b6a3954a6 100644 --- a/erpnext/patches/v13_0/germany_make_custom_fields.py +++ b/erpnext/patches/v13_0/germany_make_custom_fields.py @@ -1,9 +1,9 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + from erpnext.regional.germany.setup import make_custom_fields diff --git a/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py new file mode 100644 index 00000000000..76f8b274999 --- /dev/null +++ b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py @@ -0,0 +1,42 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name']) + if not company: + return + + hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', + fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description', + allow_on_submit=1, print_hide=1, fetch_if_empty=1) + nil_rated_exempt = dict(fieldname='is_nil_exempt', label='Is Nil Rated or Exempted', + fieldtype='Check', fetch_from='item_code.is_nil_exempt', insert_after='gst_hsn_code', + print_hide=1) + is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST', + fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt', + print_hide=1) + taxable_value = dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) + sales_invoice_gst_fields = [ + dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', + fieldtype='Data', insert_after='customer_address', read_only=1, + fetch_from='customer_address.gstin', print_hide=1), + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='shipping_address_name', + fetch_from='shipping_address_name.gstin', print_hide=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='customer_gstin', + print_hide=1, read_only=1), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + fetch_from='company_address.gstin', print_hide=1, read_only=1), + ] + + custom_fields = { + 'POS Invoice': sales_invoice_gst_fields, + 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], + } + + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/patches/v13_0/healthcare_deprecation_warning.py b/erpnext/patches/v13_0/healthcare_deprecation_warning.py new file mode 100644 index 00000000000..c6fba59371c --- /dev/null +++ b/erpnext/patches/v13_0/healthcare_deprecation_warning.py @@ -0,0 +1,10 @@ +import click + + +def execute(): + + click.secho( + "Healthcare Module is moved to a separate app and will be removed from ERPNext in version-14.\n" + "Please install the app to continue using the module: https://github.com/frappe/healthcare", + fg="yellow", + ) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index 2549a1e91ee..98ce12bc204 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field diff --git a/erpnext/patches/v13_0/item_naming_series_not_mandatory.py b/erpnext/patches/v13_0/item_naming_series_not_mandatory.py new file mode 100644 index 00000000000..5fe85a48308 --- /dev/null +++ b/erpnext/patches/v13_0/item_naming_series_not_mandatory.py @@ -0,0 +1,11 @@ +import frappe + +from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series + + +def execute(): + + stock_settings = frappe.get_doc("Stock Settings") + + set_by_naming_series("Item", "item_code", + stock_settings.get("item_naming_by")=="Naming Series", hide_name_field=True, make_mandatory=0) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index c4ad1b7ff4f..0f2ac4b4514 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -1,14 +1,24 @@ import frappe -from frappe import _ -from frappe.utils import getdate, get_time, today -from erpnext.stock.stock_ledger import update_entries_after +from frappe.utils import get_time, getdate, today + from erpnext.accounts.utils import update_gl_entries_after +from erpnext.stock.stock_ledger import update_entries_after + def execute(): - for doctype in ('repost_item_valuation', 'stock_entry_detail', 'purchase_receipt_item', - 'purchase_invoice_item', 'delivery_note_item', 'sales_invoice_item', 'packed_item'): - frappe.reload_doc('stock', 'doctype', doctype) - frappe.reload_doc('buying', 'doctype', 'purchase_receipt_item_supplied') + doctypes_to_reload = [ + ("stock", "repost_item_valuation"), + ("stock", "stock_entry_detail"), + ("stock", "purchase_receipt_item"), + ("stock", "delivery_note_item"), + ("stock", "packed_item"), + ("accounts", "sales_invoice_item"), + ("accounts", "purchase_invoice_item"), + ("buying", "purchase_receipt_item_supplied") + ] + + for module, doctype in doctypes_to_reload: + frappe.reload_doc(module, 'doctype', doctype) reposting_project_deployed_on = get_creation_time() posting_date = getdate(reposting_project_deployed_on) diff --git a/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py index d2228c3bf31..68bcd8a8da5 100644 --- a/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py +++ b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): '''`sales_invoice` field from loyalty point entry is splitted into `invoice_type` & `invoice` fields''' diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py index 73361f00262..a7bdf93332a 100644 --- a/erpnext/patches/v13_0/make_non_standard_user_type.py +++ b/erpnext/patches/v13_0/make_non_standard_user_type.py @@ -1,11 +1,12 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from six import iteritems + from erpnext.setup.install import add_non_standard_user_types + def execute(): doctype_dict = { 'projects': ['Timesheet'], @@ -13,7 +14,7 @@ def execute(): 'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request'] } - for module, doctypes in iteritems(doctype_dict): + for module, doctypes in doctype_dict.items(): for doctype in doctypes: frappe.reload_doc(module, 'doctype', doctype) diff --git a/erpnext/patches/v13_0/migrate_stripe_api.py b/erpnext/patches/v13_0/migrate_stripe_api.py new file mode 100644 index 00000000000..355421a1f42 --- /dev/null +++ b/erpnext/patches/v13_0/migrate_stripe_api.py @@ -0,0 +1,7 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + frappe.reload_doc("accounts", "doctype", "subscription_plan") + rename_field("Subscription Plan", "payment_plan_id", "product_price_id") diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py new file mode 100644 index 00000000000..1c659980099 --- /dev/null +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -0,0 +1,59 @@ +import json + +import frappe + + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'purchase_invoice_advance') + frappe.reload_doc('accounts', 'doctype', 'sales_invoice_advance') + + purchase_invoices = frappe.db.sql(""" + select + parenttype as type, parent as name + from + `tabPurchase Invoice Advance` + where + ref_exchange_rate = 1 + and docstatus = 1 + and ifnull(exchange_gain_loss, 0) != 0 + group by + parent + """, as_dict=1) + + sales_invoices = frappe.db.sql(""" + select + parenttype as type, parent as name + from + `tabSales Invoice Advance` + where + ref_exchange_rate = 1 + and docstatus = 1 + and ifnull(exchange_gain_loss, 0) != 0 + group by + parent + """, as_dict=1) + + if purchase_invoices + sales_invoices: + frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + + acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') + if acc_frozen_upto: + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + + for invoice in purchase_invoices + sales_invoices: + try: + doc = frappe.get_doc(invoice.type, invoice.name) + doc.docstatus = 2 + doc.make_gl_entries() + for advance in doc.advances: + if advance.ref_exchange_rate == 1: + advance.db_set('exchange_gain_loss', 0, False) + doc.docstatus = 1 + doc.make_gl_entries() + frappe.db.commit() + except Exception: + frappe.db.rollback() + print(f'Failed to correct gl entries of {invoice.name}') + + if acc_frozen_upto: + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file diff --git a/erpnext/patches/v13_0/move_branch_code_to_bank_account.py b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py index 24d9196d29f..350744fd41f 100644 --- a/erpnext/patches/v13_0/move_branch_code_to_bank_account.py +++ b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('accounts', 'doctype', 'bank_account') diff --git a/erpnext/patches/v13_0/move_doctype_reports_and_notification_from_hr_to_payroll.py b/erpnext/patches/v13_0/move_doctype_reports_and_notification_from_hr_to_payroll.py index 4d7c85ce2d1..c07caaef661 100644 --- a/erpnext/patches/v13_0/move_doctype_reports_and_notification_from_hr_to_payroll.py +++ b/erpnext/patches/v13_0/move_doctype_reports_and_notification_from_hr_to_payroll.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): frappe.db.sql("""UPDATE `tabPrint Format` SET module = 'Payroll' diff --git a/erpnext/patches/v13_0/move_payroll_setting_separately_from_hr_settings.py b/erpnext/patches/v13_0/move_payroll_setting_separately_from_hr_settings.py index a901064b889..fca7c09c91a 100644 --- a/erpnext/patches/v13_0/move_payroll_setting_separately_from_hr_settings.py +++ b/erpnext/patches/v13_0/move_payroll_setting_separately_from_hr_settings.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): data = frappe.db.sql('''SELECT * FROM `tabSingles` diff --git a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py index 1a91d218ba3..d1ea22f7f26 100644 --- a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py +++ b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py @@ -1,10 +1,9 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe -from frappe.model.utils.rename_field import rename_field + def execute(): if not (frappe.db.table_exists("Payroll Period") and frappe.db.table_exists("Taxable Salary Slab")): @@ -86,7 +85,7 @@ def execute(): try: employee_other_income.submit() migrated.append([proof.employee, proof.payroll_period]) - except: + except Exception: pass if not frappe.db.table_exists("Employee Tax Exemption Declaration"): @@ -108,5 +107,5 @@ def execute(): try: employee_other_income.submit() - except: + except Exception: pass diff --git a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py index 15aeb76e53f..7c10a313a56 100644 --- a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py +++ b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py @@ -1,7 +1,6 @@ -from __future__ import unicode_literals - import frappe + def execute(): if not frappe.db.table_exists("Additional Salary"): return diff --git a/erpnext/patches/v13_0/print_uom_after_quantity_patch.py b/erpnext/patches/v13_0/print_uom_after_quantity_patch.py index 0de3728f5c6..3da6f749aff 100644 --- a/erpnext/patches/v13_0/print_uom_after_quantity_patch.py +++ b/erpnext/patches/v13_0/print_uom_after_quantity_patch.py @@ -1,10 +1,9 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from erpnext.setup.install import create_print_uom_after_qty_custom_field + def execute(): create_print_uom_after_qty_custom_field() diff --git a/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py index 53da7006b98..bbe3eb5815b 100644 --- a/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py +++ b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py @@ -1,5 +1,6 @@ import frappe + def execute(): """Remove has_variants and attribute fields from item variant settings.""" frappe.reload_doc("stock", "doctype", "Item Variant Settings") diff --git a/erpnext/patches/v13_0/remove_bad_selling_defaults.py b/erpnext/patches/v13_0/remove_bad_selling_defaults.py new file mode 100644 index 00000000000..5487a6c60cc --- /dev/null +++ b/erpnext/patches/v13_0/remove_bad_selling_defaults.py @@ -0,0 +1,15 @@ +import frappe +from frappe import _ + + +def execute(): + selling_settings = frappe.get_single("Selling Settings") + + if selling_settings.customer_group in (_("All Customer Groups"), "All Customer Groups"): + selling_settings.customer_group = None + + if selling_settings.territory in (_("All Territories"), "All Territories"): + selling_settings.territory = None + + selling_settings.flags.ignore_mandatory=True + selling_settings.save(ignore_permissions=True) diff --git a/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py index 491dc82f784..3bd717d77b8 100644 --- a/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py +++ b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc("Healthcare", "doctype", "Inpatient Record") if frappe.db.has_column("Inpatient Record", "discharge_date"): diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py index 41c51c36dcb..bf5438c4d2e 100644 --- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py +++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py @@ -1,10 +1,11 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.model.utils.rename_field import rename_field + def execute(): if frappe.db.exists('DocType', 'Issue'): issues = frappe.db.get_all('Issue', fields=['name', 'response_by_variance', 'resolution_by_variance', 'mins_to_first_response'], @@ -41,6 +42,7 @@ def execute(): rename_field('Opportunity', 'mins_to_first_response', 'first_response_time') # change fieldtype to duration + frappe.reload_doc('crm', 'doctype', 'opportunity', force=True) count = 0 for entry in opportunities: mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes') @@ -58,6 +60,8 @@ def execute(): def convert_to_seconds(value, unit): seconds = 0 + if value == 0: + return seconds if unit == 'Hours': seconds = value * 3600 if unit == 'Minutes': diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py index 4ef04ad9b1b..b129cbe80bc 100644 --- a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py +++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py @@ -1,9 +1,10 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): if frappe.db.exists('DocType', 'Issue'): frappe.reload_doc("support", "doctype", "issue") diff --git a/erpnext/patches/v13_0/rename_ksa_qr_field.py b/erpnext/patches/v13_0/rename_ksa_qr_field.py new file mode 100644 index 00000000000..0bb86e04509 --- /dev/null +++ b/erpnext/patches/v13_0/rename_ksa_qr_field.py @@ -0,0 +1,16 @@ +# Copyright (c) 2020, Wahni Green Technologies and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'}) + if not company: + return + + if frappe.db.exists('DocType', 'Sales Invoice'): + frappe.reload_doc('accounts', 'doctype', 'sales_invoice', force=True) + if frappe.db.has_column('Sales Invoice', 'qr_code'): + rename_field('Sales Invoice', 'qr_code', 'ksa_einv_qr') diff --git a/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py b/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py index f60567b6b21..265e2a9b0cf 100644 --- a/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py +++ b/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field + def execute(): if frappe.db.table_exists("Membership Settings"): frappe.rename_doc("DocType", "Membership Settings", "Non Profit Settings") diff --git a/erpnext/patches/v13_0/rename_stop_to_send_birthday_reminders.py b/erpnext/patches/v13_0/rename_stop_to_send_birthday_reminders.py index 1787a560254..813fbd2d5c9 100644 --- a/erpnext/patches/v13_0/rename_stop_to_send_birthday_reminders.py +++ b/erpnext/patches/v13_0/rename_stop_to_send_birthday_reminders.py @@ -1,23 +1,24 @@ import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc('hr', 'doctype', 'hr_settings') try: # Rename the field rename_field('HR Settings', 'stop_birthday_reminders', 'send_birthday_reminders') - + # Reverse the value old_value = frappe.db.get_single_value('HR Settings', 'send_birthday_reminders') frappe.db.set_value( - 'HR Settings', - 'HR Settings', - 'send_birthday_reminders', + 'HR Settings', + 'HR Settings', + 'send_birthday_reminders', 1 if old_value == 0 else 0 ) - + except Exception as e: if e.args[0] != 1054: - raise \ No newline at end of file + raise diff --git a/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py b/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py index d8bcd7f0775..7d757b7a0fb 100644 --- a/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py +++ b/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): if frappe.db.exists("Page", "point-of-sale"): frappe.rename_doc("Page", "pos", "point-of-sale", 1, 1) diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py index bc1fc98e4da..a2c960c8f37 100644 --- a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py +++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("accounts", "doctype", "pos_payment_method") pos_profiles = frappe.get_all("POS Profile") diff --git a/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py new file mode 100644 index 00000000000..ba96fdd2266 --- /dev/null +++ b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py @@ -0,0 +1,17 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + if frappe.db.table_exists('Supplier Item Group'): + frappe.reload_doc("selling", "doctype", "party_specific_item") + sig = frappe.db.get_all("Supplier Item Group", fields=["name", "supplier", "item_group"]) + for item in sig: + psi = frappe.new_doc("Party Specific Item") + psi.party_type = "Supplier" + psi.party = item.supplier + psi.restrict_based_on = "Item Group" + psi.based_on_value = item.item_group + psi.insert() diff --git a/erpnext/patches/v13_0/requeue_failed_reposts.py b/erpnext/patches/v13_0/requeue_failed_reposts.py new file mode 100644 index 00000000000..213cb9e26e4 --- /dev/null +++ b/erpnext/patches/v13_0/requeue_failed_reposts.py @@ -0,0 +1,13 @@ +import frappe +from frappe.utils import cstr + + +def execute(): + + reposts = frappe.get_all("Repost Item Valuation", + {"status": "Failed", "modified": [">", "2021-10-05"] }, + ["name", "modified", "error_log"]) + + for repost in reposts: + if "check_freezing_date" in cstr(repost.error_log): + frappe.db.set_value("Repost Item Valuation", repost.name, "status", "Queued") diff --git a/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py index 1da5275761b..69fc6a2dbcf 100644 --- a/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py +++ b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): """ Reset Clearance Date for Payment Entries of type Internal Transfer that have only been reconciled with one Bank Transaction. @@ -35,11 +35,11 @@ def get_reconciled_bank_transactions(intra_company_pe): for payment_entry in intra_company_pe: reconciled_bank_transactions[payment_entry] = frappe.get_all( - 'Bank Transaction Payments', + 'Bank Transaction Payments', filters = { 'payment_entry': payment_entry - }, + }, pluck='parent' ) - return reconciled_bank_transactions \ No newline at end of file + return reconciled_bank_transactions diff --git a/erpnext/patches/v13_0/set_app_name.py b/erpnext/patches/v13_0/set_app_name.py index 3f886f1d159..4a88442bcdc 100644 --- a/erpnext/patches/v13_0/set_app_name.py +++ b/erpnext/patches/v13_0/set_app_name.py @@ -1,5 +1,5 @@ import frappe -from frappe import _ + def execute(): frappe.reload_doctype("System Settings") diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py index a5b93f63071..f82a0d56e0c 100644 --- a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py +++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): company = frappe.db.get_single_value('Global Defaults', 'default_company') doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection', 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment'] diff --git a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py index 13ec41ec55e..c744f35b72f 100644 --- a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py +++ b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doc('HR', 'doctype', 'Leave Allocation') frappe.reload_doc('HR', 'doctype', 'Leave Ledger Entry') diff --git a/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py new file mode 100644 index 00000000000..e26285e508d --- /dev/null +++ b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py @@ -0,0 +1,16 @@ +import frappe + + +def execute(): + frappe.reload_doc('manufacturing', 'doctype', 'bom') + frappe.reload_doc('manufacturing', 'doctype', 'bom_operation') + + frappe.db.sql(''' + UPDATE + `tabBOM Operation` + SET + time_in_mins = (operating_cost * 60) / hour_rate + WHERE + time_in_mins = 0 AND operating_cost > 0 + AND hour_rate > 0 AND docstatus = 1 AND parenttype = "BOM" + ''') diff --git a/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py b/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py index 7f75946af9f..87b3389448a 100644 --- a/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py +++ b/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): """Set the payment gateway account as Email for all the existing payment channel.""" doc_meta = frappe.get_meta("Payment Gateway Account") diff --git a/erpnext/patches/v13_0/set_pos_closing_as_failed.py b/erpnext/patches/v13_0/set_pos_closing_as_failed.py index 7968e74f50f..6a3785d8377 100644 --- a/erpnext/patches/v13_0/set_pos_closing_as_failed.py +++ b/erpnext/patches/v13_0/set_pos_closing_as_failed.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('accounts', 'doctype', 'pos_closing_entry') diff --git a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py new file mode 100644 index 00000000000..9887ad9df0c --- /dev/null +++ b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py @@ -0,0 +1,10 @@ +import frappe + + +def execute(): + frappe.reload_doc("maintenance", "doctype", "Maintenance Schedule Detail") + frappe.db.sql(""" + UPDATE `tabMaintenance Schedule Detail` + SET completion_status = 'Pending' + WHERE docstatus < 2 + """) diff --git a/erpnext/patches/v13_0/set_training_event_attendance.py b/erpnext/patches/v13_0/set_training_event_attendance.py index 3db183fb2ab..e44f32157dc 100644 --- a/erpnext/patches/v13_0/set_training_event_attendance.py +++ b/erpnext/patches/v13_0/set_training_event_attendance.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('hr', 'doctype', 'training_event') frappe.reload_doc('hr', 'doctype', 'training_event_employee') diff --git a/erpnext/patches/v13_0/set_youtube_video_id.py b/erpnext/patches/v13_0/set_youtube_video_id.py index f6104d1579f..e1eb1b9bc2f 100644 --- a/erpnext/patches/v13_0/set_youtube_video_id.py +++ b/erpnext/patches/v13_0/set_youtube_video_id.py @@ -1,7 +1,8 @@ -from __future__ import unicode_literals import frappe + from erpnext.utilities.doctype.video.video import get_id_from_url + def execute(): frappe.reload_doc("utilities", "doctype","video") diff --git a/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py b/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py index c8c160fae71..dc3f8aadc16 100644 --- a/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py +++ b/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py @@ -1,7 +1,8 @@ -from __future__ import unicode_literals import frappe + from erpnext.regional.india.setup import add_custom_roles_for_reports + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py index 833c355d5f8..7a2a2539670 100644 --- a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py +++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py @@ -1,6 +1,8 @@ import frappe + from erpnext.regional.india.setup import make_custom_fields + def execute(): if frappe.get_all('Company', filters = {'country': 'India'}): make_custom_fields() diff --git a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py index 01fd6a158e9..82cc1ff771c 100644 --- a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py +++ b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py @@ -1,9 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('payroll', 'doctype', 'gratuity_rule') frappe.reload_doc('payroll', 'doctype', 'gratuity_rule_slab') diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py deleted file mode 100644 index 83581dd4144..00000000000 --- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.healthcare.setup import setup_patient_history_settings - -def execute(): - if "Healthcare" not in frappe.get_active_domains(): - return - - frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order") - frappe.reload_doc("healthcare", "doctype", "Therapy Session") - frappe.reload_doc("healthcare", "doctype", "Clinical Procedure") - frappe.reload_doc("healthcare", "doctype", "Patient History Settings") - frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type") - frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type") - - setup_patient_history_settings() diff --git a/erpnext/patches/v13_0/setup_uae_vat_fields.py b/erpnext/patches/v13_0/setup_uae_vat_fields.py index 1830bab02ba..d89e0521d8d 100644 --- a/erpnext/patches/v13_0/setup_uae_vat_fields.py +++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py @@ -2,8 +2,10 @@ # License: GNU General Public License v3. See license.txt import frappe + from erpnext.regional.united_arab_emirates.setup import setup + def execute(): company = frappe.get_all('Company', filters = {'country': 'United Arab Emirates'}) if not company: diff --git a/erpnext/patches/v13_0/shopify_deprecation_warning.py b/erpnext/patches/v13_0/shopify_deprecation_warning.py index 6f199c87b6c..245d1a96250 100644 --- a/erpnext/patches/v13_0/shopify_deprecation_warning.py +++ b/erpnext/patches/v13_0/shopify_deprecation_warning.py @@ -1,5 +1,4 @@ import click -import frappe def execute(): diff --git a/erpnext/patches/v13_0/stock_entry_enhancements.py b/erpnext/patches/v13_0/stock_entry_enhancements.py index 7b93ce35768..968a83a4212 100644 --- a/erpnext/patches/v13_0/stock_entry_enhancements.py +++ b/erpnext/patches/v13_0/stock_entry_enhancements.py @@ -1,9 +1,10 @@ # Copyright(c) 2020, Frappe Technologies Pvt.Ltd.and Contributors # License: GNU General Public License v3.See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("stock", "doctype", "stock_entry") if frappe.db.has_column("Stock Entry", "add_to_transit"): diff --git a/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py b/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py new file mode 100644 index 00000000000..fd48c0d902d --- /dev/null +++ b/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py @@ -0,0 +1,18 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + +from erpnext.regional.india.setup import create_custom_fields, get_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': get_custom_fields().get('Sales Invoice') + } + + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py index 50f233deef4..55fd465b204 100644 --- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py +++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py @@ -1,12 +1,11 @@ - # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe.utils import add_to_date + def execute(): frappe.reload_doc("manufacturing", "doctype", "work_order") frappe.reload_doc("manufacturing", "doctype", "work_order_item") diff --git a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py index dc9ed18eade..dc973a9d451 100644 --- a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py +++ b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py @@ -1,5 +1,6 @@ import frappe + def execute(): """ Correct amount in child table of required items table.""" diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py new file mode 100644 index 00000000000..a5f5a23449a --- /dev/null +++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py @@ -0,0 +1,20 @@ +import frappe + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + frappe.reload_doc('regional', 'doctype', 'lower_deduction_certificate') + + ldc = frappe.qb.DocType("Lower Deduction Certificate").as_("ldc") + supplier = frappe.qb.DocType("Supplier") + + frappe.qb.update(ldc).inner_join(supplier).on( + ldc.supplier == supplier.name + ).set( + ldc.tax_withholding_category, supplier.tax_withholding_category + ).where( + ldc.tax_withholding_category.isnull() + ).run() \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py new file mode 100644 index 00000000000..90fb50fb42c --- /dev/null +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -0,0 +1,26 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') + + if frappe.db.has_column('Tax Withholding Rate', 'fiscal_year'): + tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) + + fiscal_year_map = {} + fiscal_year_details = frappe.get_all('Fiscal Year', fields=['name', 'year_start_date', 'year_end_date']) + + for d in fiscal_year_details: + fiscal_year_map.setdefault(d.name, d) + + for rate in tds_category_rates: + from_date = fiscal_year_map.get(rate.fiscal_year).get('year_start_date') + to_date = fiscal_year_map.get(rate.fiscal_year).get('year_end_date') + + frappe.db.set_value('Tax Withholding Rate', rate.name, { + 'from_date': from_date, + 'to_date': to_date + }) \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_deferred_settings.py b/erpnext/patches/v13_0/update_deferred_settings.py index bcc09527a29..1b63635b678 100644 --- a/erpnext/patches/v13_0/update_deferred_settings.py +++ b/erpnext/patches/v13_0/update_deferred_settings.py @@ -1,8 +1,9 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') accounts_settings.book_deferred_entries_based_on = 'Days' diff --git a/erpnext/patches/v13_0/update_export_type_for_gst.py b/erpnext/patches/v13_0/update_export_type_for_gst.py index ef70b55d94c..de578612f7d 100644 --- a/erpnext/patches/v13_0/update_export_type_for_gst.py +++ b/erpnext/patches/v13_0/update_export_type_for_gst.py @@ -1,5 +1,6 @@ import frappe + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: diff --git a/erpnext/patches/v13_0/update_job_card_details.py b/erpnext/patches/v13_0/update_job_card_details.py index 733b3a960cf..12f9006b76e 100644 --- a/erpnext/patches/v13_0/update_job_card_details.py +++ b/erpnext/patches/v13_0/update_job_card_details.py @@ -1,9 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("manufacturing", "doctype", "job_card") frappe.reload_doc("manufacturing", "doctype", "job_card_item") diff --git a/erpnext/patches/v13_0/update_job_card_status.py b/erpnext/patches/v13_0/update_job_card_status.py new file mode 100644 index 00000000000..797a3e2ae35 --- /dev/null +++ b/erpnext/patches/v13_0/update_job_card_status.py @@ -0,0 +1,18 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + + job_card = frappe.qb.DocType("Job Card") + (frappe.qb + .update(job_card) + .set(job_card.status, "Completed") + .where( + (job_card.docstatus == 1) + & (job_card.for_quantity <= job_card.total_completed_qty) + & (job_card.status.isin(["Work In Progress", "Material Transferred"])) + ) + ).run() diff --git a/erpnext/patches/v13_0/update_level_in_bom.py b/erpnext/patches/v13_0/update_level_in_bom.py index 0d03c42e980..499412ee270 100644 --- a/erpnext/patches/v13_0/update_level_in_bom.py +++ b/erpnext/patches/v13_0/update_level_in_bom.py @@ -1,9 +1,10 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): for document in ["bom", "bom_item", "bom_explosion_item"]: frappe.reload_doc('manufacturing', 'doctype', document) diff --git a/erpnext/patches/v13_0/update_member_email_address.py b/erpnext/patches/v13_0/update_member_email_address.py index 4056f84069c..e4bc1b3e5c7 100644 --- a/erpnext/patches/v13_0/update_member_email_address.py +++ b/erpnext/patches/v13_0/update_member_email_address.py @@ -1,10 +1,11 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals + import frappe from frappe.model.utils.rename_field import rename_field + def execute(): """add value to email_id column from email""" diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index 8cf09aa6925..e226f1dd567 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -1,12 +1,16 @@ -from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import nowdate, flt -from erpnext.accounts.doctype.account.test_account import create_account -from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans -from erpnext.loan_management.doctype.loan.loan import make_repayment_entry -from erpnext.loan_management.doctype.loan_repayment.loan_repayment import get_accrued_interest_entries from frappe.model.naming import make_autoname +from frappe.utils import flt, nowdate + +from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.loan_management.doctype.loan_repayment.loan_repayment import ( + get_accrued_interest_entries, +) +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_term_loans, +) + def execute(): diff --git a/erpnext/patches/v13_0/update_payment_terms_outstanding.py b/erpnext/patches/v13_0/update_payment_terms_outstanding.py index 4816b40250e..aea09ad7a37 100644 --- a/erpnext/patches/v13_0/update_payment_terms_outstanding.py +++ b/erpnext/patches/v13_0/update_payment_terms_outstanding.py @@ -1,9 +1,10 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("accounts", "doctype", "Payment Schedule") if frappe.db.count('Payment Schedule'): diff --git a/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py index 262e38dd056..b2e35591970 100644 --- a/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py +++ b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py @@ -1,9 +1,10 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("accounts", "doctype", "POS Invoice Merge Log") frappe.reload_doc("accounts", "doctype", "POS Closing Entry") diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index b41b74205c7..29debc6ad14 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -1,9 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("projects", "doctype", "project_template") frappe.reload_doc("projects", "doctype", "project_template_task") diff --git a/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py b/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py index ccdc334f306..f9bfc54502f 100644 --- a/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py +++ b/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py @@ -1,9 +1,10 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("hr", "doctype", "employee") diff --git a/erpnext/patches/v13_0/update_recipient_email_digest.py b/erpnext/patches/v13_0/update_recipient_email_digest.py index d9aa03f0fd6..d4d45afabaf 100644 --- a/erpnext/patches/v13_0/update_recipient_email_digest.py +++ b/erpnext/patches/v13_0/update_recipient_email_digest.py @@ -1,9 +1,10 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc("setup", "doctype", "Email Digest") frappe.reload_doc("setup", "doctype", "Email Digest Recipient") diff --git a/erpnext/patches/v13_0/update_response_by_variance.py b/erpnext/patches/v13_0/update_response_by_variance.py index ef4d9763837..d65e9035c0e 100644 --- a/erpnext/patches/v13_0/update_response_by_variance.py +++ b/erpnext/patches/v13_0/update_response_by_variance.py @@ -1,9 +1,10 @@ # Copyright (c) 2020, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): if frappe.db.exists('DocType', 'Issue') and frappe.db.count('Issue'): invalid_issues = frappe.get_all('Issue', { diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py index e642547ef82..dd64e05ec16 100644 --- a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py +++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py @@ -1,16 +1,19 @@ # Copyright (c) 2021, Frappe and Contributors # License: GNU General Public License v3. See license.txt import frappe + from erpnext.controllers.status_updater import OverAllowanceError + def execute(): frappe.reload_doc('stock', 'doctype', 'purchase_receipt') frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item') frappe.reload_doc('stock', 'doctype', 'delivery_note') frappe.reload_doc('stock', 'doctype', 'delivery_note_item') + frappe.reload_doc('stock', 'doctype', 'stock_settings') def update_from_return_docs(doctype): - for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}): + for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1, 'return_against': ('!=', '')}): # Update original receipt/delivery document from return return_doc = frappe.get_cached_doc(doctype, return_doc.name) try: diff --git a/erpnext/patches/v13_0/update_shipment_status.py b/erpnext/patches/v13_0/update_shipment_status.py index c425599e26b..f2d7d1d1e3f 100644 --- a/erpnext/patches/v13_0/update_shipment_status.py +++ b/erpnext/patches/v13_0/update_shipment_status.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doc("stock", "doctype", "shipment") diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py index c156ba95772..7f61020309d 100644 --- a/erpnext/patches/v13_0/update_sla_enhancements.py +++ b/erpnext/patches/v13_0/update_sla_enhancements.py @@ -1,10 +1,10 @@ # Copyright (c) 2018, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): # add holiday list and employee group fields in SLA # change response and resolution time in priorities child table diff --git a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py index 0f521cb57a8..665cc39923a 100644 --- a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py +++ b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py @@ -1,10 +1,10 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc('hr', 'doctype', 'shift_assignment') if frappe.db.has_column('Shift Assignment', 'date'): diff --git a/erpnext/patches/v13_0/update_subscription.py b/erpnext/patches/v13_0/update_subscription.py index d25e9c805b7..b67c74de1d2 100644 --- a/erpnext/patches/v13_0/update_subscription.py +++ b/erpnext/patches/v13_0/update_subscription.py @@ -1,9 +1,9 @@ # Copyright (c) 2019, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from six import iteritems + def execute(): @@ -33,7 +33,7 @@ def execute(): 'Based on price list': 'Based On Price List' } - for key, value in iteritems(price_determination_map): + for key, value in price_determination_map.items(): frappe.db.sql(""" UPDATE `tabSubscription Plan` SET price_determination = %s diff --git a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py index d9c3e453d47..e21fe578212 100644 --- a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py +++ b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py @@ -1,5 +1,6 @@ import frappe + def execute(): if frappe.db.exists('DocType', 'Member'): frappe.reload_doc('Non Profit', 'doctype', 'Member') diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py index 341b0e8e2e2..436d2e6a6da 100644 --- a/erpnext/patches/v13_0/update_tds_check_field.py +++ b/erpnext/patches/v13_0/update_tds_check_field.py @@ -1,5 +1,6 @@ import frappe + def execute(): if frappe.db.has_table("Tax Withholding Category") \ and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"): diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py index a36c84ea6e2..a5e3391d531 100644 --- a/erpnext/patches/v13_0/update_timesheet_changes.py +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc("projects", "doctype", "timesheet") frappe.reload_doc("projects", "doctype", "timesheet_detail") diff --git a/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py similarity index 80% rename from erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py rename to erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py index 01a4ae04add..902707b4b66 100644 --- a/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py +++ b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py @@ -1,7 +1,8 @@ import frappe + def execute(): - frappe.reload_doc('custom', 'doctype', 'custom_field') + frappe.reload_doc('custom', 'doctype', 'custom_field', force=True) company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: return diff --git a/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py index 7d344f9cd7e..c760a6a52f1 100644 --- a/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py +++ b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py @@ -5,6 +5,7 @@ import frappe from frappe import _ from frappe.model.utils.rename_field import rename_field + def execute(): frappe.reload_doc('Accounts', 'doctype', 'Salary Component Account') diff --git a/erpnext/patches/v13_0/validate_options_for_data_field.py b/erpnext/patches/v13_0/validate_options_for_data_field.py new file mode 100644 index 00000000000..ad777b8586d --- /dev/null +++ b/erpnext/patches/v13_0/validate_options_for_data_field.py @@ -0,0 +1,26 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + + +import frappe +from frappe.model import data_field_options + + +def execute(): + + for field in frappe.get_all('Custom Field', + fields = ['name'], + filters = { + 'fieldtype': 'Data', + 'options': ['!=', None] + }): + + if field not in data_field_options: + frappe.db.sql(""" + UPDATE + `tabCustom Field` + SET + options=NULL + WHERE + name=%s + """, (field)) diff --git a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py b/erpnext/patches/v14_0/delete_einvoicing_doctypes.py index b77d2440eb1..a3a8149be32 100644 --- a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py +++ b/erpnext/patches/v14_0/delete_einvoicing_doctypes.py @@ -1,9 +1,10 @@ import frappe + def execute(): frappe.delete_doc('DocType', 'E Invoice Settings', ignore_missing=True) frappe.delete_doc('DocType', 'E Invoice User', ignore_missing=True) frappe.delete_doc('Report', 'E-Invoice Summary', ignore_missing=True) frappe.delete_doc('Print Format', 'GST E-Invoice', ignore_missing=True) frappe.delete_doc('Custom Field', 'Sales Invoice-eway_bill_cancelled', ignore_missing=True) - frappe.delete_doc('Custom Field', 'Sales Invoice-irn_cancelled', ignore_missing=True) \ No newline at end of file + frappe.delete_doc('Custom Field', 'Sales Invoice-irn_cancelled', ignore_missing=True) diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py new file mode 100644 index 00000000000..28fc01beab5 --- /dev/null +++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py @@ -0,0 +1,49 @@ +import frappe + + +def execute(): + if "healthcare" in frappe.get_installed_apps(): + return + + frappe.delete_doc("Workspace", "Healthcare", ignore_missing=True, force=True) + + pages = frappe.get_all("Page", {"module": "healthcare"}, pluck='name') + for page in pages: + frappe.delete_doc("Page", page, ignore_missing=True, force=True) + + reports = frappe.get_all("Report", {"module": "healthcare", "is_standard": "Yes"}, pluck='name') + for report in reports: + frappe.delete_doc("Report", report, ignore_missing=True, force=True) + + print_formats = frappe.get_all("Print Format", {"module": "healthcare", "standard": "Yes"}, pluck='name') + for print_format in print_formats: + frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True) + + frappe.reload_doc("website", "doctype", "website_settings") + forms = frappe.get_all("Web Form", {"module": "healthcare", "is_standard": 1}, pluck='name') + for form in forms: + frappe.delete_doc("Web Form", form, ignore_missing=True, force=True) + + dashboards = frappe.get_all("Dashboard", {"module": "healthcare", "is_standard": 1}, pluck='name') + for dashboard in dashboards: + frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True) + + dashboards = frappe.get_all("Dashboard Chart", {"module": "healthcare", "is_standard": 1}, pluck='name') + for dashboard in dashboards: + frappe.delete_doc("Dashboard Chart", dashboard, ignore_missing=True, force=True) + + frappe.reload_doc("desk", "doctype", "number_card") + cards = frappe.get_all("Number Card", {"module": "healthcare", "is_standard": 1}, pluck='name') + for card in cards: + frappe.delete_doc("Number Card", card, ignore_missing=True, force=True) + + titles = ['Lab Test', 'Prescription', 'Patient Appointment'] + items = frappe.get_all('Portal Menu Item', filters=[['title', 'in', titles]], pluck='name') + for item in items: + frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True) + + doctypes = frappe.get_all("DocType", {"module": "healthcare", "custom": 0}, pluck='name') + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) + + frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True) diff --git a/erpnext/patches/v14_0/delete_hub_doctypes.py b/erpnext/patches/v14_0/delete_hub_doctypes.py new file mode 100644 index 00000000000..d1e9e31f0c2 --- /dev/null +++ b/erpnext/patches/v14_0/delete_hub_doctypes.py @@ -0,0 +1,10 @@ +import frappe + + +def execute(): + + doctypes = frappe.get_all("DocType", {"module": "Hub Node", "custom": 0}, pluck='name') + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) + + frappe.delete_doc("Module Def", "Hub Node", ignore_missing=True, force=True) diff --git a/erpnext/patches/v14_0/delete_shopify_doctypes.py b/erpnext/patches/v14_0/delete_shopify_doctypes.py new file mode 100644 index 00000000000..96bee3979d4 --- /dev/null +++ b/erpnext/patches/v14_0/delete_shopify_doctypes.py @@ -0,0 +1,6 @@ +import frappe + + +def execute(): + frappe.delete_doc("DocType", "Shopify Settings", ignore_missing=True) + frappe.delete_doc("DocType", "Shopify Log", ignore_missing=True) diff --git a/erpnext/patches/v14_0/migrate_crm_settings.py b/erpnext/patches/v14_0/migrate_crm_settings.py new file mode 100644 index 00000000000..30d3ea0cb1f --- /dev/null +++ b/erpnext/patches/v14_0/migrate_crm_settings.py @@ -0,0 +1,16 @@ +import frappe + + +def execute(): + settings = frappe.db.get_value('Selling Settings', 'Selling Settings', [ + 'campaign_naming_by', + 'close_opportunity_after_days', + 'default_valid_till' + ], as_dict=True) + + frappe.reload_doc('crm', 'doctype', 'crm_settings') + frappe.db.set_value('CRM Settings', 'CRM Settings', { + 'campaign_naming_by': settings.campaign_naming_by, + 'close_opportunity_after_days': settings.close_opportunity_after_days, + 'default_valid_till': settings.default_valid_till + }) diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py new file mode 100644 index 00000000000..13071478c86 --- /dev/null +++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py @@ -0,0 +1,34 @@ +import frappe +from frappe.utils import flt + +import erpnext +from erpnext.setup.utils import get_exchange_rate + + +def execute(): + frappe.reload_doc('crm', 'doctype', 'opportunity') + frappe.reload_doc('crm', 'doctype', 'opportunity_item') + + opportunities = frappe.db.get_list('Opportunity', filters={ + 'opportunity_amount': ['>', 0] + }, fields=['name', 'company', 'currency', 'opportunity_amount']) + + for opportunity in opportunities: + company_currency = erpnext.get_company_currency(opportunity.company) + + # base total and total will be 0 only since item table did not have amount field earlier + if opportunity.currency != company_currency: + conversion_rate = get_exchange_rate(opportunity.currency, company_currency) + base_opportunity_amount = flt(conversion_rate) * flt(opportunity.opportunity_amount) + grand_total = flt(opportunity.opportunity_amount) + base_grand_total = flt(conversion_rate) * flt(opportunity.opportunity_amount) + else: + conversion_rate = 1 + base_opportunity_amount = grand_total = base_grand_total = flt(opportunity.opportunity_amount) + + frappe.db.set_value('Opportunity', opportunity.name, { + 'conversion_rate': conversion_rate, + 'base_opportunity_amount': base_opportunity_amount, + 'grand_total': grand_total, + 'base_grand_total': base_grand_total + }, update_modified=False) diff --git a/erpnext/patches/v4_2/repost_reserved_qty.py b/erpnext/patches/v4_2/repost_reserved_qty.py index 36117aad8cc..c2ca9be64aa 100644 --- a/erpnext/patches/v4_2/repost_reserved_qty.py +++ b/erpnext/patches/v4_2/repost_reserved_qty.py @@ -1,9 +1,11 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty + +from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty + def execute(): for doctype in ("Sales Order Item", "Bin"): diff --git a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py index 7bb49e64dfe..42b0b04076f 100644 --- a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py +++ b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py @@ -1,11 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): - from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty, get_ordered_qty + from erpnext.stock.stock_balance import get_indented_qty, get_ordered_qty, update_bin_qty count=0 for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse from @@ -20,5 +21,5 @@ def execute(): }) if count % 200 == 0: frappe.db.commit() - except: + except Exception: frappe.db.rollback() diff --git a/erpnext/patches/v5_7/update_item_description_based_on_item_master.py b/erpnext/patches/v5_7/update_item_description_based_on_item_master.py index 2045358ddb2..c46187ca114 100644 --- a/erpnext/patches/v5_7/update_item_description_based_on_item_master.py +++ b/erpnext/patches/v5_7/update_item_description_based_on_item_master.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals import frappe + def execute(): name = frappe.db.sql(""" select name from `tabPatch Log` \ where \ diff --git a/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py b/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py index 55f5f8201fb..ed1dffe75c8 100644 --- a/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py +++ b/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py @@ -1,9 +1,10 @@ # Copyright (c) 2017, Frappe and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe + def execute(): frappe.reload_doc('core', 'doctype', 'has_role') company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/patches/v8_1/setup_gst_india.py b/erpnext/patches/v8_1/setup_gst_india.py index c214990693c..ff9e6a48e83 100644 --- a/erpnext/patches/v8_1/setup_gst_india.py +++ b/erpnext/patches/v8_1/setup_gst_india.py @@ -1,7 +1,7 @@ -from __future__ import unicode_literals import frappe from frappe.email import sendmail_to_system_managers + def execute(): frappe.reload_doc('stock', 'doctype', 'item') frappe.reload_doc("stock", "doctype", "customs_tariff_number") diff --git a/erpnext/patches/v8_7/sync_india_custom_fields.py b/erpnext/patches/v8_7/sync_india_custom_fields.py index eb24a90f013..808c833f6fc 100644 --- a/erpnext/patches/v8_7/sync_india_custom_fields.py +++ b/erpnext/patches/v8_7/sync_india_custom_fields.py @@ -1,6 +1,7 @@ -from __future__ import unicode_literals import frappe -from erpnext.regional.india.setup import make_custom_fields + +from erpnext.regional.india.setup import make_custom_fields + def execute(): company = frappe.get_all('Company', filters = {'country': 'India'}) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index 381f399e9fa..bf8bd05fcc0 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -1,22 +1,22 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document from frappe import _, bold -from frappe.utils import getdate, date_diff, comma_and, formatdate +from frappe.model.document import Document +from frappe.utils import comma_and, date_diff, formatdate, getdate + from erpnext.hr.utils import validate_active_employee + class AdditionalSalary(Document): def on_submit(self): - if self.ref_doctype == "Employee Advance" and self.ref_docname: - frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount) - + self.update_return_amount_in_employee_advance() self.update_employee_referral() def on_cancel(self): + self.update_return_amount_in_employee_advance() self.update_employee_referral(cancel=True) def validate(self): @@ -95,6 +95,17 @@ class AdditionalSalary(Document): frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format( frappe.bold("Accepted"))) + def update_return_amount_in_employee_advance(self): + if self.ref_doctype == "Employee Advance" and self.ref_docname: + return_amount = frappe.db.get_value("Employee Advance", self.ref_docname, "return_amount") + + if self.docstatus == 2: + return_amount -= self.amount + else: + return_amount += self.amount + + frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount) + def update_employee_referral(self, cancel=False): if self.ref_doctype == "Employee Referral": status = "Unpaid" if cancel else "Paid" @@ -112,27 +123,28 @@ class AdditionalSalary(Document): no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days +@frappe.whitelist() def get_additional_salaries(employee, start_date, end_date, component_type): - additional_salary_list = frappe.db.sql(""" - select name, salary_component as component, type, amount, - overwrite_salary_structure_amount as overwrite, - deduct_full_tax_on_selected_payroll_date - from `tabAdditional Salary` - where employee=%(employee)s - and docstatus = 1 - and ( - payroll_date between %(from_date)s and %(to_date)s - or - from_date <= %(to_date)s and to_date >= %(to_date)s - ) - and type = %(component_type)s - order by salary_component, overwrite ASC - """, { - 'employee': employee, - 'from_date': start_date, - 'to_date': end_date, - 'component_type': "Earning" if component_type == "earnings" else "Deduction" - }, as_dict=1) + comp_type = 'Earning' if component_type == 'earnings' else 'Deduction' + + additional_sal = frappe.qb.DocType('Additional Salary') + component_field = additional_sal.salary_component.as_('component') + overwrite_field = additional_sal.overwrite_salary_structure_amount.as_('overwrite') + + additional_salary_list = frappe.qb.from_( + additional_sal + ).select( + additional_sal.name, component_field, additional_sal.type, + additional_sal.amount, additional_sal.is_recurring, overwrite_field, + additional_sal.deduct_full_tax_on_selected_payroll_date + ).where( + (additional_sal.employee == employee) + & (additional_sal.docstatus == 1) + & (additional_sal.type == comp_type) + ).where( + additional_sal.payroll_date[start_date: end_date] + | ((additional_sal.from_date <= end_date) & (additional_sal.to_date >= end_date)) + ).run(as_dict=True) additional_salaries = [] components_to_overwrite = [] diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.js b/erpnext/payroll/doctype/additional_salary/test_additional_salary.js deleted file mode 100644 index c18e1875854..00000000000 --- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Additional Salary", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Additional Salary - () => frappe.tests.make('Additional Salary', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py index 2a9c56179e7..84de912e431 100644 --- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py @@ -1,13 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + import unittest -import frappe, erpnext -from frappe.utils import nowdate, add_days + +import frappe +from frappe.utils import add_days, nowdate + +import erpnext from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component -from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, setup_test +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + make_employee_salary_slip, + setup_test, +) from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py index a1cde08a74c..eda50150ebd 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py @@ -1,15 +1,26 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt from frappe.model.document import Document -from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor -from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure -from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holiday_dates_for_employee, get_previous_claimed_amount, validate_active_employee +from frappe.utils import add_days, cint, cstr, date_diff, getdate, rounded + +from erpnext.hr.utils import ( + get_holiday_dates_for_employee, + get_previous_claimed_amount, + get_sal_slip_total_benefit_given, + validate_active_employee, +) +from erpnext.payroll.doctype.payroll_period.payroll_period import ( + get_payroll_period_days, + get_period_factor, +) +from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import ( + get_assigned_salary_structure, +) + class EmployeeBenefitApplication(Document): def validate(self): diff --git a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js b/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js deleted file mode 100644 index b355e1c4366..00000000000 --- a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Benefit Application", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Benefit Application - () => frappe.tests.make('Employee Benefit Application', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.py index 34e1a8fbc1d..02149adfce5 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.py +++ b/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + import unittest + class TestEmployeeBenefitApplication(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.py b/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.py index 65405feaf19..51aa2c9dcfc 100644 --- a/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.py +++ b/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class EmployeeBenefitApplicationDetail(Document): pass diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py index c6713f3aa46..801ce4ba367 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -1,16 +1,21 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt from frappe.model.document import Document -from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits +from frappe.utils import flt + from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee +from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import ( + get_max_benefits, +) from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period -from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure +from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import ( + get_assigned_salary_structure, +) + class EmployeeBenefitClaim(Document): def validate(self): diff --git a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js b/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js deleted file mode 100644 index 3c808c0a560..00000000000 --- a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Benefit Claim", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Benefit Claim - () => frappe.tests.make('Employee Benefit Claim', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.py index aff73e5c816..b1d3c66ca86 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.py +++ b/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + import unittest + class TestEmployeeBenefitClaim(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py index 6b918ba76d1..a37e22425f7 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document + from erpnext.hr.utils import validate_active_employee + class EmployeeIncentive(Document): def validate(self): validate_active_employee(self.employee) diff --git a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js b/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js deleted file mode 100644 index 10bc03701fd..00000000000 --- a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Incentive", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Incentive - () => frappe.tests.make('Employee Incentive', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.py index f7597ad6057..e296fdf864f 100644 --- a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.py +++ b/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestEmployeeIncentive(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/employee_other_income/employee_other_income.py b/erpnext/payroll/doctype/employee_other_income/employee_other_income.py index ab63c0de623..51059a1364a 100644 --- a/erpnext/payroll/doctype/employee_other_income/employee_other_income.py +++ b/erpnext/payroll/doctype/employee_other_income/employee_other_income.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class EmployeeOtherIncome(Document): pass diff --git a/erpnext/payroll/doctype/employee_other_income/test_employee_other_income.py b/erpnext/payroll/doctype/employee_other_income/test_employee_other_income.py index 2eeca7a23de..8f0f6376504 100644 --- a/erpnext/payroll/doctype/employee_other_income/test_employee_other_income.py +++ b/erpnext/payroll/doctype/employee_other_income/test_employee_other_income.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestEmployeeOtherIncome(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/employee_tax_exemption_category/employee_tax_exemption_category.py b/erpnext/payroll/doctype/employee_tax_exemption_category/employee_tax_exemption_category.py index 4f705db22e5..5c109dec965 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_category/employee_tax_exemption_category.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_category/employee_tax_exemption_category.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class EmployeeTaxExemptionCategory(Document): pass diff --git a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js b/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js deleted file mode 100644 index e0e43c32e3b..00000000000 --- a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Tax Exemption Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Tax Exemption Category - () => frappe.tests.make('Employee Tax Exemption Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.py b/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.py index 669fb71f46e..84e6183f3b7 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestEmployeeTaxExemptionCategory(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py index e11d60a4649..9b5eab636f1 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py @@ -1,15 +1,20 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document -from frappe import _ -from frappe.utils import flt from frappe.model.mapper import get_mapped_doc -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ - calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period +from frappe.utils import flt + +from erpnext.hr.utils import ( + calculate_annual_eligible_hra_exemption, + get_total_exemption_amount, + validate_active_employee, + validate_duplicate_exemption_for_payroll_period, + validate_tax_declaration, +) + class EmployeeTaxExemptionDeclaration(Document): def validate(self): diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js deleted file mode 100644 index 274a3a38603..00000000000 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Tax Exemption Declaration", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Tax Exemption Declaration - () => frappe.tests.make('Employee Tax Exemption Declaration', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py index 311f3527f6e..fc28afdc3e5 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe, erpnext import unittest + +import frappe + +import erpnext from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.utils import DuplicateDeclarationError + class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): def setUp(self): make_employee("employee@taxexepmtion.com") diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.py index bff747f90de..4322f31c015 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class EmployeeTaxExemptionDeclarationCategory(Document): pass diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py index 8131ae0fa85..56e73b37dff 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py @@ -1,14 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document -from frappe import _ from frappe.utils import flt -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ - calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period + +from erpnext.hr.utils import ( + calculate_hra_exemption_for_period, + get_total_exemption_amount, + validate_active_employee, + validate_duplicate_exemption_for_payroll_period, + validate_tax_declaration, +) + class EmployeeTaxExemptionProofSubmission(Document): def validate(self): diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js deleted file mode 100644 index cec75087280..00000000000 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Tax Exemption Proof Submission", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Tax Exemption Proof Submission - () => frappe.tests.make('Employee Tax Exemption Proof Submission', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py index cb9ed5f971c..f2aa64c2878 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py @@ -1,11 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import create_exemption_category, create_payroll_period + +from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import ( + create_exemption_category, + create_payroll_period, +) + class TestEmployeeTaxExemptionProofSubmission(unittest.TestCase): def setup(self): diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.py index 0244ae66468..37209e5840b 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class EmployeeTaxExemptionProofSubmissionDetail(Document): pass diff --git a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py index d3f24c93780..4ac11f7112d 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt from frappe.model.document import Document +from frappe.utils import flt + class EmployeeTaxExemptionSubCategory(Document): def validate(self): diff --git a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js deleted file mode 100644 index 8a1a6d151dd..00000000000 --- a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Tax Exemption Sub Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Tax Exemption Sub Category - () => frappe.tests.make('Employee Tax Exemption Sub Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py index 5d705567a2d..64d2e3a1e6f 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestEmployeeTaxExemptionSubCategory(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js index 377f3c64916..d4f7c9ca091 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.js +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -3,13 +3,6 @@ frappe.ui.form.on('Gratuity', { setup: function (frm) { - frm.set_query('salary_component', function () { - return { - filters: { - type: "Earning" - } - }; - }); frm.set_query("expense_account", function () { return { filters: { @@ -31,7 +24,7 @@ frappe.ui.form.on('Gratuity', { }); }, refresh: function (frm) { - if (frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { + if (frm.doc.docstatus == 1 && frm.doc.status == "Unpaid") { frm.add_custom_button(__("Create Payment Entry"), function () { return frappe.call({ method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry', diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json index 5cffd7eebf9..48a9ce4759a 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.json +++ b/erpnext/payroll/doctype/gratuity/gratuity.json @@ -16,9 +16,6 @@ "company", "gratuity_rule", "section_break_5", - "pay_via_salary_slip", - "payroll_date", - "salary_component", "payable_account", "expense_account", "mode_of_payment", @@ -49,26 +46,12 @@ "read_only": 1, "reqd": 1 }, - { - "default": "1", - "fieldname": "pay_via_salary_slip", - "fieldtype": "Check", - "label": "Pay via Salary Slip" - }, { "fieldname": "posting_date", "fieldtype": "Date", "label": "Posting date", "reqd": 1 }, - { - "depends_on": "eval: doc.pay_via_salary_slip == 1", - "fieldname": "salary_component", - "fieldtype": "Link", - "label": "Salary Component", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1", - "options": "Salary Component" - }, { "default": "0", "fieldname": "current_work_experience", @@ -95,20 +78,18 @@ "reqd": 1 }, { - "depends_on": "eval: doc.pay_via_salary_slip == 0", "fieldname": "expense_account", "fieldtype": "Link", "label": "Expense Account", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", - "options": "Account" + "options": "Account", + "reqd": 1 }, { - "depends_on": "eval: doc.pay_via_salary_slip == 0", "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", - "options": "Mode of Payment" + "options": "Mode of Payment", + "reqd": 1 }, { "fieldname": "gratuity_rule", @@ -161,13 +142,6 @@ "fieldname": "column_break_15", "fieldtype": "Column Break" }, - { - "depends_on": "eval: doc.pay_via_salary_slip == 1", - "fieldname": "payroll_date", - "fieldtype": "Date", - "label": "Payroll Date", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1" - }, { "default": "0", "depends_on": "eval:doc.pay_via_salary_slip == 0", @@ -177,26 +151,23 @@ "read_only": 1 }, { - "depends_on": "eval: doc.pay_via_salary_slip == 0", "fieldname": "payable_account", "fieldtype": "Link", "label": "Payable Account", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", - "options": "Account" + "options": "Account", + "reqd": 1 }, { - "depends_on": "eval: doc.pay_via_salary_slip == 0", "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", "options": "Cost Center" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-02 18:21:11.971488", + "modified": "2021-07-02 15:05:57.396398", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity", diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 8cb804db6fa..476990a88e1 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -1,14 +1,16 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + +from math import floor + import frappe from frappe import _, bold from frappe.utils import flt, get_datetime, get_link_to_form + from erpnext.accounts.general_ledger import make_gl_entries from erpnext.controllers.accounts_controller import AccountsController -from math import floor + class Gratuity(AccountsController): def validate(self): @@ -19,10 +21,7 @@ class Gratuity(AccountsController): self.status = "Unpaid" def on_submit(self): - if self.pay_via_salary_slip: - self.create_additional_salary() - else: - self.create_gl_entries() + self.create_gl_entries() def on_cancel(self): self.ignore_linked_doctypes = ['GL Entry'] @@ -65,19 +64,6 @@ class Gratuity(AccountsController): return gl_entry - def create_additional_salary(self): - if self.pay_via_salary_slip: - additional_salary = frappe.new_doc('Additional Salary') - additional_salary.employee = self.employee - additional_salary.salary_component = self.salary_component - additional_salary.overwrite_salary_structure_amount = 0 - additional_salary.amount = self.amount - additional_salary.payroll_date = self.payroll_date - additional_salary.company = self.company - additional_salary.ref_doctype = self.doctype - additional_salary.ref_docname = self.name - additional_salary.submit() - def set_total_advance_paid(self): paid_amount = frappe.db.sql(""" select ifnull(sum(debit_in_account_currency), 0) as paid_amount @@ -207,7 +193,7 @@ def get_total_applicable_component_amount(employee, applicable_earnings_componen sal_slip = get_last_salary_slip(employee) if not sal_slip: frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) - component_and_amounts = frappe.get_list("Salary Detail", + component_and_amounts = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, @@ -242,7 +228,11 @@ def get_salary_structure(employee): order_by = "from_date desc")[0].salary_structure def get_last_salary_slip(employee): - return frappe.get_list("Salary Slip", filters = { + salary_slips = frappe.get_list("Salary Slip", filters = { "employee": employee, 'docstatus': 1 }, - order_by = "start_date desc")[0].name + order_by = "start_date desc" + ) + if not salary_slips: + return + return salary_slips[0].name diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py index 483e346a32d..aeadba186d0 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py +++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'reference_name', @@ -11,10 +11,6 @@ def get_data(): { 'label': _('Payment'), 'items': ['Payment Entry'] - }, - { - 'label': _('Additional Salary'), - 'items': ['Additional Salary'] } ] } diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 7daea2da474..93cba06da15 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -1,17 +1,20 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest +from frappe.utils import add_days, flt, get_datetime, getdate + from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, make_earning_salary_component, \ - make_deduction_salary_component -from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip -from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account -from frappe.utils import getdate, add_days, get_datetime, flt +from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + make_deduction_salary_component, + make_earning_salary_component, + make_employee_salary_slip, +) +from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule test_dependencies = ["Salary Component", "Salary Slip", "Account"] class TestGratuity(unittest.TestCase): @@ -22,14 +25,18 @@ class TestGratuity(unittest.TestCase): def setUp(self): frappe.db.sql("DELETE FROM `tabGratuity`") - frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") - def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self): + def test_get_last_salary_slip_should_return_none_for_new_employee(self): + new_employee = make_employee("new_employee@salary.com", company='_Test Company') + salary_slip = get_last_salary_slip(new_employee) + assert salary_slip is None + + def test_check_gratuity_amount_based_on_current_slab(self): employee, sal_slip = create_employee_and_get_last_salary_slip() rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)") - gratuity = create_gratuity(pay_via_salary_slip = 1, employee=employee, rule=rule.name) + gratuity = create_gratuity(employee=employee, rule=rule.name) #work experience calculation date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) @@ -41,7 +48,7 @@ class TestGratuity(unittest.TestCase): self.assertEqual(floor(experience), gratuity.current_work_experience) #amount Calculation - component_amount = frappe.get_list("Salary Detail", + component_amount = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, @@ -57,9 +64,6 @@ class TestGratuity(unittest.TestCase): self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) - #additional salary creation (Pay via salary slip) - self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name})) - def test_check_gratuity_amount_based_on_all_previous_slabs(self): employee, sal_slip = create_employee_and_get_last_salary_slip() rule = get_gratuity_rule("Rule Under Limited Contract (UAE)") @@ -80,7 +84,7 @@ class TestGratuity(unittest.TestCase): self.assertEqual(floor(experience), gratuity.current_work_experience) #amount Calculation - component_amount = frappe.get_list("Salary Detail", + component_amount = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, @@ -137,14 +141,9 @@ def create_gratuity(**args): gratuity.employee = args.employee gratuity.posting_date = getdate() gratuity.gratuity_rule = args.rule or "Rule Under Limited Contract (UAE)" - gratuity.pay_via_salary_slip = args.pay_via_salary_slip or 0 - if gratuity.pay_via_salary_slip: - gratuity.payroll_date = getdate() - gratuity.salary_component = "Performance Bonus" - else: - gratuity.expense_account = args.expense_account or 'Payment Account - _TC' - gratuity.payable_account = args.payable_account or get_payable_account("_Test Company") - gratuity.mode_of_payment = args.mode_of_payment or 'Cash' + gratuity.expense_account = args.expense_account or 'Payment Account - _TC' + gratuity.payable_account = args.payable_account or get_payable_account("_Test Company") + gratuity.mode_of_payment = args.mode_of_payment or 'Cash' gratuity.save() gratuity.submit() diff --git a/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py index 23e4340b04f..9c1657d21da 100644 --- a/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py +++ b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class GratuityApplicableComponent(Document): pass diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py index 29a6ebe1a6a..d30cfc64848 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py @@ -1,18 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document + class GratuityRule(Document): def validate(self): for current_slab in self.gratuity_rule_slabs: if (current_slab.from_year > current_slab.to_year) and current_slab.to_year != 0: - frappe(_("Row {0}: From (Year) can not be greater than To (Year)").format(current_slab.idx)) + frappe.throw(_("Row {0}: From (Year) can not be greater than To (Year)").format(current_slab.idx)) if current_slab.to_year == 0 and current_slab.from_year == 0 and len(self.gratuity_rule_slabs) > 1: frappe.throw(_("You can not define multiple slabs if you have a slab with no lower and upper limits.")) diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py index 0f27315cfbf..e7c67fbe11b 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'gratuity_rule', diff --git a/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py index 1f5dc4e571e..8393050b4a0 100644 --- a/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py +++ b/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestGratuityRule(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py index fa468e77beb..2ae6b547986 100644 --- a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py +++ b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class GratuityRuleSlab(Document): pass diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py index 81e364778ca..040b2c89353 100644 --- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py +++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py @@ -1,11 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + +from frappe.model.document import Document + #import frappe import erpnext -from frappe.model.document import Document + class IncomeTaxSlab(Document): def validate(self): diff --git a/erpnext/payroll/doctype/income_tax_slab/test_income_tax_slab.py b/erpnext/payroll/doctype/income_tax_slab/test_income_tax_slab.py index deaaf650a96..680cb3bb000 100644 --- a/erpnext/payroll/doctype/income_tax_slab/test_income_tax_slab.py +++ b/erpnext/payroll/doctype/income_tax_slab/test_income_tax_slab.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestIncomeTaxSlab(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.py b/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.py index b4098ecbf3e..53911a945bd 100644 --- a/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.py +++ b/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class IncomeTaxSlabOtherCharges(Document): pass diff --git a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.py b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.py index aeb11fd7e27..8cc426b9dba 100644 --- a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.py +++ b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class PayrollEmployeeDetail(Document): pass diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 13cc423fc2c..84c59a2c2b8 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -1,17 +1,30 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe.model.document import Document + +import frappe from dateutil.relativedelta import relativedelta -from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, date_diff, comma_and from frappe import _ +from frappe.desk.reportview import get_filters_cond, get_match_cond +from frappe.model.document import Document +from frappe.utils import ( + DATE_FORMAT, + add_days, + add_to_date, + cint, + comma_and, + date_diff, + flt, + getdate, +) + +import erpnext +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) from erpnext.accounts.utils import get_fiscal_year from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee -from frappe.desk.reportview import get_match_cond, get_filters_cond -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions + class PayrollEntry(Document): def onload(self): @@ -529,7 +542,8 @@ def get_end_date(start_date, frequency): def get_month_details(year, month): ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") if ysd: - import calendar, datetime + import calendar + import datetime diff_mnt = cint(month)-cint(ysd.month) if diff_mnt<0: diff_mnt = 12-int(ysd.month)+cint(month) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry_dashboard.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry_dashboard.py index 0346a7cc594..a33b28de40f 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry_dashboard.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'payroll_entry', diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py index b80b32061f3..c6f38972880 100644 --- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py @@ -1,19 +1,36 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import unittest -import erpnext + import frappe from dateutil.relativedelta import relativedelta -from erpnext.accounts.utils import get_fiscal_year, getdate, nowdate from frappe.utils import add_months -from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date + +import erpnext +from erpnext.accounts.utils import get_fiscal_year, getdate, nowdate from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \ - make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip -from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment -from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts -from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans +from erpnext.loan_management.doctype.loan.test_loan import ( + create_loan, + create_loan_accounts, + create_loan_type, + make_loan_disbursement_entry, +) +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_term_loans, +) +from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_end_date, get_start_end_dates +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + create_account, + get_salary_component_account, + make_deduction_salary_component, + make_earning_salary_component, + make_employee_salary_slip, +) +from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( + create_salary_structure_assignment, + make_salary_structure, +) test_dependencies = ['Holiday List'] diff --git a/erpnext/payroll/doctype/payroll_period/payroll_period.py b/erpnext/payroll/doctype/payroll_period/payroll_period.py index 66dec075d8f..659ec6de7b6 100644 --- a/erpnext/payroll/doctype/payroll_period/payroll_period.py +++ b/erpnext/payroll/doctype/payroll_period/payroll_period.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt, add_months from frappe.model.document import Document +from frappe.utils import add_months, cint, date_diff, flt, formatdate, getdate, month_diff + from erpnext.hr.utils import get_holiday_dates_for_employee + class PayrollPeriod(Document): def validate(self): self.validate_dates() diff --git a/erpnext/payroll/doctype/payroll_period/payroll_period_dashboard.py b/erpnext/payroll/doctype/payroll_period/payroll_period_dashboard.py index e33299559cc..8a3332fa6b8 100644 --- a/erpnext/payroll/doctype/payroll_period/payroll_period_dashboard.py +++ b/erpnext/payroll/doctype/payroll_period/payroll_period_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'payroll_period', diff --git a/erpnext/payroll/doctype/payroll_period/test_payroll_period.js b/erpnext/payroll/doctype/payroll_period/test_payroll_period.js deleted file mode 100644 index 8c4ded96f38..00000000000 --- a/erpnext/payroll/doctype/payroll_period/test_payroll_period.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Payroll Period", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Payroll Period - () => frappe.tests.make('Payroll Period', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/payroll_period/test_payroll_period.py b/erpnext/payroll/doctype/payroll_period/test_payroll_period.py index d06dc739a6e..61967c04bda 100644 --- a/erpnext/payroll/doctype/payroll_period/test_payroll_period.py +++ b/erpnext/payroll/doctype/payroll_period/test_payroll_period.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestPayrollPeriod(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/payroll_period_date/payroll_period_date.py b/erpnext/payroll/doctype/payroll_period_date/payroll_period_date.py index a3ee269d8e6..c90a76a829a 100644 --- a/erpnext/payroll/doctype/payroll_period_date/payroll_period_date.py +++ b/erpnext/payroll/doctype/payroll_period_date/payroll_period_date.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class PayrollPeriodDate(Document): pass diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py index 459b7eacb43..6fd30946f55 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe +from frappe import _ +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.model.document import Document from frappe.utils import cint -from frappe.custom.doctype.property_setter.property_setter import make_property_setter -from frappe import _ + class PayrollSettings(Document): def validate(self): diff --git a/erpnext/payroll/doctype/payroll_settings/test_payroll_settings.py b/erpnext/payroll/doctype/payroll_settings/test_payroll_settings.py index 314866e128e..3b96db6ed4c 100644 --- a/erpnext/payroll/doctype/payroll_settings/test_payroll_settings.py +++ b/erpnext/payroll/doctype/payroll_settings/test_payroll_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestPayrollSettings(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py index 055bea74108..10e8381007b 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document from frappe.utils import getdate + from erpnext.hr.utils import validate_active_employee + + class RetentionBonus(Document): def validate(self): validate_active_employee(self.employee) diff --git a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js b/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js deleted file mode 100644 index a4b95d3cb10..00000000000 --- a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Retention Bonus", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Retention Bonus - () => frappe.tests.make('Retention Bonus', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.py index eef4f1444e1..c86bf335114 100644 --- a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.py +++ b/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestRetentionBonus(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/salary_component/salary_component.py b/erpnext/payroll/doctype/salary_component/salary_component.py index 7c926314a21..b8def58643a 100644 --- a/erpnext/payroll/doctype/salary_component/salary_component.py +++ b/erpnext/payroll/doctype/salary_component/salary_component.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document from frappe.model.naming import append_number_if_name_exists + class SalaryComponent(Document): def validate(self): self.validate_abbr() diff --git a/erpnext/payroll/doctype/salary_component/test_salary_component.js b/erpnext/payroll/doctype/salary_component/test_salary_component.js deleted file mode 100644 index c47d32d9965..00000000000 --- a/erpnext/payroll/doctype/salary_component/test_salary_component.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Salary Component", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Salary Component - () => frappe.tests.make('Salary Component', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/salary_component/test_salary_component.py b/erpnext/payroll/doctype/salary_component/test_salary_component.py index 4f7db0c71ca..6e00971a230 100644 --- a/erpnext/payroll/doctype/salary_component/test_salary_component.py +++ b/erpnext/payroll/doctype/salary_component/test_salary_component.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest # test_records = frappe.get_test_records('Salary Component') diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json index 393f647cc88..665f0a8297e 100644 --- a/erpnext/payroll/doctype/salary_detail/salary_detail.json +++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json @@ -12,6 +12,7 @@ "year_to_date", "section_break_5", "additional_salary", + "is_recurring_additional_salary", "statistical_component", "depends_on_payment_days", "exempted_from_income_tax", @@ -235,11 +236,19 @@ "label": "Year To Date", "options": "currency", "read_only": 1 - } + }, + { + "default": "0", + "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.additional_salary", + "fieldname": "is_recurring_additional_salary", + "fieldtype": "Check", + "label": "Is Recurring Additional Salary", + "read_only": 1 + } ], "istable": 1, "links": [], - "modified": "2021-01-14 13:39:15.847158", + "modified": "2021-08-30 13:39:15.847158", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Detail", diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.py b/erpnext/payroll/doctype/salary_detail/salary_detail.py index 0b187543d48..c74bd546eb3 100644 --- a/erpnext/payroll/doctype/salary_detail/salary_detail.py +++ b/erpnext/payroll/doctype/salary_detail/salary_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class SalaryDetail(Document): pass diff --git a/erpnext/payroll/doctype/salary_slip/__init__.py b/erpnext/payroll/doctype/salary_slip/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/payroll/doctype/salary_slip/__init__.py +++ b/erpnext/payroll/doctype/salary_slip/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index 5258f3aff9e..3ef9762a839 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -135,15 +135,15 @@ frappe.ui.form.on("Salary Slip", { change_form_labels: function(frm, company_currency) { frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction", - "base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"], + "base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date", "gross_base_year_to_date"], company_currency); - frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date"], + frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date", "gross_year_to_date"], frm.doc.currency); // toggle fields frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction", - "base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"], + "base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date", "base_gross_year_to_date"], frm.doc.currency != company_currency); }, diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 42a0f290cb4..7a80e69374f 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -56,6 +56,8 @@ "totals", "gross_pay", "base_gross_pay", + "gross_year_to_date", + "base_gross_year_to_date", "column_break_25", "total_deduction", "base_total_deduction", @@ -327,7 +329,7 @@ { "fieldname": "earning_deduction", "fieldtype": "Section Break", - "label": "Earning & Deduction", + "label": "Earnings & Deductions", "oldfieldtype": "Section Break" }, { @@ -378,7 +380,7 @@ "depends_on": "total_loan_repayment", "fieldname": "loan_repayment", "fieldtype": "Section Break", - "label": "Loan repayment" + "label": "Loan Repayment" }, { "fieldname": "loans", @@ -423,7 +425,7 @@ { "fieldname": "net_pay_info", "fieldtype": "Section Break", - "label": "net pay info" + "label": "Net Pay Info" }, { "fieldname": "net_pay", @@ -625,13 +627,27 @@ "label": "Leave Details", "options": "Salary Slip Leave", "read_only": 1 + }, + { + "fieldname": "gross_year_to_date", + "fieldtype": "Currency", + "label": "Gross Year To Date", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "base_gross_year_to_date", + "fieldtype": "Currency", + "label": "Gross Year To Date(Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:44:09.772331", + "modified": "2021-10-08 11:47:47.098248", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", @@ -672,4 +688,4 @@ "sort_order": "DESC", "timeline_field": "employee", "title_field": "employee_name" -} \ No newline at end of file +} diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 6325351debc..05af09e49db 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1,27 +1,49 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -import datetime, math -from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day +import datetime +import math + +import frappe +from frappe import _, msgprint from frappe.model.naming import make_autoname - -from frappe import msgprint, _ -from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates -from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee -from erpnext.hr.utils import get_holiday_dates_for_employee -from erpnext.utilities.transaction_base import TransactionBase +from frappe.utils import ( + add_days, + cint, + cstr, + date_diff, + flt, + formatdate, + get_first_day, + getdate, + money_in_words, + rounded, +) from frappe.utils.background_jobs import enqueue -from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries -from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period -from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount -from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits -from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry + +import erpnext from erpnext.accounts.utils import get_fiscal_year -from erpnext.hr.utils import validate_active_employee -from six import iteritems +from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee +from erpnext.loan_management.doctype.loan_repayment.loan_repayment import ( + calculate_amounts, + create_repayment_entry, +) +from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries +from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import ( + get_benefit_component_amount, +) +from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import ( + get_benefit_claim_amount, + get_last_payroll_period_benefits, +) +from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates +from erpnext.payroll.doctype.payroll_period.payroll_period import ( + get_payroll_period, + get_period_factor, +) +from erpnext.utilities.transaction_base import TransactionBase + class SalarySlip(TransactionBase): def __init__(self, *args, **kwargs): @@ -148,7 +170,6 @@ class SalarySlip(TransactionBase): and employee = %s and name != %s {0}""".format(cond), (self.start_date, self.end_date, self.employee, self.name)) if ret_exist: - self.employee = '' frappe.throw(_("Salary Slip of employee {0} already created for this period").format(self.employee)) else: for data in self.timesheets: @@ -463,7 +484,7 @@ class SalarySlip(TransactionBase): self.calculate_component_amounts("deductions") self.set_loan_repayment() - self.set_component_amounts_based_on_payment_days() + self.set_precision_for_component_amounts() self.set_net_pay() def set_net_pay(self): @@ -606,7 +627,8 @@ class SalarySlip(TransactionBase): get_salary_component_data(additional_salary.component), additional_salary.amount, component_type, - additional_salary + additional_salary, + is_recurring = additional_salary.is_recurring ) def add_tax_components(self, payroll_period): @@ -627,7 +649,7 @@ class SalarySlip(TransactionBase): tax_row = get_salary_component_data(d) self.update_component_row(tax_row, tax_amount, "deductions") - def update_component_row(self, component_data, amount, component_type, additional_salary=None): + def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0): component_row = None for d in self.get(component_type): if d.salary_component != component_data.salary_component: @@ -674,6 +696,8 @@ class SalarySlip(TransactionBase): else: component_row.default_amount = 0 component_row.additional_amount = amount + + component_row.is_recurring_additional_salary = is_recurring component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date @@ -685,6 +709,17 @@ class SalarySlip(TransactionBase): component_row.amount = amount + self.update_component_amount_based_on_payment_days(component_row) + + def update_component_amount_based_on_payment_days(self, component_row): + joining_date, relieving_date = self.get_joining_and_relieving_dates() + component_row.amount = self.get_amount_based_on_payment_days(component_row, joining_date, relieving_date)[0] + + def set_precision_for_component_amounts(self): + for component_type in ("earnings", "deductions"): + for component_row in self.get(component_type): + component_row.amount = flt(component_row.amount, component_row.precision("amount")) + def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period): if not payroll_period: frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.") @@ -842,14 +877,7 @@ class SalarySlip(TransactionBase): return total_tax_paid def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0): - joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, - ["date_of_joining", "relieving_date"]) - - if not relieving_date: - relieving_date = getdate(self.end_date) - - if not joining_date: - frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) + joining_date, relieving_date = self.get_joining_and_relieving_dates() taxable_earnings = 0 additional_income = 0 @@ -860,28 +888,39 @@ class SalarySlip(TransactionBase): if based_on_payment_days: amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date) else: - amount, additional_amount = earning.amount, earning.additional_amount + if earning.additional_amount: + amount, additional_amount = earning.amount, earning.additional_amount + else: + amount, additional_amount = earning.default_amount, earning.additional_amount if earning.is_tax_applicable: - if additional_amount: - taxable_earnings += (amount - additional_amount) - additional_income += additional_amount - if earning.deduct_full_tax_on_selected_payroll_date: - additional_income_with_full_tax += additional_amount - continue - if earning.is_flexible_benefit: flexi_benefits += amount else: - taxable_earnings += amount + taxable_earnings += (amount - additional_amount) + additional_income += additional_amount + + # Get additional amount based on future recurring additional salary + if additional_amount and earning.is_recurring_additional_salary: + additional_income += self.get_future_recurring_additional_amount(earning.additional_salary, + earning.additional_amount) # Used earning.additional_amount to consider the amount for the full month + + if earning.deduct_full_tax_on_selected_payroll_date: + additional_income_with_full_tax += additional_amount if allow_tax_exemption: for ded in self.deductions: if ded.exempted_from_income_tax: - amount = ded.amount + amount, additional_amount = ded.amount, ded.additional_amount if based_on_payment_days: - amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)[0] - taxable_earnings -= flt(amount) + amount, additional_amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date) + + taxable_earnings -= flt(amount - additional_amount) + additional_income -= additional_amount + + if additional_amount and ded.is_recurring_additional_salary: + additional_income -= self.get_future_recurring_additional_amount(ded.additional_salary, + ded.additional_amount) # Used ded.additional_amount to consider the amount for the full month return frappe._dict({ "taxable_earnings": taxable_earnings, @@ -890,11 +929,21 @@ class SalarySlip(TransactionBase): "flexi_benefits": flexi_benefits }) + def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount): + future_recurring_additional_amount = 0 + to_date = frappe.db.get_value("Additional Salary", additional_salary, 'to_date') + # future month count excluding current + future_recurring_period = (getdate(to_date).month - getdate(self.start_date).month) + if future_recurring_period > 0: + future_recurring_additional_amount = monthly_additional_amount * future_recurring_period # Used earning.additional_amount to consider the amount for the full month + return future_recurring_additional_amount + def get_amount_based_on_payment_days(self, row, joining_date, relieving_date): amount, additional_amount = row.amount, row.additional_amount if (self.salary_structure and - cint(row.depends_on_payment_days) and cint(self.total_working_days) and - (not self.salary_slip_based_on_timesheet or + cint(row.depends_on_payment_days) and cint(self.total_working_days) + and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary + and (not self.salary_slip_based_on_timesheet or getdate(self.start_date) < joining_date or (relieving_date and getdate(self.end_date) > relieving_date) )): @@ -1031,7 +1080,7 @@ class SalarySlip(TransactionBase): total += amount return total - def set_component_amounts_based_on_payment_days(self): + def get_joining_and_relieving_dates(self): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -1041,9 +1090,7 @@ class SalarySlip(TransactionBase): if not joining_date: frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) - for component_type in ("earnings", "deductions"): - for d in self.get(component_type): - d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount")) + return joining_date, relieving_date def set_loan_repayment(self): self.total_loan_repayment = 0 @@ -1214,25 +1261,28 @@ class SalarySlip(TransactionBase): period_start_date, period_end_date = self.get_year_to_date_period() salary_slip_sum = frappe.get_list('Salary Slip', - fields = ['sum(net_pay) as sum'], - filters = {'employee_name' : self.employee_name, + fields = ['sum(net_pay) as net_sum', 'sum(gross_pay) as gross_sum'], + filters = {'employee' : self.employee, 'start_date' : ['>=', period_start_date], 'end_date' : ['<', period_end_date], 'name': ['!=', self.name], 'docstatus': 1 }) - year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0 + year_to_date = flt(salary_slip_sum[0].net_sum) if salary_slip_sum else 0.0 + gross_year_to_date = flt(salary_slip_sum[0].gross_sum) if salary_slip_sum else 0.0 year_to_date += self.net_pay + gross_year_to_date += self.gross_pay self.year_to_date = year_to_date + self.gross_year_to_date = gross_year_to_date def compute_month_to_date(self): month_to_date = 0 first_day_of_the_month = get_first_day(self.start_date) salary_slip_sum = frappe.get_list('Salary Slip', fields = ['sum(net_pay) as sum'], - filters = {'employee_name' : self.employee_name, + filters = {'employee' : self.employee, 'start_date' : ['>=', first_day_of_the_month], 'end_date' : ['<', self.start_date], 'name': ['!=', self.name], @@ -1256,13 +1306,13 @@ class SalarySlip(TransactionBase): INNER JOIN `tabSalary Slip` as salary_slip ON detail.parent = salary_slip.name WHERE - salary_slip.employee_name = %(employee_name)s + salary_slip.employee = %(employee)s AND detail.salary_component = %(component)s AND salary_slip.start_date >= %(period_start_date)s AND salary_slip.end_date < %(period_end_date)s AND salary_slip.name != %(docname)s AND salary_slip.docstatus = 1""", - {'employee_name': self.employee_name, 'component': component.salary_component, 'period_start_date': period_start_date, + {'employee': self.employee, 'component': component.salary_component, 'period_start_date': period_start_date, 'period_end_date': period_end_date, 'docname': self.name} ) @@ -1291,7 +1341,7 @@ class SalarySlip(TransactionBase): from erpnext.hr.doctype.leave_application.leave_application import get_leave_details leave_details = get_leave_details(self.employee, self.end_date) - for leave_type, leave_values in iteritems(leave_details['leave_allocation']): + for leave_type, leave_values in leave_details['leave_allocation'].items(): self.append('leave_details', { 'leave_type': leave_type, 'total_allocated_leaves': flt(leave_values.get('total_leaves')), diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index d730fcf1fa9..e4618c31f20 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -1,21 +1,35 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import unittest -import frappe -import erpnext import calendar import random +import unittest + +import frappe +from frappe.utils import ( + add_days, + add_months, + cstr, + flt, + get_first_day, + get_last_day, + getdate, + nowdate, +) +from frappe.utils.make_random import get_random + +import erpnext from erpnext.accounts.utils import get_fiscal_year -from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day, cstr -from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip -from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type -from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration \ - import create_payroll_period, create_exemption_category +from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import ( + create_exemption_category, + create_payroll_period, +) +from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details +from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip + class TestSalarySlip(unittest.TestCase): def setUp(self): @@ -120,6 +134,59 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") + def test_component_amount_dependent_on_another_payment_days_based_component(self): + from erpnext.hr.doctype.attendance.attendance import mark_attendance + from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( + create_salary_structure_assignment, + ) + + # Payroll based on attendance + frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance") + + salary_structure = make_salary_structure_for_payment_days_based_component_dependency() + employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company") + + # base = 50000 + create_salary_structure_assignment(employee, salary_structure.name, company="_Test Company", currency="INR") + + # mark employee absent for a day since this case works fine if payment days are equal to working days + month_start_date = get_first_day(nowdate()) + month_end_date = get_last_day(nowdate()) + + first_sunday = frappe.db.sql(""" + select holiday_date from `tabHoliday` + where parent = 'Salary Slip Test Holiday List' + and holiday_date between %s and %s + order by holiday_date + """, (month_start_date, month_end_date))[0][0] + + mark_attendance(employee, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent + + # make salary slip and assert payment days + ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name) + self.assertEqual(ss.absent_days, 1) + + ss.reload() + payment_days_based_comp_amount = 0 + for component in ss.earnings: + if component.salary_component == "HRA - Payment Days": + payment_days_based_comp_amount = flt(component.amount, component.precision("amount")) + break + + # check if the dependent component is calculated using the amount updated after payment days + actual_amount = 0 + precision = 0 + for component in ss.deductions: + if component.salary_component == "P - Employee Provident Fund": + precision = component.precision("amount") + actual_amount = flt(component.amount, precision) + break + + expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision) + + self.assertEqual(actual_amount, expected_amount) + frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") + def test_salary_slip_with_holidays_included(self): no_of_days = self.get_no_of_days() frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1) @@ -154,7 +221,9 @@ class TestSalarySlip(unittest.TestCase): self.assertEqual(ss.gross_pay, 78000) def test_payment_days(self): - from erpnext.payroll.doctype.salary_structure.test_salary_structure import create_salary_structure_assignment + from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( + create_salary_structure_assignment, + ) no_of_days = self.get_no_of_days() # Holidays not included in working days @@ -231,8 +300,15 @@ class TestSalarySlip(unittest.TestCase): self.assertTrue(email_queue) def test_loan_repayment_salary_slip(self): - from erpnext.loan_management.doctype.loan.test_loan import create_loan_type, create_loan, make_loan_disbursement_entry, create_loan_accounts - from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans + from erpnext.loan_management.doctype.loan.test_loan import ( + create_loan, + create_loan_accounts, + create_loan_type, + make_loan_disbursement_entry, + ) + from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_term_loans, + ) from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure applicant = make_employee("test_loan_repayment_salary_slip@salary.com", company="_Test Company") @@ -386,8 +462,7 @@ class TestSalarySlip(unittest.TestCase): for doc in delete_docs: frappe.db.sql("delete from `tab%s` where employee='%s'" % (doc, employee)) - from erpnext.payroll.doctype.salary_structure.test_salary_structure import \ - make_salary_structure, create_salary_structure_assignment + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure salary_structure = make_salary_structure("Stucture to test tax", "Monthly", other_details={"max_benefits": 100000}, test_tax=True, @@ -460,6 +535,61 @@ class TestSalarySlip(unittest.TestCase): # undelete fixture data frappe.db.rollback() + def test_tax_for_recurring_additional_salary(self): + frappe.db.sql("""delete from `tabPayroll Period`""") + frappe.db.sql("""delete from `tabSalary Component`""") + + payroll_period = create_payroll_period() + + create_tax_slab(payroll_period, allow_tax_exemption=True) + + employee = make_employee("test_tax@salary.slip") + delete_docs = [ + "Salary Slip", + "Additional Salary", + "Employee Tax Exemption Declaration", + "Employee Tax Exemption Proof Submission", + "Employee Benefit Claim", + "Salary Structure Assignment" + ] + for doc in delete_docs: + frappe.db.sql("delete from `tab%s` where employee='%s'" % (doc, employee)) + + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + + salary_structure = make_salary_structure("Stucture to test tax", "Monthly", + other_details={"max_benefits": 100000}, test_tax=True, + employee=employee, payroll_period=payroll_period) + + + create_salary_slips_for_payroll_period(employee, salary_structure.name, + payroll_period, deduct_random=False, num=3) + + tax_paid = get_tax_paid_in_period(employee) + + annual_tax = 23196.0 + self.assertEqual(tax_paid, annual_tax) + + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + + #------------------------------------ + # Recurring additional salary + start_date = add_months(payroll_period.start_date, 3) + end_date = add_months(payroll_period.start_date, 5) + create_recurring_additional_salary(employee, "Performance Bonus", 20000, start_date, end_date) + + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + + create_salary_slips_for_payroll_period(employee, salary_structure.name, + payroll_period, deduct_random=False, num=4) + + tax_paid = get_tax_paid_in_period(employee) + + annual_tax = 32315.0 + self.assertEqual(tax_paid, annual_tax) + + frappe.db.rollback() + def make_activity_for_employee(self): activity_type = frappe.get_doc("Activity Type", "_Test Activity Type") activity_type.billing_rate = 50 @@ -828,7 +958,8 @@ def setup_test(): def make_holiday_list(): fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company()) - if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"): + holiday_list = frappe.db.exists("Holiday List", "Salary Slip Test Holiday List") + if not holiday_list: holiday_list = frappe.get_doc({ "doctype": "Holiday List", "holiday_list_name": "Salary Slip Test Holiday List", @@ -838,3 +969,109 @@ def make_holiday_list(): }).insert() holiday_list.get_weekly_off_dates() holiday_list.save() + holiday_list = holiday_list.name + + return holiday_list + +def make_salary_structure_for_payment_days_based_component_dependency(): + earnings = [ + { + "salary_component": "Basic Salary - Payment Days", + "abbr": "P_BS", + "type": "Earning", + "formula": "base", + "amount_based_on_formula": 1 + }, + { + "salary_component": "HRA - Payment Days", + "abbr": "P_HRA", + "type": "Earning", + "depends_on_payment_days": 1, + "amount_based_on_formula": 1, + "formula": "base * 0.20" + } + ] + + make_salary_component(earnings, False, company_list=["_Test Company"]) + + deductions = [ + { + "salary_component": "P - Professional Tax", + "abbr": "P_PT", + "type": "Deduction", + "depends_on_payment_days": 1, + "amount": 200.00 + }, + { + "salary_component": "P - Employee Provident Fund", + "abbr": "P_EPF", + "type": "Deduction", + "exempted_from_income_tax": 1, + "amount_based_on_formula": 1, + "depends_on_payment_days": 0, + "formula": "(gross_pay - P_HRA) * 0.12" + } + ] + + make_salary_component(deductions, False, company_list=["_Test Company"]) + + salary_structure = "Salary Structure with PF" + if frappe.db.exists("Salary Structure", salary_structure): + frappe.db.delete("Salary Structure", salary_structure) + + details = { + "doctype": "Salary Structure", + "name": salary_structure, + "company": "_Test Company", + "payroll_frequency": "Monthly", + "payment_account": get_random("Account", filters={"account_currency": "INR"}), + "currency": "INR" + } + + salary_structure_doc = frappe.get_doc(details) + + for entry in earnings: + salary_structure_doc.append("earnings", entry) + + for entry in deductions: + salary_structure_doc.append("deductions", entry) + + salary_structure_doc.insert() + salary_structure_doc.submit() + + return salary_structure_doc + +def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure): + employee = frappe.db.get_value( + "Employee", + {"user_id": employee}, + ["name", "company", "employee_name"], + as_dict=True + ) + + salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": employee.name}) + + if not salary_slip_name: + salary_slip = make_salary_slip(salary_structure, employee=employee.name) + salary_slip.employee_name = employee.employee_name + salary_slip.payroll_frequency = "Monthly" + salary_slip.posting_date = nowdate() + salary_slip.insert() + else: + salary_slip = frappe.get_doc("Salary Slip", salary_slip_name) + + return salary_slip + +def create_recurring_additional_salary(employee, salary_component, amount, from_date, to_date, company=None): + frappe.get_doc({ + "doctype": "Additional Salary", + "employee": employee, + "company": company or erpnext.get_default_company(), + "salary_component": salary_component, + "is_recurring": 1, + "from_date": from_date, + "to_date": to_date, + "amount": amount, + "type": "Earning", + "currency": erpnext.get_default_currency() + }).submit() diff --git a/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py index 7a92bf18f76..b29a60bd4a8 100644 --- a/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py +++ b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class SalarySlipLeave(Document): pass diff --git a/erpnext/payroll/doctype/salary_slip_timesheet/salary_slip_timesheet.py b/erpnext/payroll/doctype/salary_slip_timesheet/salary_slip_timesheet.py index 7adb12e83a4..022eba05fdd 100644 --- a/erpnext/payroll/doctype/salary_slip_timesheet/salary_slip_timesheet.py +++ b/erpnext/payroll/doctype/salary_slip_timesheet/salary_slip_timesheet.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class SalarySlipTimesheet(Document): pass diff --git a/erpnext/payroll/doctype/salary_structure/__init__.py b/erpnext/payroll/doctype/salary_structure/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/payroll/doctype/salary_structure/__init__.py +++ b/erpnext/payroll/doctype/salary_structure/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py index 6dfb3a57d5d..ae83c046a5e 100644 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py @@ -1,14 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe.utils import flt, cint, cstr +import frappe from frappe import _ -from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document -from six import iteritems +from frappe.model.mapper import get_mapped_doc +from frappe.utils import cint, cstr, flt + +import erpnext + class SalaryStructure(Document): def validate(self): diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure_dashboard.py b/erpnext/payroll/doctype/salary_structure/salary_structure_dashboard.py index 0159e3530fb..014d0bab12a 100644 --- a/erpnext/payroll/doctype/salary_structure/salary_structure_dashboard.py +++ b/erpnext/payroll/doctype/salary_structure/salary_structure_dashboard.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from frappe import _ - def get_data(): return { 'fieldname': 'salary_structure', diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index 3957d834d33..e2d0d1c864c 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -1,18 +1,24 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -import erpnext +from frappe.utils import add_years, date_diff, get_first_day, nowdate from frappe.utils.make_random import get_random -from frappe.utils import nowdate, add_years, get_first_day, date_diff -from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip -from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_earning_salary_component,\ - make_deduction_salary_component, make_employee_salary_slip, create_tax_slab -from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import create_payroll_period +import erpnext +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import ( + create_payroll_period, +) +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + create_tax_slab, + make_deduction_salary_component, + make_earning_salary_component, + make_employee_salary_slip, +) +from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip test_dependencies = ["Fiscal Year"] diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py index 5fb3ce2a98e..e1ff9ca9f04 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import getdate from frappe.model.document import Document +from frappe.utils import getdate + class DuplicateAssignment(frappe.ValidationError): pass diff --git a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js b/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js deleted file mode 100644 index 2f52576c7a7..00000000000 --- a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Salary Structure Assignment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Salary Structure Assignment - () => frappe.tests.make('Salary Structure Assignment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.py index a9833bf7338..56dd0d0fe50 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.py +++ b/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestSalaryStructureAssignment(unittest.TestCase): pass diff --git a/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.py b/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.py index 49c52557dbc..d1ccbe38582 100644 --- a/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.py +++ b/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class TaxableSalarySlab(Document): pass diff --git a/erpnext/payroll/notification/retention_bonus/retention_bonus.py b/erpnext/payroll/notification/retention_bonus/retention_bonus.py index 2334f8b26d8..02e3e933330 100644 --- a/erpnext/payroll/notification/retention_bonus/retention_bonus.py +++ b/erpnext/payroll/notification/retention_bonus/retention_bonus.py @@ -1,7 +1,3 @@ -from __future__ import unicode_literals - -import frappe - def get_context(context): # do your magic here pass diff --git a/erpnext/payroll/report/bank_remittance/bank_remittance.py b/erpnext/payroll/report/bank_remittance/bank_remittance.py index 05a5366a5c1..6c3bd37b043 100644 --- a/erpnext/payroll/report/bank_remittance/bank_remittance.py +++ b/erpnext/payroll/report/bank_remittance/bank_remittance.py @@ -1,12 +1,11 @@ # 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.utils import formatdate -import itertools from frappe import _, get_all + def execute(filters=None): columns = [ { diff --git a/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py b/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py index 8a79416edbf..75a9f97ea58 100644 --- a/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py +++ b/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py @@ -1,10 +1,13 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ +import erpnext + + def execute(filters=None): data = get_data(filters) columns = get_columns(filters) if len(data) else [] diff --git a/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py b/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py index a0dab63654e..fa68575e688 100644 --- a/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py +++ b/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py @@ -1,10 +1,15 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ -from erpnext.regional.report.provident_fund_deductions.provident_fund_deductions import get_conditions + +import erpnext +from erpnext.regional.report.provident_fund_deductions.provident_fund_deductions import ( + get_conditions, +) + def execute(filters=None): mode_of_payments = get_payment_modes() diff --git a/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py b/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py index d09745c37bb..578c8164009 100644 --- a/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py +++ b/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py @@ -1,10 +1,13 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, erpnext + +import frappe from frappe import _ +import erpnext + + def execute(filters=None): columns = get_columns(filters) data = get_data(filters) diff --git a/erpnext/payroll/report/salary_register/salary_register.py b/erpnext/payroll/report/salary_register/salary_register.py index a1b1a8c56b5..78deb227783 100644 --- a/erpnext/payroll/report/salary_register/salary_register.py +++ b/erpnext/payroll/report/salary_register/salary_register.py @@ -1,10 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe.utils import flt + +import frappe from frappe import _ +from frappe.utils import flt + +import erpnext + def execute(filters=None): if not filters: filters = {} @@ -131,11 +134,11 @@ def get_ss_earning_map(salary_slips, currency, company_currency): ss_earning_map = {} for d in ss_earnings: - ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, []) + ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0) if currency == company_currency: - ss_earning_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1) + ss_earning_map[d.parent][d.salary_component] += flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1) else: - ss_earning_map[d.parent][d.salary_component] = flt(d.amount) + ss_earning_map[d.parent][d.salary_component] += flt(d.amount) return ss_earning_map @@ -146,10 +149,10 @@ def get_ss_ded_map(salary_slips, currency, company_currency): ss_ded_map = {} for d in ss_deductions: - ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, []) + ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0) if currency == company_currency: - ss_ded_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1) + ss_ded_map[d.parent][d.salary_component] += flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1) else: - ss_ded_map[d.parent][d.salary_component] = flt(d.amount) + ss_ded_map[d.parent][d.salary_component] += flt(d.amount) return ss_ded_map diff --git a/erpnext/payroll/workspace/payroll/payroll.json b/erpnext/payroll/workspace/payroll/payroll.json index b55bdc77112..7246dae5bc9 100644 --- a/erpnext/payroll/workspace/payroll/payroll.json +++ b/erpnext/payroll/workspace/payroll/payroll.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Outgoing Salary", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Payroll\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Structure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payroll Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Slip\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Income Tax Slab\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payroll\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Compensations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-05-27 19:54:23.405607", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "money-coins-1", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Payroll", "links": [ { @@ -319,15 +312,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.335324", + "modified": "2021-08-05 12:16:01.335325", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll", - "onboarding": "Payroll", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/portal/doctype/homepage/homepage.py b/erpnext/portal/doctype/homepage/homepage.py index 54ea7c62df4..1e056a6dacb 100644 --- a/erpnext/portal/doctype/homepage/homepage.py +++ b/erpnext/portal/doctype/homepage/homepage.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document from frappe.website.utils import delete_page_cache + class Homepage(Document): def validate(self): if not self.description: diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py index e646775ab32..fb0367f9031 100644 --- a/erpnext/portal/doctype/homepage/test_homepage.py +++ b/erpnext/portal/doctype/homepage/test_homepage.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest from frappe.utils import set_request from frappe.website.serve import get_response + class TestHomepage(unittest.TestCase): def test_homepage_load(self): set_request(method='GET', path='home') diff --git a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py index 936e07d34ea..c21461d631d 100644 --- a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py +++ b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class HomepageFeaturedProduct(Document): pass diff --git a/erpnext/portal/doctype/homepage_section/homepage_section.py b/erpnext/portal/doctype/homepage_section/homepage_section.py index 1ed703050b8..7181affbea7 100644 --- a/erpnext/portal/doctype/homepage_section/homepage_section.py +++ b/erpnext/portal/doctype/homepage_section/homepage_section.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document from frappe.utils import cint + class HomepageSection(Document): @property def column_value(self): diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py index 5bb9682bc56..b30d983adc3 100644 --- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py +++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest from bs4 import BeautifulSoup from frappe.utils import set_request from frappe.website.serve import get_response + class TestHomepageSection(unittest.TestCase): def test_homepage_section_card(self): try: diff --git a/erpnext/portal/doctype/homepage_section_card/homepage_section_card.py b/erpnext/portal/doctype/homepage_section_card/homepage_section_card.py index bd17279f99a..eeff63c3f35 100644 --- a/erpnext/portal/doctype/homepage_section_card/homepage_section_card.py +++ b/erpnext/portal/doctype/homepage_section_card/homepage_section_card.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class HomepageSectionCard(Document): pass diff --git a/erpnext/portal/doctype/products_settings/products_settings.py b/erpnext/portal/doctype/products_settings/products_settings.py index 9a708924ae0..0e106c634b8 100644 --- a/erpnext/portal/doctype/products_settings/products_settings.py +++ b/erpnext/portal/doctype/products_settings/products_settings.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import cint from frappe import _ from frappe.model.document import Document +from frappe.utils import cint + class ProductsSettings(Document): def validate(self): diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.js b/erpnext/portal/doctype/products_settings/test_products_settings.js deleted file mode 100644 index b7049b37e18..00000000000 --- a/erpnext/portal/doctype/products_settings/test_products_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Products Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Products Settings - () => frappe.tests.make('Products Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.py b/erpnext/portal/doctype/products_settings/test_products_settings.py index d04a0098829..66026fc0467 100644 --- a/erpnext/portal/doctype/products_settings/test_products_settings.py +++ b/erpnext/portal/doctype/products_settings/test_products_settings.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestProductsSettings(unittest.TestCase): pass diff --git a/erpnext/portal/doctype/website_attribute/website_attribute.py b/erpnext/portal/doctype/website_attribute/website_attribute.py index b8b667a6135..58a73768be2 100644 --- a/erpnext/portal/doctype/website_attribute/website_attribute.py +++ b/erpnext/portal/doctype/website_attribute/website_attribute.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class WebsiteAttribute(Document): pass diff --git a/erpnext/portal/doctype/website_filter_field/website_filter_field.py b/erpnext/portal/doctype/website_filter_field/website_filter_field.py index 2aa8a6f98d4..8067ebba0fa 100644 --- a/erpnext/portal/doctype/website_filter_field/website_filter_field.py +++ b/erpnext/portal/doctype/website_filter_field/website_filter_field.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class WebsiteFilterField(Document): pass diff --git a/erpnext/portal/product_configurator/item_variants_cache.py b/erpnext/portal/product_configurator/item_variants_cache.py index fc294ce58bb..636ae8d4917 100644 --- a/erpnext/portal/product_configurator/item_variants_cache.py +++ b/erpnext/portal/product_configurator/item_variants_cache.py @@ -1,5 +1,6 @@ import frappe + class ItemVariantsCacheManager: def __init__(self, item_code): self.item_code = item_code diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py index ec7c83aa397..b478489920d 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -1,8 +1,9 @@ -from __future__ import unicode_literals +import unittest +import frappe from bs4 import BeautifulSoup -import frappe, unittest from frappe.utils import get_html_for_route + from erpnext.portal.product_configurator.utils import get_products_for_website test_dependencies = ["Item"] diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index d60b1a2b05e..cf623c8d429 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -1,8 +1,10 @@ import frappe from frappe.utils import cint + from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager -from erpnext.shopping_cart.product_info import get_product_info_for_website from erpnext.setup.doctype.item_group.item_group import get_child_groups +from erpnext.shopping_cart.product_info import get_product_info_for_website + def get_field_filter_data(): product_settings = get_product_settings() diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index d6d44694203..974b51ef0a5 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -1,9 +1,12 @@ -from __future__ import unicode_literals import frappe -from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import get_shopping_cart_settings -from erpnext.shopping_cart.cart import get_debtors_account from frappe.utils.nestedset import get_root_of +from erpnext.shopping_cart.cart import get_debtors_account +from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import ( + get_shopping_cart_settings, +) + + def set_default_role(doc, method): '''Set customer, supplier, student, guardian based on email''' if frappe.flags.setting_role or frappe.flags.in_migrate: diff --git a/erpnext/projects/doctype/__init__.py b/erpnext/projects/doctype/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/projects/doctype/__init__.py +++ b/erpnext/projects/doctype/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.py b/erpnext/projects/doctype/activity_cost/activity_cost.py index 99226ea581c..bc4bb9dcba4 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/activity_cost.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.model.document import Document + class DuplicationError(frappe.ValidationError): pass class ActivityCost(Document): diff --git a/erpnext/projects/doctype/activity_cost/test_activity_cost.py b/erpnext/projects/doctype/activity_cost/test_activity_cost.py index 5f35f299b36..d53e582adc6 100644 --- a/erpnext/projects/doctype/activity_cost/test_activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/test_activity_cost.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest +import frappe + from erpnext.projects.doctype.activity_cost.activity_cost import DuplicationError + class TestActivityCost(unittest.TestCase): def test_duplication(self): frappe.db.sql("delete from `tabActivity Cost`") diff --git a/erpnext/projects/doctype/activity_type/activity_type.py b/erpnext/projects/doctype/activity_type/activity_type.py index 50e18ef4de9..5151098bec3 100644 --- a/erpnext/projects/doctype/activity_type/activity_type.py +++ b/erpnext/projects/doctype/activity_type/activity_type.py @@ -1,8 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class ActivityType(Document): pass diff --git a/erpnext/projects/doctype/activity_type/test_activity_type.py b/erpnext/projects/doctype/activity_type/test_activity_type.py index dcb01018de0..bb74b881f4c 100644 --- a/erpnext/projects/doctype/activity_type/test_activity_type.py +++ b/erpnext/projects/doctype/activity_type/test_activity_type.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Activity Type') diff --git a/erpnext/projects/doctype/dependent_task/dependent_task.py b/erpnext/projects/doctype/dependent_task/dependent_task.py index 90a96ac1b7c..73ce8f9c3d3 100644 --- a/erpnext/projects/doctype/dependent_task/dependent_task.py +++ b/erpnext/projects/doctype/dependent_task/dependent_task.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class DependentTask(Document): pass diff --git a/erpnext/projects/doctype/project/__init__.py b/erpnext/projects/doctype/project/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/projects/doctype/project/__init__.py +++ b/erpnext/projects/doctype/project/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 1e4b2b0b865..6281c1a8185 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -1,20 +1,20 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe import _ -from six import iteritems from email_reply_parser import EmailReplyParser -from frappe.utils import (flt, getdate, get_url, now, - nowtime, get_time, today, get_datetime, add_days) -from erpnext.controllers.queries import get_filters_cond +from frappe import _ from frappe.desk.reportview import get_match_cond +from frappe.model.document import Document +from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today + +from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status +from erpnext.controllers.queries import get_filters_cond +from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_email from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday -from frappe.model.document import Document -from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list -from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status + class Project(Document): def get_feed(self): @@ -143,6 +143,9 @@ class Project(Document): if self.sales_order: frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) + def on_trash(self): + frappe.db.set_value("Sales Order", {"project": self.name}, "project", "") + def update_percent_complete(self): if self.percent_complete_method == "Manual": if self.status == "Completed": diff --git a/erpnext/projects/doctype/project/project_dashboard.py b/erpnext/projects/doctype/project/project_dashboard.py index 39cf016d61f..a7d1835848d 100644 --- a/erpnext/projects/doctype/project/project_dashboard.py +++ b/erpnext/projects/doctype/project/project_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'heatmap': True, diff --git a/erpnext/projects/doctype/project/test_project.js b/erpnext/projects/doctype/project/test_project.js deleted file mode 100644 index 16494f62b60..00000000000 --- a/erpnext/projects/doctype/project/test_project.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Project", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Project - () => frappe.tests.make('Project', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 70139c6da84..df42e82ad47 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -1,13 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +import unittest -import frappe, unittest -from frappe.utils import getdate, nowdate, add_days +import frappe +from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.project_template.test_project_template import make_project_template from erpnext.projects.doctype.task.test_task import create_task +from erpnext.selling.doctype.sales_order.sales_order import make_project as make_project_from_so +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] @@ -95,6 +97,21 @@ class TestProject(unittest.TestCase): self.assertEqual(len(tasks), 2) + def test_project_linking_with_sales_order(self): + so = make_sales_order() + project = make_project_from_so(so.name) + + project.save() + self.assertEqual(project.sales_order, so.name) + + so.reload() + self.assertEqual(so.project, project.name) + + project.delete() + + so.reload() + self.assertFalse(so.project) + def get_project(name, template): project = frappe.get_doc(dict( diff --git a/erpnext/projects/doctype/project_template/project_template.py b/erpnext/projects/doctype/project_template/project_template.py index 2426fd2af89..3cc8d6855f2 100644 --- a/erpnext/projects/doctype/project_template/project_template.py +++ b/erpnext/projects/doctype/project_template/project_template.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document from frappe.utils import get_link_to_form + class ProjectTemplate(Document): def validate(self): diff --git a/erpnext/projects/doctype/project_template/project_template_dashboard.py b/erpnext/projects/doctype/project_template/project_template_dashboard.py index 67f74f54f06..a03a57dd80d 100644 --- a/erpnext/projects/doctype/project_template/project_template_dashboard.py +++ b/erpnext/projects/doctype/project_template/project_template_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_data(): return { 'fieldname': 'project_template', diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index d546fd09a30..842483343a2 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest + from erpnext.projects.doctype.task.test_task import create_task + class TestProjectTemplate(unittest.TestCase): pass diff --git a/erpnext/projects/doctype/project_template_task/project_template_task.py b/erpnext/projects/doctype/project_template_task/project_template_task.py index 57bc4f1835e..01ec93500c3 100644 --- a/erpnext/projects/doctype/project_template_task/project_template_task.py +++ b/erpnext/projects/doctype/project_template_task/project_template_task.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class ProjectTemplateTask(Document): pass diff --git a/erpnext/projects/doctype/project_type/project_type.py b/erpnext/projects/doctype/project_type/project_type.py index 36137ca0186..4a3724d6a5e 100644 --- a/erpnext/projects/doctype/project_type/project_type.py +++ b/erpnext/projects/doctype/project_type/project_type.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from frappe.model.document import Document + import frappe from frappe import _ +from frappe.model.document import Document + class ProjectType(Document): def on_trash(self): diff --git a/erpnext/projects/doctype/project_type/test_project_type.js b/erpnext/projects/doctype/project_type/test_project_type.js deleted file mode 100644 index c2198c452c0..00000000000 --- a/erpnext/projects/doctype/project_type/test_project_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Project Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Project Type', [ - // insert a new Project Type - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/project_type/test_project_type.py b/erpnext/projects/doctype/project_type/test_project_type.py index ee23390f53f..3e670d06bd5 100644 --- a/erpnext/projects/doctype/project_type/test_project_type.py +++ b/erpnext/projects/doctype/project_type/test_project_type.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestProjectType(unittest.TestCase): pass diff --git a/erpnext/projects/doctype/project_update/project_update.py b/erpnext/projects/doctype/project_update/project_update.py index 2e1ec746ed6..42ba5f6075c 100644 --- a/erpnext/projects/doctype/project_update/project_update.py +++ b/erpnext/projects/doctype/project_update/project_update.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class ProjectUpdate(Document): pass diff --git a/erpnext/projects/doctype/project_update/test_project_update.js b/erpnext/projects/doctype/project_update/test_project_update.js deleted file mode 100644 index bda510b921a..00000000000 --- a/erpnext/projects/doctype/project_update/test_project_update.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Project Update", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Project Update - () => frappe.tests.make('Project Update', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/project_update/test_project_update.py b/erpnext/projects/doctype/project_update/test_project_update.py index 2edd2f85a31..f29c931ac05 100644 --- a/erpnext/projects/doctype/project_update/test_project_update.py +++ b/erpnext/projects/doctype/project_update/test_project_update.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest + class TestProjectUpdate(unittest.TestCase): pass diff --git a/erpnext/projects/doctype/project_user/project_user.py b/erpnext/projects/doctype/project_user/project_user.py index 3198f3b0893..a52bcb170a0 100644 --- a/erpnext/projects/doctype/project_user/project_user.py +++ b/erpnext/projects/doctype/project_user/project_user.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class ProjectUser(Document): pass diff --git a/erpnext/projects/doctype/projects_settings/projects_settings.py b/erpnext/projects/doctype/projects_settings/projects_settings.py index 9dcac14f9ad..db1dc45d764 100644 --- a/erpnext/projects/doctype/projects_settings/projects_settings.py +++ b/erpnext/projects/doctype/projects_settings/projects_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class ProjectsSettings(Document): pass diff --git a/erpnext/projects/doctype/projects_settings/test_projects_settings.js b/erpnext/projects/doctype/projects_settings/test_projects_settings.js deleted file mode 100644 index f6feaa48049..00000000000 --- a/erpnext/projects/doctype/projects_settings/test_projects_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Projects Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Projects Settings - () => frappe.tests.make('Projects Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/projects_settings/test_projects_settings.py b/erpnext/projects/doctype/projects_settings/test_projects_settings.py index d671da73b77..79e78320bb4 100644 --- a/erpnext/projects/doctype/projects_settings/test_projects_settings.py +++ b/erpnext/projects/doctype/projects_settings/test_projects_settings.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestProjectsSettings(unittest.TestCase): pass diff --git a/erpnext/projects/doctype/task/__init__.py b/erpnext/projects/doctype/task/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/projects/doctype/task/__init__.py +++ b/erpnext/projects/doctype/task/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 5976e016fa9..9b1ea043be6 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import json @@ -9,7 +8,7 @@ import frappe from frappe import _, throw from frappe.desk.form.assign_to import clear, close_all_assignments from frappe.model.mapper import get_mapped_doc -from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today, flt +from frappe.utils import add_days, cstr, date_diff, flt, get_link_to_form, getdate, today from frappe.utils.nestedset import NestedSet diff --git a/erpnext/projects/doctype/task/task_dashboard.py b/erpnext/projects/doctype/task/task_dashboard.py index b776b98f676..f7470f8972e 100644 --- a/erpnext/projects/doctype/task/task_dashboard.py +++ b/erpnext/projects/doctype/task/task_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from frappe import _ diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 0fad5e88074..a0ac7c14978 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -1,12 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + import unittest -from frappe.utils import getdate, nowdate, add_days + +import frappe +from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.task.task import CircularReferenceError + class TestTask(unittest.TestCase): def test_circular_reference(self): task1 = create_task("_Test Task 1", add_days(nowdate(), -15), add_days(nowdate(), -10)) diff --git a/erpnext/projects/doctype/task_depends_on/task_depends_on.py b/erpnext/projects/doctype/task_depends_on/task_depends_on.py index 723a0fc339b..0db1f81f28d 100644 --- a/erpnext/projects/doctype/task_depends_on/task_depends_on.py +++ b/erpnext/projects/doctype/task_depends_on/task_depends_on.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class TaskDependsOn(Document): pass diff --git a/erpnext/projects/doctype/task_type/task_type.py b/erpnext/projects/doctype/task_type/task_type.py index 9c0b5325c64..08bed6973d0 100644 --- a/erpnext/projects/doctype/task_type/task_type.py +++ b/erpnext/projects/doctype/task_type/task_type.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class TaskType(Document): pass diff --git a/erpnext/projects/doctype/task_type/test_task_type.py b/erpnext/projects/doctype/task_type/test_task_type.py index 1db6e27ed74..ef99402f7ad 100644 --- a/erpnext/projects/doctype/task_type/test_task_type.py +++ b/erpnext/projects/doctype/task_type/test_task_type.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestTaskType(unittest.TestCase): pass diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.js b/erpnext/projects/doctype/timesheet/test_timesheet.js deleted file mode 100644 index c081d6f8ea4..00000000000 --- a/erpnext/projects/doctype/timesheet/test_timesheet.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Timesheet", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Timesheet - () => frappe.tests.make('Timesheet', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 2b0c3abdd77..d59cc01013b 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -1,23 +1,28 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import datetime +import unittest import frappe -import unittest -import datetime -from frappe.utils.make_random import get_random -from frappe.utils import now_datetime, nowdate, add_days, add_months -from erpnext.projects.doctype.timesheet.timesheet import OverlapError -from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice +from frappe.utils import add_months, now_datetime, nowdate + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.payroll.doctype.salary_structure.test_salary_structure \ - import make_salary_structure, create_salary_structure_assignment -from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( - make_earning_salary_component, - make_deduction_salary_component -) from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + make_deduction_salary_component, + make_earning_salary_component, +) +from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( + create_salary_structure_assignment, + make_salary_structure, +) +from erpnext.projects.doctype.timesheet.timesheet import ( + OverlapError, + make_salary_slip, + make_sales_invoice, +) + class TestTimesheet(unittest.TestCase): @classmethod diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 1655b76b988..f615f051f0c 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -32,12 +32,12 @@ frappe.ui.form.on("Timesheet", { }; }, - onload: function(frm){ + onload: function(frm) { if (frm.doc.__islocal && frm.doc.time_logs) { calculate_time_and_amount(frm); } - if (frm.is_new()) { + if (frm.is_new() && !frm.doc.employee) { set_employee_and_company(frm); } }, @@ -283,7 +283,9 @@ frappe.ui.form.on("Timesheet Detail", { calculate_time_and_amount(frm); }, - activity_type: function(frm, cdt, cdn) { + activity_type: function (frm, cdt, cdn) { + if (!frappe.get_doc(cdt, cdn).activity_type) return; + frappe.call({ method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost", args: { @@ -291,10 +293,10 @@ frappe.ui.form.on("Timesheet Detail", { activity_type: frm.selected_doc.activity_type, currency: frm.doc.currency }, - callback: function(r){ - if(r.message){ - frappe.model.set_value(cdt, cdn, 'billing_rate', r.message['billing_rate']); - frappe.model.set_value(cdt, cdn, 'costing_rate', r.message['costing_rate']); + callback: function (r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, "billing_rate", r.message["billing_rate"]); + frappe.model.set_value(cdt, cdn, "costing_rate", r.message["costing_rate"]); calculate_billing_costing_amount(frm, cdt, cdn); } } diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 5f569d6bcd4..e92785e06cf 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -1,21 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from frappe import _ import json -from datetime import timedelta -from erpnext.controllers.queries import get_match_cond -from frappe.utils import flt, time_diff_in_hours, get_datetime, getdate, cint, date_diff, add_to_date + +import frappe +from frappe import _ from frappe.model.document import Document -from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours, - WorkstationHolidayError) -from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations -from erpnext.setup.utils import get_exchange_rate +from frappe.utils import flt, getdate, time_diff_in_hours + +from erpnext.controllers.queries import get_match_cond from erpnext.hr.utils import validate_active_employee +from erpnext.setup.utils import get_exchange_rate + class OverlapError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass @@ -216,25 +213,47 @@ class Timesheet(Document): @frappe.whitelist() def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to_time=None): - condition = '' + condition = "" if project: - condition += "and tsd.project = %(project)s" + condition += "AND tsd.project = %(project)s " if parent: - condition += "AND tsd.parent = %(parent)s" + condition += "AND tsd.parent = %(parent)s " if from_time and to_time: condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" - return frappe.db.sql("""SELECT tsd.name as name, - tsd.parent as parent, tsd.billing_hours as billing_hours, - tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, - tsd.description as description, ts.currency as currency, - tsd.project_name as project_name - FROM `tabTimesheet Detail` tsd - INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent - WHERE tsd.parenttype = 'Timesheet' - and tsd.docstatus=1 {0} - and tsd.is_billable = 1 - and tsd.sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) + query = f""" + SELECT + tsd.name as name, + tsd.parent as time_sheet, + tsd.from_time as from_time, + tsd.to_time as to_time, + tsd.billing_hours as billing_hours, + tsd.billing_amount as billing_amount, + tsd.activity_type as activity_type, + tsd.description as description, + ts.currency as currency, + tsd.project_name as project_name + FROM `tabTimesheet Detail` tsd + INNER JOIN `tabTimesheet` ts + ON ts.name = tsd.parent + WHERE + tsd.parenttype = 'Timesheet' + AND tsd.docstatus = 1 + AND tsd.is_billable = 1 + AND tsd.sales_invoice is NULL + {condition} + ORDER BY tsd.from_time ASC + """ + + filters = { + "project": project, + "parent": parent, + "from_time": from_time, + "to_time": to_time + } + + return frappe.db.sql(query, filters, as_dict=1) + @frappe.whitelist() def get_timesheet_detail_rate(timelog, currency): diff --git a/erpnext/projects/doctype/timesheet/timesheet_dashboard.py b/erpnext/projects/doctype/timesheet/timesheet_dashboard.py index 088d98c4d5f..15fe797e23a 100644 --- a/erpnext/projects/doctype/timesheet/timesheet_dashboard.py +++ b/erpnext/projects/doctype/timesheet/timesheet_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'time_sheet', diff --git a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.py b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.py index 7da94b77772..d527a3c9223 100644 --- a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.py +++ b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class TimesheetDetail(Document): pass diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py index a22ed7b8338..46479d0a19b 100644 --- a/erpnext/projects/report/billing_summary.py +++ b/erpnext/projects/report/billing_summary.py @@ -2,10 +2,11 @@ # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import time_diff_in_hours, flt +from frappe.utils import flt, time_diff_in_hours + def get_columns(): return [ diff --git a/erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py b/erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py index 3dcae5b1b53..f73376871aa 100644 --- a/erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py +++ b/erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py @@ -1,11 +1,12 @@ # 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 frappe.desk.reportview import build_match_conditions + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py index cdabe6487ea..3ab2bb652b0 100644 --- a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py @@ -1,10 +1,11 @@ # 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.utils import date_diff, nowdate + def execute(filters=None): columns, data = [], [] data = get_data(filters) diff --git a/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py index 78291b2d781..e2ba7c2c268 100644 --- a/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py +++ b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py @@ -1,10 +1,12 @@ -from __future__ import unicode_literals import unittest + import frappe -from frappe.utils import nowdate, add_days, add_months +from frappe.utils import add_days, add_months, nowdate + from erpnext.projects.doctype.task.test_task import create_task from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute + class TestDelayedTasksSummary(unittest.TestCase): @classmethod def setUp(self): diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py index 17c92c234d5..a2f7378d1b7 100644 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py @@ -1,11 +1,12 @@ # 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 erpnext.projects.report.billing_summary import get_columns, get_data + def execute(filters=None): filters = frappe._dict(filters or {}) columns = get_columns() diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 4d22f462463..dd4f8ea7865 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -1,11 +1,11 @@ # 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 frappe.utils import flt, getdate -from six import iteritems + def execute(filters=None): return EmployeeHoursReport(filters).run() @@ -109,7 +109,7 @@ class EmployeeHoursReport: self.data = [] - for emp, data in iteritems(self.stats_by_employee): + for emp, data in self.stats_by_employee.items(): row = frappe._dict() row['employee'] = emp row.update(data) @@ -179,7 +179,7 @@ class EmployeeHoursReport: def calculate_utilizations(self): TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2) - for emp, data in iteritems(self.stats_by_employee): + for emp, data in self.stats_by_employee.items(): data['total_hours'] = TOTAL_HOURS data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 969fc556e8d..b500bc8ab7e 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -1,11 +1,14 @@ -from __future__ import unicode_literals import unittest -import frappe +import frappe from frappe.utils.make_random import get_random -from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import execute + from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.project.test_project import make_project +from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import ( + execute, +) + class TestEmployeeUtilization(unittest.TestCase): @classmethod diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.py b/erpnext/projects/report/project_billing_summary/project_billing_summary.py index 17c92c234d5..a2f7378d1b7 100644 --- a/erpnext/projects/report/project_billing_summary/project_billing_summary.py +++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.py @@ -1,11 +1,12 @@ # 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 erpnext.projects.report.billing_summary import get_columns, get_data + def execute(filters=None): filters = frappe._dict(filters or {}) columns = get_columns() diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 0a52f7bf904..9520cd17be2 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -1,11 +1,12 @@ # 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 frappe.utils import flt + def execute(filters=None): columns, data = [], [] data = get_data(filters) diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 180926fe258..04156902f7f 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -1,28 +1,38 @@ -from __future__ import unicode_literals import unittest + import frappe -from frappe.utils import getdate, nowdate, add_days +from frappe.utils import add_days, getdate + from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet +from erpnext.projects.doctype.timesheet.test_timesheet import ( + make_salary_structure_for_timesheet, + make_timesheet, +) from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice from erpnext.projects.report.project_profitability.project_profitability import execute -class TestProjectProfitability(unittest.TestCase): +class TestProjectProfitability(unittest.TestCase): def setUp(self): + frappe.db.sql('delete from `tabTimesheet`') emp = make_employee('test_employee_9@salary.com', company='_Test Company') + if not frappe.db.exists('Salary Component', 'Timesheet Component'): frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert() + make_salary_structure_for_timesheet(emp, company='_Test Company') - self.timesheet = make_timesheet(emp, simulate = True, is_billable=1) + date = getdate() + + self.timesheet = make_timesheet(emp, is_billable=1) self.salary_slip = make_salary_slip(self.timesheet.name) - holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate()) + + holidays = self.salary_slip.get_holidays_for_employee(date, date) if holidays: frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1) self.salary_slip.submit() self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') - self.sales_invoice.due_date = nowdate() + self.sales_invoice.due_date = date self.sales_invoice.submit() frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8) @@ -58,6 +68,4 @@ class TestProjectProfitability(unittest.TestCase): self.assertEqual(fractional_cost, row.fractional_cost) def tearDown(self): - frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() - frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() - frappe.get_doc("Timesheet", self.timesheet.name).cancel() + frappe.db.rollback() diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index 98dd617f9b3..ce1b70160c8 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): columns = get_columns() data = [] diff --git a/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py b/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py index c13d7cf3344..31bcc3b2ca3 100644 --- a/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py +++ b/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py @@ -1,10 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ + def execute(filters=None): columns = get_columns() proj_details = get_project_details() diff --git a/erpnext/projects/utils.py b/erpnext/projects/utils.py index c39f908e43e..5d7455039af 100644 --- a/erpnext/projects/utils.py +++ b/erpnext/projects/utils.py @@ -3,9 +3,10 @@ # For license information, please see license.txt -from __future__ import unicode_literals + import frappe + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def query_task(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/projects/web_form/tasks/tasks.py b/erpnext/projects/web_form/tasks/tasks.py index e5a94048be1..99249edff14 100644 --- a/erpnext/projects/web_form/tasks/tasks.py +++ b/erpnext/projects/web_form/tasks/tasks.py @@ -1,7 +1,6 @@ -from __future__ import unicode_literals - import frappe + def get_context(context): if frappe.form_dict.project: context.parents = [{'title': frappe.form_dict.project, 'route': '/projects?project='+ frappe.form_dict.project}] diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 065f1eda1f3..1df2b089839 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Project Summary", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"chart\", \"data\": {\"chart_name\": \"Open Projects\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Task\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Timesheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project Billing Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Projects\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Time Tracking\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:46:04.874669", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "project", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Projects", "links": [ { @@ -201,15 +194,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.540145", + "modified": "2021-08-05 12:16:01.540147", "modified_by": "Administrator", "module": "Projects", "name": "Projects", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 3c60e3ee500..f8e817770d5 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -1,14 +1,10 @@ { "css/erpnext.css": [ "public/less/erpnext.less", - "public/less/hub.less", "public/scss/call_popup.scss", "public/scss/point-of-sale.scss", "public/scss/hierarchy_chart.scss" ], - "css/marketplace.css": [ - "public/less/hub.less" - ], "js/erpnext-web.min.js": [ "public/js/website_utils.js", "public/js/shopping_cart.js" @@ -17,9 +13,6 @@ "public/scss/website.scss", "public/scss/shopping_cart.scss" ], - "js/marketplace.min.js": [ - "public/js/hub/marketplace.js" - ], "js/erpnext.min.js": [ "public/js/conf.js", "public/js/utils.js", @@ -38,9 +31,9 @@ "public/js/templates/item_quick_entry.html", "public/js/utils/item_quick_entry.js", "public/js/utils/customer_quick_entry.js", + "public/js/utils/supplier_quick_entry.js", "public/js/education/student_button.html", "public/js/education/assessment_result_tool.html", - "public/js/hub/hub_factory.js", "public/js/call_popup/call_popup.js", "public/js/utils/dimension_tree_filter.js", "public/js/telephony.js", diff --git a/erpnext/public/images/hub_logo.svg b/erpnext/public/images/hub_logo.svg deleted file mode 100644 index 4af482176ea..00000000000 --- a/erpnext/public/images/hub_logo.svg +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index 239fbb92b11..ca73393c546 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -227,7 +227,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { { fieldtype: "HTML", fieldname: "no_matching_vouchers", - options: "
No Matching Vouchers Found
" + options: "
No Matching Vouchers Found
" }, { fieldtype: "Section Break", diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 86dadd36d6e..d696ef55ae6 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -165,45 +165,33 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac } qty(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) { - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - - if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "received_qty"])){ return } - - if(!item.rejected_qty && item.qty) { - item.received_qty = item.qty; - } - - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); - item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty); + this.calculate_received_qty(doc, cdt, cdn) } super.qty(doc, cdt, cdn); } + rejected_qty(doc, cdt, cdn) { + this.calculate_received_qty(doc, cdt, cdn) + } + + calculate_received_qty(doc, cdt, cdn){ + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["qty", "rejected_qty"]); + + if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "rejected_qty"])){ return } + + let received_qty = flt(item.qty + item.rejected_qty, precision("received_qty", item)); + let received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(received_qty); + + frappe.model.set_value(cdt, cdn, "received_qty", received_qty); + frappe.model.set_value(cdt, cdn, "received_stock_qty", received_stock_qty); + } + batch_no(doc, cdt, cdn) { super.batch_no(doc, cdt, cdn); } - received_qty(doc, cdt, cdn) { - this.calculate_accepted_qty(doc, cdt, cdn) - } - - rejected_qty(doc, cdt, cdn) { - this.calculate_accepted_qty(doc, cdt, cdn) - } - - calculate_accepted_qty(doc, cdt, cdn){ - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]); - - if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["received_qty", "rejected_qty"])){ return } - - item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item)); - this.qty(doc, cdt, cdn); - } - validate_negative_quantity(cdt, cdn, item, fieldnames){ if(!item || !fieldnames) { return } diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 702064fe556..7c1c8c7e46e 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -81,6 +81,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.initialize_taxes(); this.determine_exclusive_rate(); this.calculate_net_total(); + this.calculate_shipping_charges(); this.calculate_taxes(); this.manipulate_grand_total_for_inclusive_tax(); this.calculate_totals(); @@ -137,7 +138,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var me = this; $.each(this.frm.doc["taxes"] || [], function(i, tax) { - tax.item_wise_tax_detail = {}; + if (!tax.dont_recompute_tax) { + tax.item_wise_tax_detail = {}; + } var tax_fields = ["total", "tax_amount_after_discount_amount", "tax_amount_for_current_item", "grand_total_for_current_item", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]; @@ -264,8 +267,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { me.frm.doc.net_total += item.net_amount; me.frm.doc.base_net_total += item.base_net_amount; }); + } + calculate_shipping_charges() { frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); + if (frappe.meta.get_docfield(this.frm.doc.doctype, "shipping_rule", this.frm.doc.name)) { + this.shipping_rule(); + } } add_taxes_from_item_tax_template(item_tax_map) { @@ -421,7 +429,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { current_tax_amount = tax_rate * item.qty; } - this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); + if (!tax.dont_recompute_tax) { + this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); + } return current_tax_amount; } @@ -589,7 +599,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { delete tax[fieldname]; }); - tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); + if (!tax.dont_recompute_tax) { + tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); + } }); } } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 2538852bfa5..773d53c5521 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -345,26 +345,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } scan_barcode() { - let scan_barcode_field = this.frm.fields_dict["scan_barcode"]; - - let show_description = function(idx, exist = null) { - if (exist) { - frappe.show_alert({ - message: __('Row #{0}: Qty increased by 1', [idx]), - indicator: 'green' - }); - } else { - frappe.show_alert({ - message: __('Row #{0}: Item added', [idx]), - indicator: 'green' - }); - } - } + let me = this; if(this.frm.doc.scan_barcode) { frappe.call({ method: "erpnext.selling.page.point_of_sale.point_of_sale.search_for_serial_or_batch_or_barcode_number", - args: { search_value: this.frm.doc.scan_barcode } + args: { + search_value: this.frm.doc.scan_barcode + } }).then(r => { const data = r && r.message; if (!data || Object.keys(data).length === 0) { @@ -375,49 +363,96 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return; } - let cur_grid = this.frm.fields_dict.items.grid; - - let row_to_modify = null; - const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code); - const blank_item_row = this.frm.doc.items.find(d => !d.item_code); - - if (existing_item_row) { - row_to_modify = existing_item_row; - } else if (blank_item_row) { - row_to_modify = blank_item_row; - } - - if (!row_to_modify) { - // add new row - row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items'); - } - - show_description(row_to_modify.idx, row_to_modify.item_code); - - this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1; - frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, { - item_code: data.item_code, - qty: (row_to_modify.qty || 0) + 1 - }); - - ['serial_no', 'batch_no', 'barcode'].forEach(field => { - if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { - - let value = (row_to_modify[field] && field === "serial_no") - ? row_to_modify[field] + '\n' + data[field] : data[field]; - - frappe.model.set_value(row_to_modify.doctype, - row_to_modify.name, field, value); - } - }); - - scan_barcode_field.set_value(''); - refresh_field("items"); + me.modify_table_after_scan(data); }); } return false; } + modify_table_after_scan(data) { + let scan_barcode_field = this.frm.fields_dict["scan_barcode"]; + let cur_grid = this.frm.fields_dict.items.grid; + let row_to_modify = null; + + // Check if batch is scanned and table has batch no field + let batch_no_scan = Boolean(data.batch_no) && frappe.meta.has_field(cur_grid.doctype, "batch_no"); + + if (batch_no_scan) { + row_to_modify = this.get_batch_row_to_modify(data.batch_no); + } else { + // serial or barcode scan + row_to_modify = this.get_row_to_modify_on_scan(row_to_modify, data); + } + + if (!row_to_modify) { + // add new row if new item/batch is scanned + row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items'); + } + + this.show_scan_message(row_to_modify.idx, row_to_modify.item_code); + this.set_scanned_values(row_to_modify, data, scan_barcode_field); + } + + set_scanned_values(row_to_modify, data, scan_barcode_field) { + // increase qty and set scanned value and item in row + this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1; + frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, { + item_code: data.item_code, + qty: (row_to_modify.qty || 0) + 1 + }); + + ['serial_no', 'batch_no', 'barcode'].forEach(field => { + if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { + let is_serial_no = row_to_modify[field] && field === "serial_no"; + let value = data[field]; + + if (is_serial_no) { + value = row_to_modify[field] + '\n' + data[field]; + } + + frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, field, value); + } + }); + + scan_barcode_field.set_value(''); + refresh_field("items"); + } + + get_row_to_modify_on_scan(row_to_modify, data) { + // get an existing item row to increment or blank row to modify + const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code); + const blank_item_row = this.frm.doc.items.find(d => !d.item_code); + + if (existing_item_row) { + row_to_modify = existing_item_row; + } else if (blank_item_row) { + row_to_modify = blank_item_row; + } + + return row_to_modify; + } + + get_batch_row_to_modify(batch_no) { + // get row if batch already exists in table + const existing_batch_row = this.frm.doc.items.find(d => d.batch_no === batch_no); + return existing_batch_row || null; + } + + show_scan_message (idx, exist = null) { + // show new row or qty increase toast + if (exist) { + frappe.show_alert({ + message: __('Row #{0}: Qty increased by 1', [idx]), + indicator: 'green' + }); + } else { + frappe.show_alert({ + message: __('Row #{0}: Item added', [idx]), + indicator: 'green' + }); + } + } + apply_default_taxes() { var me = this; var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges", @@ -487,6 +522,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe var me = this; var item = frappe.get_doc(cdt, cdn); var update_stock = 0, show_batch_dialog = 0; + + item.weight_per_unit = 0; + item.weight_uom = ''; + if(['Sales Invoice'].includes(this.frm.doc.doctype)) { update_stock = cint(me.frm.doc.update_stock); show_batch_dialog = update_stock; @@ -613,6 +652,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe me.frm.script_manager.trigger('qty', item.doctype, item.name); if (!me.frm.doc.set_warehouse) me.frm.script_manager.trigger('warehouse', item.doctype, item.name); + me.apply_price_list(item, true); }, undefined, !frappe.flags.hide_serial_batch_dialog); } }, @@ -866,21 +906,27 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") && in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) { - erpnext.utils.get_shipping_address(this.frm, function(){ + erpnext.utils.get_shipping_address(this.frm, function() { set_party_account(set_pricing); }); // Get default company billing address in Purchase Invoice, Order and Receipt - frappe.call({ - 'method': 'frappe.contacts.doctype.address.address.get_default_address', - 'args': { - 'doctype': 'Company', - 'name': this.frm.doc.company - }, - 'callback': function(r) { - me.frm.set_value('billing_address', r.message); - } - }); + if (this.frm.doc.company && frappe.meta.get_docfield(this.frm.doctype, "billing_address")) { + frappe.call({ + method: "erpnext.setup.doctype.company.company.get_default_company_address", + args: {name: this.frm.doc.company, existing_address: this.frm.doc.billing_address || ""}, + debounce: 2000, + callback: function(r) { + if (r.message) { + me.frm.set_value("billing_address", r.message); + } else { + if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) { + me.frm.set_value("company_address", ""); + } + } + } + }); + } } else { set_party_account(set_pricing); @@ -1039,16 +1085,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return this.frm.call({ doc: this.frm.doc, method: "apply_shipping_rule", - callback: function(r) { - if(!r.exc) { - me.calculate_taxes_and_totals(); - } - } }).fail(() => this.frm.set_value('shipping_rule', '')); } - else { - me.calculate_taxes_and_totals(); - } } set_margin_amount_based_on_currency(exchange_rate) { @@ -1068,7 +1106,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe $.each(this.frm.doc.taxes || [], function(i, d) { if(d.charge_type == "Actual") { frappe.model.set_value(d.doctype, d.name, "tax_amount", - flt(d.tax_amount) / flt(exchange_rate)); + flt(d.base_tax_amount) / flt(exchange_rate)); } }); } diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 9f7f29ad72b..5259bdcc765 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -15,9 +15,9 @@ import "./agriculture/ternary_plot"; import "./templates/item_quick_entry.html"; import "./utils/item_quick_entry"; import "./utils/customer_quick_entry"; +import "./utils/supplier_quick_entry"; import "./education/student_button.html"; import "./education/assessment_result_tool.html"; -import "./hub/hub_factory"; import "./call_popup/call_popup"; import "./utils/dimension_tree_filter"; import "./telephony"; diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 0d79b10c041..1a309ba0156 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -113,15 +113,15 @@ function get_filters() { "fieldname":"period_start_date", "label": __("Start Date"), "fieldtype": "Date", - "hidden": 1, - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Date Range'" }, { "fieldname":"period_end_date", "label": __("End Date"), "fieldtype": "Date", - "hidden": 1, - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Date Range'" }, { "fieldname":"from_fiscal_year", @@ -129,7 +129,8 @@ function get_filters() { "fieldtype": "Link", "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, { "fieldname":"to_fiscal_year", @@ -137,7 +138,8 @@ function get_filters() { "fieldtype": "Link", "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, { "fieldname": "periodicity", diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index d0c935f4887..b643ccae947 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -5,7 +5,7 @@ const docsUrl = "https://erpnext.com/docs/"; frappe.help.help_links["Form/Rename Tool"] = [ { label: "Bulk Rename", - url: docsUrl + "user/manual/en/setting-up/data/bulk-rename", + url: docsUrl + "user/manual/en/using-erpnext/articles/bulk-rename", }, ]; @@ -59,10 +59,23 @@ frappe.help.help_links["Form/System Settings"] = [ }, ]; -frappe.help.help_links["data-import-tool"] = [ +frappe.help.help_links["Form/Data Import"] = [ { label: "Importing and Exporting Data", - url: docsUrl + "user/manual/en/setting-up/data/data-import-tool", + url: docsUrl + "user/manual/en/setting-up/data/data-import", + }, + { + label: "Overwriting Data from Data Import Tool", + url: + docsUrl + + "user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool", + }, +]; + +frappe.help.help_links["List/Data Import"] = [ + { + label: "Importing and Exporting Data", + url: docsUrl + "user/manual/en/setting-up/data/data-import", }, { label: "Overwriting Data from Data Import Tool", @@ -101,14 +114,14 @@ frappe.help.help_links["Form/Global Defaults"] = [ }, ]; -frappe.help.help_links["Form/Email Digest"] = [ +frappe.help.help_links["List/Print Heading"] = [ { - label: "Email Digest", - url: docsUrl + "user/manual/en/setting-up/email/email-digest", + label: "Print Heading", + url: docsUrl + "user/manual/en/setting-up/print/print-headings", }, ]; -frappe.help.help_links["List/Print Heading"] = [ +frappe.help.help_links["Form/Print Heading"] = [ { label: "Print Heading", url: docsUrl + "user/manual/en/setting-up/print/print-headings", @@ -153,18 +166,25 @@ frappe.help.help_links["List/Email Account"] = [ frappe.help.help_links["List/Notification"] = [ { label: "Notification", - url: docsUrl + "user/manual/en/setting-up/email/notifications", + url: docsUrl + "user/manual/en/setting-up/notifications", }, ]; frappe.help.help_links["Form/Notification"] = [ { label: "Notification", - url: docsUrl + "user/manual/en/setting-up/email/notifications", + url: docsUrl + "user/manual/en/setting-up/notifications", }, ]; -frappe.help.help_links["List/Email Digest"] = [ +frappe.help.help_links["Form/Email Digest"] = [ + { + label: "Email Digest", + url: docsUrl + "user/manual/en/setting-up/email/email-digest", + }, +]; + +frappe.help.help_links["Form/Email Digest"] = [ { label: "Email Digest", url: docsUrl + "user/manual/en/setting-up/email/email-digest", @@ -174,7 +194,7 @@ frappe.help.help_links["List/Email Digest"] = [ frappe.help.help_links["List/Auto Email Report"] = [ { label: "Auto Email Reports", - url: docsUrl + "user/manual/en/setting-up/email/email-reports", + url: docsUrl + "user/manual/en/setting-up/email/auto-email-reports", }, ]; @@ -188,14 +208,7 @@ frappe.help.help_links["Form/Print Settings"] = [ frappe.help.help_links["print-format-builder"] = [ { label: "Print Format Builder", - url: docsUrl + "user/manual/en/setting-up/print/print-settings", - }, -]; - -frappe.help.help_links["List/Print Heading"] = [ - { - label: "Print Heading", - url: docsUrl + "user/manual/en/setting-up/print/print-headings", + url: docsUrl + "user/manual/en/setting-up/print/print-format-builder", }, ]; @@ -300,7 +313,7 @@ frappe.help.help_links["List/Sales Order"] = [ }, { label: "Recurring Sales Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, { label: "Applying Discount", @@ -315,7 +328,7 @@ frappe.help.help_links["Form/Sales Order"] = [ }, { label: "Recurring Sales Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, { label: "Applying Discount", @@ -344,14 +357,14 @@ frappe.help.help_links["Form/Sales Order"] = [ frappe.help.help_links["Form/Product Bundle"] = [ { label: "Product Bundle", - url: docsUrl + "user/manual/en/selling/setup/product-bundle", + url: docsUrl + "user/manual/en/selling/product-bundle", }, ]; frappe.help.help_links["Form/Selling Settings"] = [ { label: "Selling Settings", - url: docsUrl + "user/manual/en/selling/setup/selling-settings", + url: docsUrl + "user/manual/en/selling/selling-settings", }, ]; @@ -397,7 +410,7 @@ frappe.help.help_links["List/Purchase Order"] = [ }, { label: "Recurring Purchase Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; @@ -420,7 +433,7 @@ frappe.help.help_links["Form/Purchase Order"] = [ }, { label: "Recurring Purchase Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, { label: "Subcontracting", @@ -435,24 +448,17 @@ frappe.help.help_links["List/Purchase Taxes and Charges Template"] = [ }, ]; -frappe.help.help_links["List/POS Profile"] = [ - { - label: "POS Profile", - url: docsUrl + "user/manual/en/setting-up/pos-setting", - }, -]; - frappe.help.help_links["List/Price List"] = [ { label: "Price List", - url: docsUrl + "user/manual/en/setting-up/price-lists", + url: docsUrl + "user/manual/en/stock/price-lists", }, ]; frappe.help.help_links["List/Authorization Rule"] = [ { label: "Authorization Rule", - url: docsUrl + "user/manual/en/setting-up/authorization-rule", + url: docsUrl + "user/manual/en/customize-erpnext/authorization-rule", }, ]; @@ -468,27 +474,14 @@ frappe.help.help_links["List/Stock Reconciliation"] = [ label: "Stock Reconciliation", url: docsUrl + - "user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item", + "user/manual/en/stock/stock-reconciliation", }, ]; frappe.help.help_links["Tree/Territory"] = [ { label: "Territory", - url: docsUrl + "user/manual/en/setting-up/territory", - }, -]; - -frappe.help.help_links["Form/Dropbox Backup"] = [ - { - label: "Dropbox Backup", - url: docsUrl + "user/manual/en/setting-up/third-party-backups", - }, - { - label: "Setting Up Dropbox Backup", - url: - docsUrl + - "user/manual/en/setting-up/articles/setting-up-dropbox-backups", + url: docsUrl + "user/manual/en/selling/territory", }, ]; @@ -501,12 +494,6 @@ frappe.help.help_links["List/Company"] = [ label: "Company", url: docsUrl + "user/manual/en/setting-up/company-setup", }, - { - label: "Managing Multiple Companies", - url: - docsUrl + - "user/manual/en/setting-up/articles/managing-multiple-companies", - }, { label: "Delete All Related Transactions for a Company", url: @@ -517,21 +504,6 @@ frappe.help.help_links["List/Company"] = [ //Accounts -frappe.help.help_links["modules/Accounts"] = [ - { - label: "Introduction to Accounts", - url: docsUrl + "user/manual/en/accounts/", - }, - { - label: "Chart of Accounts", - url: docsUrl + "user/manual/en/accounts/chart-of-accounts.html", - }, - { - label: "Multi Currency Accounting", - url: docsUrl + "user/manual/en/accounts/multi-currency-accounting", - }, -]; - frappe.help.help_links["Tree/Account"] = [ { label: "Chart of Accounts", @@ -552,7 +524,7 @@ frappe.help.help_links["Form/Sales Invoice"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balance", }, { label: "Sales Return", @@ -560,7 +532,7 @@ frappe.help.help_links["Form/Sales Invoice"] = [ }, { label: "Recurring Sales Invoice", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; @@ -571,7 +543,7 @@ frappe.help.help_links["List/Sales Invoice"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balances", }, { label: "Sales Return", @@ -579,21 +551,28 @@ frappe.help.help_links["List/Sales Invoice"] = [ }, { label: "Recurring Sales Invoice", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; -frappe.help.help_links["pos"] = [ +frappe.help.help_links["point-of-sale"] = [ { label: "Point of Sale Invoice", - url: docsUrl + "user/manual/en/accounts/point-of-sale-pos-invoice", + url: docsUrl + "user/manual/en/accounts/point-of-sales", }, ]; frappe.help.help_links["List/POS Profile"] = [ { label: "Point of Sale Profile", - url: docsUrl + "user/manual/en/setting-up/pos-setting", + url: docsUrl + "user/manual/en/accounts/pos-profile", + }, +]; + +frappe.help.help_links["Form/POS Profile"] = [ + { + label: "POS Profile", + url: docsUrl + "user/manual/en/accounts/pos-profile", }, ]; @@ -604,11 +583,11 @@ frappe.help.help_links["List/Purchase Invoice"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balance", }, { label: "Recurring Purchase Invoice", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; @@ -623,7 +602,7 @@ frappe.help.help_links["List/Journal Entry"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balance", }, ]; @@ -644,7 +623,7 @@ frappe.help.help_links["List/Payment Request"] = [ frappe.help.help_links["List/Asset"] = [ { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, ]; @@ -659,6 +638,8 @@ frappe.help.help_links["Tree/Cost Center"] = [ { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, ]; +//Stock + frappe.help.help_links["List/Item"] = [ { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, { @@ -676,7 +657,7 @@ frappe.help.help_links["List/Item"] = [ }, { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, { label: "Item Codification", @@ -711,7 +692,7 @@ frappe.help.help_links["Form/Item"] = [ }, { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, { label: "Item Codification", @@ -771,10 +752,6 @@ frappe.help.help_links["Form/Delivery Note"] = [ url: docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", }, - { - label: "Subcontracting", - url: docsUrl + "user/manual/en/manufacturing/subcontracting", - }, ]; frappe.help.help_links["List/Installation Note"] = [ @@ -784,21 +761,10 @@ frappe.help.help_links["List/Installation Note"] = [ }, ]; -frappe.help.help_links["Tree"] = [ - { - label: "Managing Tree Structure Masters", - url: - docsUrl + - "user/manual/en/setting-up/articles/managing-tree-structure-masters", - }, -]; - frappe.help.help_links["List/Budget"] = [ { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, ]; -//Stock - frappe.help.help_links["List/Material Request"] = [ { label: "Material Request", @@ -861,6 +827,10 @@ frappe.help.help_links["Form/Serial No"] = [ { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" }, ]; +frappe.help.help_links["List/Batch"] = [ + { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, +]; + frappe.help.help_links["Form/Batch"] = [ { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, ]; @@ -868,35 +838,35 @@ frappe.help.help_links["Form/Batch"] = [ frappe.help.help_links["Form/Packing Slip"] = [ { label: "Packing Slip", - url: docsUrl + "user/manual/en/stock/tools/packing-slip", + url: docsUrl + "user/manual/en/stock/packing-slip", }, ]; frappe.help.help_links["Form/Quality Inspection"] = [ { label: "Quality Inspection", - url: docsUrl + "user/manual/en/stock/tools/quality-inspection", + url: docsUrl + "user/manual/en/stock/quality-inspection", }, ]; frappe.help.help_links["Form/Landed Cost Voucher"] = [ { label: "Landed Cost Voucher", - url: docsUrl + "user/manual/en/stock/tools/landed-cost-voucher", + url: docsUrl + "user/manual/en/stock/landed-cost-voucher", }, ]; frappe.help.help_links["Tree/Item Group"] = [ { label: "Item Group", - url: docsUrl + "user/manual/en/stock/setup/item-group", + url: docsUrl + "user/manual/en/stock/item-group", }, ]; frappe.help.help_links["Form/Item Attribute"] = [ { label: "Item Attribute", - url: docsUrl + "user/manual/en/stock/setup/item-attribute", + url: docsUrl + "user/manual/en/stock/item-attribute", }, ]; @@ -911,7 +881,7 @@ frappe.help.help_links["Form/UOM"] = [ frappe.help.help_links["Form/Stock Reconciliation"] = [ { label: "Opening Stock Entry", - url: docsUrl + "user/manual/en/stock/opening-stock", + url: docsUrl + "user/manual/en/stock/stock-reconciliation", }, ]; @@ -938,13 +908,13 @@ frappe.help.help_links["Form/Newsletter"] = [ ]; frappe.help.help_links["Form/Campaign"] = [ - { label: "Campaign", url: docsUrl + "user/manual/en/CRM/setup/campaign" }, + { label: "Campaign", url: docsUrl + "user/manual/en/CRM/campaign" }, ]; frappe.help.help_links["Tree/Sales Person"] = [ { label: "Sales Person", - url: docsUrl + "user/manual/en/CRM/setup/sales-person", + url: docsUrl + "user/manual/en/CRM/sales-person", }, ]; @@ -953,30 +923,13 @@ frappe.help.help_links["Form/Sales Person"] = [ label: "Sales Person Target", url: docsUrl + - "user/manual/en/selling/setup/sales-person-target-allocation", + "user/manual/en/selling/sales-person-target-allocation", }, -]; - -//Support - -frappe.help.help_links["List/Feedback Trigger"] = [ { - label: "Feedback Trigger", - url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback", - }, -]; - -frappe.help.help_links["List/Feedback Request"] = [ - { - label: "Feedback Request", - url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", - }, -]; - -frappe.help.help_links["List/Feedback Request"] = [ - { - label: "Feedback Request", - url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", + label: "Sales Person in Transactions", + url: + docsUrl + + "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions", }, ]; @@ -1019,7 +972,7 @@ frappe.help.help_links["Form/Operation"] = [ frappe.help.help_links["Form/BOM Update Tool"] = [ { label: "BOM Update Tool", - url: docsUrl + "user/manual/en/manufacturing/tools/bom-update-tool", + url: docsUrl + "user/manual/en/manufacturing/bom-update-tool", }, ]; @@ -1036,7 +989,7 @@ frappe.help.help_links["Form/Customize Form"] = [ }, ]; -frappe.help.help_links["Form/Custom Field"] = [ +frappe.help.help_links["List/Custom Field"] = [ { label: "Custom Field", url: docsUrl + "user/manual/en/customize-erpnext/custom-field", diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js index 23ec2fdb849..831626aa915 100644 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js @@ -63,12 +63,10 @@ erpnext.HierarchyChart = class { }); node.parent.append(node_card); - node.$link = $(`#${node.id}`); + node.$link = $(`[id="${node.id}"]`); } show() { - frappe.breadcrumbs.add('HR'); - this.setup_actions(); if ($(`[data-fieldname="company"]`).length) return; let me = this; @@ -83,8 +81,9 @@ erpnext.HierarchyChart = class { reqd: 1, change: () => { me.company = undefined; + $('#hierarchy-chart-wrapper').remove(); - if (company.get_value() && me.company != company.get_value()) { + if (company.get_value()) { me.company = company.get_value(); // svg for connectors @@ -92,6 +91,8 @@ erpnext.HierarchyChart = class { me.setup_hierarchy(); me.render_root_nodes(); me.all_nodes_expanded = false; + } else { + frappe.throw(__('Please select a company first.')); } } }); @@ -172,11 +173,11 @@ erpnext.HierarchyChart = class { `); this.page.main - .find('#hierarchy-chart-wrapper') + .find('#hierarchy-chart') + .empty() .append(this.$hierarchy); this.nodes = {}; - this.all_nodes_expanded = false; } make_svg_markers() { @@ -203,6 +204,8 @@ erpnext.HierarchyChart = class { +
+

`); } @@ -219,7 +222,10 @@ erpnext.HierarchyChart = class { let expand_node = undefined; let node = undefined; - $.each(r.message, (i, data) => { + $.each(r.message, (_i, data) => { + if ($(`[id="${data.id}"]`).length) + return; + node = new me.Node({ id: data.id, parent: $('
  • ').appendTo(me.$hierarchy.find('.node-children')), @@ -257,7 +263,7 @@ erpnext.HierarchyChart = class { this.refresh_connectors(node.parent_id); // rebuild incoming connections - let grandparent = $(`#${node.parent_id}`).attr('data-parent'); + let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); this.refresh_connectors(grandparent); } @@ -276,7 +282,7 @@ erpnext.HierarchyChart = class { show_active_path(node) { // mark node parent on active path - $(`#${node.parent_id}`).addClass('active-path'); + $(`[id="${node.parent_id}"]`).addClass('active-path'); } load_children(node, deep=false) { @@ -290,7 +296,7 @@ erpnext.HierarchyChart = class { () => frappe.dom.freeze(), () => this.setup_hierarchy(), () => this.render_root_nodes(true), - () => this.get_all_nodes(node.id, node.name), + () => this.get_all_nodes(), (data_list) => this.render_children_of_all_nodes(data_list), () => frappe.dom.unfreeze() ]); @@ -311,7 +317,7 @@ erpnext.HierarchyChart = class { render_child_nodes(node, child_nodes) { const last_level = this.$hierarchy.find('.level:last').index(); - const current_level = $(`#${node.id}`).parent().parent().parent().index(); + const current_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); if (last_level === current_level) { this.$hierarchy.append(` @@ -328,10 +334,12 @@ erpnext.HierarchyChart = class { if (child_nodes) { $.each(child_nodes, (_i, data) => { - this.add_node(node, data); - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); + if (!$(`[id="${data.id}"]`).length) { + this.add_node(node, data); + setTimeout(() => { + this.add_connector(node.id, data.id); + }, 250); + } }); } } @@ -341,15 +349,13 @@ erpnext.HierarchyChart = class { node.expanded = true; } - get_all_nodes(node_id, node_name) { + get_all_nodes() { return new Promise(resolve => { frappe.call({ method: 'erpnext.utilities.hierarchy_chart.get_all_nodes', args: { method: this.method, - company: this.company, - parent: node_id, - parent_name: node_name + company: this.company }, callback: (r) => { resolve(r.message); @@ -378,7 +384,7 @@ erpnext.HierarchyChart = class { node.$children = $('
      '); const last_level = this.$hierarchy.find('.level:last').index(); - const node_level = $(`#${node.id}`).parent().parent().parent().index(); + const node_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); if (last_level === node_level) { this.$hierarchy.append(` @@ -485,7 +491,7 @@ erpnext.HierarchyChart = class { set_path_attributes(path, parent_id, child_id) { path.setAttribute("data-parent", parent_id); path.setAttribute("data-child", child_id); - const parent = $(`#${parent_id}`); + const parent = $(`[id="${parent_id}"]`); if (parent.hasClass('active')) { path.setAttribute("class", "active-connector"); @@ -509,7 +515,7 @@ erpnext.HierarchyChart = class { } collapse_previous_level_nodes(node) { - let node_parent = $(`#${node.parent_id}`); + let node_parent = $(`[id="${node.parent_id}"]`); let previous_level_nodes = node_parent.parent().parent().children('li'); let node_card = undefined; @@ -541,7 +547,7 @@ erpnext.HierarchyChart = class { setup_node_click_action(node) { let me = this; - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); node_element.click(function() { const is_sibling = me.selected_node.parent_id === node.parent_id; @@ -559,7 +565,7 @@ erpnext.HierarchyChart = class { } setup_edit_node_action(node) { - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); let me = this; node_element.find('.btn-edit-node').click(function() { @@ -568,7 +574,7 @@ erpnext.HierarchyChart = class { } remove_levels_after_node(node) { - let level = $(`#${node.id}`).parent().parent().parent().index(); + let level = $(`[id="${node.id}"]`).parent().parent().parent().index(); level = $('.hierarchy > li:eq('+ level + ')'); level.nextAll('li').remove(); @@ -591,7 +597,7 @@ erpnext.HierarchyChart = class { const parent = $(path).data('parent'); const child = $(path).data('child'); - if ($(`#${parent}`).length && $(`#${child}`).length) + if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) return; $(path).remove(); diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js index b1b78c05174..0a8ba78f643 100644 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js @@ -54,13 +54,11 @@ erpnext.HierarchyChartMobile = class { }); node.parent.append(node_card); - node.$link = $(`#${node.id}`); + node.$link = $(`[id="${node.id}"]`); node.$link.addClass('mobile-node'); } show() { - frappe.breadcrumbs.add('HR'); - let me = this; if ($(`[data-fieldname="company"]`).length) return; @@ -186,7 +184,7 @@ erpnext.HierarchyChartMobile = class { this.refresh_connectors(node.parent_id, node.id); // rebuild incoming connections of parent - let grandparent = $(`#${node.parent_id}`).attr('data-parent'); + let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); this.refresh_connectors(grandparent, node.parent_id); } @@ -223,7 +221,7 @@ erpnext.HierarchyChartMobile = class { show_active_path(node) { // mark node parent on active path - $(`#${node.parent_id}`).addClass('active-path'); + $(`[id="${node.parent_id}"]`).addClass('active-path'); } load_children(node) { @@ -258,7 +256,7 @@ erpnext.HierarchyChartMobile = class { if (child_nodes) { $.each(child_nodes, (_i, data) => { this.add_node(node, data); - $(`#${data.id}`).addClass('active-child'); + $(`[id="${data.id}"]`).addClass('active-child'); setTimeout(() => { this.add_connector(node.id, data.id); @@ -295,9 +293,9 @@ erpnext.HierarchyChartMobile = class { let connector = undefined; - if ($(`#${parent_id}`).hasClass('active')) { + if ($(`[id="${parent_id}"]`).hasClass('active')) { connector = this.get_connector_for_active_node(parent_node, child_node); - } else if ($(`#${parent_id}`).hasClass('active-path')) { + } else if ($(`[id="${parent_id}"]`).hasClass('active-path')) { connector = this.get_connector_for_collapsed_node(parent_node, child_node); } @@ -353,7 +351,7 @@ erpnext.HierarchyChartMobile = class { set_path_attributes(path, parent_id, child_id) { path.setAttribute("data-parent", parent_id); path.setAttribute("data-child", child_id); - const parent = $(`#${parent_id}`); + const parent = $(`[id="${parent_id}"]`); if (parent.hasClass('active')) { path.setAttribute("class", "active-connector"); @@ -376,7 +374,7 @@ erpnext.HierarchyChartMobile = class { setup_node_click_action(node) { let me = this; - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); node_element.click(function() { let el = undefined; @@ -400,7 +398,7 @@ erpnext.HierarchyChartMobile = class { } setup_edit_node_action(node) { - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); let me = this; node_element.find('.btn-edit-node').click(function() { @@ -514,7 +512,7 @@ erpnext.HierarchyChartMobile = class { } remove_levels_after_node(node) { - let level = $(`#${node.id}`).parent().parent().index(); + let level = $(`[id="${node.id}"]`).parent().parent().index(); level = $('.hierarchy-mobile > li:eq('+ level + ')'); level.nextAll('li').remove(); @@ -535,7 +533,7 @@ erpnext.HierarchyChartMobile = class { const parent = $(path).data('parent'); const child = $(path).data('child'); - if ($(`#${parent}`).length && $(`#${child}`).length) + if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) return; $(path).remove(); diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue deleted file mode 100644 index 54c359766d3..00000000000 --- a/erpnext/public/js/hub/PageContainer.vue +++ /dev/null @@ -1,119 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/Sidebar.vue b/erpnext/public/js/hub/Sidebar.vue deleted file mode 100644 index 66c291ec52f..00000000000 --- a/erpnext/public/js/hub/Sidebar.vue +++ /dev/null @@ -1,110 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/CommentInput.vue b/erpnext/public/js/hub/components/CommentInput.vue deleted file mode 100644 index 31562c7a284..00000000000 --- a/erpnext/public/js/hub/components/CommentInput.vue +++ /dev/null @@ -1,39 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/DetailHeaderItem.vue b/erpnext/public/js/hub/components/DetailHeaderItem.vue deleted file mode 100644 index a6c5f066f28..00000000000 --- a/erpnext/public/js/hub/components/DetailHeaderItem.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/DetailView.vue b/erpnext/public/js/hub/components/DetailView.vue deleted file mode 100644 index 942c1ebdb34..00000000000 --- a/erpnext/public/js/hub/components/DetailView.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/EmptyState.vue b/erpnext/public/js/hub/components/EmptyState.vue deleted file mode 100644 index e3a33a08306..00000000000 --- a/erpnext/public/js/hub/components/EmptyState.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/Image.vue b/erpnext/public/js/hub/components/Image.vue deleted file mode 100644 index 9acf4210322..00000000000 --- a/erpnext/public/js/hub/components/Image.vue +++ /dev/null @@ -1,40 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/ItemCard.vue b/erpnext/public/js/hub/components/ItemCard.vue deleted file mode 100644 index 675ad86645b..00000000000 --- a/erpnext/public/js/hub/components/ItemCard.vue +++ /dev/null @@ -1,142 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/ItemCardsContainer.vue b/erpnext/public/js/hub/components/ItemCardsContainer.vue deleted file mode 100644 index 0a20bcdc636..00000000000 --- a/erpnext/public/js/hub/components/ItemCardsContainer.vue +++ /dev/null @@ -1,62 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/ItemListCard.vue b/erpnext/public/js/hub/components/ItemListCard.vue deleted file mode 100644 index 7f6fb77d76e..00000000000 --- a/erpnext/public/js/hub/components/ItemListCard.vue +++ /dev/null @@ -1,21 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/NotificationMessage.vue b/erpnext/public/js/hub/components/NotificationMessage.vue deleted file mode 100644 index c26672635c3..00000000000 --- a/erpnext/public/js/hub/components/NotificationMessage.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/Rating.vue b/erpnext/public/js/hub/components/Rating.vue deleted file mode 100644 index 33290b8e202..00000000000 --- a/erpnext/public/js/hub/components/Rating.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/ReviewArea.vue b/erpnext/public/js/hub/components/ReviewArea.vue deleted file mode 100644 index aa83bb0e465..00000000000 --- a/erpnext/public/js/hub/components/ReviewArea.vue +++ /dev/null @@ -1,140 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/ReviewTimelineItem.vue b/erpnext/public/js/hub/components/ReviewTimelineItem.vue deleted file mode 100644 index d0e83f3b1cd..00000000000 --- a/erpnext/public/js/hub/components/ReviewTimelineItem.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/SearchInput.vue b/erpnext/public/js/hub/components/SearchInput.vue deleted file mode 100644 index 4b1ce6e4ef9..00000000000 --- a/erpnext/public/js/hub/components/SearchInput.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/SectionHeader.vue b/erpnext/public/js/hub/components/SectionHeader.vue deleted file mode 100644 index 05a2f838a0a..00000000000 --- a/erpnext/public/js/hub/components/SectionHeader.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/erpnext/public/js/hub/components/TimelineItem.vue b/erpnext/public/js/hub/components/TimelineItem.vue deleted file mode 100644 index d13c842beb8..00000000000 --- a/erpnext/public/js/hub/components/TimelineItem.vue +++ /dev/null @@ -1,9 +0,0 @@ -/* Saving this for later */ - diff --git a/erpnext/public/js/hub/components/edit_details_dialog.js b/erpnext/public/js/hub/components/edit_details_dialog.js deleted file mode 100644 index 97c5f83a13a..00000000000 --- a/erpnext/public/js/hub/components/edit_details_dialog.js +++ /dev/null @@ -1,41 +0,0 @@ -function edit_details_dialog(params) { - let dialog = new frappe.ui.Dialog({ - title: __('Update Details'), - fields: [ - { - label: 'Item Name', - fieldname: 'item_name', - fieldtype: 'Data', - default: params.defaults.item_name, - reqd: 1 - }, - { - label: 'Hub Category', - fieldname: 'hub_category', - fieldtype: 'Autocomplete', - default: params.defaults.hub_category, - options: [], - reqd: 1 - }, - { - label: 'Description', - fieldname: 'description', - fieldtype: 'Text', - default: params.defaults.description, - options: [], - reqd: 1 - } - ], - primary_action_label: params.primary_action.label || __('Update Details'), - primary_action: params.primary_action.fn - }); - - hub.call('get_categories').then(categories => { - categories = categories.map(d => d.name); - dialog.fields_dict.hub_category.set_data(categories); - }); - - return dialog; -} - -export { edit_details_dialog }; diff --git a/erpnext/public/js/hub/components/item_publish_dialog.js b/erpnext/public/js/hub/components/item_publish_dialog.js deleted file mode 100644 index 08de5b30b6c..00000000000 --- a/erpnext/public/js/hub/components/item_publish_dialog.js +++ /dev/null @@ -1,39 +0,0 @@ -function ItemPublishDialog(primary_action, secondary_action) { - let dialog = new frappe.ui.Dialog({ - title: __('Edit Publishing Details'), - fields: [ - { - label: __('Item Code'), - fieldname: 'item_code', - fieldtype: 'Data', - read_only: 1 - }, - { - label: __('Hub Category'), - fieldname: 'hub_category', - fieldtype: 'Autocomplete', - options: [], - reqd: 1 - }, - { - label: __('Images'), - fieldname: 'image_list', - fieldtype: 'MultiSelect', - options: [], - reqd: 1 - } - ], - primary_action_label: primary_action.label || __('Set Details'), - primary_action: primary_action.fn, - secondary_action: secondary_action.fn - }); - - hub.call('get_categories').then(categories => { - categories = categories.map(d => d.name); - dialog.fields_dict.hub_category.set_data(categories); - }); - - return dialog; -} - -export { ItemPublishDialog }; diff --git a/erpnext/public/js/hub/components/profile_dialog.js b/erpnext/public/js/hub/components/profile_dialog.js deleted file mode 100644 index 8e3abc37eb8..00000000000 --- a/erpnext/public/js/hub/components/profile_dialog.js +++ /dev/null @@ -1,56 +0,0 @@ -const ProfileDialog = (title = __('Edit Profile'), action={}) => { - const fields = [ - { - fieldtype: 'Link', - fieldname: 'company', - label: __('Company'), - options: 'Company' - }, - { - fieldtype: 'Read Only', - fieldname: 'email', - label: __('Email') - }, - { - label: __('About your company'), - fieldname: 'company_description', - fieldtype: 'Text' - } - ]; - - let dialog = new frappe.ui.Dialog({ - title: title, - fields: fields, - primary_action_label: action.label || __('Update'), - primary_action: () => { - const form_values = dialog.get_values(); - let values_filled = true; - - // TODO: Say "we notice that the company description and logo isn't set. Please set them in master." - // Only then allow to register - - const mandatory_fields = ['company', 'company_description']; - mandatory_fields.forEach(field => { - const value = form_values[field]; - if (!value) { - dialog.set_df_property(field, 'reqd', 1); - values_filled = false; - } - }); - if (!values_filled) return; - - action.on_submit(form_values); - } - }); - - // Post create - const default_company = frappe.defaults.get_default('company'); - dialog.set_value('company', default_company); - dialog.set_value('email', frappe.session.user); - - return dialog; -} - -export { - ProfileDialog -} diff --git a/erpnext/public/js/hub/components/reviews.js b/erpnext/public/js/hub/components/reviews.js deleted file mode 100644 index e34d68038f7..00000000000 --- a/erpnext/public/js/hub/components/reviews.js +++ /dev/null @@ -1,80 +0,0 @@ -function get_review_html(review) { - let username = review.username || review.user || __("Anonymous"); - - let image_html = review.user_image - ? `
      ` - : `
      ${frappe.get_abbr(username)}
      ` - - let edit_html = review.own - ? ` -
      - - ${'data.edit'} - -
      ` - : ''; - - let rating_html = get_rating_html(review.rating); - - return get_timeline_item(review, image_html, edit_html, rating_html); -} - -function get_timeline_item(data, image_html, edit_html, rating_html) { - return `
      - -
      -
      -
      ${edit_html}
      - -
      - - ${image_html} - - -
      - - - ${data.username} - - - - - -
      -
      -
      -
      -

      - ${rating_html} -

      -
      ${data.subject}
      -

      - ${data.content} -

      -
      -
      -
      -
      -
      `; -} - -function get_rating_html(rating) { - let rating_html = ``; - for (var i = 0; i < 5; i++) { - let star_class = 'fa-star'; - if (i >= rating) star_class = 'fa-star-o'; - rating_html += ``; - } - return rating_html; -} - -export { - get_review_html, - get_rating_html -} diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js deleted file mode 100644 index 5545a4935b3..00000000000 --- a/erpnext/public/js/hub/hub_call.js +++ /dev/null @@ -1,68 +0,0 @@ -frappe.provide('hub'); -frappe.provide('erpnext.hub'); - -erpnext.hub.cache = {}; -hub.call = function call_hub_method(method, args={}, clear_cache_on_event) { // eslint-disable-line - return new Promise((resolve, reject) => { - - // cache - const key = method + JSON.stringify(args); - if (erpnext.hub.cache[key]) { - resolve(erpnext.hub.cache[key]); - } - - // cache invalidation - const clear_cache = () => delete erpnext.hub.cache[key]; - - if (!clear_cache_on_event) { - invalidate_after_5_mins(clear_cache); - } else { - erpnext.hub.on(clear_cache_on_event, () => { - clear_cache(key); - }); - } - - let res; - if (hub.is_server) { - res = frappe.call({ - method: 'hub.hub.api.' + method, - args - }); - } else { - res = frappe.call({ - method: 'erpnext.hub_node.api.call_hub_method', - args: { - method, - params: args - } - }); - } - - res.then(r => { - if (r.message) { - const response = r.message; - if (response.error) { - frappe.throw({ - title: __('Marketplace Error'), - message: response.error - }); - } - - erpnext.hub.cache[key] = response; - erpnext.hub.trigger(`response:${key}`, { response }); - resolve(response); - } - reject(r); - - }).fail(reject); - }); -}; - -function invalidate_after_5_mins(clear_cache) { - // cache invalidation after 5 minutes - const timeout = 5 * 60 * 1000; - - setTimeout(() => { - clear_cache(); - }, timeout); -} diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js deleted file mode 100644 index 9c67c1cf9f0..00000000000 --- a/erpnext/public/js/hub/hub_factory.js +++ /dev/null @@ -1,34 +0,0 @@ -frappe.provide('erpnext.hub'); - -frappe.views.MarketplaceFactory = class MarketplaceFactory extends frappe.views.Factory { - show() { - is_marketplace_disabled() - .then(disabled => { - if (disabled) { - frappe.show_not_found('Marketplace'); - return; - } - - if (frappe.pages.marketplace) { - frappe.container.change_to('marketplace'); - erpnext.hub.marketplace.refresh(); - } else { - this.make('marketplace'); - } - }); - } - - make(page_name) { - frappe.require('marketplace.bundle.js', () => { - erpnext.hub.marketplace = new erpnext.hub.Marketplace({ - parent: this.make_page(true, page_name) - }); - }); - } -}; - -function is_marketplace_disabled() { - return frappe.call({ - method: "erpnext.hub_node.doctype.marketplace_settings.marketplace_settings.is_marketplace_enabled" - }).then(r => r.message) -} diff --git a/erpnext/public/js/hub/marketplace.bundle.js b/erpnext/public/js/hub/marketplace.bundle.js deleted file mode 100644 index a1596e0043b..00000000000 --- a/erpnext/public/js/hub/marketplace.bundle.js +++ /dev/null @@ -1,225 +0,0 @@ -import Vue from 'vue/dist/vue.js'; -import './vue-plugins'; - -// components -import PageContainer from './PageContainer.vue'; -import Sidebar from './Sidebar.vue'; -import { ProfileDialog } from './components/profile_dialog'; - -// helpers -import './hub_call'; - -frappe.provide('hub'); -frappe.provide('erpnext.hub'); -frappe.provide('frappe.route'); - -frappe.utils.make_event_emitter(frappe.route); -frappe.utils.make_event_emitter(erpnext.hub); - -erpnext.hub.Marketplace = class Marketplace { - constructor({ parent }) { - this.$parent = $(parent); - this.page = parent.page; - - this.update_hub_settings().then(() => { - - this.setup_header(); - this.make_sidebar(); - this.make_body(); - this.setup_events(); - this.refresh(); - - if (!hub.is_server) { - if (!hub.is_seller_registered()) { - this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this)) - } else { - this.page.set_secondary_action('Add Users', this.show_add_user_dialog.bind(this)); - } - } - }); - } - - setup_header() { - if (hub.is_server) return; - this.page.set_title(__('Marketplace')); - } - - setup_events() { - this.$parent.on('click', '[data-route]', (e) => { - const $target = $(e.currentTarget); - const route = $target.data().route; - frappe.set_route(route); - }); - - // generic action handler - this.$parent.on('click', '[data-action]', e => { - const $target = $(e.currentTarget); - const action = $target.data().action; - - if (action && this[action]) { - this[action].apply(this, $target); - } - }) - } - - make_sidebar() { - this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); - - new Vue({ - el: $('
      ').appendTo(this.$sidebar)[0], - render: h => h(Sidebar) - }); - } - - make_body() { - this.$body = this.$parent.find('.layout-main-section'); - this.$page_container = $('
      ').appendTo(this.$body); - - new Vue({ - el: '.hub-page-container', - render: h => h(PageContainer) - }); - - if (!hub.is_server) { - erpnext.hub.on('seller-registered', () => { - this.page.clear_primary_action(); - }); - } - } - - refresh() { - - } - - show_register_dialog() { - if(frappe.session.user === 'Administrator') { - frappe.msgprint(__('You need to be a user other than Administrator with System Manager and Item Manager roles to register on Marketplace.')); - return; - } - - if (!is_subset(['System Manager', 'Item Manager'], frappe.user_roles)) { - frappe.msgprint(__('You need to be a user with System Manager and Item Manager roles to register on Marketplace.')); - return; - } - - this.register_dialog = ProfileDialog( - __('Become a Seller'), - { - label: __('Register'), - on_submit: this.register_marketplace.bind(this) - } - ); - - this.register_dialog.show(); - } - - register_marketplace({company, company_description}) { - frappe.call({ - method: 'erpnext.hub_node.api.register_marketplace', - args: { - company, - company_description - } - }).then((r) => { - if (r.message && r.message.ok) { - this.register_dialog.hide(); - - this.update_hub_settings() - .then(() => { - frappe.set_route('marketplace', 'publish'); - erpnext.hub.trigger('seller-registered'); - }); - } - }); - } - - show_add_user_dialog() { - if (!is_subset(['System Manager', 'Item Manager'], frappe.user_roles)) { - frappe.msgprint(__('You need to be a user with System Manager and Item Manager roles to add users to Marketplace.')); - return; - } - - this.get_unregistered_users() - .then(r => { - const user_list = r.message; - - const d = new frappe.ui.Dialog({ - title: __('Add Users to Marketplace'), - fields: [ - { - label: __('Users'), - fieldname: 'users', - fieldtype: 'MultiSelect', - reqd: 1, - get_data() { - return user_list; - } - } - ], - primary_action({ users }) { - const selected_users = users.split(',').map(d => d.trim()).filter(Boolean); - - if (!selected_users.every(user => user_list.includes(user))) { - d.set_df_property('users', 'description', __('Some emails are invalid')); - return; - } else { - d.set_df_property('users', 'description', ''); - } - - frappe.call('erpnext.hub_node.api.register_users', { - user_list: selected_users - }) - .then(r => { - d.hide(); - - if (r.message && r.message.length) { - frappe.show_alert(__('Added {0} users', [r.message.length])); - } - }); - } - }); - - d.show(); - }); - } - - get_unregistered_users() { - return frappe.call('erpnext.hub_node.api.get_unregistered_users') - } - - update_hub_settings() { - return hub.get_settings().then(doc => { - hub.settings = doc; - }); - } -} - -Object.assign(hub, { - is_seller_registered() { - return hub.settings.registered; - }, - - is_user_registered() { - return this.is_seller_registered() && hub.settings.users - .filter(hub_user => hub_user.user === frappe.session.user) - .length === 1; - }, - - get_settings() { - if (frappe.session.user === 'Guest') { - return Promise.resolve({ - registered: 0 - }); - } - return frappe.db.get_doc('Marketplace Settings'); - } -}); - -/** - * Returns true if list_a is subset of list_b - * @param {Array} list_a - * @param {Array} list_b - */ -function is_subset(list_a, list_b) { - return list_a.every(item => list_b.includes(item)); -} diff --git a/erpnext/public/js/hub/pages/Buying.vue b/erpnext/public/js/hub/pages/Buying.vue deleted file mode 100644 index ebf593aca4d..00000000000 --- a/erpnext/public/js/hub/pages/Buying.vue +++ /dev/null @@ -1,56 +0,0 @@ - - diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue deleted file mode 100644 index 16d06018ff0..00000000000 --- a/erpnext/public/js/hub/pages/Category.vue +++ /dev/null @@ -1,76 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/FeaturedItems.vue b/erpnext/public/js/hub/pages/FeaturedItems.vue deleted file mode 100644 index 8380b2b2c0b..00000000000 --- a/erpnext/public/js/hub/pages/FeaturedItems.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue deleted file mode 100644 index 8fe824566db..00000000000 --- a/erpnext/public/js/hub/pages/Home.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue deleted file mode 100644 index 93002a7b27a..00000000000 --- a/erpnext/public/js/hub/pages/Item.vue +++ /dev/null @@ -1,356 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Messages.vue b/erpnext/public/js/hub/pages/Messages.vue deleted file mode 100644 index 73506e99266..00000000000 --- a/erpnext/public/js/hub/pages/Messages.vue +++ /dev/null @@ -1,104 +0,0 @@ - - diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue deleted file mode 100644 index 8901b97802d..00000000000 --- a/erpnext/public/js/hub/pages/NotFound.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue deleted file mode 100644 index ecba4b1e5a8..00000000000 --- a/erpnext/public/js/hub/pages/Publish.vue +++ /dev/null @@ -1,212 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/PublishedItems.vue b/erpnext/public/js/hub/pages/PublishedItems.vue deleted file mode 100644 index cbb22164e6e..00000000000 --- a/erpnext/public/js/hub/pages/PublishedItems.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/SavedItems.vue b/erpnext/public/js/hub/pages/SavedItems.vue deleted file mode 100644 index 7007ddcf8e7..00000000000 --- a/erpnext/public/js/hub/pages/SavedItems.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue deleted file mode 100644 index c10841e9848..00000000000 --- a/erpnext/public/js/hub/pages/Search.vue +++ /dev/null @@ -1,81 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue deleted file mode 100644 index 3c9b800f4a0..00000000000 --- a/erpnext/public/js/hub/pages/Seller.vue +++ /dev/null @@ -1,201 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/SellerItems.vue b/erpnext/public/js/hub/pages/SellerItems.vue deleted file mode 100644 index 852fbaee3ff..00000000000 --- a/erpnext/public/js/hub/pages/SellerItems.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Selling.vue b/erpnext/public/js/hub/pages/Selling.vue deleted file mode 100644 index 8743027885d..00000000000 --- a/erpnext/public/js/hub/pages/Selling.vue +++ /dev/null @@ -1,66 +0,0 @@ - - diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js deleted file mode 100644 index 4912d684991..00000000000 --- a/erpnext/public/js/hub/vue-plugins.js +++ /dev/null @@ -1,58 +0,0 @@ -import Vue from 'vue/dist/vue.js'; - -// Global components -import ItemCardsContainer from './components/ItemCardsContainer.vue'; -import SectionHeader from './components/SectionHeader.vue'; -import SearchInput from './components/SearchInput.vue'; -import DetailView from './components/DetailView.vue'; -import DetailHeaderItem from './components/DetailHeaderItem.vue'; -import EmptyState from './components/EmptyState.vue'; -import Image from './components/Image.vue'; - -Vue.prototype.__ = window.__; -Vue.prototype.frappe = window.frappe; - -Vue.component('item-cards-container', ItemCardsContainer); -Vue.component('section-header', SectionHeader); -Vue.component('search-input', SearchInput); -Vue.component('detail-view', DetailView); -Vue.component('detail-header-item', DetailHeaderItem); -Vue.component('empty-state', EmptyState); -Vue.component('base-image', Image); - -Vue.directive('route', { - bind(el, binding) { - const route = binding.value; - if (!route) return; - el.classList.add('cursor-pointer'); - el.dataset.route = route; - el.addEventListener('click', () => frappe.set_route(route)); - }, - unbind(el) { - el.classList.remove('cursor-pointer'); - } -}); - -const handleImage = (el, src) => { - let img = new Image(); - // add loading class - el.src = ''; - el.classList.add('img-loading'); - - img.onload = () => { - // image loaded, remove loading class - el.classList.remove('img-loading'); - // set src - el.src = src; - } - img.onerror = () => { - el.classList.remove('img-loading'); - el.classList.add('no-image'); - el.src = null; - } - img.src = src; -} - -Vue.filter('striphtml', function (text) { - return strip_html(text || ''); -}); diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 9caf1defe97..cad16595618 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -84,11 +84,15 @@ $.extend(erpnext, { }); }, + route_to_pending_reposts: (args) => { + frappe.set_route('List', 'Repost Item Valuation', args); + }, + proceed_save_with_reminders_frequency_change: () => { frappe.ui.hide_open_dialog(); - + frappe.call({ - method: 'erpnext.hr.doctype.hr_settings.hr_settings.set_proceed_with_frequency_change', + method: 'erpnext.hr.doctype.hr_settings.hr_settings.set_proceed_with_frequency_change', callback: () => { cur_frm.save(); } @@ -709,14 +713,21 @@ erpnext.utils.map_current_doc = function(opts) { setters: opts.setters, get_query: opts.get_query, add_filters_group: 1, + allow_child_item_selection: opts.allow_child_item_selection, + child_fieldname: opts.child_fielname, + child_columns: opts.child_columns, + size: opts.size, action: function(selections, args) { let values = selections; - if(values.length === 0){ + if (values.length === 0) { frappe.msgprint(__("Please select {0}", [opts.source_doctype])) return; } opts.source_name = values; - opts.setters = args; + if (opts.allow_child_item_selection) { + // args contains filtered child docnames + opts.args = args; + } d.dialog.hide(); _map(); }, @@ -744,9 +755,13 @@ frappe.form.link_formatters['Item'] = function(value, doc) { } frappe.form.link_formatters['Employee'] = function(value, doc) { - if(doc && doc.employee_name && doc.employee_name !== value) { - return value? value + ': ' + doc.employee_name: doc.employee_name; + if (doc && value && doc.employee_name && doc.employee_name !== value && doc.employee === value) { + return value + ': ' + doc.employee_name; + } else if (!value && doc.doctype && doc.employee_name) { + // format blank value in child table + return doc.employee; } else { + // if value is blank in report view or project name and name are the same, return as is return value; } } diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 4d432e3d5cc..a492b32a9f6 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -289,8 +289,8 @@ erpnext.utils.get_shipping_address = function(frm, callback) { company: frm.doc.company, address: frm.doc.shipping_address }, - callback: function(r){ - if (r.message){ + callback: function(r) { + if (r.message) { frm.set_value("shipping_address", r.message[0]) //Address title or name frm.set_value("shipping_address_display", r.message[1]) //Address to be displayed on the page } diff --git a/erpnext/public/js/utils/supplier_quick_entry.js b/erpnext/public/js/utils/supplier_quick_entry.js new file mode 100644 index 00000000000..8d591a96510 --- /dev/null +++ b/erpnext/public/js/utils/supplier_quick_entry.js @@ -0,0 +1,77 @@ +frappe.provide('frappe.ui.form'); + +frappe.ui.form.SupplierQuickEntryForm = class SupplierQuickEntryForm extends frappe.ui.form.QuickEntryForm { + constructor(doctype, after_insert, init_callback, doc, force) { + super(doctype, after_insert, init_callback, doc, force); + this.skip_redirect_on_error = true; + } + + render_dialog() { + this.mandatory = this.mandatory.concat(this.get_variant_fields()); + super.render_dialog(); + } + + get_variant_fields() { + var variant_fields = [ + { + fieldtype: "Section Break", + label: __("Primary Contact Details"), + collapsible: 1 + }, + { + label: __("Email Id"), + fieldname: "email_id", + fieldtype: "Data" + }, + { + fieldtype: "Column Break" + }, + { + label: __("Mobile Number"), + fieldname: "mobile_no", + fieldtype: "Data" + }, + { + fieldtype: "Section Break", + label: __("Primary Address Details"), + collapsible: 1 + }, + { + label: __("Address Line 1"), + fieldname: "address_line1", + fieldtype: "Data" + }, + { + label: __("Address Line 2"), + fieldname: "address_line2", + fieldtype: "Data" + }, + { + label: __("ZIP Code"), + fieldname: "pincode", + fieldtype: "Data" + }, + { + fieldtype: "Column Break" + }, + { + label: __("City"), + fieldname: "city", + fieldtype: "Data" + }, + { + label: __("State"), + fieldname: "state", + fieldtype: "Data" + }, + { + label: __("Country"), + fieldname: "country", + fieldtype: "Link", + options: "Country" + } + ]; + + return variant_fields; + } +}; diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 1677e9b3de4..7a3854cc611 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -495,6 +495,11 @@ font-size: var(--text-md); } + > .item-qty-total-container { + @extend .net-total-container; + padding: 5px 0px 0px 0px; + } + > .taxes-container { display: none; flex-direction: column; diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 490a7c4af73..fef1e76154f 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -31,6 +31,14 @@ body.product-page { .carousel-control-prev, .carousel-control-next { opacity: 1; + width: 8%; + + @media (max-width: 1200px) { + width: 10%; + } + @media (max-width: 768px) { + width: 15%; + } } .carousel-body { @@ -43,6 +51,8 @@ body.product-page { .carousel-content { max-width: 400px; + margin-left: 5rem; + margin-right: 5rem; } .card { diff --git a/erpnext/quality_management/doctype/non_conformance/non_conformance.py b/erpnext/quality_management/doctype/non_conformance/non_conformance.py index d4e8cc7a716..a2198f374ad 100644 --- a/erpnext/quality_management/doctype/non_conformance/non_conformance.py +++ b/erpnext/quality_management/doctype/non_conformance/non_conformance.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class NonConformance(Document): pass diff --git a/erpnext/quality_management/doctype/non_conformance/test_non_conformance.py b/erpnext/quality_management/doctype/non_conformance/test_non_conformance.py index 54f8b58cfb0..3e94b35745c 100644 --- a/erpnext/quality_management/doctype/non_conformance/test_non_conformance.py +++ b/erpnext/quality_management/doctype/non_conformance/test_non_conformance.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestNonConformance(unittest.TestCase): pass diff --git a/erpnext/quality_management/doctype/quality_action/quality_action.py b/erpnext/quality_management/doctype/quality_action/quality_action.py index 02401ba689d..87245f9a3f8 100644 --- a/erpnext/quality_management/doctype/quality_action/quality_action.py +++ b/erpnext/quality_management/doctype/quality_action/quality_action.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class QualityAction(Document): def validate(self): self.status = 'Open' if any([d.status=='Open' for d in self.resolutions]) else 'Completed' diff --git a/erpnext/quality_management/doctype/quality_action/test_quality_action.py b/erpnext/quality_management/doctype/quality_action/test_quality_action.py index 98d665f3910..fefa9dfe9cd 100644 --- a/erpnext/quality_management/doctype/quality_action/test_quality_action.py +++ b/erpnext/quality_management/doctype/quality_action/test_quality_action.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestQualityAction(unittest.TestCase): # quality action has no code pass diff --git a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.py b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.py index de8873feb0c..7ede3e4de00 100644 --- a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.py +++ b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityActionResolution(Document): pass diff --git a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py index d3e96cf2d94..ec5d67f4f03 100644 --- a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py +++ b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class QualityFeedback(Document): @frappe.whitelist() def set_parameters(self): diff --git a/erpnext/quality_management/doctype/quality_feedback/test_quality_feedback.py b/erpnext/quality_management/doctype/quality_feedback/test_quality_feedback.py index 5a8bd5ce30c..fe36cc6e5b1 100644 --- a/erpnext/quality_management/doctype/quality_feedback/test_quality_feedback.py +++ b/erpnext/quality_management/doctype/quality_feedback/test_quality_feedback.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest class TestQualityFeedback(unittest.TestCase): diff --git a/erpnext/quality_management/doctype/quality_feedback_parameter/quality_feedback_parameter.py b/erpnext/quality_management/doctype/quality_feedback_parameter/quality_feedback_parameter.py index d652e8a57bb..ff2c8415766 100644 --- a/erpnext/quality_management/doctype/quality_feedback_parameter/quality_feedback_parameter.py +++ b/erpnext/quality_management/doctype/quality_feedback_parameter/quality_feedback_parameter.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityFeedbackParameter(Document): pass diff --git a/erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.py b/erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.py index 0c6dfc07802..4590f9d3a05 100644 --- a/erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.py +++ b/erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityFeedbackTemplate(Document): pass diff --git a/erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py b/erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py index afed14b6ad0..4b8bc0f0437 100644 --- a/erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py +++ b/erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestQualityFeedbackTemplate(unittest.TestCase): pass diff --git a/erpnext/quality_management/doctype/quality_feedback_template_parameter/quality_feedback_template_parameter.py b/erpnext/quality_management/doctype/quality_feedback_template_parameter/quality_feedback_template_parameter.py index 3f3348fd7fa..13e215f954a 100644 --- a/erpnext/quality_management/doctype/quality_feedback_template_parameter/quality_feedback_template_parameter.py +++ b/erpnext/quality_management/doctype/quality_feedback_template_parameter/quality_feedback_template_parameter.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityFeedbackTemplateParameter(Document): pass diff --git a/erpnext/quality_management/doctype/quality_goal/quality_goal.py b/erpnext/quality_management/doctype/quality_goal/quality_goal.py index 3e616b75ceb..22ba81073d1 100644 --- a/erpnext/quality_management/doctype/quality_goal/quality_goal.py +++ b/erpnext/quality_management/doctype/quality_goal/quality_goal.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class QualityGoal(Document): def validate(self): pass diff --git a/erpnext/quality_management/doctype/quality_goal/test_quality_goal.py b/erpnext/quality_management/doctype/quality_goal/test_quality_goal.py index 0e135b50212..67fdaca6d9b 100644 --- a/erpnext/quality_management/doctype/quality_goal/test_quality_goal.py +++ b/erpnext/quality_management/doctype/quality_goal/test_quality_goal.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest -from erpnext.quality_management.doctype.quality_procedure.test_quality_procedure import create_procedure + class TestQualityGoal(unittest.TestCase): def test_quality_goal(self): diff --git a/erpnext/quality_management/doctype/quality_goal_objective/quality_goal_objective.py b/erpnext/quality_management/doctype/quality_goal_objective/quality_goal_objective.py index f4bd357f1b2..eaa8db21563 100644 --- a/erpnext/quality_management/doctype/quality_goal_objective/quality_goal_objective.py +++ b/erpnext/quality_management/doctype/quality_goal_objective/quality_goal_objective.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityGoalObjective(Document): pass diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.py b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.py index 9e453ebfc2e..481b3c17f11 100644 --- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.py +++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class QualityMeeting(Document): pass diff --git a/erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py b/erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py index 6bf4c179c6b..910b8a1130a 100644 --- a/erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py +++ b/erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestQualityMeeting(unittest.TestCase): # nothing to test pass diff --git a/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.py b/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.py index 5d77975d74c..c2f5b3f17c1 100644 --- a/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.py +++ b/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityMeetingAgenda(Document): pass diff --git a/erpnext/quality_management/doctype/quality_meeting_agenda/test_quality_meeting_agenda.py b/erpnext/quality_management/doctype/quality_meeting_agenda/test_quality_meeting_agenda.py index 4750cc1f7af..8b09f6db7fc 100644 --- a/erpnext/quality_management/doctype/quality_meeting_agenda/test_quality_meeting_agenda.py +++ b/erpnext/quality_management/doctype/quality_meeting_agenda/test_quality_meeting_agenda.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestQualityMeetingAgenda(unittest.TestCase): pass diff --git a/erpnext/quality_management/doctype/quality_meeting_minutes/quality_meeting_minutes.py b/erpnext/quality_management/doctype/quality_meeting_minutes/quality_meeting_minutes.py index 47b2c95bd9e..f6998df35cb 100644 --- a/erpnext/quality_management/doctype/quality_meeting_minutes/quality_meeting_minutes.py +++ b/erpnext/quality_management/doctype/quality_meeting_minutes/quality_meeting_minutes.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityMeetingMinutes(Document): pass diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py index 117db0012ba..0f535ba2e11 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils.nestedset import NestedSet, rebuild_tree from frappe import _ +from frappe.utils.nestedset import NestedSet + class QualityProcedure(NestedSet): nsm_parent_field = 'parent_quality_procedure' diff --git a/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py index 4fa7734bc68..6130895e38d 100644 --- a/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py +++ b/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest +import frappe + from .quality_procedure import add_node + class TestQualityProcedure(unittest.TestCase): def test_add_node(self): try: diff --git a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.py b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.py index 0d9a286052d..a03c871a85d 100644 --- a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.py +++ b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityProcedureProcess(Document): pass diff --git a/erpnext/quality_management/doctype/quality_review/quality_review.py b/erpnext/quality_management/doctype/quality_review/quality_review.py index 34cc890e219..b896f8dfe0c 100644 --- a/erpnext/quality_management/doctype/quality_review/quality_review.py +++ b/erpnext/quality_management/doctype/quality_review/quality_review.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class QualityReview(Document): def validate(self): # fetch targets from goal diff --git a/erpnext/quality_management/doctype/quality_review/test_quality_review.py b/erpnext/quality_management/doctype/quality_review/test_quality_review.py index 161ecd01ef1..8a254dba2a5 100644 --- a/erpnext/quality_management/doctype/quality_review/test_quality_review.py +++ b/erpnext/quality_management/doctype/quality_review/test_quality_review.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest from ..quality_goal.test_quality_goal import get_quality_goal from .quality_review import review + class TestQualityReview(unittest.TestCase): def test_review_creation(self): quality_goal = get_quality_goal() diff --git a/erpnext/quality_management/doctype/quality_review_objective/quality_review_objective.py b/erpnext/quality_management/doctype/quality_review_objective/quality_review_objective.py index 3092a1e9979..462a97118ef 100644 --- a/erpnext/quality_management/doctype/quality_review_objective/quality_review_objective.py +++ b/erpnext/quality_management/doctype/quality_review_objective/quality_review_objective.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QualityReviewObjective(Document): pass diff --git a/erpnext/quality_management/workspace/quality/quality.json b/erpnext/quality_management/workspace/quality/quality.json index 4dc8129d890..ae284701824 100644 --- a/erpnext/quality_management/workspace/quality/quality.json +++ b/erpnext/quality_management/workspace/quality/quality.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Goal\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Procedure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Inspection\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Review\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Action\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Conformance\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goal and Procedure\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Feedback\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Meeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Review and Action\", \"col\": 4}}]", "creation": "2020-03-02 15:49:28.632014", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "quality", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Quality", "links": [ { @@ -149,15 +142,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.699912", + "modified": "2021-08-05 12:16:01.699913", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index faa59129a5a..c460286078d 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -1,11 +1,13 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ + from erpnext import get_region + def check_deletion_permission(doc, method): region = get_region(doc.company) if region in ["Nepal", "France"] and doc.docstatus != 0: @@ -28,3 +30,4 @@ def create_transaction_log(doc, method): "document_name": doc.name, "data": data }).insert(ignore_permissions=True) + diff --git a/erpnext/regional/address_template/test_regional_address_template.py b/erpnext/regional/address_template/test_regional_address_template.py index 8a05ea26f45..780db401584 100644 --- a/erpnext/regional/address_template/test_regional_address_template.py +++ b/erpnext/regional/address_template/test_regional_address_template.py @@ -1,9 +1,9 @@ -from __future__ import unicode_literals from unittest import TestCase import frappe -from erpnext.regional.address_template.setup import get_address_templates -from erpnext.regional.address_template.setup import update_address_template + +from erpnext.regional.address_template.setup import get_address_templates, update_address_template + def ensure_country(country): if frappe.db.exists("Country", country): diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.py b/erpnext/regional/doctype/datev_settings/datev_settings.py index cff5bba58fd..686a93e529d 100644 --- a/erpnext/regional/doctype/datev_settings/datev_settings.py +++ b/erpnext/regional/doctype/datev_settings/datev_settings.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class DATEVSettings(Document): pass diff --git a/erpnext/regional/doctype/datev_settings/test_datev_settings.py b/erpnext/regional/doctype/datev_settings/test_datev_settings.py index 0271329f4d2..ba70eb472fe 100644 --- a/erpnext/regional/doctype/datev_settings/test_datev_settings.py +++ b/erpnext/regional/doctype/datev_settings/test_datev_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestDATEVSettings(unittest.TestCase): pass diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py index 4791dc26753..3b73a5c23ec 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class GSTHSNCode(Document): pass diff --git a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js deleted file mode 100644 index 24c5fd355ff..00000000000 --- a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GST HSN Code", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new GST HSN Code - () => frappe.tests.make('GST HSN Code', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.py b/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.py index ed54f207139..6dbca1af1a9 100644 --- a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.py +++ b/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestGSTHSNCode(unittest.TestCase): pass diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.json b/erpnext/regional/doctype/gst_settings/gst_settings.json index 95b930c4c86..fc579d4b38c 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.json +++ b/erpnext/regional/doctype/gst_settings/gst_settings.json @@ -6,8 +6,10 @@ "engine": "InnoDB", "field_order": [ "gst_summary", - "column_break_2", + "gst_tax_settings_section", "round_off_gst_values", + "column_break_4", + "hsn_wise_tax_breakup", "gstin_email_sent_on", "section_break_4", "gst_accounts", @@ -17,37 +19,23 @@ { "fieldname": "gst_summary", "fieldtype": "HTML", - "label": "GST Summary", - "show_days": 1, - "show_seconds": 1 - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "label": "GST Summary" }, { "fieldname": "gstin_email_sent_on", "fieldtype": "Date", "label": "GSTIN Email Sent On", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "section_break_4", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "gst_accounts", "fieldtype": "Table", "label": "GST Accounts", - "options": "GST Account", - "show_days": 1, - "show_seconds": 1 + "options": "GST Account" }, { "default": "250000", @@ -56,24 +44,35 @@ "fieldtype": "Data", "in_list_view": 1, "label": "B2C Limit", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "default": "0", "description": "Enabling this option will round off individual GST components in all the Invoices", "fieldname": "round_off_gst_values", "fieldtype": "Check", - "label": "Round Off GST Values", - "show_days": 1, - "show_seconds": 1 + "label": "Round Off GST Values" + }, + { + "default": "0", + "fieldname": "hsn_wise_tax_breakup", + "fieldtype": "Check", + "label": "Tax Breakup Table Based On HSN Code" + }, + { + "fieldname": "gst_tax_settings_section", + "fieldtype": "Section Break", + "label": "GST Tax Settings" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-28 17:19:47.969260", + "modified": "2021-10-11 18:10:14.242614", "modified_by": "Administrator", "module": "Regional", "name": "GST Settings", @@ -83,4 +82,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 - } \ No newline at end of file +} \ No newline at end of file diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.py b/erpnext/regional/doctype/gst_settings/gst_settings.py index af3d92e59a7..13ef3e04885 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.py +++ b/erpnext/regional/doctype/gst_settings/gst_settings.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, os + +import os + +import frappe from frappe import _ -from frappe.utils import get_url, nowdate, date_diff -from frappe.model.document import Document from frappe.contacts.doctype.contact.contact import get_default_contact +from frappe.model.document import Document +from frappe.utils import date_diff, get_url, nowdate + class EmailMissing(frappe.ValidationError): pass diff --git a/erpnext/regional/doctype/gst_settings/test_gst_settings.js b/erpnext/regional/doctype/gst_settings/test_gst_settings.js deleted file mode 100644 index 00fcca6f326..00000000000 --- a/erpnext/regional/doctype/gst_settings/test_gst_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GST Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new GST Settings - () => frappe.tests.make('GST Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/regional/doctype/gst_settings/test_gst_settings.py b/erpnext/regional/doctype/gst_settings/test_gst_settings.py index d118dee6177..5c7d2b4602f 100644 --- a/erpnext/regional/doctype/gst_settings/test_gst_settings.py +++ b/erpnext/regional/doctype/gst_settings/test_gst_settings.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestGSTSettings(unittest.TestCase): pass diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 0ee5b097b54..d48cd67c384 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -1,17 +1,18 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import os + import json +import os + import frappe -from six import iteritems from frappe import _ from frappe.model.document import Document -from frappe.utils import flt, cstr +from frappe.utils import cstr, flt + from erpnext.regional.india import state_numbers + class GSTR3BReport(Document): def validate(self): self.get_data() @@ -279,7 +280,7 @@ class GSTR3BReport(Document): if self.get('invoice_items'): # Build itemised tax for export invoices, nil and exempted where tax table is blank - for invoice, items in iteritems(self.invoice_items): + for invoice, items in self.invoice_items.items(): if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \ == "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) @@ -347,7 +348,7 @@ class GSTR3BReport(Document): self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value def set_inter_state_supply(self, inter_state_supply): - for key, value in iteritems(inter_state_supply): + for key, value in inter_state_supply.items(): if key[0] == "Unregistered": self.report_dict["inter_sup"]["unreg_details"].append(value) diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 065f80d610a..e12e3d7b800 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -1,15 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import json +import unittest import frappe -import unittest from frappe.utils import getdate -from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.stock.doctype.item.test_item import make_item -import json test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"] @@ -103,6 +103,45 @@ class TestGSTR3BReport(unittest.TestCase): gst_settings.round_off_gst_values = 1 gst_settings.save() + def test_gst_category_auto_update(self): + if not frappe.db.exists("Customer", "_Test GST Customer With GSTIN"): + customer = frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "_Test GST Customer With GSTIN", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory" + }).insert() + + self.assertEqual(customer.gst_category, 'Unregistered') + + if not frappe.db.exists('Address', '_Test GST Category-1-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test GST Category-1", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gstin": "29AZWPS7135H1ZG", + "gst_state": "Karnataka", + "gst_state_number": "29" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test GST Customer With GSTIN" + }) + + address.save() + + customer.load_from_db() + self.assertEqual(customer.gst_category, 'Registered Regular') + + def make_sales_invoice(): si = create_sales_invoice(company="_Test Company GST", customer = '_Test GST Customer', diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 00300539e9a..97b8488c2fe 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -1,23 +1,22 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -from decimal import Decimal -import json import re -import traceback import zipfile -import frappe, erpnext + +import dateutil +import frappe +from bs4 import BeautifulSoup as bs from frappe import _ from frappe.model.document import Document -from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.utils import flt, get_datetime_str, today from frappe.utils.data import format_datetime -from bs4 import BeautifulSoup as bs -from frappe.utils import cint, flt, today, nowdate, add_days, get_files_path, get_datetime_str -import dateutil from frappe.utils.file_manager import save_file +import erpnext + + class ImportSupplierInvoice(Document): def validate(self): if not frappe.db.get_value("Stock Settings", fieldname="stock_uom"): diff --git a/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py index d1caf77fc2c..78c07c558a5 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestImportSupplierInvoice(unittest.TestCase): pass diff --git a/erpnext/healthcare/doctype/healthcare_settings/__init__.py b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/healthcare_settings/__init__.py rename to erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json new file mode 100644 index 00000000000..89ba3e977af --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 09:17:09.862163", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:38.205597", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Purchase Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py new file mode 100644 index 00000000000..3920bc546c1 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class KSAVATPurchaseAccount(Document): + pass diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/__init__.py b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/inpatient_medication_entry/__init__.py rename to erpnext/regional/doctype/ksa_vat_sales_account/__init__.py diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js new file mode 100644 index 00000000000..72613f4064f --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Sales Account', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json new file mode 100644 index 00000000000..df2747891dc --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 08:46:33.820968", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:00.081407", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Sales Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py new file mode 100644 index 00000000000..7c2689f530e --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class KSAVATSalesAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py new file mode 100644 index 00000000000..1d6a6a793dc --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestKSAVATSalesAccount(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry_detail/__init__.py b/erpnext/regional/doctype/ksa_vat_setting/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/inpatient_medication_entry_detail/__init__.py rename to erpnext/regional/doctype/ksa_vat_setting/__init__.py diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js new file mode 100644 index 00000000000..00b62b9adfb --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Setting', { + onload: function () { + frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); + } +}); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json new file mode 100644 index 00000000000..33619467ed0 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-13 08:49:01.100356", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "ksa_vat_sales_accounts", + "ksa_vat_purchase_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "ksa_vat_sales_accounts", + "fieldtype": "Table", + "label": "KSA VAT Sales Accounts", + "options": "KSA VAT Sales Account", + "reqd": 1 + }, + { + "fieldname": "ksa_vat_purchase_accounts", + "fieldtype": "Table", + "label": "KSA VAT Purchase Accounts", + "options": "KSA VAT Purchase Account", + "reqd": 1 + } + ], + "links": [], + "modified": "2021-08-26 04:29:06.499378", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Setting", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "company", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py new file mode 100644 index 00000000000..bdae1161fd7 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class KSAVATSetting(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js new file mode 100644 index 00000000000..269cbec5fb4 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -0,0 +1,5 @@ +frappe.listview_settings['KSA VAT Setting'] = { + onload () { + frappe.breadcrumbs.add('Accounts'); + } +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py new file mode 100644 index 00000000000..7207901fd43 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestKSAVATSetting(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json index f48fe6f4763..c32ab6bec24 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json @@ -7,7 +7,7 @@ "engine": "InnoDB", "field_order": [ "certificate_details_section", - "section_code", + "tax_withholding_category", "fiscal_year", "column_break_3", "certificate_no", @@ -33,13 +33,6 @@ "reqd": 1, "unique": 1 }, - { - "fieldname": "section_code", - "fieldtype": "Select", - "label": "Section Code", - "options": "192\n193\n194\n194A\n194C\n194D\n194H\n194I\n194J\n194LA\n194LBB\n194LBC\n195", - "reqd": 1 - }, { "fieldname": "section_break_3", "fieldtype": "Section Break", @@ -123,13 +116,22 @@ "label": "Fiscal Year", "options": "Fiscal Year", "reqd": 1 + }, + { + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category", + "reqd": 1 } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-04-23 23:04:41.203721", + "modified": "2021-10-23 18:33:38.962622", "modified_by": "Administrator", "module": "Regional", "name": "Lower Deduction Certificate", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index 656c3296e58..f14888189a0 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -1,18 +1,19 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import getdate, get_link_to_form from frappe.model.document import Document +from frappe.utils import get_link_to_form, getdate + from erpnext.accounts.utils import get_fiscal_year + class LowerDeductionCertificate(Document): def validate(self): self.validate_dates() - self.validate_supplier_against_section_code() + self.validate_supplier_against_tax_category() def validate_dates(self): if getdate(self.valid_upto) < getdate(self.valid_from): @@ -28,12 +29,14 @@ class LowerDeductionCertificate(Document): <= fiscal_year.year_end_date): frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year))) - def validate_supplier_against_section_code(self): - duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'section_code': self.section_code}, ['name', 'valid_from', 'valid_upto'], as_dict=True) + def validate_supplier_against_tax_category(self): + duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', + {'supplier': self.supplier, 'tax_withholding_category': self.tax_withholding_category, 'name': ("!=", self.name)}, + ['name', 'valid_from', 'valid_upto'], as_dict=True) if duplicate_certificate and self.are_dates_overlapping(duplicate_certificate): certificate_link = get_link_to_form('Lower Deduction Certificate', duplicate_certificate.name) - frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against Section Code {2} for this time period.") - .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.section_code))) + frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against category {2} for this time period.") + .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.tax_withholding_category))) def are_dates_overlapping(self,duplicate_certificate): valid_from = duplicate_certificate.valid_from diff --git a/erpnext/regional/doctype/lower_deduction_certificate/test_lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/test_lower_deduction_certificate.py index 7e950206fcc..d8e78019311 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/test_lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/test_lower_deduction_certificate.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLowerDeductionCertificate(unittest.TestCase): pass diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/__init__.py b/erpnext/regional/doctype/product_tax_category/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/inpatient_medication_order/__init__.py rename to erpnext/regional/doctype/product_tax_category/__init__.py diff --git a/erpnext/regional/doctype/product_tax_category/product_tax_category.js b/erpnext/regional/doctype/product_tax_category/product_tax_category.js new file mode 100644 index 00000000000..9f8e7950156 --- /dev/null +++ b/erpnext/regional/doctype/product_tax_category/product_tax_category.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Product Tax Category', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/product_tax_category/product_tax_category.json b/erpnext/regional/doctype/product_tax_category/product_tax_category.json new file mode 100644 index 00000000000..147cb34425d --- /dev/null +++ b/erpnext/regional/doctype/product_tax_category/product_tax_category.json @@ -0,0 +1,70 @@ +{ + "actions": [], + "autoname": "field:product_tax_code", + "creation": "2021-08-23 12:33:37.910225", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "product_tax_code", + "column_break_2", + "category_name", + "section_break_4", + "description" + ], + "fields": [ + { + "fieldname": "product_tax_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Product Tax Code", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "category_name", + "fieldtype": "Data", + "label": "Category Name", + "length": 255 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-08-24 09:10:25.313642", + "modified_by": "Administrator", + "module": "Regional", + "name": "Product Tax Category", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "category_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/product_tax_category/product_tax_category.py b/erpnext/regional/doctype/product_tax_category/product_tax_category.py new file mode 100644 index 00000000000..b6be9e0920b --- /dev/null +++ b/erpnext/regional/doctype/product_tax_category/product_tax_category.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ProductTaxCategory(Document): + pass diff --git a/erpnext/regional/doctype/product_tax_category/test_product_tax_category.py b/erpnext/regional/doctype/product_tax_category/test_product_tax_category.py new file mode 100644 index 00000000000..2668f2391af --- /dev/null +++ b/erpnext/regional/doctype/product_tax_category/test_product_tax_category.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestProductTaxCategory(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py index d74154bfe78..4c3e8a78e9d 100644 --- a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class SouthAfricaVATSettings(Document): pass diff --git a/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py index 1c36652ad6e..0f19f25f88d 100644 --- a/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py +++ b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestSouthAfricaVATSettings(unittest.TestCase): pass diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index 41a0f1193bc..0f0897841b4 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.model.document import Document -from frappe.utils import getdate, flt, get_link_to_form -from erpnext.accounts.utils import get_fiscal_year from frappe.contacts.doctype.address.address import get_company_address +from frappe.model.document import Document +from frappe.utils import flt, get_link_to_form, getdate + +from erpnext.accounts.utils import get_fiscal_year + class TaxExemption80GCertificate(Document): def validate(self): @@ -81,7 +82,6 @@ class TaxExemption80GCertificate(Document): memberships = frappe.db.get_all('Membership', { 'member': self.member, 'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], - 'to_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], 'membership_status': ('!=', 'Cancelled') }, ['from_date', 'amount', 'name', 'invoice', 'payment_id'], order_by='from_date') diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py index 41b42036687..6fa3b85d061 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py @@ -1,16 +1,21 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest from frappe.utils import getdate + from erpnext.accounts.utils import get_fiscal_year -from erpnext.non_profit.doctype.donation.test_donation import create_donor, create_mode_of_payment, create_donor_type from erpnext.non_profit.doctype.donation.donation import create_donation -from erpnext.non_profit.doctype.membership.test_membership import setup_membership, make_membership +from erpnext.non_profit.doctype.donation.test_donation import ( + create_donor, + create_donor_type, + create_mode_of_payment, +) from erpnext.non_profit.doctype.member.member import create_member +from erpnext.non_profit.doctype.membership.test_membership import make_membership, setup_membership + class TestTaxExemption80GCertificate(unittest.TestCase): def setUp(self): diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py b/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py index bdad798d980..bb7f07f6883 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class TaxExemption80GCertificateDetail(Document): pass diff --git a/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py index 80d6b3a5f1f..f2fc34d391a 100644 --- a/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py +++ b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class UAEVATAccount(Document): pass diff --git a/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py b/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py index b88439f9b85..464c2e67be2 100644 --- a/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py +++ b/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestUAEVATSettings(unittest.TestCase): pass diff --git a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py index 20dc604510b..1af32e4be34 100644 --- a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py +++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class UAEVATSettings(Document): pass diff --git a/erpnext/regional/france/setup.py b/erpnext/regional/france/setup.py index db6419e9462..5d482030233 100644 --- a/erpnext/regional/france/setup.py +++ b/erpnext/regional/france/setup.py @@ -1,7 +1,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields diff --git a/erpnext/regional/france/utils.py b/erpnext/regional/france/utils.py index 424615dbbc0..841316586dc 100644 --- a/erpnext/regional/france/utils.py +++ b/erpnext/regional/france/utils.py @@ -1,8 +1,7 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + # don't remove this function it is used in tests def test_method(): diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index c1fa6e492d1..a68cecc0d63 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -1,4 +1,3 @@ -import os import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields diff --git a/erpnext/regional/germany/utils/datev/datev_constants.py b/erpnext/regional/germany/utils/datev/datev_constants.py index be3d7a3e542..8f2dc2dec39 100644 --- a/erpnext/regional/germany/utils/datev/datev_constants.py +++ b/erpnext/regional/germany/utils/datev/datev_constants.py @@ -1,4 +1,3 @@ -# coding: utf-8 """Constants used in datev.py.""" TRANSACTION_COLUMNS = [ diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index 122c15fd811..2d1e02eadba 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -1,15 +1,12 @@ -# coding: utf-8 -from __future__ import unicode_literals - import datetime import zipfile from csv import QUOTE_NONNUMERIC -from six import BytesIO -import six import frappe import pandas as pd from frappe import _ +from six import BytesIO + from .datev_constants import DataCategory @@ -33,12 +30,20 @@ def get_datev_csv(data, filters, csv_class): if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS: result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) + result['Beleginfo - Inhalt 6'] = pd.to_datetime(result['Beleginfo - Inhalt 6']) + result['Beleginfo - Inhalt 6'] = result['Beleginfo - Inhalt 6'].dt.strftime('%d%m%Y') + + result['Fälligkeit'] = pd.to_datetime(result['Fälligkeit']) + result['Fälligkeit'] = result['Fälligkeit'].dt.strftime('%d%m%y') + + result.sort_values(by='Belegdatum', inplace=True, kind='stable', ignore_index=True) + if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES: result['Sprach-ID'] = 'de-DE' data = result.to_csv( # Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035 - sep=str(';'), + sep=';', # European decimal seperator decimal=',', # Windows "ANSI" encoding diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py index faeb36fc693..c703575d7de 100644 --- a/erpnext/regional/india/__init__.py +++ b/erpnext/regional/india/__init__.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals -from six import iteritems states = [ '', @@ -82,4 +80,4 @@ state_numbers = { "West Bengal": "19", } -number_state_mapping = {v: k for k, v in iteritems(state_numbers)} +number_state_mapping = {v: k for k, v in state_numbers.items()} diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py deleted file mode 100644 index 765b51f435f..00000000000 --- a/erpnext/regional/india/e_invoice/utils.py +++ /dev/null @@ -1,1131 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import os -import re -import jwt -import sys -import json -import base64 -import frappe -import six -import traceback -import io -from frappe import _, bold -from pyqrcode import create as qrcreate -from frappe.utils.background_jobs import enqueue -from frappe.utils.scheduler import is_scheduler_inactive -from frappe.core.page.background_jobs.background_jobs import get_info -from frappe.integrations.utils import make_post_request, make_get_request -from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply -from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form, getdate, time_diff_in_hours - -@frappe.whitelist() -def validate_eligibility(doc): - if isinstance(doc, six.string_types): - doc = json.loads(doc) - - invalid_doctype = doc.get('doctype') != 'Sales Invoice' - if invalid_doctype: - return False - - einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable')) - if not einvoicing_enabled: - return False - - einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' - if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): - return False - - invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) - invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] - company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') - - # if export invoice, then taxes can be empty - # invoice can only be ineligible if no taxes applied and is not an export invoice - no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas' - has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst')) - - if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: - return False - - return True - -def validate_einvoice_fields(doc): - invoice_eligible = validate_eligibility(doc) - - if not invoice_eligible: - return - - if doc.docstatus == 0 and doc._action == 'save': - if doc.irn: - frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) - if len(doc.name) > 16: - raise_document_name_too_long_error() - - doc.einvoice_status = 'Pending' - - elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: - frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) - - elif doc.irn and doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: - frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) - -def raise_document_name_too_long_error(): - title = _('Document ID Too Long') - msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice') - msg += ', ' - msg += _('document id {} exceed 16 letters.').format(bold(_('should not'))) - msg += '

      ' - msg += _('You must {} your {} in order to have document id of {} length 16.').format( - bold(_('modify')), bold(_('naming series')), bold(_('maximum')) - ) - msg += _('Please account for ammended documents too.') - frappe.throw(msg, title=title) - -def read_json(name): - file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name)) - with open(file_path, 'r') as f: - return cstr(f.read()) - -def get_transaction_details(invoice): - supply_type = '' - if invoice.gst_category == 'Registered Regular': supply_type = 'B2B' - elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP' - elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP' - elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP' - - if not supply_type: - rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export') - frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export), - title=_('Invalid Supply Type')) - - return frappe._dict(dict( - tax_scheme='GST', - supply_type=supply_type, - reverse_charge=invoice.reverse_charge - )) - -def get_doc_details(invoice): - if getdate(invoice.posting_date) < getdate('2021-01-01'): - frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed')) - - invoice_type = 'CRN' if invoice.is_return else 'INV' - - invoice_name = invoice.name - invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') - - return frappe._dict(dict( - invoice_type=invoice_type, - invoice_name=invoice_name, - invoice_date=invoice_date - )) - -def validate_address_fields(address, is_shipping_address): - if ((not address.gstin and not is_shipping_address) - or not address.city - or not address.pincode - or not address.address_title - or not address.address_line1 - or not address.gst_state_number): - - frappe.throw( - msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name), - title=_('Missing Address Fields') - ) - -def get_party_details(address_name, is_shipping_address=False): - addr = frappe.get_doc('Address', address_name) - - validate_address_fields(addr, is_shipping_address) - - if addr.gst_state_number == 97: - # according to einvoice standard - addr.pincode = 999999 - - party_address_details = frappe._dict(dict( - legal_name=sanitize_for_json(addr.address_title), - location=sanitize_for_json(addr.city), - pincode=addr.pincode, gstin=addr.gstin, - state_code=addr.gst_state_number, - address_line1=sanitize_for_json(addr.address_line1), - address_line2=sanitize_for_json(addr.address_line2) - )) - - return party_address_details - -def get_overseas_address_details(address_name): - address_title, address_line1, address_line2, city = frappe.db.get_value( - 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city'] - ) - - if not address_title or not address_line1 or not city: - frappe.throw( - msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format( - get_link_to_form('Address', address_name) - ), - title=_('Missing Address Fields') - ) - - return frappe._dict(dict( - gstin='URP', - legal_name=sanitize_for_json(address_title), - location=city, - address_line1=sanitize_for_json(address_line1), - address_line2=sanitize_for_json(address_line2), - pincode=999999, state_code=96, place_of_supply=96 - )) - -def get_item_list(invoice): - item_list = [] - - for d in invoice.items: - einvoice_item_schema = read_json('einv_item_template') - item = frappe._dict({}) - item.update(d.as_dict()) - - item.sr_no = d.idx - item.description = sanitize_for_json(d.item_name) - - item.qty = abs(item.qty) - if flt(item.qty) != 0.0: - item.unit_rate = abs(item.taxable_value / item.qty) - else: - item.unit_rate = abs(item.taxable_value) - item.gross_amount = abs(item.taxable_value) - item.taxable_value = abs(item.taxable_value) - item.discount_amount = 0 - - item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None - item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None - item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N' - item.serial_no = "" - - item = update_item_taxes(invoice, item) - - item.total_value = abs( - item.taxable_value + item.igst_amount + item.sgst_amount + - item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges - ) - einv_item = einvoice_item_schema.format(item=item) - item_list.append(einv_item) - - return ', '.join(item_list) - -def update_item_taxes(invoice, item): - gst_accounts = get_gst_accounts(invoice.company) - gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] - - for attr in [ - 'tax_rate', 'cess_rate', 'cess_nadv_amount', - 'cgst_amount', 'sgst_amount', 'igst_amount', - 'cess_amount', 'cess_nadv_amount', 'other_charges' - ]: - item[attr] = 0 - - for t in invoice.taxes: - is_applicable = t.tax_amount and t.account_head in gst_accounts_list - if is_applicable: - # this contains item wise tax rate & tax amount (incl. discount) - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name) - - item_tax_rate = item_tax_detail[0] - # item tax amount excluding discount amount - item_tax_amount = (item_tax_rate / 100) * item.taxable_value - - if t.account_head in gst_accounts.cess_account: - item_tax_amount_after_discount = item_tax_detail[1] - if t.charge_type == 'On Item Quantity': - item.cess_nadv_amount += abs(item_tax_amount_after_discount) - else: - item.cess_rate += item_tax_rate - item.cess_amount += abs(item_tax_amount_after_discount) - - for tax_type in ['igst', 'cgst', 'sgst']: - if t.account_head in gst_accounts[f'{tax_type}_account']: - item.tax_rate += item_tax_rate - item[f'{tax_type}_amount'] += abs(item_tax_amount) - else: - # TODO: other charges per item - pass - - return item - -def get_invoice_value_details(invoice): - invoice_value_details = frappe._dict(dict()) - invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) - invoice_value_details.invoice_discount_amt = 0 - - invoice_value_details.round_off = invoice.base_rounding_adjustment - invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) - invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) - - invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) - - return invoice_value_details - -def update_invoice_taxes(invoice, invoice_value_details): - gst_accounts = get_gst_accounts(invoice.company) - gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] - - invoice_value_details.total_cgst_amt = 0 - invoice_value_details.total_sgst_amt = 0 - invoice_value_details.total_igst_amt = 0 - invoice_value_details.total_cess_amt = 0 - invoice_value_details.total_other_charges = 0 - considered_rows = [] - - for t in invoice.taxes: - tax_amount = t.base_tax_amount_after_discount_amount - if t.account_head in gst_accounts_list: - if t.account_head in gst_accounts.cess_account: - # using after discount amt since item also uses after discount amt for cess calc - invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) - - for tax_type in ['igst', 'cgst', 'sgst']: - if t.account_head in gst_accounts[f'{tax_type}_account']: - - invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount) - update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows) - else: - invoice_value_details.total_other_charges += abs(tax_amount) - - return invoice_value_details - -def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows): - prev_row_id = cint(tax_row.row_id) - 1 - if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows: - if tax_row.charge_type == 'On Previous Row Amount': - amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount - invoice_value_details.total_other_charges -= abs(amount) - considered_rows.append(prev_row_id) - if tax_row.charge_type == 'On Previous Row Total': - amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total - invoice_value_details.total_other_charges -= abs(amount) - considered_rows.append(prev_row_id) - -def get_payment_details(invoice): - payee_name = invoice.company - mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments]) - paid_amount = invoice.base_paid_amount - outstanding_amount = invoice.outstanding_amount - - return frappe._dict(dict( - payee_name=payee_name, mode_of_payment=mode_of_payment, - paid_amount=paid_amount, outstanding_amount=outstanding_amount - )) - -def get_return_doc_reference(invoice): - invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') - return frappe._dict(dict( - invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') - )) - -def get_eway_bill_details(invoice): - if invoice.is_return: - frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'), - title=_('Invalid Fields')) - - - mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } - vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } - - return frappe._dict(dict( - gstin=invoice.gst_transporter_id, - name=invoice.transporter_name, - mode_of_transport=mode_of_transport[invoice.mode_of_transport], - distance=invoice.distance or 0, - document_name=invoice.lr_no, - document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'), - vehicle_no=invoice.vehicle_no, - vehicle_type=vehicle_type[invoice.gst_vehicle_type] - )) - -def validate_mandatory_fields(invoice): - if not invoice.company_address: - frappe.throw( - _('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'), - title=_('Missing Fields') - ) - if not invoice.customer_address: - frappe.throw( - _('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'), - title=_('Missing Fields') - ) - if not frappe.db.get_value('Address', invoice.company_address, 'gstin'): - frappe.throw( - _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), - title=_('Missing Fields') - ) - if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): - frappe.throw( - _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'), - title=_('Missing Fields') - ) - -def validate_totals(einvoice): - item_list = einvoice['ItemList'] - value_details = einvoice['ValDtls'] - - total_item_ass_value = 0 - total_item_cgst_value = 0 - total_item_sgst_value = 0 - total_item_igst_value = 0 - total_item_value = 0 - for item in item_list: - total_item_ass_value += flt(item['AssAmt']) - total_item_cgst_value += flt(item['CgstAmt']) - total_item_sgst_value += flt(item['SgstAmt']) - total_item_igst_value += flt(item['IgstAmt']) - total_item_value += flt(item['TotItemVal']) - - if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1: - frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx)) - - if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: - frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) - - if abs( - flt(value_details['TotInvVal']) + flt(value_details['Discount']) - - flt(value_details['OthChrg']) - flt(value_details['RndOffAmt']) - - total_item_value) > 1: - frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) - - calculated_invoice_value = \ - flt(value_details['AssVal']) + flt(value_details['CgstVal']) \ - + flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \ - + flt(value_details['OthChrg']) + flt(value_details['RndOffAmt']) - flt(value_details['Discount']) - - if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1: - frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.')) - -def make_einvoice(invoice): - validate_mandatory_fields(invoice) - - schema = read_json('einv_template') - - transaction_details = get_transaction_details(invoice) - item_list = get_item_list(invoice) - doc_details = get_doc_details(invoice) - invoice_value_details = get_invoice_value_details(invoice) - seller_details = get_party_details(invoice.company_address) - - if invoice.gst_category == 'Overseas': - buyer_details = get_overseas_address_details(invoice.customer_address) - else: - buyer_details = get_party_details(invoice.customer_address) - place_of_supply = get_place_of_supply(invoice, invoice.doctype) - if place_of_supply: - place_of_supply = place_of_supply.split('-')[0] - else: - place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2] - buyer_details.update(dict(place_of_supply=place_of_supply)) - - seller_details.update(dict(legal_name=invoice.company)) - buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer)) - - shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) - if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: - if invoice.gst_category == 'Overseas': - shipping_details = get_overseas_address_details(invoice.shipping_address_name) - else: - shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True) - - if invoice.is_pos and invoice.base_paid_amount: - payment_details = get_payment_details(invoice) - - if invoice.is_return and invoice.return_against: - prev_doc_details = get_return_doc_reference(invoice) - - if invoice.transporter and not invoice.is_return: - eway_bill_details = get_eway_bill_details(invoice) - - # not yet implemented - dispatch_details = period_details = export_details = frappe._dict({}) - - einvoice = schema.format( - transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details, - seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, - item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details, - period_details=period_details, prev_doc_details=prev_doc_details, - export_details=export_details, eway_bill_details=eway_bill_details - ) - - try: - einvoice = safe_json_load(einvoice) - einvoice = santize_einvoice_fields(einvoice) - except Exception: - show_link_to_error_log(invoice, einvoice) - - validate_totals(einvoice) - - return einvoice - -def show_link_to_error_log(invoice, einvoice): - err_log = log_error(einvoice) - link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log') - frappe.throw( - _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format( - invoice.name, link_to_error_log), - title=_('E Invoice Creation Failed') - ) - -def log_error(data=None): - if isinstance(data, six.string_types): - data = json.loads(data) - - seperator = "--" * 50 - err_tb = traceback.format_exc() - err_msg = str(sys.exc_info()[1]) - data = json.dumps(data, indent=4) - - message = "\n".join([ - "Error", err_msg, seperator, - "Data:", data, seperator, - "Exception:", err_tb - ]) - frappe.log_error(title=_('E Invoice Request Failed'), message=message) - -def santize_einvoice_fields(einvoice): - int_fields = ["Pin","Distance","CrDay"] - float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",] - copy = einvoice.copy() - for key, value in copy.items(): - if isinstance(value, list): - for idx, d in enumerate(value): - santized_dict = santize_einvoice_fields(d) - if santized_dict: - einvoice[key][idx] = santized_dict - else: - einvoice[key].pop(idx) - - if not einvoice[key]: - einvoice.pop(key, None) - - elif isinstance(value, dict): - santized_dict = santize_einvoice_fields(value) - if santized_dict: - einvoice[key] = santized_dict - else: - einvoice.pop(key, None) - - elif not value or value == "None": - einvoice.pop(key, None) - - elif key in float_fields: - einvoice[key] = flt(value, 2) - - elif key in int_fields: - einvoice[key] = cint(value) - - return einvoice - -def safe_json_load(json_string): - try: - return json.loads(json_string) - except json.JSONDecodeError as e: - # print a snippet of 40 characters around the location where error occured - pos = e.pos - start, end = max(0, pos-20), min(len(json_string)-1, pos+20) - snippet = json_string[start:end] - frappe.throw(_("Error in input data. Please check for any special characters near following input:
      {}").format(snippet)) - -class RequestFailed(Exception): - pass -class CancellationNotAllowed(Exception): - pass - -class GSPConnector(): - def __init__(self, doctype=None, docname=None): - self.doctype = doctype - self.docname = docname - - self.set_invoice() - self.set_credentials() - - # authenticate url is same for sandbox & live - self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token' - self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test' - - self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' - self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' - self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice' - self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' - self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB' - self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' - - def set_invoice(self): - self.invoice = None - if self.doctype and self.docname: - self.invoice = frappe.get_cached_doc(self.doctype, self.docname) - - def set_credentials(self): - self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') - - if not self.e_invoice_settings.enable: - frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) - - if self.invoice: - gstin = self.get_seller_gstin() - credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin] - if credentials_for_gstin: - self.credentials = credentials_for_gstin[0] - else: - frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings')) - else: - self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None - - def get_seller_gstin(self): - gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin') - if not gstin: - frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.')) - return gstin - - def get_auth_token(self): - if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0: - self.fetch_auth_token() - - return self.e_invoice_settings.auth_token - - def make_request(self, request_type, url, headers=None, data=None): - if request_type == 'post': - res = make_post_request(url, headers=headers, data=data) - else: - res = make_get_request(url, headers=headers, data=data) - - self.log_request(url, headers, data, res) - return res - - def log_request(self, url, headers, data, res): - headers.update({ 'password': self.credentials.password }) - request_log = frappe.get_doc({ - "doctype": "E Invoice Request Log", - "user": frappe.session.user, - "reference_invoice": self.invoice.name if self.invoice else None, - "url": url, - "headers": json.dumps(headers, indent=4) if headers else None, - "data": json.dumps(data, indent=4) if isinstance(data, dict) else data, - "response": json.dumps(res, indent=4) if res else None - }) - request_log.save(ignore_permissions=True) - frappe.db.commit() - - def fetch_auth_token(self): - headers = { - 'gspappid': frappe.conf.einvoice_client_id, - 'gspappsecret': frappe.conf.einvoice_client_secret - } - res = {} - try: - res = self.make_request('post', self.authenticate_url, headers) - self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token')) - self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in')) - self.e_invoice_settings.save(ignore_permissions=True) - self.e_invoice_settings.reload() - - except Exception: - log_error(res) - self.raise_error(True) - - def get_headers(self): - return { - 'content-type': 'application/json', - 'user_name': self.credentials.username, - 'password': self.credentials.get_password(), - 'gstin': self.credentials.gstin, - 'authorization': self.get_auth_token(), - 'requestid': str(base64.b64encode(os.urandom(18))), - } - - def fetch_gstin_details(self, gstin): - headers = self.get_headers() - - try: - params = '?gstin={gstin}'.format(gstin=gstin) - res = self.make_request('get', self.gstin_details_url + params, headers) - if res.get('success'): - return res.get('result') - else: - log_error(res) - raise RequestFailed - - except RequestFailed: - self.raise_error() - - except Exception: - log_error() - self.raise_error(True) - @staticmethod - def get_gstin_details(gstin): - '''fetch and cache GSTIN details''' - if not hasattr(frappe.local, 'gstin_cache'): - frappe.local.gstin_cache = {} - - key = gstin - gsp_connector = GSPConnector() - details = gsp_connector.fetch_gstin_details(gstin) - - frappe.local.gstin_cache[key] = details - frappe.cache().hset('gstin_cache', key, details) - return details - - def generate_irn(self): - data = {} - try: - headers = self.get_headers() - einvoice = make_einvoice(self.invoice) - data = json.dumps(einvoice, indent=4) - res = self.make_request('post', self.generate_irn_url, headers, data) - - if res.get('success'): - self.set_einvoice_data(res.get('result')) - - elif '2150' in res.get('message'): - # IRN already generated but not updated in invoice - # Extract the IRN from the response description and fetch irn details - irn = res.get('result')[0].get('Desc').get('Irn') - irn_details = self.get_irn_details(irn) - if irn_details: - self.set_einvoice_data(irn_details) - else: - raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \ - Contact ERPNext support to resolve the issue.') - - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.set_failed_status(errors=errors) - self.raise_error(errors=errors) - - except Exception as e: - self.set_failed_status(errors=str(e)) - log_error(data) - self.raise_error(True) - - @staticmethod - def bulk_generate_irn(invoices): - gsp_connector = GSPConnector() - gsp_connector.doctype = 'Sales Invoice' - - failed = [] - - for invoice in invoices: - try: - gsp_connector.docname = invoice - gsp_connector.set_invoice() - gsp_connector.set_credentials() - gsp_connector.generate_irn() - - except Exception as e: - failed.append({ - 'docname': invoice, - 'message': str(e) - }) - - return failed - - def get_irn_details(self, irn): - headers = self.get_headers() - - try: - params = '?irn={irn}'.format(irn=irn) - res = self.make_request('get', self.irn_details_url + params, headers) - if res.get('success'): - return res.get('result') - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.raise_error(errors=errors) - - except Exception: - log_error() - self.raise_error(True) - - def cancel_irn(self, irn, reason, remark): - data, res = {}, {} - try: - # validate cancellation - if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24: - frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed) - if not irn: - frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed) - - headers = self.get_headers() - data = json.dumps({ - 'Irn': irn, - 'Cnlrsn': reason, - 'Cnlrem': remark - }, indent=4) - - res = self.make_request('post', self.cancel_irn_url, headers, data) - if res.get('success') or '9999' in res.get('message'): - self.invoice.irn_cancelled = 1 - self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else "" - self.invoice.einvoice_status = 'Cancelled' - self.invoice.flags.updater_reference = { - 'doctype': self.invoice.doctype, - 'docname': self.invoice.name, - 'label': _('IRN Cancelled - {}').format(remark) - } - self.update_invoice() - - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.set_failed_status(errors=errors) - self.raise_error(errors=errors) - - except CancellationNotAllowed as e: - self.set_failed_status(errors=str(e)) - self.raise_error(errors=str(e)) - - except Exception as e: - self.set_failed_status(errors=str(e)) - log_error(data) - self.raise_error(True) - - @staticmethod - def bulk_cancel_irn(invoices, reason, remark): - gsp_connector = GSPConnector() - gsp_connector.doctype = 'Sales Invoice' - - failed = [] - - for invoice in invoices: - try: - gsp_connector.docname = invoice - gsp_connector.set_invoice() - gsp_connector.set_credentials() - irn = gsp_connector.invoice.irn - gsp_connector.cancel_irn(irn, reason, remark) - - except Exception as e: - failed.append({ - 'docname': invoice, - 'message': str(e) - }) - - return failed - - def generate_eway_bill(self, **kwargs): - args = frappe._dict(kwargs) - - headers = self.get_headers() - eway_bill_details = get_eway_bill_details(args) - data = json.dumps({ - 'Irn': args.irn, - 'Distance': cint(eway_bill_details.distance), - 'TransMode': eway_bill_details.mode_of_transport, - 'TransId': eway_bill_details.gstin, - 'TransName': eway_bill_details.transporter, - 'TrnDocDt': eway_bill_details.document_date, - 'TrnDocNo': eway_bill_details.document_name, - 'VehNo': eway_bill_details.vehicle_no, - 'VehType': eway_bill_details.vehicle_type - }, indent=4) - - try: - res = self.make_request('post', self.generate_ewaybill_url, headers, data) - if res.get('success'): - self.invoice.ewaybill = res.get('result').get('EwbNo') - self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill') - self.invoice.eway_bill_cancelled = 0 - self.invoice.update(args) - self.invoice.flags.updater_reference = { - 'doctype': self.invoice.doctype, - 'docname': self.invoice.name, - 'label': _('E-Way Bill Generated') - } - self.update_invoice() - - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.raise_error(errors=errors) - - except Exception: - log_error(data) - self.raise_error(True) - - def cancel_eway_bill(self, eway_bill, reason, remark): - headers = self.get_headers() - data = json.dumps({ - 'ewbNo': eway_bill, - 'cancelRsnCode': reason, - 'cancelRmrk': remark - }, indent=4) - headers["username"] = headers["user_name"] - del headers["user_name"] - try: - res = self.make_request('post', self.cancel_ewaybill_url, headers, data) - if res.get('success'): - self.invoice.ewaybill = '' - self.invoice.eway_bill_cancelled = 1 - self.invoice.flags.updater_reference = { - 'doctype': self.invoice.doctype, - 'docname': self.invoice.name, - 'label': _('E-Way Bill Cancelled - {}').format(remark) - } - self.update_invoice() - - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.raise_error(errors=errors) - - except Exception: - log_error(data) - self.raise_error(True) - - def sanitize_error_message(self, message): - ''' - On validation errors, response message looks something like this: - message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, - 3095 : Supplier GSTIN is inactive' - we search for string between ':' to extract the error messages - errors = [ - ': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ', - ': Test' - ] - then we trim down the message by looping over errors - ''' - if not message: - return [] - - errors = re.findall(': [^:]+', message) - for idx, e in enumerate(errors): - # remove colons - errors[idx] = errors[idx].replace(':', '').strip() - # if not last - if idx != len(errors) - 1: - # remove last 7 chars eg: ', 3095 ' - errors[idx] = errors[idx][:-6] - - return errors - - def raise_error(self, raise_exception=False, errors=[]): - title = _('E Invoice Request Failed') - if errors: - frappe.throw(errors, title=title, as_list=1) - else: - link_to_error_list = 'Error Log' - frappe.msgprint( - _('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list), - title=title, - raise_exception=raise_exception, - indicator='red' - ) - - def set_einvoice_data(self, res): - enc_signed_invoice = res.get('SignedInvoice') - dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data'] - - self.invoice.irn = res.get('Irn') - self.invoice.ewaybill = res.get('EwbNo') - self.invoice.eway_bill_validity = res.get('EwbValidTill') - self.invoice.ack_no = res.get('AckNo') - self.invoice.ack_date = res.get('AckDt') - self.invoice.signed_einvoice = dec_signed_invoice - self.invoice.ack_no = res.get('AckNo') - self.invoice.ack_date = res.get('AckDt') - self.invoice.signed_qr_code = res.get('SignedQRCode') - self.invoice.einvoice_status = 'Generated' - - self.attach_qrcode_image() - - self.invoice.flags.updater_reference = { - 'doctype': self.invoice.doctype, - 'docname': self.invoice.name, - 'label': _('IRN Generated') - } - self.update_invoice() - - def attach_qrcode_image(self): - qrcode = self.invoice.signed_qr_code - doctype = self.invoice.doctype - docname = self.invoice.name - filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__") - - qr_image = io.BytesIO() - url = qrcreate(qrcode, error='L') - url.png(qr_image, scale=2, quiet_zone=1) - _file = frappe.get_doc({ - "doctype": "File", - "file_name": filename, - "attached_to_doctype": doctype, - "attached_to_name": docname, - "attached_to_field": "qrcode_image", - "is_private": 0, - "content": qr_image.getvalue()}) - _file.save() - frappe.db.commit() - self.invoice.qrcode_image = _file.file_url - - def update_invoice(self): - self.invoice.flags.ignore_validate_update_after_submit = True - self.invoice.flags.ignore_validate = True - self.invoice.save() - - def set_failed_status(self, errors=None): - frappe.db.rollback() - self.invoice.einvoice_status = 'Failed' - self.invoice.failure_description = self.get_failure_message(errors) if errors else "" - self.update_invoice() - frappe.db.commit() - - def get_failure_message(self, errors): - if isinstance(errors, list): - errors = ', '.join(errors) - return errors - -def sanitize_for_json(string): - """Escape JSON specific characters from a string.""" - - # json.dumps adds double-quotes to the string. Indexing to remove them. - return json.dumps(string)[1:-1] - -@frappe.whitelist() -def get_einvoice(doctype, docname): - invoice = frappe.get_doc(doctype, docname) - return make_einvoice(invoice) - -@frappe.whitelist() -def generate_irn(doctype, docname): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.generate_irn() - -@frappe.whitelist() -def cancel_irn(doctype, docname, irn, reason, remark): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.cancel_irn(irn, reason, remark) - -@frappe.whitelist() -def generate_eway_bill(doctype, docname, **kwargs): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.generate_eway_bill(**kwargs) - -@frappe.whitelist() -def cancel_eway_bill(doctype, docname): - # TODO: uncomment when eway_bill api from Adequare is enabled - # gsp_connector = GSPConnector(doctype, docname) - # gsp_connector.cancel_eway_bill(eway_bill, reason, remark) - - frappe.db.set_value(doctype, docname, 'ewaybill', '') - frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) - -@frappe.whitelist() -def generate_einvoices(docnames): - docnames = json.loads(docnames) or [] - - if len(docnames) < 10: - failures = GSPConnector.bulk_generate_irn(docnames) - frappe.local.message_log = [] - - if failures: - show_bulk_action_failure_message(failures) - - success = len(docnames) - len(failures) - frappe.msgprint( - _('{} e-invoices generated successfully').format(success), - title=_('Bulk E-Invoice Generation Complete') - ) - - else: - enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames) - -def schedule_bulk_generate_irn(docnames): - failures = GSPConnector.bulk_generate_irn(docnames) - frappe.local.message_log = [] - - frappe.publish_realtime("bulk_einvoice_generation_complete", { - "user": frappe.session.user, - "failures": failures, - "invoices": docnames - }) - -def show_bulk_action_failure_message(failures): - for doc in failures: - docname = '{0}'.format(doc.get('docname')) - message = doc.get('message').replace("'", '"') - if message[0] == '[': - errors = json.loads(message) - error_list = ''.join(['
    • {}
    • '.format(err) for err in errors]) - message = '''{} has following errors:
      -
        {}
      '''.format(docname, error_list) - else: - message = '{} - {}'.format(docname, message) - - frappe.msgprint( - message, - title=_('Bulk E-Invoice Generation Complete'), - indicator='red' - ) - -@frappe.whitelist() -def cancel_irns(docnames, reason, remark): - docnames = json.loads(docnames) or [] - - if len(docnames) < 10: - failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) - frappe.local.message_log = [] - - if failures: - show_bulk_action_failure_message(failures) - - success = len(docnames) - len(failures) - frappe.msgprint( - _('{} e-invoices cancelled successfully').format(success), - title=_('Bulk E-Invoice Cancellation Complete') - ) - else: - enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark) - -def schedule_bulk_cancel_irn(docnames, reason, remark): - failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) - frappe.local.message_log = [] - - frappe.publish_realtime("bulk_einvoice_cancellation_complete", { - "user": frappe.session.user, - "failures": failures, - "invoices": docnames - }) - -def enqueue_bulk_action(job, **kwargs): - check_scheduler_status() - - enqueue( - job, - **kwargs, - queue="long", - timeout=10000, - event="processing_bulk_einvoice_action", - now=frappe.conf.developer_mode or frappe.flags.in_test, - ) - - if job == schedule_bulk_generate_irn: - msg = _('E-Invoices will be generated in a background process.') - else: - msg = _('E-Invoices will be cancelled in a background process.') - - frappe.msgprint(msg, alert=1) - -def check_scheduler_status(): - if is_scheduler_inactive() and not frappe.flags.in_test: - frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive")) - -def job_already_enqueued(job_name): - enqueued_jobs = [d.get("job_name") for d in get_info()] - if job_name in enqueued_jobs: - return True diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 4db5551cb30..58654240285 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields @@ -13,7 +12,7 @@ from frappe.utils import today def setup(company=None, patch=True): # Company independent fixtures should be called only once at the first company setup - if frappe.db.count('Company', {'country': 'India'}) <=1: + if patch or frappe.db.count('Company', {'country': 'India'}) <=1: setup_company_independent_fixtures(patch=patch) if not patch: @@ -132,6 +131,10 @@ def make_property_setters(patch=False): make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') def make_custom_fields(update=True): + custom_fields = get_custom_fields() + create_custom_fields(custom_fields, update=update) + +def get_custom_fields(): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description', allow_on_submit=1, print_hide=1, fetch_if_empty=1) @@ -165,12 +168,12 @@ def make_custom_fields(update=True): dict(fieldname='gst_category', label='GST Category', fieldtype='Select', insert_after='gst_section', print_hide=1, options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', - fetch_from='customer.gst_category', fetch_if_empty=1), + fetch_from='customer.gst_category', fetch_if_empty=1, length=25), dict(fieldname='export_type', label='Export Type', fieldtype='Select', insert_after='gst_category', print_hide=1, depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', options='\nWith Payment of Tax\nWithout Payment of Tax', fetch_from='customer.export_type', - fetch_if_empty=1), + fetch_if_empty=1, length=25), ] delivery_note_gst_category = [ @@ -181,18 +184,18 @@ def make_custom_fields(update=True): ] invoice_gst_fields = [ - dict(fieldname='invoice_copy', label='Invoice Copy', + dict(fieldname='invoice_copy', label='Invoice Copy', length=30, fieldtype='Select', insert_after='export_type', print_hide=1, allow_on_submit=1, options='Original for Recipient\nDuplicate for Transporter\nDuplicate for Supplier\nTriplicate for Supplier'), - dict(fieldname='reverse_charge', label='Reverse Charge', + dict(fieldname='reverse_charge', label='Reverse Charge', length=2, fieldtype='Select', insert_after='invoice_copy', print_hide=1, options='Y\nN', default='N'), - dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN', + dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN', length=15, fieldtype='Data', insert_after='export_type', print_hide=1), dict(fieldname='gst_col_break', fieldtype='Column Break', insert_after='ecommerce_gstin'), dict(fieldname='reason_for_issuing_document', label='Reason For Issuing document', fieldtype='Select', insert_after='gst_col_break', print_hide=1, - depends_on='eval:doc.is_return==1', + depends_on='eval:doc.is_return==1', length=45, 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') ] @@ -230,25 +233,25 @@ def make_custom_fields(update=True): sales_invoice_gst_fields = [ dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', fieldtype='Data', insert_after='customer_address', read_only=1, - fetch_from='customer_address.gstin', print_hide=1), + fetch_from='customer_address.gstin', print_hide=1, length=15), dict(fieldname='customer_gstin', label='Customer GSTIN', fieldtype='Data', insert_after='shipping_address_name', - fetch_from='shipping_address_name.gstin', print_hide=1), + fetch_from='shipping_address_name.gstin', print_hide=1, length=15), dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='customer_gstin', - print_hide=1, read_only=1), + print_hide=1, read_only=1, length=50), dict(fieldname='company_gstin', label='Company GSTIN', fieldtype='Data', insert_after='company_address', - fetch_from='company_address.gstin', print_hide=1, read_only=1), + fetch_from='company_address.gstin', print_hide=1, read_only=1, length=15), ] sales_invoice_shipping_fields = [ dict(fieldname='port_code', label='Port Code', fieldtype='Data', insert_after='reason_for_issuing_document', print_hide=1, - depends_on="eval:doc.gst_category=='Overseas' "), + depends_on="eval:doc.gst_category=='Overseas' ", length=15), dict(fieldname='shipping_bill_number', label=' Shipping Bill Number', fieldtype='Data', insert_after='port_code', print_hide=1, - depends_on="eval:doc.gst_category=='Overseas' "), + depends_on="eval:doc.gst_category=='Overseas' ", length=50), dict(fieldname='shipping_bill_date', label='Shipping Bill Date', fieldtype='Date', insert_after='shipping_bill_number', print_hide=1, depends_on="eval:doc.gst_category=='Overseas' "), @@ -354,7 +357,8 @@ def make_custom_fields(update=True): 'insert_after': 'transporter', 'fetch_from': 'transporter.gst_transporter_id', 'print_hide': 1, - 'translatable': 0 + 'translatable': 0, + 'length': 20 }, { 'fieldname': 'driver', @@ -370,7 +374,8 @@ def make_custom_fields(update=True): 'fieldtype': 'Data', 'insert_after': 'driver', 'print_hide': 1, - 'translatable': 0 + 'translatable': 0, + 'length': 30 }, { 'fieldname': 'vehicle_no', @@ -378,7 +383,8 @@ def make_custom_fields(update=True): 'fieldtype': 'Data', 'insert_after': 'lr_no', 'print_hide': 1, - 'translatable': 0 + 'translatable': 0, + 'length': 10 }, { 'fieldname': 'distance', @@ -395,7 +401,7 @@ def make_custom_fields(update=True): { 'fieldname': 'transporter_name', 'label': 'Transporter Name', - 'fieldtype': 'Data', + 'fieldtype': 'Small Text', 'insert_after': 'transporter_col_break', 'fetch_from': 'transporter.name', 'read_only': 1, @@ -409,12 +415,13 @@ def make_custom_fields(update=True): 'options': '\nRoad\nAir\nRail\nShip', 'insert_after': 'transporter_name', 'print_hide': 1, - 'translatable': 0 + 'translatable': 0, + 'length': 5 }, { 'fieldname': 'driver_name', 'label': 'Driver Name', - 'fieldtype': 'Data', + 'fieldtype': 'Small Text', 'insert_after': 'mode_of_transport', 'fetch_from': 'driver.full_name', 'print_hide': 1, @@ -437,7 +444,8 @@ def make_custom_fields(update=True): 'default': 'Regular', 'insert_after': 'lr_date', 'print_hide': 1, - 'translatable': 0 + 'translatable': 0, + 'length': 30 }, { 'fieldname': 'ewaybill', @@ -446,10 +454,31 @@ def make_custom_fields(update=True): 'depends_on': 'eval:(doc.docstatus === 1)', 'allow_on_submit': 1, 'insert_after': 'tax_id', - 'translatable': 0 + 'translatable': 0, + 'length': 20 } ] + payment_entry_fields = [ + dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions', + print_hide=1, collapsible=1), + dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section', + print_hide=1, options='Address'), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + fetch_from='company_address.gstin', print_hide=1, read_only=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='company_gstin', + print_hide=1, read_only=1), + dict(fieldname='gst_column_break', fieldtype='Column Break', + insert_after='place_of_supply'), + dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='gst_column_break', + print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'), + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='customer_address', + fetch_from='customer_address.gstin', print_hide=1, read_only=1) + ] + custom_fields = { 'Address': [ dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data', @@ -463,7 +492,9 @@ def make_custom_fields(update=True): 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, + 'POS Invoice': sales_invoice_gst_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, + 'Payment Entry': payment_entry_fields, 'Journal Entry': journal_entry_fields, 'Sales Order': sales_invoice_gst_fields, 'Tax Category': inter_state_gst_field, @@ -480,6 +511,7 @@ def make_custom_fields(update=True): 'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], + 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], @@ -581,11 +613,17 @@ def make_custom_fields(update=True): fieldtype='Currency', insert_after='monthly_hra_exemption', read_only=1, depends_on='house_rent_payment_amount') ], 'Supplier': [ + { + 'fieldname': 'pan', + 'label': 'PAN', + 'fieldtype': 'Data', + 'insert_after': 'supplier_type' + }, { 'fieldname': 'gst_transporter_id', 'label': 'GST Transporter ID', 'fieldtype': 'Data', - 'insert_after': 'supplier_type', + 'insert_after': 'pan', 'depends_on': 'eval:doc.is_transporter' }, { @@ -607,11 +645,17 @@ def make_custom_fields(update=True): } ], 'Customer': [ + { + 'fieldname': 'pan', + 'label': 'PAN', + 'fieldtype': 'Data', + 'insert_after': 'customer_type' + }, { 'fieldname': 'gst_category', 'label': 'GST Category', 'fieldtype': 'Select', - 'insert_after': 'customer_type', + 'insert_after': 'pan', 'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', 'default': 'Unregistered' }, @@ -640,9 +684,19 @@ def make_custom_fields(update=True): 'fieldtype': 'Data', 'insert_after': 'email' } + ], + 'Finance Book': [ + { + 'fieldname': 'for_income_tax', + 'label': 'For Income Tax', + 'fieldtype': 'Check', + 'insert_after': 'finance_book_name', + 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.' + } ] } - create_custom_fields(custom_fields, update=update) + + return custom_fields def make_fixtures(company=None): docs = [] @@ -729,7 +783,7 @@ def set_salary_components(docs): def set_tax_withholding_category(company): accounts = [] - fiscal_year = None + fiscal_year_details = None abbr = frappe.get_value("Company", company, "abbr") tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name') @@ -737,11 +791,11 @@ def set_tax_withholding_category(company): accounts = [dict(company=company, account=tds_account)] try: - fiscal_year = get_fiscal_year(today(), verbose=0, company=company)[0] + fiscal_year_details = get_fiscal_year(today(), verbose=0) except FiscalYearError: pass - docs = get_tds_details(accounts, fiscal_year) + docs = get_tds_details(accounts, fiscal_year_details) for d in docs: if not frappe.db.exists("Tax Withholding Category", d.get("name")): @@ -756,9 +810,10 @@ def set_tax_withholding_category(company): if accounts: doc.append("accounts", accounts[0]) - if fiscal_year: + if fiscal_year_details: # if fiscal year don't match with any of the already entered data, append rate row - fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year] + fy_exist = [k for k in doc.get('rates') if k.get('from_date') <= fiscal_year_details[1] \ + and k.get('to_date') >= fiscal_year_details[2]] if not fy_exist: doc.append("rates", d.get('rates')[0]) @@ -781,149 +836,149 @@ def set_tds_account(docs, company): } ]) -def get_tds_details(accounts, fiscal_year): +def get_tds_details(accounts, fiscal_year_details): # bootstrap default tax withholding sections return [ dict(name="TDS - 194C - Company", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194C - Individual", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194C - No PAN / Invalid PAN", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194D - Company", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - Company Assessee", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - Individual", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - No PAN / Invalid PAN", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - Company", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - Individual", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - No PAN / Invalid PAN", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - Company", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - Individual", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - No PAN / Invalid PAN", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - Company", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - Individual", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - No PAN / Invalid PAN", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - Company", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - Individual", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - No PAN / Invalid PAN", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - Company", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - Individual", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - No PAN / Invalid PAN", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - Company", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - Individual", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - No PAN / Invalid PAN", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - Company", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 2500, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 2500, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - Individual", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 2500, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 2500, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - No PAN / Invalid PAN", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 2500, "cumulative_threshold": 0}]) + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 2500, "cumulative_threshold": 0}]) ] def create_gratuity_rule(): diff --git a/erpnext/regional/india/test_utils.py b/erpnext/regional/india/test_utils.py index a16f56c704a..c95a0b3cc65 100644 --- a/erpnext/regional/india/test_utils.py +++ b/erpnext/regional/india/test_utils.py @@ -1,8 +1,8 @@ -from __future__ import unicode_literals - import unittest -import frappe from unittest.mock import patch + +import frappe + from erpnext.regional.india.utils import validate_document_name diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index ce5aa10902e..fd3ec3c08ce 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -1,19 +1,16 @@ -from __future__ import unicode_literals -import frappe, re, json +import json +import re + +import frappe from frappe import _ -import erpnext -from frappe.utils import cstr, flt, cint, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate -from erpnext.regional.india import states, state_numbers -from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount +from frappe.model.utils import get_fetch_values +from frappe.utils import cint, cstr, date_diff, flt, getdate, nowdate + from erpnext.controllers.accounts_controller import get_taxes_and_charges +from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount from erpnext.hr.utils import get_salary_assignment from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip -from erpnext.regional.india import number_state_mapping -from six import string_types -from erpnext.accounts.general_ledger import make_gl_entries -from erpnext.accounts.utils import get_account_currency -from frappe.model.utils import get_fetch_values - +from erpnext.regional.india import number_state_mapping, state_numbers, states GST_INVOICE_NUMBER_FORMAT = re.compile(r"^[a-zA-Z0-9\-/]+$") #alphanumeric and - / GSTIN_FORMAT = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") @@ -29,12 +26,13 @@ def validate_gstin_for_india(doc, method): gst_category = [] - if len(doc.links): - link_doctype = doc.links[0].get("link_doctype") - link_name = doc.links[0].get("link_name") + if hasattr(doc, 'gst_category'): + if len(doc.links): + link_doctype = doc.links[0].get("link_doctype") + link_name = doc.links[0].get("link_name") - if link_doctype in ["Customer", "Supplier"]: - gst_category = frappe.db.get_value(link_doctype, {'name': link_name}, ['gst_category']) + if link_doctype in ["Customer", "Supplier"]: + gst_category = frappe.db.get_value(link_doctype, {'name': link_name}, ['gst_category']) doc.gstin = doc.gstin.upper().strip() if not doc.gstin or doc.gstin == 'NA': @@ -62,7 +60,7 @@ def validate_gstin_for_india(doc, method): .format(doc.gst_state_number), title=_("Invalid GSTIN")) def validate_pan_for_india(doc, method): - if doc.get('country') != 'India' or not doc.pan: + if doc.get('country') != 'India' or not doc.get('pan'): return if not PAN_NUMBER_FORMAT.match(doc.pan): @@ -78,10 +76,9 @@ def validate_tax_category(doc, method): def update_gst_category(doc, method): for link in doc.links: if link.link_doctype in ['Customer', 'Supplier']: - if doc.get('gstin'): - frappe.db.sql(""" - UPDATE `tab{0}` SET gst_category = %s WHERE name = %s AND gst_category = 'Unregistered' - """.format(link.link_doctype), ("Registered Regular", link.link_name)) #nosec + meta = frappe.get_meta(link.link_doctype) + if doc.get('gstin') and meta.has_field('gst_category'): + frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular') def set_gst_state_and_state_number(doc): if not doc.gst_state: @@ -112,12 +109,13 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): - if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): + hsn_wise_in_gst_settings = frappe.db.get_single_value('GST Settings','hsn_wise_tax_breakup') + if frappe.get_meta(item_doctype).has_field('gst_hsn_code') and hsn_wise_in_gst_settings: return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts else: return [_("Item"), _("Taxable Amount")] + tax_accounts -def get_itemised_tax_breakup_data(doc, account_wise=False): +def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) itemised_taxable_amount = get_itemised_taxable_amount(doc.items) @@ -125,28 +123,32 @@ def get_itemised_tax_breakup_data(doc, account_wise=False): if not frappe.get_meta(doc.doctype + " Item").has_field('gst_hsn_code'): return itemised_tax, itemised_taxable_amount - item_hsn_map = frappe._dict() - for d in doc.items: - item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) + hsn_wise_in_gst_settings = frappe.db.get_single_value('GST Settings','hsn_wise_tax_breakup') + + tax_breakup_hsn_wise = hsn_wise or hsn_wise_in_gst_settings + if tax_breakup_hsn_wise: + item_hsn_map = frappe._dict() + for d in doc.items: + item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) hsn_tax = {} for item, taxes in itemised_tax.items(): - hsn_code = item_hsn_map.get(item) - hsn_tax.setdefault(hsn_code, frappe._dict()) + item_or_hsn = item if not tax_breakup_hsn_wise else item_hsn_map.get(item) + hsn_tax.setdefault(item_or_hsn, frappe._dict()) for tax_desc, tax_detail in taxes.items(): key = tax_desc if account_wise: key = tax_detail.get('tax_account') - hsn_tax[hsn_code].setdefault(key, {"tax_rate": 0, "tax_amount": 0}) - hsn_tax[hsn_code][key]["tax_rate"] = tax_detail.get("tax_rate") - hsn_tax[hsn_code][key]["tax_amount"] += tax_detail.get("tax_amount") + hsn_tax[item_or_hsn].setdefault(key, {"tax_rate": 0, "tax_amount": 0}) + hsn_tax[item_or_hsn][key]["tax_rate"] = tax_detail.get("tax_rate") + hsn_tax[item_or_hsn][key]["tax_amount"] += tax_detail.get("tax_amount") # set taxable amount hsn_taxable_amount = frappe._dict() for item in itemised_taxable_amount: - hsn_code = item_hsn_map.get(item) - hsn_taxable_amount.setdefault(hsn_code, 0) - hsn_taxable_amount[hsn_code] += itemised_taxable_amount.get(item) + item_or_hsn = item if not tax_breakup_hsn_wise else item_hsn_map.get(item) + hsn_taxable_amount.setdefault(item_or_hsn, 0) + hsn_taxable_amount[item_or_hsn] += itemised_taxable_amount.get(item) return hsn_tax, hsn_taxable_amount @@ -189,7 +191,7 @@ def get_place_of_supply(party_details, doctype): @frappe.whitelist() def get_regional_address_details(party_details, doctype, company): - if isinstance(party_details, string_types): + if isinstance(party_details, str): party_details = json.loads(party_details) party_details = frappe._dict(party_details) @@ -204,26 +206,17 @@ def get_regional_address_details(party_details, doctype, company): if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" - get_tax_template_based_on_category(master_doctype, company, party_details) - - if party_details.get('taxes_and_charges'): - return party_details - - if not party_details.company_gstin: - return party_details + tax_template_by_category = get_tax_template_based_on_category(master_doctype, company, party_details) elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" - get_tax_template_based_on_category(master_doctype, company, party_details) + tax_template_by_category = get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges'): - return party_details - - if not party_details.supplier_gstin: - return party_details + if tax_template_by_category: + party_details['taxes_and_charges'] = tax_template_by_category + return if not party_details.place_of_supply: return party_details - if not party_details.company_gstin: return party_details if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin @@ -235,6 +228,7 @@ def get_regional_address_details(party_details, doctype, company): if not default_tax: return party_details + party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) @@ -251,6 +245,9 @@ def is_internal_transfer(party_details, doctype): elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): destination_gstin = party_details.supplier_gstin + if not destination_gstin or party_details.gstin: + return False + if party_details.gstin == destination_gstin: return True else: @@ -263,9 +260,7 @@ def get_tax_template_based_on_category(master_doctype, company, party_details): default_tax = frappe.db.get_value(master_doctype, {'company': company, 'tax_category': party_details.get('tax_category')}, 'name') - if default_tax: - party_details["taxes_and_charges"] = default_tax - party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) + return default_tax def get_tax_template(master_doctype, company, is_inter_state, state_code): tax_categories = frappe.get_all('Tax Category', fields = ['name', 'is_inter_state', 'gst_state'], @@ -438,9 +433,9 @@ def get_ewb_data(dt, dn): data = get_address_details(data, doc, company_address, billing_address, dispatch_address) data.itemList = [] - data.totalValue = doc.total + data.totalValue = doc.net_total - data = get_item_list(data, doc) + data = get_item_list(data, doc, hsn_wise=True) disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') data.totInvValue = doc.grand_total if disable_rounded else doc.rounded_total @@ -551,7 +546,7 @@ def get_address_details(data, doc, company_address, billing_address, dispatch_ad return data -def get_item_list(data, doc): +def get_item_list(data, doc, hsn_wise=False): for attr in ['cgstValue', 'sgstValue', 'igstValue', 'cessValue', 'OthValue']: data[attr] = 0 @@ -563,18 +558,18 @@ def get_item_list(data, doc): 'cess_account': ['cessRate', 'cessValue'] } item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol'] - hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True) - for hsn_code, taxable_amount in hsn_taxable_amount.items(): + hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise) + for item_or_hsn, taxable_amount in hsn_taxable_amount.items(): item_data = frappe._dict() - if not hsn_code: + if not item_or_hsn: frappe.throw(_('GST HSN Code does not exist for one or more items')) - item_data.hsnCode = int(hsn_code) + item_data.hsnCode = int(item_or_hsn) if hsn_wise else item_or_hsn item_data.taxableAmount = taxable_amount item_data.qtyUnit = "" for attr in item_data_attrs: item_data[attr] = 0 - for account, tax_detail in hsn_wise_charges.get(hsn_code, {}).items(): + for account, tax_detail in hsn_wise_charges.get(item_or_hsn, {}).items(): account_type = gst_accounts.get(account, '') for tax_acc, attrs in tax_map.items(): if account_type == tax_acc: @@ -767,6 +762,15 @@ def update_itc_availed_fields(doc, method): if tax.account_head in gst_accounts.get('cess_account', []): doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount) +def update_place_of_supply(doc, method): + country = frappe.get_cached_value('Company', doc.company, 'country') + if country != 'India': + return + + address = frappe.db.get_value("Address", doc.get('customer_address'), ["gst_state", "gst_state_number"], as_dict=1) + if address and address.gst_state and address.gst_state_number: + doc.place_of_supply = cstr(address.gst_state_number) + "-" + cstr(address.gst_state) + @frappe.whitelist() def get_regional_round_off_accounts(company, account_list): country = frappe.get_cached_value('Company', company, 'country') @@ -774,7 +778,7 @@ def get_regional_round_off_accounts(company, account_list): if country != 'India': return - if isinstance(account_list, string_types): + if isinstance(account_list, str): account_list = json.loads(account_list) if not frappe.db.get_single_value('GST Settings', 'round_off_gst_values'): @@ -833,13 +837,11 @@ def update_taxable_values(doc, method): doc.get('items')[item_count - 1].taxable_value += diff def get_depreciation_amount(asset, depreciable_value, row): - depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) - if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time if not asset.flags.increase_in_asset_life: - depreciation_amount = (flt(row.value_after_depreciation) - - flt(row.expected_value_after_useful_life)) / depreciation_left + depreciation_amount = (flt(asset.gross_purchase_amount) - + flt(row.expected_value_after_useful_life)) / flt(row.total_number_of_depreciations) # if the Depreciation Schedule is being modified after Asset Repair else: @@ -850,12 +852,13 @@ def get_depreciation_amount(asset, depreciable_value, row): rate_of_depreciation = row.rate_of_depreciation # if its the first depreciation if depreciable_value == asset.gross_purchase_amount: - # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 - diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) - if diff <= 180: - rate_of_depreciation = rate_of_depreciation / 2 - frappe.msgprint( - _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) + if row.finance_book and frappe.db.get_value('Finance Book', row.finance_book, 'for_income_tax'): + # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 + diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) + if diff <= 180: + rate_of_depreciation = rate_of_depreciation / 2 + frappe.msgprint( + _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) @@ -887,4 +890,3 @@ def delete_gst_settings_for_company(doc, method): gst_settings.remove(d) gst_settings.save() - diff --git a/erpnext/regional/italy/__init__.py b/erpnext/regional/italy/__init__.py index 4932f660ca5..eb20f65afb4 100644 --- a/erpnext/regional/italy/__init__.py +++ b/erpnext/regional/italy/__init__.py @@ -1,5 +1,3 @@ -# coding=utf-8 - fiscal_regimes = [ "RF01-Ordinario", "RF02-Contribuenti minimi (art.1, c.96-117, L. 244/07)", diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 7db2f6b0f8d..531f10d3f99 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt # coding=utf-8 -from __future__ import unicode_literals import frappe from frappe import _ diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 56f609eb234..c82557b9de0 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -1,13 +1,12 @@ -from __future__ import unicode_literals - import io import json + import frappe -from frappe.utils import flt, cstr -from erpnext.controllers.taxes_and_totals import get_itemised_tax from frappe import _ +from frappe.utils import cstr, flt from frappe.utils.file_manager import remove_file -from six import string_types + +from erpnext.controllers.taxes_and_totals import get_itemised_tax from erpnext.regional.italy import state_codes @@ -166,7 +165,7 @@ def get_invoice_summary(items, taxes): if tax.rate == 0: for item in items: item_tax_rate = item.item_tax_rate - if isinstance(item.item_tax_rate, string_types): + if isinstance(item.item_tax_rate, str): item_tax_rate = json.loads(item.item_tax_rate) if item_tax_rate and tax.account_head in item_tax_rate: diff --git a/erpnext/healthcare/doctype/inpatient_medication_order_entry/__init__.py b/erpnext/regional/print_format/ksa_pos_invoice/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/inpatient_medication_order_entry/__init__.py rename to erpnext/regional/print_format/ksa_pos_invoice/__init__.py diff --git a/erpnext/regional/print_format/ksa_pos_invoice/ksa_pos_invoice.json b/erpnext/regional/print_format/ksa_pos_invoice/ksa_pos_invoice.json new file mode 100644 index 00000000000..c2a309231dd --- /dev/null +++ b/erpnext/regional/print_format/ksa_pos_invoice/ksa_pos_invoice.json @@ -0,0 +1,32 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2021-12-07 13:25:05.424827", + "css": "", + "custom_format": 1, + "default_print_language": "en", + "disabled": 1, + "doc_type": "POS Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font_size": 0, + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

      \n\t{{ doc.company }}
      \n\t{{ doc.select_print_heading or _(\"Invoice\") }}
      \n\t\n

      \n

      \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
      \n\t{{ _(\"Cashier\") }}: {{ doc.owner }}
      \n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}
      \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
      \n\t{{ _(\"Time\") }}: {{ doc.get_formatted(\"posting_time\") }}
      \n

      \n\n
      \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
      {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
      \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
      {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
      {{ _(\"SR.No\") }}:
      \n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
      {{ item.qty }}{{ item.get_formatted(\"net_amount\") }}
      \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
      \n\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t
      \n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
      \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
      \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
      \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
      \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
      \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
      \n
      \n

      {{ doc.terms or \"\" }}

      \n

      {{ _(\"Thank you, please visit again.\") }}

      ", + "idx": 0, + "line_breaks": 0, + "margin_bottom": 0.0, + "margin_left": 0.0, + "margin_right": 0.0, + "margin_top": 0.0, + "modified": "2021-12-08 10:25:01.930885", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA POS Invoice", + "owner": "Administrator", + "page_number": "Hide", + "print_format_builder": 0, + "print_format_builder_beta": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/__init__.py b/erpnext/regional/print_format/ksa_vat_invoice/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/inpatient_occupancy/__init__.py rename to erpnext/regional/print_format/ksa_vat_invoice/__init__.py diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json new file mode 100644 index 00000000000..6b64d474535 --- /dev/null +++ b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json @@ -0,0 +1,32 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2021-10-29 22:46:26.039023", + "css": ".qr-code{\n float:right;\n}\n\n.invoice-heading {\n margin: 0;\n}\n\n.ksa-invoice-table {\n border: 1px solid #888a8e;\n border-collapse: collapse;\n width: 100%;\n margin: 20px 0;\n font-size: 16px;\n}\n\n.ksa-invoice-table.two-columns td:nth-child(2) {\n direction: rtl;\n}\n\n.ksa-invoice-table th {\n border: 1px solid #888a8e;\n max-width: 50%;\n padding: 8px;\n}\n\n.ksa-invoice-table td {\n padding: 5px;\n border: 1px solid #888a8e;\n max-width: 50%;\n}\n\n.ksa-invoice-table thead,\n.ksa-invoice-table tfoot {\n text-transform: uppercase;\n}\n\n.qr-rtl {\n direction: rtl;\n}\n\n.qr-flex{\n display: flex;\n justify-content: space-between;\n}", + "custom_format": 1, + "default_print_language": "en", + "disabled": 1, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font_size": 14, + "html": "
      \n
      \n
      \n

      TAX INVOICE

      \n

      \u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

      \n
      \n \n \n
      \n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\t\t{% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
      {{ company.name }}{{ company.company_name_in_arabic }}
      Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
      Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
      Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
      Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
      Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
      {{ company.tax_id }}{{ company.tax_id }}
      {{ company.name }}{{ company.company_name_in_arabic }}
      {{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
      Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
      Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
      CUSTOMER:\u0639\u0645\u064a\u0644:
      Customer Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:
      {{ customer_tax_id }}{{ customer_tax_id }}
      {{ doc.customer }} {{ doc.customer_name_in_arabic }}
      {{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
      SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
      {{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
      OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
      Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
      Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
      \n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set key = item.item_code or item.item_name %}\n {% set tax_amount = frappe.utils.flt(data_object[key][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
      Nature of goods or services
      \u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
      \n Unit price
      \n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
      \n Quantity
      \n \u0627\u0644\u0643\u0645\u064a\u0629\n
      \n Taxable Amount
      \n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
      {{row.description}}\n Total
      \n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
      {{ item.item_code or item.item_name }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
      \n {%- if(data_object[key][0])-%}\n {{ frappe.format(data_object[key][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[key][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n
      \n
      {{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}
      \n {{ doc.get_formatted(\"total\") }}
      \n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
      \n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
      \n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
      \n Total (Excluding VAT)\n
      \n Total VAT\n
      \n {{ doc.get_formatted(\"total\") }}
      \n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
      {{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
      \n\n\t{%- if doc.terms -%}\n

      \n {{doc.terms}}\n

      \n\t{%- endif -%}\n
      \n", + "idx": 0, + "line_breaks": 0, + "margin_bottom": 15.0, + "margin_left": 15.0, + "margin_right": 15.0, + "margin_top": 15.0, + "modified": "2021-12-07 13:43:38.018593", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Invoice", + "owner": "Administrator", + "page_number": "Hide", + "print_format_builder": 0, + "print_format_builder_beta": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 86aed2ef814..beac7ed65cd 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Provide a report and downloadable CSV according to the German DATEV format. @@ -7,16 +6,19 @@ Provide a report and downloadable CSV according to the German DATEV format. - CSV download functionality `download_datev_csv` that provides a CSV file with all required columns. Used to import the data into the DATEV Software. """ -from __future__ import unicode_literals import json -import frappe -from six import string_types +import frappe from frappe import _ + from erpnext.accounts.utils import get_fiscal_year -from erpnext.regional.germany.utils.datev.datev_csv import zip_and_download, get_datev_csv -from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames +from erpnext.regional.germany.utils.datev.datev_constants import ( + AccountNames, + DebtorsCreditors, + Transactions, +) +from erpnext.regional.germany.utils.datev.datev_csv import get_datev_csv, zip_and_download COLUMNS = [ { @@ -43,6 +45,12 @@ COLUMNS = [ "fieldtype": "Data", "width": 100 }, + { + "label": "BU-Schlüssel", + "fieldname": "BU-Schlüssel", + "fieldtype": "Data", + "width": 100 + }, { "label": "Belegdatum", "fieldname": "Belegdatum", @@ -114,6 +122,36 @@ COLUMNS = [ "fieldname": "Beleginfo - Inhalt 4", "fieldtype": "Data", "width": 150 + }, + { + "label": "Beleginfo - Art 5", + "fieldname": "Beleginfo - Art 5", + "fieldtype": "Data", + "width": 150 + }, + { + "label": "Beleginfo - Inhalt 5", + "fieldname": "Beleginfo - Inhalt 5", + "fieldtype": "Data", + "width": 100 + }, + { + "label": "Beleginfo - Art 6", + "fieldname": "Beleginfo - Art 6", + "fieldtype": "Data", + "width": 150 + }, + { + "label": "Beleginfo - Inhalt 6", + "fieldname": "Beleginfo - Inhalt 6", + "fieldtype": "Date", + "width": 100 + }, + { + "label": "Fälligkeit", + "fieldname": "Fälligkeit", + "fieldtype": "Date", + "width": 100 } ] @@ -161,6 +199,125 @@ def validate_fiscal_year(from_date, to_date, company): def get_transactions(filters, as_dict=1): + def run(params_method, filters): + extra_fields, extra_joins, extra_filters = params_method(filters) + return run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=as_dict) + + def sort_by(row): + # "Belegdatum" is in the fifth column when list format is used + return row["Belegdatum" if as_dict else 5] + + type_map = { + # specific query methods for some voucher types + "Payment Entry": get_payment_entry_params, + "Sales Invoice": get_sales_invoice_params, + "Purchase Invoice": get_purchase_invoice_params + } + + only_voucher_type = filters.get("voucher_type") + transactions = [] + + for voucher_type, get_voucher_params in type_map.items(): + if only_voucher_type and only_voucher_type != voucher_type: + continue + + transactions.extend(run(params_method=get_voucher_params, filters=filters)) + + if not only_voucher_type or only_voucher_type not in type_map: + # generic query method for all other voucher types + filters["exclude_voucher_types"] = type_map.keys() + transactions.extend(run(params_method=get_generic_params, filters=filters)) + + return sorted(transactions, key=sort_by) + + +def get_payment_entry_params(filters): + extra_fields = """ + , 'Zahlungsreferenz' as 'Beleginfo - Art 5' + , pe.reference_no as 'Beleginfo - Inhalt 5' + , 'Buchungstag' as 'Beleginfo - Art 6' + , pe.reference_date as 'Beleginfo - Inhalt 6' + , '' as 'Fälligkeit' + """ + + extra_joins = """ + LEFT JOIN `tabPayment Entry` pe + ON gl.voucher_no = pe.name + """ + + extra_filters = """ + AND gl.voucher_type = 'Payment Entry' + """ + + return extra_fields, extra_joins, extra_filters + + +def get_sales_invoice_params(filters): + extra_fields = """ + , '' as 'Beleginfo - Art 5' + , '' as 'Beleginfo - Inhalt 5' + , '' as 'Beleginfo - Art 6' + , '' as 'Beleginfo - Inhalt 6' + , si.due_date as 'Fälligkeit' + """ + + extra_joins = """ + LEFT JOIN `tabSales Invoice` si + ON gl.voucher_no = si.name + """ + + extra_filters = """ + AND gl.voucher_type = 'Sales Invoice' + """ + + return extra_fields, extra_joins, extra_filters + + +def get_purchase_invoice_params(filters): + extra_fields = """ + , 'Lieferanten-Rechnungsnummer' as 'Beleginfo - Art 5' + , pi.bill_no as 'Beleginfo - Inhalt 5' + , 'Lieferanten-Rechnungsdatum' as 'Beleginfo - Art 6' + , pi.bill_date as 'Beleginfo - Inhalt 6' + , pi.due_date as 'Fälligkeit' + """ + + extra_joins = """ + LEFT JOIN `tabPurchase Invoice` pi + ON gl.voucher_no = pi.name + """ + + extra_filters = """ + AND gl.voucher_type = 'Purchase Invoice' + """ + + return extra_fields, extra_joins, extra_filters + + +def get_generic_params(filters): + # produce empty fields so all rows will have the same length + extra_fields = """ + , '' as 'Beleginfo - Art 5' + , '' as 'Beleginfo - Inhalt 5' + , '' as 'Beleginfo - Art 6' + , '' as 'Beleginfo - Inhalt 6' + , '' as 'Fälligkeit' + """ + extra_joins = "" + + if filters.get("exclude_voucher_types"): + # exclude voucher types that are queried by a dedicated method + exclude = "({})".format(', '.join("'{}'".format(key) for key in filters.get("exclude_voucher_types"))) + extra_filters = "AND gl.voucher_type NOT IN {}".format(exclude) + + # if voucher type filter is set, allow only this type + if filters.get("voucher_type"): + extra_filters += " AND gl.voucher_type = %(voucher_type)s" + + return extra_fields, extra_joins, extra_filters + + +def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1): """ Get a list of accounting entries. @@ -171,8 +328,7 @@ def get_transactions(filters, as_dict=1): filters -- dict of filters to be passed to the sql query as_dict -- return as list of dicts [0,1] """ - filter_by_voucher = 'AND gl.voucher_type = %(voucher_type)s' if filters.get('voucher_type') else '' - gl_entries = frappe.db.sql(""" + query = """ SELECT /* either debit or credit amount; always positive */ @@ -187,9 +343,12 @@ def get_transactions(filters, as_dict=1): /* against number or, if empty, party against number */ %(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)', + /* disable automatic VAT deduction */ + '40' as 'BU-Schlüssel', + gl.posting_date as 'Belegdatum', gl.voucher_no as 'Belegfeld 1', - LEFT(gl.remarks, 60) as 'Buchungstext', + REPLACE(LEFT(gl.remarks, 60), '\n', ' ') as 'Buchungstext', gl.voucher_type as 'Beleginfo - Art 1', gl.voucher_no as 'Beleginfo - Inhalt 1', gl.against_voucher_type as 'Beleginfo - Art 2', @@ -199,30 +358,34 @@ def get_transactions(filters, as_dict=1): case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4', par.debtor_creditor_number as 'Beleginfo - Inhalt 4' + {extra_fields} + FROM `tabGL Entry` gl /* Kontonummer */ - left join `tabAccount` acc - on gl.account = acc.name + LEFT JOIN `tabAccount` acc + ON gl.account = acc.name - left join `tabCustomer` cus - on gl.party_type = 'Customer' - and gl.party = cus.name + LEFT JOIN `tabParty Account` par + ON par.parent = gl.party + AND par.parenttype = gl.party_type + AND par.company = %(company)s - left join `tabSupplier` sup - on gl.party_type = 'Supplier' - and gl.party = sup.name - - left join `tabParty Account` par - on par.parent = gl.party - and par.parenttype = gl.party_type - and par.company = %(company)s + {extra_joins} WHERE gl.company = %(company)s AND DATE(gl.posting_date) >= %(from_date)s AND DATE(gl.posting_date) <= %(to_date)s - {} - ORDER BY 'Belegdatum', gl.voucher_no""".format(filter_by_voucher), filters, as_dict=as_dict) + + {extra_filters} + + ORDER BY 'Belegdatum', gl.voucher_no""".format( + extra_fields=extra_fields, + extra_joins=extra_joins, + extra_filters=extra_filters + ) + + gl_entries = frappe.db.sql(query, filters, as_dict=as_dict) return gl_entries @@ -380,7 +543,7 @@ def download_datev_csv(filters): Arguments / Params: filters -- dict of filters to be passed to the sql query """ - if isinstance(filters, string_types): + if isinstance(filters, str): filters = json.loads(filters) validate(filters) diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py index 59b878e94a5..14d5495eed7 100644 --- a/erpnext/regional/report/datev/test_datev.py +++ b/erpnext/regional/report/datev/test_datev.py @@ -1,22 +1,25 @@ -# coding=utf-8 -from __future__ import unicode_literals - import zipfile -import frappe -from six import BytesIO from unittest import TestCase -from frappe.utils import today, now_datetime, cstr + +import frappe +from frappe.utils import cstr, now_datetime, today +from six import BytesIO + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice - -from erpnext.regional.report.datev.datev import validate -from erpnext.regional.report.datev.datev import get_transactions -from erpnext.regional.report.datev.datev import get_customers -from erpnext.regional.report.datev.datev import get_suppliers -from erpnext.regional.report.datev.datev import get_account_names -from erpnext.regional.report.datev.datev import download_datev_csv - +from erpnext.regional.germany.utils.datev.datev_constants import ( + AccountNames, + DebtorsCreditors, + Transactions, +) from erpnext.regional.germany.utils.datev.datev_csv import get_datev_csv, get_header -from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames +from erpnext.regional.report.datev.datev import ( + download_datev_csv, + get_account_names, + get_customers, + get_suppliers, + get_transactions, +) + def make_company(company_name, abbr): if not frappe.db.exists("Company", company_name): diff --git a/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.py b/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.py index 376ba3ee471..1ae3d16c97a 100644 --- a/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.py +++ b/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.py @@ -1,8 +1,9 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from erpnext.accounts.report.sales_register.sales_register import _execute + def execute(filters=None): return _execute(filters) diff --git a/erpnext/regional/report/eway_bill/eway_bill.py b/erpnext/regional/report/eway_bill/eway_bill.py index 4f777fcf7e3..f3fe5e88488 100644 --- a/erpnext/regional/report/eway_bill/eway_bill.py +++ b/erpnext/regional/report/eway_bill/eway_bill.py @@ -1,13 +1,15 @@ # Copyright (c) 2013, FinByz Tech Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + import json import re + +import frappe from frappe import _ from frappe.utils import nowdate + def execute(filters=None): if not filters: filters.setdefault('posting_date', [nowdate(), nowdate()]) columns, data = [], [] @@ -41,7 +43,7 @@ def get_data(filters): } # Regular expression set to remove all the special characters - special_characters = "[$%^*()+\\[\]{};':\"\\|<>.?]" + special_characters = r"[$%^*()+\\[\]{};':\"\\|<>.?]" for row in data: set_defaults(row) @@ -104,14 +106,14 @@ def set_address_details(row, special_characters): row.update({'ship_to_state': row.to_state}) def set_taxes(row, filters): - taxes = frappe.get_list("Sales Taxes and Charges", + taxes = frappe.get_all("Sales Taxes and Charges", filters={ 'parent': row.dn_id }, fields=('item_wise_tax_detail', 'account_head')) account_list = ["cgst_account", "sgst_account", "igst_account", "cess_account"] - taxes_list = frappe.get_list("GST Account", + taxes_list = frappe.get_all("GST Account", filters={ "parent": "GST Settings", "company": filters.company diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py index e903c9f00a4..59888ff94e7 100644 --- a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py +++ b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py @@ -1,12 +1,14 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import format_datetime -from frappe import _ + import re +import frappe +from frappe import _ +from frappe.utils import format_datetime + + def execute(filters=None): account_details = {} for acc in frappe.db.sql("""select name, is_group from tabAccount""", as_dict=1): @@ -116,7 +118,7 @@ def get_result_as_list(data, filters): if d.get("voucher_no").startswith("{0}-".format(JournalCode)) or d.get("voucher_no").startswith("{0}/".format(JournalCode)): EcritureNum = re.split("-|/", d.get("voucher_no"))[1] else: - EcritureNum = re.search("{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE).group(1) + EcritureNum = re.search(r"{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE).group(1) EcritureDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd") diff --git a/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py b/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py index b5948f9952d..528868cf176 100644 --- a/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py +++ b/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py @@ -1,9 +1,11 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from erpnext.accounts.report.item_wise_purchase_register.item_wise_purchase_register import _execute +from erpnext.accounts.report.item_wise_purchase_register.item_wise_purchase_register import ( + _execute, +) + def execute(filters=None): return _execute(filters, additional_table_columns=[ diff --git a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py index e13f509f475..386e2197569 100644 --- a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py +++ b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py @@ -1,10 +1,10 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import _execute + def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Customer GSTIN', fieldname="customer_gstin", width=120), diff --git a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py index 12e9676b4ba..2d994082c31 100644 --- a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py +++ b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py @@ -1,10 +1,10 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals from erpnext.accounts.report.purchase_register.purchase_register import _execute + def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Supplier GSTIN', fieldname="supplier_gstin", width=120), diff --git a/erpnext/regional/report/gst_sales_register/gst_sales_register.py b/erpnext/regional/report/gst_sales_register/gst_sales_register.py index 075bd483cf0..a6f2b3dbf4d 100644 --- a/erpnext/regional/report/gst_sales_register/gst_sales_register.py +++ b/erpnext/regional/report/gst_sales_register/gst_sales_register.py @@ -1,10 +1,10 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals from erpnext.accounts.report.sales_register.sales_register import _execute + def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Customer GSTIN', fieldname="customer_gstin", width=120), diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 444f5dbb8ca..ef2bdb67980 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -51,7 +51,9 @@ frappe.query_reports["GSTR-1"] = { { "value": "B2C Large", "label": __("B2C(Large) Invoices - 5A, 5B") }, { "value": "B2C Small", "label": __("B2C(Small) Invoices - 7") }, { "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") }, - { "value": "EXPORT", "label": __("Export Invoice - 6A") } + { "value": "CDNR-UNREG", "label": __("Credit/Debit Notes (Unregistered) - 9B") }, + { "value": "EXPORT", "label": __("Export Invoice - 6A") }, + { "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") } ], "default": "B2B" } diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 9d4f9206f50..11b684d3f6a 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -1,15 +1,17 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, json -from frappe import _ -from frappe.utils import flt, formatdate, now_datetime, getdate + +import json from datetime import date -from six import iteritems -from erpnext.regional.doctype.gstr_3b_report.gstr_3b_report import get_period + +import frappe +from frappe import _ +from frappe.utils import flt, formatdate, getdate + from erpnext.regional.india.utils import get_gst_accounts + def execute(filters=None): return Gstr1Report(filters).run() @@ -50,63 +52,83 @@ class Gstr1Report(object): self.get_invoice_items() self.get_items_based_on_tax_rate() self.invoice_fields = [d["fieldname"] for d in self.invoice_columns] - self.get_data() + + self.get_data() return self.columns, self.data def get_data(self): if self.filters.get("type_of_business") in ("B2C Small", "B2C Large"): self.get_b2c_data() - else: + elif self.filters.get("type_of_business") == "Advances": + self.get_advance_data() + elif self.invoices: for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) for rate, items in items_based_on_rate.items(): row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - if self.filters.get("type_of_business") == "CDNR-REG": + if self.filters.get("type_of_business") in ("CDNR-REG", "CDNR-UNREG"): row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") row.append("C" if invoice_details.is_return else "D") if taxable_value: self.data.append(row) + def get_advance_data(self): + advances_data = {} + advances = self.get_advance_entries() + for entry in advances: + # only consider IGST and SGST so as to avoid duplication of taxable amount + if entry.account_head in self.gst_accounts.igst_account or \ + entry.account_head in self.gst_accounts.sgst_account: + advances_data.setdefault((entry.place_of_supply, entry.rate), [0.0, 0.0]) + advances_data[(entry.place_of_supply, entry.rate)][0] += (entry.amount * 100 / entry.rate) + elif entry.account_head in self.gst_accounts.cess_account: + advances_data[(entry.place_of_supply, entry.rate)][1] += entry.amount + + for key, value in advances_data.items(): + row= [key[0], key[1], value[0], value[1]] + self.data.append(row) + def get_b2c_data(self): b2cs_output = {} - for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): - invoice_details = self.invoices.get(inv) - for rate, items in items_based_on_rate.items(): - place_of_supply = invoice_details.get("place_of_supply") - ecommerce_gstin = invoice_details.get("ecommerce_gstin") + if self.invoices: + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): + invoice_details = self.invoices.get(inv) + for rate, items in items_based_on_rate.items(): + place_of_supply = invoice_details.get("place_of_supply") + ecommerce_gstin = invoice_details.get("ecommerce_gstin") - b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{ - "place_of_supply": "", - "ecommerce_gstin": "", - "rate": "", - "taxable_value": 0, - "cess_amount": 0, - "type": "", - "invoice_number": invoice_details.get("invoice_number"), - "posting_date": invoice_details.get("posting_date"), - "invoice_value": invoice_details.get("base_grand_total"), - }) + b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin), { + "place_of_supply": "", + "ecommerce_gstin": "", + "rate": "", + "taxable_value": 0, + "cess_amount": 0, + "type": "", + "invoice_number": invoice_details.get("invoice_number"), + "posting_date": invoice_details.get("posting_date"), + "invoice_value": invoice_details.get("base_grand_total"), + }) - row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) - row["place_of_supply"] = place_of_supply - row["ecommerce_gstin"] = ecommerce_gstin - row["rate"] = rate - row["taxable_value"] += sum([abs(net_amount) - for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items]) - row["cess_amount"] += flt(self.invoice_cess.get(inv), 2) - row["type"] = "E" if ecommerce_gstin else "OE" + row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) + row["place_of_supply"] = place_of_supply + row["ecommerce_gstin"] = ecommerce_gstin + row["rate"] = rate + row["taxable_value"] += sum([abs(net_amount) + for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items]) + row["cess_amount"] += flt(self.invoice_cess.get(inv), 2) + row["type"] = "E" if ecommerce_gstin else "OE" - for key, value in iteritems(b2cs_output): - self.data.append(value) + for key, value in b2cs_output.items(): + self.data.append(value) def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items): row = [] for fieldname in self.invoice_fields: - if self.filters.get("type_of_business") == "CDNR-REG" and fieldname == "invoice_value": + if self.filters.get("type_of_business") in ("CDNR-REG", "CDNR-UNREG") 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) @@ -148,12 +170,6 @@ class Gstr1Report(object): self.invoices = frappe._dict() conditions = self.get_conditions() - company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True) - - self.filters.update({ - 'company_gstins': company_gstins - }) - invoice_data = frappe.db.sql(""" select {select_columns} @@ -167,6 +183,16 @@ class Gstr1Report(object): for d in invoice_data: self.invoices.setdefault(d.invoice_number, d) + def get_advance_entries(self): + return frappe.db.sql(""" + SELECT SUM(a.base_tax_amount) as amount, a.account_head, a.rate, p.place_of_supply + FROM `tabPayment Entry` p, `tabAdvance Taxes and Charges` a + WHERE p.docstatus = 1 + AND p.name = a.parent + AND posting_date between %s and %s + GROUP BY a.account_head, p.place_of_supply, a.rate + """, (self.filters.get('from_date'), self.filters.get('to_date')), as_dict=1) + def get_conditions(self): conditions = "" @@ -179,7 +205,7 @@ class Gstr1Report(object): if self.filters.get("type_of_business") == "B2B": - conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1" + conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1 AND is_debit_note !=1" if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') @@ -188,7 +214,7 @@ class Gstr1Report(object): if self.filters.get("type_of_business") == "B2C Large": conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') - AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + AND grand_total > {0} AND is_return != 1 AND is_debit_note !=1 AND gst_category ='Unregistered' """.format(flt(b2c_limit)) elif self.filters.get("type_of_business") == "B2C Small": conditions += """ AND ( @@ -198,10 +224,16 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "CDNR-REG": conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ')""" + elif self.filters.get("type_of_business") == "CDNR-UNREG": + b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') + conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') + AND (is_return = 1 OR is_debit_note = 1) + AND IFNULL(gst_category, '') in ('Unregistered', 'Overseas')""" + elif self.filters.get("type_of_business") == "EXPORT": conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ - conditions += " AND IFNULL(billing_address_gstin, '') NOT IN %(company_gstins)s" + conditions += " AND IFNULL(billing_address_gstin, '') != company_gstin" return conditions @@ -216,18 +248,17 @@ class Gstr1Report(object): """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) for d in items: - if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) - self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) - item_tax_rate = {} + item_tax_rate = {} - if d.item_tax_rate: - item_tax_rate = json.loads(d.item_tax_rate) + if d.item_tax_rate: + item_tax_rate = json.loads(d.item_tax_rate) - for account, rate in item_tax_rate.items(): - tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, []) - tax_rate_dict.append(rate) + for account, rate in item_tax_rate.items(): + tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, []) + tax_rate_dict.append(rate) def get_items_based_on_tax_rate(self): self.tax_details = frappe.db.sql(""" @@ -284,7 +315,7 @@ class Gstr1Report(object): + "
      " + "
      ".join(unidentified_gst_accounts), alert=True) # Build itemised tax for export invoices where tax table is blank - for invoice, items in iteritems(self.invoice_items): + for invoice, items in self.invoice_items.items(): if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \ and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \ and self.invoices.get(invoice, {}).get('gst_category') == "Overseas": @@ -503,6 +534,84 @@ class Gstr1Report(object): "width": 80 } ] + elif self.filters.get("type_of_business") == "CDNR-UNREG": + self.invoice_columns = [ + { + "fieldname": "customer_name", + "label": "Receiver Name", + "fieldtype": "Data", + "width": 120 + }, + { + "fieldname": "return_against", + "label": "Issued Against", + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 120 + }, + { + "fieldname": "posting_date", + "label": "Note Date", + "fieldtype": "Date", + "width": 120 + }, + { + "fieldname": "invoice_number", + "label": "Note Number", + "fieldtype": "Link", + "options": "Sales Invoice", + "width":120 + }, + { + "fieldname": "export_type", + "label": "Export Type", + "fieldtype": "Data", + "hidden": 1 + }, + { + "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": "gst_category", + "label": "GST Category", + "fieldtype": "Data" + }, + { + "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 = [ { @@ -578,6 +687,25 @@ class Gstr1Report(object): "width": 120 } ] + elif self.filters.get("type_of_business") == "Advances": + self.invoice_columns = [ + { + "fieldname": "place_of_supply", + "label": "Place Of Supply", + "fieldtype": "Data", + "width": 120 + } + ] + + self.other_columns = [ + { + "fieldname": "cess_amount", + "label": "Cess Amount", + "fieldtype": "Currency", + "width": 100 + } + ] + self.columns = self.invoice_columns + self.tax_columns + self.other_columns @frappe.whitelist() @@ -616,12 +744,29 @@ def get_json(filters, report_name, data): out = get_export_json(res) gst_json["exp"] = out - elif filters["type_of_business"] == 'CDNR-REG': + elif filters["type_of_business"] == "CDNR-REG": for item in report_data[:-1]: res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item) out = get_cdnr_reg_json(res, gstin) gst_json["cdnr"] = out + elif filters["type_of_business"] == "CDNR-UNREG": + for item in report_data[:-1]: + res.setdefault(item["invoice_number"],[]).append(item) + + out = get_cdnr_unreg_json(res, gstin) + gst_json["cdnur"] = out + + elif filters["type_of_business"] == "Advances": + for item in report_data[:-1]: + if not item.get("place_of_supply"): + frappe.throw(_("""{0} not entered in some entries. + Please update and try again""").format(frappe.bold("Place Of Supply"))) + + res.setdefault(item["place_of_supply"],[]).append(item) + + out = get_advances_json(res, gstin) + gst_json["at"] = out return { 'report_name': report_name, @@ -635,7 +780,7 @@ def get_b2b_json(res, gstin): b2b_item, inv = {"ctin": gst_in, "inv": []}, [] if not gst_in: continue - for number, invoice in iteritems(res[gst_in]): + for number, invoice in res[gst_in].items(): if not invoice[0]["place_of_supply"]: frappe.throw(_("""{0} not entered in Invoice {1}. Please update and try again""").format(frappe.bold("Place Of Supply"), @@ -701,6 +846,40 @@ def get_b2cs_json(data, gstin): return out +def get_advances_json(data, gstin): + company_state_number = gstin[0:2] + out = [] + for place_of_supply, items in data.items(): + supply_type = "INTRA" if company_state_number == place_of_supply.split('-')[0] else "INTER" + row = { + "pos": place_of_supply.split('-')[0], + "itms": [], + "sply_ty": supply_type + } + + for item in items: + itms = { + 'rt': item['rate'], + 'ad_amount': flt(item.get('taxable_value')), + 'csamt': flt(item.get('cess_amount')) + } + + if supply_type == "INTRA": + itms.update({ + "samt": flt((itms["ad_amount"] * itms["rt"]) / 100), + "camt": flt((itms["ad_amount"] * itms["rt"]) / 100), + "rt": itms["rt"] * 2 + }) + else: + itms.update({ + "iamt": flt((itms["ad_amount"] * itms["rt"]) / 100) + }) + + row['itms'].append(itms) + out.append(row) + + return out + def get_b2cl_json(res, gstin): out = [] for pos in res: @@ -752,7 +931,7 @@ def get_cdnr_reg_json(res, gstin): cdnr_item, inv = {"ctin": gst_in, "nt": []}, [] if not gst_in: continue - for number, invoice in iteritems(res[gst_in]): + for number, invoice in res[gst_in].items(): if not invoice[0]["place_of_supply"]: frappe.throw(_("""{0} not entered in Invoice {1}. Please update and try again""").format(frappe.bold("Place Of Supply"), @@ -780,6 +959,27 @@ def get_cdnr_reg_json(res, gstin): return out +def get_cdnr_unreg_json(res, gstin): + out = [] + + for invoice, items in res.items(): + inv_item = { + "nt_num": items[0]["invoice_number"], + "nt_dt": getdate(items[0]["posting_date"]).strftime('%d-%m-%Y'), + "val": abs(flt(items[0]["invoice_value"])), + "ntty": items[0]["document_type"], + "pos": "%02d" % int(items[0]["place_of_supply"].split('-')[0]), + "typ": get_invoice_type_for_cdnrur(items[0]) + } + + inv_item["itms"] = [] + for item in items: + inv_item["itms"].append(get_rate_and_tax_details(item, gstin)) + + out.append(inv_item) + + return out + def get_invoice_type_for_cdnr(row): if row.get('gst_category') == 'SEZ': if row.get('export_type') == 'WPAY': @@ -787,12 +987,23 @@ def get_invoice_type_for_cdnr(row): else: invoice_type = 'SEWOP' elif row.get('gst_category') == 'Deemed Export': - row.invoice_type = 'DE' + invoice_type = 'DE' elif row.get('gst_category') == 'Registered Regular': invoice_type = 'R' return invoice_type +def get_invoice_type_for_cdnrur(row): + if row.get('gst_category') == 'Overseas': + if row.get('export_type') == 'WPAY': + invoice_type = 'EXPWP' + else: + invoice_type = 'EXPWOP' + elif row.get('gst_category') == 'Unregistered': + invoice_type = 'B2CL' + + return invoice_type + def get_basic_invoice_detail(row): return { "inum": row["invoice_number"], @@ -831,8 +1042,9 @@ def get_company_gstin_number(company, address=None, all_gstins=False): ["Dynamic Link", "link_doctype", "=", "Company"], ["Dynamic Link", "link_name", "=", company], ["Dynamic Link", "parenttype", "=", "Address"], + ["gstin", "!=", ''] ] - gstin = frappe.get_all("Address", filters=filters, pluck="gstin") + gstin = frappe.get_all("Address", filters=filters, pluck="gstin", order_by="is_primary_address desc") if gstin and not all_gstins: gstin = gstin[0] diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py index 616c2b853df..47c856dfaae 100644 --- a/erpnext/regional/report/gstr_2/gstr_2.py +++ b/erpnext/regional/report/gstr_2/gstr_2.py @@ -1,11 +1,14 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from datetime import date + +import frappe + from erpnext.regional.report.gstr_1.gstr_1 import Gstr1Report + def execute(filters=None): return Gstr2Report(filters).run() diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 1adddbdae57..e03ad374aef 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -1,17 +1,19 @@ # Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe import _ -from frappe.utils import flt, getdate, cstr -from frappe.model.meta import get_field_precision -from frappe.utils.xlsxutils import handle_html -from six import iteritems + import json + +import frappe +from frappe import _ +from frappe.model.meta import get_field_precision +from frappe.utils import cstr, flt, getdate + +import erpnext from erpnext.regional.india.utils import get_gst_accounts from erpnext.regional.report.gstr_1.gstr_1 import get_company_gstin_number + def execute(filters=None): return _execute(filters) @@ -209,7 +211,7 @@ def get_merged_data(columns, data): else: merged_hsn_dict[row[0]][d['fieldname']] = row[i] - for key, value in iteritems(merged_hsn_dict): + for key, value in merged_hsn_dict.items(): result.append(value) return result diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py index f67d622fdf8..b1a5d109621 100644 --- a/erpnext/regional/report/irs_1099/irs_1099.py +++ b/erpnext/regional/report/irs_1099/irs_1099.py @@ -3,16 +3,16 @@ import json -from PyPDF2 import PdfFileWriter - import frappe -from erpnext.accounts.utils import get_fiscal_year from frappe import _ from frappe.utils import cstr, nowdate from frappe.utils.data import fmt_money from frappe.utils.jinja import render_template from frappe.utils.pdf import get_pdf from frappe.utils.print_format import read_multi_pdf +from PyPDF2 import PdfFileWriter + +from erpnext.accounts.utils import get_fiscal_year IRS_1099_FORMS_FILE_EXTENSION = ".pdf" diff --git a/erpnext/healthcare/doctype/inpatient_record/__init__.py b/erpnext/regional/report/ksa_vat/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/inpatient_record/__init__.py rename to erpnext/regional/report/ksa_vat/__init__.py diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js new file mode 100644 index 00000000000..59e72c3e638 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -0,0 +1,59 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["KSA VAT"] = { + onload() { + frappe.breadcrumbs.add('Accounts'); + }, + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data + && (data.title=='VAT on Sales' || data.title=='VAT on Purchases') + && data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

      ").parent().html(); + return value + }else if (data.title=='Grand Total'){ + if (data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

      ").parent().html(); + return value + }else{ + value = default_formatter(value, row, column, data); + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

      ").parent().html(); + return value + } + }else{ + value = default_formatter(value, row, column, data); + return value; + } + }, +}; diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.json b/erpnext/regional/report/ksa_vat/ksa_vat.json new file mode 100644 index 00000000000..036e2603103 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-13 08:54:38.000949", + "disable_prepared_report": 1, + "disabled": 1, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-08-26 04:14:37.202594", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "GL Entry", + "report_name": "KSA VAT", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py new file mode 100644 index 00000000000..b41b2b0428f --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -0,0 +1,175 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.utils import get_url_to_list + + +def execute(filters=None): + columns = columns = get_columns() + data = get_data(filters) + return columns, data + +def get_columns(): + return [ + { + "fieldname": "title", + "label": _("Title"), + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "amount", + "label": _("Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "adjustment_amount", + "label": _("Adjustment (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "vat_amount", + "label": _("VAT Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + } + ] + +def get_data(filters): + data = [] + + # Validate if vat settings exist + company = filters.get('company') + if frappe.db.exists('KSA VAT Setting', company) is None: + url = get_url_to_list('KSA VAT Setting') + frappe.msgprint(_('Create KSA VAT Setting for this company').format(url)) + return data + + ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) + + # Sales Heading + append_data(data, 'VAT on Sales', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Sales Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax) + + # Blank Line + append_data(data, '', '', '', '') + + # Purchase Heading + append_data(data, 'VAT on Purchases', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Purchase Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax) + + return data + +def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): + ''' + (KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n + calculates and returns \n + total_taxable_amount, total_taxable_adjustment_amount, total_tax''' + from_date = filters.get('from_date') + to_date = filters.get('to_date') + + # Initiate variables + total_taxable_amount = 0 + total_taxable_adjustment_amount = 0 + total_tax = 0 + # Fetch All Invoices + invoices = frappe.get_all(doctype, + filters ={ + 'docstatus': 1, + 'posting_date': ['between', [from_date, to_date]] + }, fields =['name', 'is_return']) + + for invoice in invoices: + invoice_items = frappe.get_all(f'{doctype} Item', + filters ={ + 'docstatus': 1, + 'parent': invoice.name, + 'item_tax_template': vat_setting.item_tax_template + }, fields =['item_code', 'net_amount']) + + for item in invoice_items: + # Summing up total taxable amount + if invoice.is_return == 0: + total_taxable_amount += item.net_amount + + if invoice.is_return == 1: + total_taxable_adjustment_amount += item.net_amount + + # Summing up total tax + total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) + + return total_taxable_amount, total_taxable_adjustment_amount, total_tax + + + +def append_data(data, title, amount, adjustment_amount, vat_amount): + """Returns data with appended value.""" + data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + +def get_tax_amount(item_code, account_head, doctype, parent): + if doctype == 'Sales Invoice': + tax_doctype = 'Sales Taxes and Charges' + + elif doctype == 'Purchase Invoice': + tax_doctype = 'Purchase Taxes and Charges' + + item_wise_tax_detail = frappe.get_value(tax_doctype, { + 'docstatus': 1, + 'parent': parent, + 'account_head': account_head + }, 'item_wise_tax_detail') + + tax_amount = 0 + if item_wise_tax_detail and len(item_wise_tax_detail) > 0: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + for key, value in item_wise_tax_detail.items(): + if key == item_code: + tax_amount = value[1] + break + + return tax_amount diff --git a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py index 54808e59e1a..def43798289 100644 --- a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py +++ b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py @@ -1,10 +1,14 @@ # 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 erpnext.regional.report.provident_fund_deductions.provident_fund_deductions import get_conditions + +from erpnext.regional.report.provident_fund_deductions.provident_fund_deductions import ( + get_conditions, +) + def execute(filters=None): data = get_data(filters) diff --git a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py index 82423f005cc..190f408fe0e 100644 --- a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py +++ b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py @@ -1,10 +1,11 @@ # 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.utils import getdate from frappe import _ +from frappe.utils import getdate + def execute(filters=None): data = get_data(filters) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index daa69768c57..41336873ac9 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -1,21 +1,20 @@ -# coding=utf-8 -from __future__ import unicode_literals +from unittest import TestCase + +import frappe import erpnext -import frappe -from unittest import TestCase -from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice -from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse_account +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.regional.report.uae_vat_201.uae_vat_201 import ( - get_total_emiratewise, - get_tourist_tax_return_total, - get_tourist_tax_return_tax, - get_zero_rated_total, get_exempt_total, - get_standard_rated_expenses_total, get_standard_rated_expenses_tax, + get_standard_rated_expenses_total, + get_total_emiratewise, + get_tourist_tax_return_tax, + get_tourist_tax_return_total, + get_zero_rated_total, ) +from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse_account test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"] diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index b0614238ba0..f8379aa17ab 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -1,10 +1,11 @@ # 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 _ + def execute(filters=None): columns = get_columns() data, emirates, amounts_by_emirate = get_data(filters) @@ -120,7 +121,7 @@ def get_total_emiratewise(filters): try: return frappe.db.sql(""" select - s.vat_emirate as emirate, sum(i.base_amount) as total, sum(s.total_taxes_and_charges) + s.vat_emirate as emirate, sum(i.base_amount) as total, sum(i.tax_amount) from `tabSales Invoice Item` i inner join `tabSales Invoice` s on diff --git a/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py new file mode 100644 index 00000000000..f22abae1ff8 --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py @@ -0,0 +1,194 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +from unittest import TestCase + +import frappe +from frappe.utils import today + +from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.regional.report.vat_audit_report.vat_audit_report import execute + + +class TestVATAuditReport(TestCase): + def setUp(self): + frappe.set_user("Administrator") + make_company("_Test Company SA VAT", "_TCSV") + + create_account(account_name="VAT - 0%", account_type="Tax", + parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT") + create_account(account_name="VAT - 15%", account_type="Tax", + parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT") + set_sa_vat_accounts() + + make_item("_Test SA VAT Item") + make_item("_Test SA VAT Zero Rated Item", properties = {"is_zero_rated": 1}) + + make_customer() + make_supplier() + + make_sales_invoices() + create_purchase_invoices() + + def tearDown(self): + frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company SA VAT'") + frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company SA VAT'") + + def test_vat_audit_report(self): + filters = { + "company": "_Test Company SA VAT", + "from_date": today(), + "to_date": today() + } + columns, data = execute(filters) + total_tax_amount = 0 + total_row_tax = 0 + for row in data: + keys = row.keys() + # skips total row tax_amount in if.. and skips section header in elif.. + if 'voucher_no' in keys: + total_tax_amount = total_tax_amount + row['tax_amount'] + elif 'tax_amount' in keys: + total_row_tax = total_row_tax + row['tax_amount'] + + self.assertEqual(total_tax_amount, total_row_tax) + +def make_company(company_name, abbr): + if not frappe.db.exists("Company", company_name): + company = frappe.get_doc({ + "doctype": "Company", + "company_name": company_name, + "abbr": abbr, + "default_currency": "ZAR", + "country": "South Africa", + "create_chart_of_accounts_based_on": "Standard Template" + }) + company.insert() + else: + company = frappe.get_doc("Company", company_name) + + company.create_default_warehouses() + + if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}): + company.create_default_cost_center() + + company.save() + + return company + +def set_sa_vat_accounts(): + if not frappe.db.exists("South Africa VAT Settings", "_Test Company SA VAT"): + vat_accounts = frappe.get_all( + "Account", + fields=["name"], + filters = { + "company": "_Test Company SA VAT", + "is_group": 0, + "account_type": "Tax" + } + ) + + sa_vat_accounts = [] + for account in vat_accounts: + sa_vat_accounts.append({ + "doctype": "South Africa VAT Account", + "account": account.name + }) + + frappe.get_doc({ + "company": "_Test Company SA VAT", + "vat_accounts": sa_vat_accounts, + "doctype": "South Africa VAT Settings", + }).insert() + +def make_customer(): + if not frappe.db.exists("Customer", "_Test SA Customer"): + frappe.get_doc({ + "doctype": "Customer", + "customer_name": "_Test SA Customer", + "customer_type": "Company", + }).insert() + +def make_supplier(): + if not frappe.db.exists("Supplier", "_Test SA Supplier"): + frappe.get_doc({ + "doctype": "Supplier", + "supplier_name": "_Test SA Supplier", + "supplier_type": "Company", + "supplier_group":"All Supplier Groups" + }).insert() + +def make_item(item_code, properties=None): + if not frappe.db.exists("Item", item_code): + item = frappe.get_doc({ + "doctype": "Item", + "item_code": item_code, + "item_name": item_code, + "description": item_code, + "item_group": "Products" + }) + + if properties: + item.update(properties) + + item.insert() + +def make_sales_invoices(): + def make_sales_invoices_wrapper(item, rate, tax_account, tax_rate, tax=True): + si = create_sales_invoice( + company="_Test Company SA VAT", + customer = "_Test SA Customer", + currency = "ZAR", + item=item, + rate=rate, + warehouse = "Finished Goods - _TCSV", + debit_to = "Debtors - _TCSV", + income_account = "Sales - _TCSV", + expense_account = "Cost of Goods Sold - _TCSV", + cost_center = "Main - _TCSV", + do_not_save=1 + ) + if tax: + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": tax_account, + "cost_center": "Main - _TCSV", + "description": "VAT 15% @ 15.0", + "rate": tax_rate + }) + + si.submit() + + test_item = "_Test SA VAT Item" + test_zero_rated_item = "_Test SA VAT Zero Rated Item" + + make_sales_invoices_wrapper(test_item, 100.0, "VAT - 15% - _TCSV", 15.0) + make_sales_invoices_wrapper(test_zero_rated_item, 100.0, "VAT - 0% - _TCSV", 0.0) + +def create_purchase_invoices(): + pi = make_purchase_invoice( + company = "_Test Company SA VAT", + supplier = "_Test SA Supplier", + supplier_warehouse = "Finished Goods - _TCSV", + warehouse = "Finished Goods - _TCSV", + currency = "ZAR", + cost_center = "Main - _TCSV", + expense_account = "Cost of Goods Sold - _TCSV", + item = "_Test SA VAT Item", + qty = 1, + rate = 100, + uom = "Nos", + do_not_save = 1 + ) + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "VAT - 15% - _TCSV", + "cost_center": "Main - _TCSV", + "description": "VAT 15% @ 15.0", + "rate": 15.0 + }) + + pi.submit() diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index 17aca17afda..17e50648b3b 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -1,11 +1,13 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + import json + +import frappe from frappe import _ -from frappe.utils import formatdate +from frappe.utils import formatdate, get_link_to_form + def execute(filters=None): return VATAuditReport(filters).run() @@ -39,10 +41,11 @@ class VATAuditReport(object): return self.columns, self.data def get_sa_vat_accounts(self): - self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", + self.sa_vat_accounts = frappe.get_all("South Africa VAT Account", filters = {"parent": self.filters.company}, pluck="account") if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: - frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings")) + link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings") + frappe.throw(_("Please set VAT Accounts in {0}").format(link_to_settings)) def get_invoice_data(self, doctype): conditions = self.get_conditions() @@ -69,7 +72,7 @@ class VATAuditReport(object): items = frappe.db.sql(""" SELECT - item_code, parent, taxable_value, base_net_amount, is_zero_rated + item_code, parent, base_net_amount, is_zero_rated FROM `tab%s Item` WHERE @@ -79,7 +82,7 @@ class VATAuditReport(object): if d.item_code not in self.invoice_items.get(d.parent, {}): self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, { 'net_amount': 0.0}) - self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) + self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('base_net_amount', 0) self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated def get_items_based_on_tax_rate(self, doctype): diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 9b3677d2c64..2e31c03d5c6 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -1,11 +1,81 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - -from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats - +import frappe +from frappe.permissions import add_permission, update_permission_property +from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields +from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def setup(company=None, patch=True): - make_custom_fields() + uae_custom_fields() add_print_formats() + add_permissions() + make_custom_fields() + +def add_print_formats(): + frappe.reload_doc("regional", "print_format", "detailed_tax_invoice", force=True) + frappe.reload_doc("regional", "print_format", "simplified_tax_invoice", force=True) + frappe.reload_doc("regional", "print_format", "tax_invoice", force=True) + frappe.reload_doc("regional", "print_format", "ksa_vat_invoice", force=True) + frappe.reload_doc("regional", "print_format", "ksa_pos_invoice", force=True) + + for d in ('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice', 'KSA VAT Invoice', 'KSA POS Invoice'): + frappe.db.set_value("Print Format", d, "disabled", 0) + +def add_permissions(): + """Add Permissions for KSA VAT Setting.""" + add_permission('KSA VAT Setting', 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission('KSA VAT Setting', role, 0) + update_permission_property('KSA VAT Setting', role, 0, 'write', 1) + update_permission_property('KSA VAT Setting', role, 0, 'create', 1) + + """Enable KSA VAT Report""" + frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) + +def make_custom_fields(): + """Create Custom fields + - QR code Image file + - Company Name in Arabic + - Address in Arabic + """ + custom_fields = { + 'Sales Invoice': [ + dict( + fieldname='ksa_einv_qr', + label='KSA E-Invoicing QR', + fieldtype='Attach Image', + read_only=1, no_copy=1, hidden=1 + ) + ], + 'POS Invoice': [ + dict( + fieldname='ksa_einv_qr', + label='KSA E-Invoicing QR', + fieldtype='Attach Image', + read_only=1, no_copy=1, hidden=1 + ) + ], + 'Address': [ + dict( + fieldname='address_in_arabic', + label='Address in Arabic', + fieldtype='Data', + insert_after='address_line2' + ) + ], + 'Company': [ + dict( + fieldname='company_name_in_arabic', + label='Company Name In Arabic', + fieldtype='Data', + insert_after='company_name' + ) + ] + } + + create_custom_fields(custom_fields, update=True) + +def update_regional_tax_settings(country, company): + create_ksa_vat_setting(company) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py new file mode 100644 index 00000000000..a03c3f0994d --- /dev/null +++ b/erpnext/regional/saudi_arabia/utils.py @@ -0,0 +1,149 @@ +import io +import os +from base64 import b64encode + +import frappe +from frappe import _ +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from frappe.utils.data import add_to_date, get_time, getdate +from pyqrcode import create as qr_create + +from erpnext import get_region + + +def create_qr_code(doc, method=None): + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + # if QR Code field not present, create it. Invoices without QR are invalid as per law. + if not hasattr(doc, 'ksa_einv_qr'): + create_custom_fields({ + doc.doctype: [ + dict( + fieldname='ksa_einv_qr', + label='KSA E-Invoicing QR', + fieldtype='Attach Image', + read_only=1, no_copy=1, hidden=1 + ) + ] + }) + + # Don't create QR Code if it already exists + qr_code = doc.get("ksa_einv_qr") + if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): + return + + meta = frappe.get_meta(doc.doctype) + + if "ksa_einv_qr" in [d.fieldname for d in meta.get_image_fields()]: + ''' TLV conversion for + 1. Seller's Name + 2. VAT Number + 3. Time Stamp + 4. Invoice Amount + 5. VAT Amount + ''' + tlv_array = [] + # Sellers Name + + seller_name = frappe.db.get_value( + 'Company', + doc.company, + 'company_name_in_arabic') + + if not seller_name: + frappe.throw(_('Arabic name missing for {} in the company document').format(doc.company)) + + tag = bytes([1]).hex() + length = bytes([len(seller_name.encode('utf-8'))]).hex() + value = seller_name.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # VAT Number + tax_id = frappe.db.get_value('Company', doc.company, 'tax_id') + if not tax_id: + frappe.throw(_('Tax ID missing for {} in the company document').format(doc.company)) + + tag = bytes([2]).hex() + length = bytes([len(tax_id)]).hex() + value = tax_id.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # Time Stamp + posting_date = getdate(doc.posting_date) + time = get_time(doc.posting_time) + seconds = time.hour * 60 * 60 + time.minute * 60 + time.second + time_stamp = add_to_date(posting_date, seconds=seconds) + time_stamp = time_stamp.strftime('%Y-%m-%dT%H:%M:%SZ') + + tag = bytes([3]).hex() + length = bytes([len(time_stamp)]).hex() + value = time_stamp.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # Invoice Amount + invoice_amount = str(doc.grand_total) + tag = bytes([4]).hex() + length = bytes([len(invoice_amount)]).hex() + value = invoice_amount.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # VAT Amount + vat_amount = str(doc.total_taxes_and_charges) + + tag = bytes([5]).hex() + length = bytes([len(vat_amount)]).hex() + value = vat_amount.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # Joining bytes into one + tlv_buff = ''.join(tlv_array) + + # base64 conversion for QR Code + base64_string = b64encode(bytes.fromhex(tlv_buff)).decode() + + qr_image = io.BytesIO() + url = qr_create(base64_string, error='L') + url.png(qr_image, scale=2, quiet_zone=1) + + name = frappe.generate_hash(doc.name, 5) + + # making file + filename = f"QRCode-{name}.png".replace(os.path.sep, "__") + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "is_private": 0, + "content": qr_image.getvalue(), + "attached_to_doctype": doc.get("doctype"), + "attached_to_name": doc.get("name"), + "attached_to_field": "ksa_einv_qr" + }) + + _file.save() + + # assigning to document + doc.db_set('ksa_einv_qr', _file.file_url) + doc.notify_update() + + +def delete_qr_code_file(doc, method=None): + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + if hasattr(doc, 'ksa_einv_qr'): + if doc.get('ksa_einv_qr'): + file_doc = frappe.get_list('File', { + 'file_url': doc.get('ksa_einv_qr') + }) + if len(file_doc): + frappe.delete_doc('File', file_doc[0].name) + +def delete_vat_settings_for_company(doc, method=None): + if doc.country != 'Saudi Arabia': + return + + if frappe.db.exists('KSA VAT Setting', doc.name): + frappe.delete_doc('KSA VAT Setting', doc.name) diff --git a/erpnext/healthcare/doctype/lab_prescription/__init__.py b/erpnext/regional/saudi_arabia/wizard/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/lab_prescription/__init__.py rename to erpnext/regional/saudi_arabia/wizard/__init__.py diff --git a/erpnext/healthcare/doctype/lab_test/__init__.py b/erpnext/regional/saudi_arabia/wizard/data/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/lab_test/__init__.py rename to erpnext/regional/saudi_arabia/wizard/data/__init__.py diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json new file mode 100644 index 00000000000..60951a9ceca --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json @@ -0,0 +1,47 @@ +[ + { + "type": "Sales Account", + "accounts": [ + { + "title": "Standard rated Sales", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Zero rated domestic sales", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted sales", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Exempted" + } + ] + }, + { + "type": "Purchase Account", + "accounts": [ + { + "title": "Standard rated domestic purchases", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Imports subject to VAT paid at customs", + "item_tax_template": "KSA Excise 50%", + "account": "Excise 50%" + }, + { + "title": "Zero rated purchases", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted purchases", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Exempted" + } + ] + } +] \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test_group_template/__init__.py b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/lab_test_group_template/__init__.py rename to erpnext/regional/saudi_arabia/wizard/operations/__init__.py diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py new file mode 100644 index 00000000000..97300dc3782 --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -0,0 +1,43 @@ +import json +import os + +import frappe + + +def create_ksa_vat_setting(company): + """On creation of first company. Creates KSA VAT Setting""" + + company = frappe.get_doc('Company', company) + + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') + with open(file_path, 'r') as json_file: + account_data = json.load(json_file) + + # Creating KSA VAT Setting + ksa_vat_setting = frappe.get_doc({ + 'doctype': 'KSA VAT Setting', + 'company': company.name + }) + + for data in account_data: + if data['type'] == 'Sales Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_sales_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + elif data['type'] == 'Purchase Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_purchase_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + ksa_vat_setting.save() diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py index 8a75987c3d7..6af135b960a 100644 --- a/erpnext/regional/south_africa/setup.py +++ b/erpnext/regional/south_africa/setup.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields diff --git a/erpnext/regional/turkey/setup.py b/erpnext/regional/turkey/setup.py index 2396aab91f5..1d3770aefcb 100644 --- a/erpnext/regional/turkey/setup.py +++ b/erpnext/regional/turkey/setup.py @@ -1,4 +1,2 @@ -from __future__ import unicode_literals - def setup(company=None, patch=True): pass diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index bd12d661f00..922443b924a 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, os, json +import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property from erpnext.payroll.doctype.gratuity_rule.gratuity_rule import get_gratuity_rule diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 7d5fd6ecf86..f350ec4ec2c 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -1,10 +1,10 @@ -from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import flt, money_in_words, round_based_on_smallest_currency_fraction + import erpnext -from frappe.utils import flt, round_based_on_smallest_currency_fraction, money_in_words from erpnext.controllers.taxes_and_totals import get_itemised_tax -from six import iteritems + def update_itemised_tax_data(doc): if not doc.taxes: return @@ -21,7 +21,7 @@ def update_itemised_tax_data(doc): # First check if tax rate is present # If not then look up in item_wise_tax_detail if item_tax_rate: - for account, rate in iteritems(item_tax_rate): + for account, rate in item_tax_rate.items(): tax_rate += rate elif row.item_code and itemised_tax.get(row.item_code): tax_rate = sum([tax.get('tax_rate', 0) for d, tax in itemised_tax.get(row.item_code).items()]) diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py index 24ab1cf049f..db6a9c35475 100644 --- a/erpnext/regional/united_states/setup.py +++ b/erpnext/regional/united_states/setup.py @@ -1,11 +1,18 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe +import os +import json +from frappe.permissions import add_permission, update_permission_property from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def setup(company=None, patch=True): + # Company independent fixtures should be called only once at the first company setup + if frappe.db.count('Company', {'country': 'United States'}) <=1: + setup_company_independent_fixtures(patch=patch) + +def setup_company_independent_fixtures(company=None, patch=True): make_custom_fields() add_print_formats() diff --git a/erpnext/regional/united_states/test_united_states.py b/erpnext/regional/united_states/test_united_states.py index 513570ed6df..652b4835a18 100644 --- a/erpnext/regional/united_states/test_united_states.py +++ b/erpnext/regional/united_states/test_united_states.py @@ -1,8 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + import unittest + +import frappe + from erpnext.regional.report.irs_1099.irs_1099 import execute as execute_1099_report diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.py b/erpnext/restaurant/doctype/restaurant/restaurant.py index 0bb7b692c75..67838d29a37 100644 --- a/erpnext/restaurant/doctype/restaurant/restaurant.py +++ b/erpnext/restaurant/doctype/restaurant/restaurant.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class Restaurant(Document): pass diff --git a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py b/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py index adce5c73352..bfdd052753e 100644 --- a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py +++ b/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'restaurant', diff --git a/erpnext/restaurant/doctype/restaurant/test_restaurant.py b/erpnext/restaurant/doctype/restaurant/test_restaurant.py index 3ba7f5785eb..f88f9801290 100644 --- a/erpnext/restaurant/doctype/restaurant/test_restaurant.py +++ b/erpnext/restaurant/doctype/restaurant/test_restaurant.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest test_records = [ diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py index 952c46769b7..64eb40f3645 100644 --- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py +++ b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class RestaurantMenu(Document): def validate(self): for d in self.items: diff --git a/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py index 29f95fd8b1f..27020eb869f 100644 --- a/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py +++ b/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest test_records = [ dict(doctype='Item', item_code='Food Item 1', diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py b/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py index cc86bb3165e..98b245edece 100644 --- a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py +++ b/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class RestaurantMenuItem(Document): pass diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py index 357deaac007..f9e75b47a0f 100644 --- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py +++ b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py @@ -1,13 +1,16 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, json -from frappe.model.document import Document + +import json + +import frappe from frappe import _ +from frappe.model.document import Document + from erpnext.controllers.queries import item_query + class RestaurantOrderEntry(Document): pass diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py b/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py index e0c051b1ad7..0d9c236c0ea 100644 --- a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py +++ b/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class RestaurantOrderEntryItem(Document): pass diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py index f96de44c3d6..02ffaf6c20b 100644 --- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py +++ b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document + from datetime import timedelta + +from frappe.model.document import Document from frappe.utils import get_datetime + class RestaurantReservation(Document): def validate(self): if not self.reservation_end_time: diff --git a/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py b/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py index 71681b2f183..11a3541bd56 100644 --- a/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py +++ b/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestRestaurantReservation(unittest.TestCase): pass diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py index d5ea9d53981..29f8a1a12b1 100644 --- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py +++ b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe, re + +import re + from frappe.model.document import Document from frappe.model.naming import make_autoname + class RestaurantTable(Document): def autoname(self): prefix = re.sub('-+', '-', self.restaurant.replace(' ', '-')) diff --git a/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py b/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py index ffdb6f742a3..00d14d2bb2a 100644 --- a/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py +++ b/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest test_records = [ diff --git a/erpnext/selling/doctype/__init__.py b/erpnext/selling/doctype/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/__init__.py +++ b/erpnext/selling/doctype/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/customer/__init__.py b/erpnext/selling/doctype/customer/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/customer/__init__.py +++ b/erpnext/selling/doctype/customer/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 2f06e988804..107e4a4759d 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -111,20 +111,20 @@ frappe.ui.form.on("Customer", { } frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'} - frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); if(!frm.doc.__islocal) { frappe.contacts.render_address_and_contact(frm); // custom buttons - frm.add_custom_button(__('Accounting Ledger'), function() { - frappe.set_route('query-report', 'General Ledger', - {party_type:'Customer', party:frm.doc.name}); - }); - frm.add_custom_button(__('Accounts Receivable'), function() { + frm.add_custom_button(__('Accounts Receivable'), function () { frappe.set_route('query-report', 'Accounts Receivable', {customer:frm.doc.name}); - }); + }, __('View')); + + frm.add_custom_button(__('Accounting Ledger'), function () { + frappe.set_route('query-report', 'General Ledger', + {party_type: 'Customer', party: frm.doc.name}); + }, __('View')); frm.add_custom_button(__('Pricing Rule'), function () { erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name); @@ -134,6 +134,12 @@ frappe.ui.form.on("Customer", { frm.trigger("get_customer_group_details"); }, __('Actions')); + if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) { + frm.add_custom_button(__('Link with Supplier'), function () { + frm.trigger('show_party_link_dialog'); + }, __('Actions')); + } + // indicator erpnext.utils.set_party_dashboard_indicators(frm); @@ -158,5 +164,42 @@ frappe.ui.form.on("Customer", { } }); + }, + show_party_link_dialog: function(frm) { + const dialog = new frappe.ui.Dialog({ + title: __('Select a Supplier'), + fields: [{ + fieldtype: 'Link', label: __('Supplier'), + options: 'Supplier', fieldname: 'supplier', reqd: 1 + }], + primary_action: function({ supplier }) { + frappe.call({ + method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link', + args: { + primary_role: 'Customer', + primary_party: frm.doc.name, + secondary_party: supplier + }, + freeze: true, + callback: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Successfully linked to Supplier'), + alert: true + }); + }, + error: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Linking to Supplier Failed. Please try again.'), + title: __('Linking Failed'), + indicator: 'red' + }); + } + }); + }, + primary_action_label: __('Create Link') + }); + dialog.show(); } }); diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 0d839fc8228..ae406306170 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -16,7 +16,6 @@ "customer_name", "gender", "customer_type", - "pan", "tax_withholding_category", "default_bank_account", "lead_name", @@ -268,6 +267,7 @@ "options": "fa fa-map-marker" }, { + "depends_on": "eval: !doc.__islocal", "fieldname": "address_html", "fieldtype": "HTML", "label": "Address HTML", @@ -284,6 +284,7 @@ "width": "50%" }, { + "depends_on": "eval: !doc.__islocal", "fieldname": "contact_html", "fieldtype": "HTML", "label": "Contact HTML", @@ -484,11 +485,6 @@ "fieldtype": "Check", "label": "Allow Sales Invoice Creation Without Delivery Note" }, - { - "fieldname": "pan", - "fieldtype": "Data", - "label": "PAN" - }, { "fieldname": "tax_withholding_category", "fieldtype": "Link", @@ -508,12 +504,19 @@ "idx": 363, "image_field": "image", "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-08-25 18:56:09.929905", + "links": [ + { + "group": "Allowed Items", + "link_doctype": "Party Specific Item", + "link_fieldname": "party" + } + ], + "modified": "2021-10-20 22:07:52.485809", "modified_by": "Administrator", "module": "Selling", "name": "Customer", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index abf146c43f4..0c8c53aabe3 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -1,21 +1,30 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + import json -from frappe.model.naming import set_name_by_naming_series -from frappe import _, msgprint + +import frappe import frappe.defaults -from frappe.utils import flt, cint, cstr, today, get_formatted_email +from frappe import _, msgprint +from frappe.contacts.address_and_contact import ( + delete_contact_and_address, + load_address_and_contact, +) from frappe.desk.reportview import build_match_conditions, get_filters_cond -from erpnext.utilities.transaction_base import TransactionBase -from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this -from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address -from frappe.model.rename_doc import update_linked_doctypes from frappe.model.mapper import get_mapped_doc +from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options +from frappe.model.rename_doc import update_linked_doctypes +from frappe.utils import cint, cstr, flt, get_formatted_email, today from frappe.utils.user import get_users_with_role +from erpnext.accounts.party import ( # noqa + get_dashboard_info, + get_timeline_data, + validate_party_accounts, +) +from erpnext.utilities.transaction_base import TransactionBase + class Customer(TransactionBase): def get_feed(self): @@ -34,8 +43,10 @@ class Customer(TransactionBase): cust_master_name = frappe.defaults.get_global_default('cust_master_name') if cust_master_name == 'Customer Name': self.name = self.get_customer_name() - else: + elif cust_master_name == 'Naming Series': set_name_by_naming_series(self) + else: + self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self) def get_customer_name(self): @@ -150,8 +161,14 @@ class Customer(TransactionBase): self.db_set('email_id', self.email_id) def create_primary_address(self): + from frappe.contacts.doctype.address.address import get_address_display + if self.flags.is_new_doc and self.get('address_line1'): - make_address(self) + address = make_address(self) + address_display = get_address_display(address.name) + + self.db_set("customer_primary_address", address.name) + self.db_set("primary_address", address_display) def update_lead_status(self): '''If Customer created from Lead, update lead status to "Converted" @@ -246,9 +263,15 @@ class Customer(TransactionBase): def on_trash(self): if self.customer_primary_contact: - frappe.db.sql("""update `tabCustomer` - set customer_primary_contact=null, mobile_no=null, email_id=null - where name=%s""", self.name) + frappe.db.sql(""" + UPDATE `tabCustomer` + SET + customer_primary_contact=null, + customer_primary_address=null, + mobile_no=null, + email_id=null, + primary_address=null + WHERE name=%(name)s""", {"name": self.name}) delete_contact_and_address('Customer', self.name) if self.lead_name: @@ -444,11 +467,14 @@ def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None): def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, extra_amount=0): + credit_limit = get_credit_limit(customer, company) + if not credit_limit: + return + customer_outstanding = get_customer_outstanding(customer, company, ignore_outstanding_sales_order) if extra_amount > 0: customer_outstanding += flt(extra_amount) - credit_limit = get_credit_limit(customer, company) if credit_limit > 0 and flt(customer_outstanding) > credit_limit: msgprint(_("Credit limit has been crossed for customer {0} ({1}/{2})") .format(customer, customer_outstanding, credit_limit)) diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index 532c11b86e4..58394d0acb7 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from frappe import _ diff --git a/erpnext/selling/doctype/customer/test_customer.js b/erpnext/selling/doctype/customer/test_customer.js deleted file mode 100644 index 65b81af32c1..00000000000 --- a/erpnext/selling/doctype/customer/test_customer.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Customer", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Customer - () => frappe.tests.make('Customer', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index b1a5b52f963..7d6b74d0665 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -1,15 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe import unittest -from erpnext.accounts.party import get_due_date +import frappe from frappe.test_runner import make_test_records -from erpnext.exceptions import PartyFrozen, PartyDisabled from frappe.utils import flt + +from erpnext.accounts.party import get_due_date +from erpnext.exceptions import PartyDisabled, PartyFrozen from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding from erpnext.tests.utils import create_test_contact_and_address @@ -17,7 +17,7 @@ test_ignore = ["Price List"] test_dependencies = ['Payment Term', 'Payment Terms Template'] test_records = frappe.get_test_records('Customer') -from six import iteritems + class TestCustomer(unittest.TestCase): def setUp(self): @@ -89,7 +89,7 @@ class TestCustomer(unittest.TestCase): details = get_party_details("_Test Customer") - for key, value in iteritems(to_check): + for key, value in to_check.items(): val = details.get(key) if not val and not isinstance(val, list): val = None @@ -253,10 +253,10 @@ class TestCustomer(unittest.TestCase): return get_customer_outstanding('_Test Customer', '_Test Company') def test_customer_credit_limit(self): - from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice - from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note outstanding_amt = self.get_customer_outstanding_amount() credit_limit = get_credit_limit('_Test Customer', '_Test Company') @@ -352,3 +352,26 @@ def set_credit_limit(customer, company, credit_limit): 'credit_limit': credit_limit }) customer.credit_limits[-1].db_insert() + +def create_internal_customer(customer_name, represents_company, allowed_to_interact_with): + if not frappe.db.exists("Customer", customer_name): + customer = frappe.get_doc({ + "doctype": "Customer", + "customer_group": "_Test Customer Group", + "customer_name": customer_name, + "customer_type": "Individual", + "territory": "_Test Territory", + "is_internal_customer": 1, + "represents_company": represents_company + }) + + customer.append("companies", { + "company": allowed_to_interact_with + }) + + customer.insert() + customer_name = customer.name + else: + customer_name = frappe.db.get_value("Customer", customer_name) + + return customer_name diff --git a/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.py b/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.py index 60a4a9a5d25..193027b89a4 100644 --- a/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.py +++ b/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class CustomerCreditLimit(Document): pass diff --git a/erpnext/selling/doctype/industry_type/__init__.py b/erpnext/selling/doctype/industry_type/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/industry_type/__init__.py +++ b/erpnext/selling/doctype/industry_type/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/industry_type/industry_type.py b/erpnext/selling/doctype/industry_type/industry_type.py index 7a30d6524a0..fbe0131dd31 100644 --- a/erpnext/selling/doctype/industry_type/industry_type.py +++ b/erpnext/selling/doctype/industry_type/industry_type.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class IndustryType(Document): pass diff --git a/erpnext/selling/doctype/industry_type/test_industry_type.py b/erpnext/selling/doctype/industry_type/test_industry_type.py index ebc6366155e..250c2bec485 100644 --- a/erpnext/selling/doctype/industry_type/test_industry_type.py +++ b/erpnext/selling/doctype/industry_type/test_industry_type.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Industry Type') diff --git a/erpnext/selling/doctype/installation_note/__init__.py b/erpnext/selling/doctype/installation_note/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/installation_note/__init__.py +++ b/erpnext/selling/doctype/installation_note/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/installation_note/installation_note.py b/erpnext/selling/doctype/installation_note/installation_note.py index ffcbb2daf09..36acdbea612 100644 --- a/erpnext/selling/doctype/installation_note/installation_note.py +++ b/erpnext/selling/doctype/installation_note/installation_note.py @@ -1,16 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe +import frappe +from frappe import _ from frappe.utils import cstr, getdate -from frappe import _ from erpnext.stock.utils import get_valid_serial_nos - from erpnext.utilities.transaction_base import TransactionBase + class InstallationNote(TransactionBase): def __init__(self, *args, **kwargs): super(InstallationNote, self).__init__(*args, **kwargs) diff --git a/erpnext/selling/doctype/installation_note/test_installation_note.py b/erpnext/selling/doctype/installation_note/test_installation_note.py index 553d070da0f..d3c8be53574 100644 --- a/erpnext/selling/doctype/installation_note/test_installation_note.py +++ b/erpnext/selling/doctype/installation_note/test_installation_note.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Installation Note') diff --git a/erpnext/selling/doctype/installation_note_item/__init__.py b/erpnext/selling/doctype/installation_note_item/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/installation_note_item/__init__.py +++ b/erpnext/selling/doctype/installation_note_item/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/installation_note_item/installation_note_item.py b/erpnext/selling/doctype/installation_note_item/installation_note_item.py index 7e1205231bb..2169a7b6144 100644 --- a/erpnext/selling/doctype/installation_note_item/installation_note_item.py +++ b/erpnext/selling/doctype/installation_note_item/installation_note_item.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class InstallationNoteItem(Document): pass diff --git a/erpnext/healthcare/doctype/lab_test_sample/__init__.py b/erpnext/selling/doctype/party_specific_item/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/lab_test_sample/__init__.py rename to erpnext/selling/doctype/party_specific_item/__init__.py diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.js b/erpnext/selling/doctype/party_specific_item/party_specific_item.js new file mode 100644 index 00000000000..077b93631ec --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Party Specific Item', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.json b/erpnext/selling/doctype/party_specific_item/party_specific_item.json new file mode 100644 index 00000000000..32b5d478bb5 --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.json @@ -0,0 +1,77 @@ +{ + "actions": [], + "creation": "2021-08-27 19:28:07.559978", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "party_type", + "party", + "column_break_3", + "restrict_based_on", + "based_on_value" + ], + "fields": [ + { + "fieldname": "party_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Party Type", + "options": "Customer\nSupplier", + "reqd": 1 + }, + { + "fieldname": "party", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Party Name", + "options": "party_type", + "reqd": 1 + }, + { + "fieldname": "restrict_based_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Restrict Items Based On", + "options": "Item\nItem Group\nBrand", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "based_on_value", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Based On Value", + "options": "restrict_based_on", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-09-14 13:27:58.612334", + "modified_by": "Administrator", + "module": "Selling", + "name": "Party Specific Item", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "party", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.py b/erpnext/selling/doctype/party_specific_item/party_specific_item.py new file mode 100644 index 00000000000..a408af56420 --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class PartySpecificItem(Document): + def validate(self): + exists = frappe.db.exists({ + 'doctype': 'Party Specific Item', + 'party_type': self.party_type, + 'party': self.party, + 'restrict_based_on': self.restrict_based_on, + 'based_on': self.based_on_value, + }) + if exists: + frappe.throw(_("This item filter has already been applied for the {0}").format(self.party_type)) diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py new file mode 100644 index 00000000000..874a3645929 --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py @@ -0,0 +1,38 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import unittest + +import frappe + +from erpnext.controllers.queries import item_query + +test_dependencies = ['Item', 'Customer', 'Supplier'] + +def create_party_specific_item(**args): + psi = frappe.new_doc("Party Specific Item") + psi.party_type = args.get('party_type') + psi.party = args.get('party') + psi.restrict_based_on = args.get('restrict_based_on') + psi.based_on_value = args.get('based_on_value') + psi.insert() + +class TestPartySpecificItem(unittest.TestCase): + def setUp(self): + self.customer = frappe.get_last_doc("Customer") + self.supplier = frappe.get_last_doc("Supplier") + self.item = frappe.get_last_doc("Item") + + def test_item_query_for_customer(self): + create_party_specific_item(party_type='Customer', party=self.customer.name, restrict_based_on='Item', based_on_value=self.item.name) + filters = {'is_sales_item': 1, 'customer': self.customer.name} + items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False) + for item in items: + self.assertEqual(item[0], self.item.name) + + def test_item_query_for_supplier(self): + create_party_specific_item(party_type='Supplier', party=self.supplier.name, restrict_based_on='Item Group', based_on_value=self.item.item_group) + filters = {'supplier': self.supplier.name, 'is_purchase_item': 1} + items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False) + for item in items: + self.assertEqual(item[2], self.item.item_group) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index ae3482f402b..2bb876e6d0f 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -1,14 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe +import frappe +from frappe import _ +from frappe.model.document import Document from frappe.utils import get_link_to_form -from frappe import _ - -from frappe.model.document import Document class ProductBundle(Document): def autoname(self): diff --git a/erpnext/selling/doctype/product_bundle/test_product_bundle.py b/erpnext/selling/doctype/product_bundle/test_product_bundle.py index 7d1d372b111..c1e2fdee8b8 100644 --- a/erpnext/selling/doctype/product_bundle/test_product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/test_product_bundle.py @@ -1,10 +1,8 @@ - # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Product Bundle') def make_product_bundle(parent, items, qty=None): diff --git a/erpnext/selling/doctype/product_bundle_item/product_bundle_item.py b/erpnext/selling/doctype/product_bundle_item/product_bundle_item.py index 8721bfad866..5c95a555c8f 100644 --- a/erpnext/selling/doctype/product_bundle_item/product_bundle_item.py +++ b/erpnext/selling/doctype/product_bundle_item/product_bundle_item.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe.model.document import Document + class ProductBundleItem(Document): pass diff --git a/erpnext/selling/doctype/quotation/__init__.py b/erpnext/selling/doctype/quotation/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/quotation/__init__.py +++ b/erpnext/selling/doctype/quotation/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 12234495493..0e1a915deb7 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -18,6 +18,8 @@ frappe.ui.form.on('Quotation', { } }); + frm.set_df_property('packed_items', 'cannot_add_rows', true); + frm.set_df_property('packed_items', 'cannot_delete_rows', true); }, refresh: function(frm) { diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 3eba62bc193..ee5b0ea760a 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -43,6 +43,8 @@ "ignore_pricing_rule", "items_section", "items", + "bundle_items_section", + "packed_items", "pricing_rule_details", "pricing_rules", "sec_break23", @@ -108,7 +110,8 @@ "enq_det", "supplier_quotation", "opportunity", - "lost_reasons" + "lost_reasons", + "competitors" ], "fields": [ { @@ -926,17 +929,43 @@ "label": "Lost Reasons", "options": "Quotation Lost Reason Detail", "read_only": 1 + }, + { + "depends_on": "packed_items", + "fieldname": "packed_items", + "fieldtype": "Table", + "label": "Bundle Items", + "options": "Packed Item", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "packed_items", + "depends_on": "packed_items", + "fieldname": "bundle_items_section", + "fieldtype": "Section Break", + "label": "Bundle Items", + "options": "fa fa-suitcase", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "competitors", + "fieldtype": "Table MultiSelect", + "label": "Competitors", + "options": "Competitor Detail", + "read_only": 1 } ], "icon": "fa fa-shopping-cart", "idx": 82, "is_submittable": 1, "links": [], - "max_attachments": 1, - "modified": "2020-10-30 13:58:59.212060", + "modified": "2021-11-30 01:33:21.106073", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index e4f8a475816..c4752aebb5a 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -1,11 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe.model.mapper import get_mapped_doc -from frappe.utils import flt, nowdate, getdate from frappe import _ +from frappe.model.mapper import get_mapped_doc +from frappe.utils import flt, getdate, nowdate from erpnext.controllers.selling_controller import SellingController @@ -31,6 +31,9 @@ class Quotation(SellingController): if self.items: self.with_items = 1 + from erpnext.stock.doctype.packed_item.packed_item import make_packing_list + make_packing_list(self) + def validate_valid_till(self): if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date): frappe.throw(_("Valid till date cannot be before transaction date")) @@ -65,7 +68,7 @@ class Quotation(SellingController): opp.set_status(status=status, update=True) @frappe.whitelist() - def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None): + def declare_enquiry_lost(self, lost_reasons_list, competitors, detailed_reason=None): if not self.has_sales_order(): get_lost_reasons = frappe.get_list('Quotation Lost Reason', fields = ["name"]) @@ -81,6 +84,9 @@ class Quotation(SellingController): else: frappe.throw(_("Invalid lost reason {0}, please create a new lost reason").format(frappe.bold(reason.get('lost_reason')))) + for competitor in competitors: + self.append('competitors', competitor) + self.update_opportunity('Lost') self.update_lead() self.save() diff --git a/erpnext/selling/doctype/quotation/quotation_dashboard.py b/erpnext/selling/doctype/quotation/quotation_dashboard.py index d1bb788937b..0a1aad7bb6e 100644 --- a/erpnext/selling/doctype/quotation/quotation_dashboard.py +++ b/erpnext/selling/doctype/quotation/quotation_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'prevdoc_docname', diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 527a999aefa..aa83726304f 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -1,10 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + +import unittest import frappe -from frappe.utils import flt, add_days, nowdate, add_months, getdate -import unittest +from frappe.utils import add_days, add_months, flt, getdate, nowdate test_dependencies = ["Product Bundle"] @@ -133,8 +133,10 @@ class TestQuotation(unittest.TestCase): def test_create_quotation_with_margin(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order - from erpnext.selling.doctype.sales_order.sales_order \ - import make_delivery_note, make_sales_invoice + from erpnext.selling.doctype.sales_order.sales_order import ( + make_delivery_note, + make_sales_invoice, + ) rate_with_margin = flt((1500*18.75)/100 + 1500) @@ -226,9 +228,190 @@ class TestQuotation(unittest.TestCase): expired_quotation.reload() self.assertEqual(expired_quotation.status, "Expired") + def test_product_bundle_mapping_on_creating_so(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.selling.doctype.quotation.quotation import make_sales_order + from erpnext.stock.doctype.item.test_item import make_item + + make_item("_Test Product Bundle", {"is_stock_item": 0}) + make_item("_Test Bundle Item 1", {"is_stock_item": 1}) + make_item("_Test Bundle Item 2", {"is_stock_item": 1}) + + make_product_bundle("_Test Product Bundle", + ["_Test Bundle Item 1", "_Test Bundle Item 2"]) + + quotation = make_quotation(item_code="_Test Product Bundle", qty=1, rate=100) + sales_order = make_sales_order(quotation.name) + + quotation_item = [quotation.items[0].item_code, quotation.items[0].rate, quotation.items[0].qty, quotation.items[0].amount] + so_item = [sales_order.items[0].item_code, sales_order.items[0].rate, sales_order.items[0].qty, sales_order.items[0].amount] + + self.assertEqual(quotation_item, so_item) + + quotation_packed_items = [ + [quotation.packed_items[0].parent_item, quotation.packed_items[0].item_code, quotation.packed_items[0].qty], + [quotation.packed_items[1].parent_item, quotation.packed_items[1].item_code, quotation.packed_items[1].qty] + ] + so_packed_items = [ + [sales_order.packed_items[0].parent_item, sales_order.packed_items[0].item_code, sales_order.packed_items[0].qty], + [sales_order.packed_items[1].parent_item, sales_order.packed_items[1].item_code, sales_order.packed_items[1].qty] + ] + + self.assertEqual(quotation_packed_items, so_packed_items) + + def test_product_bundle_price_calculation_when_calculate_bundle_price_is_unchecked(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import make_item + + make_item("_Test Product Bundle", {"is_stock_item": 0}) + bundle_item1 = make_item("_Test Bundle Item 1", {"is_stock_item": 1}) + bundle_item2 = make_item("_Test Bundle Item 2", {"is_stock_item": 1}) + + make_product_bundle("_Test Product Bundle", + ["_Test Bundle Item 1", "_Test Bundle Item 2"]) + + bundle_item1.valuation_rate = 100 + bundle_item1.save() + + bundle_item2.valuation_rate = 200 + bundle_item2.save() + + quotation = make_quotation(item_code="_Test Product Bundle", qty=2, rate=100) + self.assertEqual(quotation.items[0].amount, 200) + + def test_product_bundle_price_calculation_when_calculate_bundle_price_is_checked(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import make_item + + make_item("_Test Product Bundle", {"is_stock_item": 0}) + make_item("_Test Bundle Item 1", {"is_stock_item": 1}) + make_item("_Test Bundle Item 2", {"is_stock_item": 1}) + + make_product_bundle("_Test Product Bundle", + ["_Test Bundle Item 1", "_Test Bundle Item 2"]) + + enable_calculate_bundle_price() + + quotation = make_quotation(item_code="_Test Product Bundle", qty=2, rate=100, do_not_submit=1) + quotation.packed_items[0].rate = 100 + quotation.packed_items[1].rate = 200 + quotation.save() + + self.assertEqual(quotation.items[0].amount, 600) + self.assertEqual(quotation.items[0].rate, 300) + + enable_calculate_bundle_price(enable=0) + + def test_product_bundle_price_calculation_for_multiple_product_bundles_when_calculate_bundle_price_is_checked(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import make_item + + make_item("_Test Product Bundle 1", {"is_stock_item": 0}) + make_item("_Test Product Bundle 2", {"is_stock_item": 0}) + make_item("_Test Bundle Item 1", {"is_stock_item": 1}) + make_item("_Test Bundle Item 2", {"is_stock_item": 1}) + make_item("_Test Bundle Item 3", {"is_stock_item": 1}) + + make_product_bundle("_Test Product Bundle 1", + ["_Test Bundle Item 1", "_Test Bundle Item 2"]) + make_product_bundle("_Test Product Bundle 2", + ["_Test Bundle Item 2", "_Test Bundle Item 3"]) + + enable_calculate_bundle_price() + + item_list = [ + { + "item_code": "_Test Product Bundle 1", + "warehouse": "", + "qty": 1, + "rate": 400, + "delivered_by_supplier": 1, + "supplier": '_Test Supplier' + }, + { + "item_code": "_Test Product Bundle 2", + "warehouse": "", + "qty": 1, + "rate": 400, + "delivered_by_supplier": 1, + "supplier": '_Test Supplier' + } + ] + + quotation = make_quotation(item_list=item_list, do_not_submit=1) + quotation.packed_items[0].rate = 100 + quotation.packed_items[1].rate = 200 + quotation.packed_items[2].rate = 200 + quotation.packed_items[3].rate = 300 + quotation.save() + + expected_values = [300, 500] + + for item in quotation.items: + self.assertEqual(item.amount, expected_values[item.idx-1]) + + enable_calculate_bundle_price(enable=0) + + def test_packed_items_indices_are_reset_when_product_bundle_is_deleted_from_items_table(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import make_item + + make_item("_Test Product Bundle 1", {"is_stock_item": 0}) + make_item("_Test Product Bundle 2", {"is_stock_item": 0}) + make_item("_Test Product Bundle 3", {"is_stock_item": 0}) + make_item("_Test Bundle Item 1", {"is_stock_item": 1}) + make_item("_Test Bundle Item 2", {"is_stock_item": 1}) + make_item("_Test Bundle Item 3", {"is_stock_item": 1}) + + make_product_bundle("_Test Product Bundle 1", + ["_Test Bundle Item 1", "_Test Bundle Item 2"]) + make_product_bundle("_Test Product Bundle 2", + ["_Test Bundle Item 2", "_Test Bundle Item 3"]) + make_product_bundle("_Test Product Bundle 3", + ["_Test Bundle Item 3", "_Test Bundle Item 1"]) + + item_list = [ + { + "item_code": "_Test Product Bundle 1", + "warehouse": "", + "qty": 1, + "rate": 400, + "delivered_by_supplier": 1, + "supplier": '_Test Supplier' + }, + { + "item_code": "_Test Product Bundle 2", + "warehouse": "", + "qty": 1, + "rate": 400, + "delivered_by_supplier": 1, + "supplier": '_Test Supplier' + }, + { + "item_code": "_Test Product Bundle 3", + "warehouse": "", + "qty": 1, + "rate": 400, + "delivered_by_supplier": 1, + "supplier": '_Test Supplier' + } + ] + + quotation = make_quotation(item_list=item_list, do_not_submit=1) + del quotation.items[1] + quotation.save() + + for id, item in enumerate(quotation.packed_items): + expected_index = id + 1 + self.assertEqual(item.idx, expected_index) test_records = frappe.get_test_records('Quotation') +def enable_calculate_bundle_price(enable=1): + selling_settings = frappe.get_doc("Selling Settings") + selling_settings.editable_bundle_item_rates = enable + selling_settings.save() + def get_quotation_dict(party_name=None, item_code=None): if not party_name: party_name = '_Test Customer' diff --git a/erpnext/selling/doctype/quotation_item/__init__.py b/erpnext/selling/doctype/quotation_item/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/quotation_item/__init__.py +++ b/erpnext/selling/doctype/quotation_item/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.py b/erpnext/selling/doctype/quotation_item/quotation_item.py index 7384871ed44..8c2aabbf3fa 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.py +++ b/erpnext/selling/doctype/quotation_item/quotation_item.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class QuotationItem(Document): pass diff --git a/erpnext/selling/doctype/sales_order/__init__.py b/erpnext/selling/doctype/sales_order/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/sales_order/__init__.py +++ b/erpnext/selling/doctype/sales_order/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index b42c6153129..79e9e17e414 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -43,6 +43,9 @@ frappe.ui.form.on("Sales Order", { } } }); + + frm.set_df_property('packed_items', 'cannot_add_rows', true); + frm.set_df_property('packed_items', 'cannot_delete_rows', true); }, refresh: function(frm) { if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed' @@ -75,6 +78,8 @@ frappe.ui.form.on("Sales Order", { }); erpnext.queries.setup_warehouse_query(frm); + + frm.ignore_doctypes_on_cancel_all = ['Purchase Order']; }, delivery_date: function(frm) { @@ -314,7 +319,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex title: __('Select Items to Manufacture'), fields: fields, primary_action: function() { - var data = d.get_values(); + var data = {items: d.fields_dict.items.grid.get_selected_children()}; me.frm.call({ method: 'make_work_orders', args: { diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 38ea5c81d49..7e99a062439 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -55,6 +55,8 @@ "items_section", "scan_barcode", "items", + "packing_list", + "packed_items", "pricing_rule_details", "pricing_rules", "section_break_31", @@ -101,8 +103,6 @@ "in_words", "advance_paid", "disable_rounded_total", - "packing_list", - "packed_items", "payment_schedule_section", "payment_terms_template", "payment_schedule", @@ -134,6 +134,7 @@ "sales_team_section_break", "sales_partner", "column_break7", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break1", @@ -1019,6 +1020,7 @@ { "collapsible": 1, "collapsible_depends_on": "packed_items", + "depends_on": "packed_items", "fieldname": "packing_list", "fieldtype": "Section Break", "hide_days": 1, @@ -1029,14 +1031,14 @@ "print_hide": 1 }, { + "depends_on": "packed_items", "fieldname": "packed_items", "fieldtype": "Table", "hide_days": 1, "hide_seconds": 1, "label": "Packed Items", "options": "Packed Item", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "payment_schedule_section", @@ -1479,6 +1481,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1505,16 +1508,23 @@ "fieldtype": "Small Text", "label": "Dispatch Address", "read_only": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-08-17 20:15:26.531553", + "modified": "2021-10-05 12:16:40.775704", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index bba54018aef..cc951850a4a 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1,25 +1,31 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + import json + +import frappe import frappe.utils -from frappe.utils import cstr, flt, getdate, cint, nowdate, add_days, get_link_to_form, strip_html from frappe import _ -from six import string_types -from frappe.model.utils import get_fetch_values -from frappe.model.mapper import get_mapped_doc -from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty -from frappe.desk.notifications import clear_doctype_notifications from frappe.contacts.doctype.address.address import get_company_address +from frappe.desk.notifications import clear_doctype_notifications +from frappe.model.mapper import get_mapped_doc +from frappe.model.utils import get_fetch_values +from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html + +from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + unlink_inter_company_doc, + update_linked_doc, + validate_inter_company_party, +) from erpnext.controllers.selling_controller import SellingController +from erpnext.manufacturing.doctype.production_plan.production_plan import ( + get_items_for_material_requests, +) from erpnext.selling.doctype.customer.customer import check_credit_limit -from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults -from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests -from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ - unlink_inter_company_doc +from erpnext.stock.doctype.item.item import get_item_defaults +from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -57,6 +63,8 @@ class SalesOrder(SellingController): if not self.billing_status: self.billing_status = 'Not Billed' if not self.delivery_status: self.delivery_status = 'Not Delivered' + self.reset_default_field_value("set_warehouse", "items", "warehouse") + def validate_po(self): # validate p.o date v/s delivery date if self.po_date and not self.skip_delivery_note: @@ -102,7 +110,7 @@ class SalesOrder(SellingController): if self.order_type == 'Sales' and not self.skip_delivery_note: delivery_date_list = [d.delivery_date for d in self.get("items") if d.delivery_date] max_delivery_date = max(delivery_date_list) if delivery_date_list else None - if not self.delivery_date: + if (max_delivery_date and not self.delivery_date) or (max_delivery_date and getdate(self.delivery_date) != getdate(max_delivery_date)): self.delivery_date = max_delivery_date if self.delivery_date: for d in self.get("items"): @@ -111,8 +119,6 @@ class SalesOrder(SellingController): if getdate(self.transaction_date) > getdate(d.delivery_date): frappe.msgprint(_("Expected Delivery Date should be after Sales Order Date"), indicator='orange', title=_('Warning')) - if getdate(self.delivery_date) != getdate(max_delivery_date): - self.delivery_date = max_delivery_date else: frappe.throw(_("Please enter Delivery Date")) @@ -219,60 +225,15 @@ class SalesOrder(SellingController): check_credit_limit(self.customer, self.company) def check_nextdoc_docstatus(self): - # Checks Delivery Note - submit_dn = frappe.db.sql_list(""" - select t1.name - from `tabDelivery Note` t1,`tabDelivery Note Item` t2 - where t1.name = t2.parent and t2.against_sales_order = %s and t1.docstatus = 1""", self.name) - - if submit_dn: - submit_dn = [get_link_to_form("Delivery Note", dn) for dn in submit_dn] - frappe.throw(_("Delivery Notes {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(submit_dn))) - - # Checks Sales Invoice - submit_rv = frappe.db.sql_list("""select t1.name + linked_invoices = frappe.db.sql_list("""select distinct t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 - where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus < 2""", + where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 0""", self.name) - if submit_rv: - submit_rv = [get_link_to_form("Sales Invoice", si) for si in submit_rv] - frappe.throw(_("Sales Invoice {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(submit_rv))) - - #check maintenance schedule - submit_ms = frappe.db.sql_list(""" - select t1.name - from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2 - where t2.parent=t1.name and t2.sales_order = %s and t1.docstatus = 1""", self.name) - - if submit_ms: - submit_ms = [get_link_to_form("Maintenance Schedule", ms) for ms in submit_ms] - frappe.throw(_("Maintenance Schedule {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(submit_ms))) - - # check maintenance visit - submit_mv = frappe.db.sql_list(""" - select t1.name - from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 - where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1""",self.name) - - if submit_mv: - submit_mv = [get_link_to_form("Maintenance Visit", mv) for mv in submit_mv] - frappe.throw(_("Maintenance Visit {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(submit_mv))) - - # check work order - pro_order = frappe.db.sql_list(""" - select name - from `tabWork Order` - where sales_order = %s and docstatus = 1""", self.name) - - if pro_order: - pro_order = [get_link_to_form("Work Order", po) for po in pro_order] - frappe.throw(_("Work Order {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(pro_order))) + if linked_invoices: + linked_invoices = [get_link_to_form("Sales Invoice", si) for si in linked_invoices] + frappe.throw(_("Sales Invoice {0} must be deleted before cancelling this Sales Order") + .format(", ".join(linked_invoices))) def check_modified_date(self): mod_db = frappe.db.get_value("Sales Order", self.name, "modified") @@ -718,8 +679,7 @@ def make_maintenance_schedule(source_name, target_doc=None): "doctype": "Maintenance Schedule Item", "field_map": { "parent": "sales_order" - }, - "add_if_empty": True + } } }, target_doc) @@ -745,8 +705,7 @@ def make_maintenance_visit(source_name, target_doc=None): "field_map": { "parent": "prevdoc_docname", "parenttype": "prevdoc_doctype" - }, - "add_if_empty": True + } } }, target_doc) @@ -787,7 +746,7 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t """Creates Purchase Order for each Supplier. Returns a list of doc objects.""" if not selected_items: return - if isinstance(selected_items, string_types): + if isinstance(selected_items, str): selected_items = json.loads(selected_items) def set_missing_values(source, target): @@ -888,7 +847,7 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t def make_purchase_order(source_name, selected_items=None, target_doc=None): if not selected_items: return - if isinstance(selected_items, string_types): + if isinstance(selected_items, str): selected_items = json.loads(selected_items) items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')] @@ -949,11 +908,53 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): "pricing_rules" ], "postprocess": update_item, - "condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.item_code in items_to_map + "condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.item_code in items_to_map and not is_product_bundle(doc.item_code) + }, + "Packed Item": { + "doctype": "Purchase Order Item", + "field_map": [ + ["parent", "sales_order"], + ["uom", "uom"], + ["conversion_factor", "conversion_factor"], + ["parent_item", "product_bundle"], + ["rate", "rate"] + ], + "field_no_map": [ + "price_list_rate", + "item_tax_template", + "discount_percentage", + "discount_amount", + "supplier", + "pricing_rules" + ], + "condition": lambda doc: doc.parent_item in items_to_map } }, target_doc, set_missing_values) + + set_delivery_date(doc.items, source_name) + return doc +def set_delivery_date(items, sales_order): + delivery_dates = frappe.get_all( + 'Sales Order Item', + filters = { + 'parent': sales_order + }, + fields = ['delivery_date', 'item_code'] + ) + + delivery_by_item = frappe._dict() + for date in delivery_dates: + delivery_by_item[date.item_code] = date.delivery_date + + for item in items: + if item.product_bundle: + item.schedule_date = delivery_by_item[item.product_bundle] + +def is_product_bundle(item_code): + return frappe.db.exists('Product Bundle', item_code) + @frappe.whitelist() def make_work_orders(items, sales_order, company, project=None): '''Make Work Orders against the given Sales Order for the given `items`''' @@ -979,6 +980,7 @@ def make_work_orders(items, sales_order, company, project=None): description=i['description'] )).insert() work_order.set_work_order_operations() + work_order.flags.ignore_mandatory = True work_order.save() out.append(work_order) @@ -1001,7 +1003,7 @@ def make_raw_material_request(items, company, sales_order, project=None): if not frappe.has_permission("Sales Order", "write"): frappe.throw(_("Not permitted"), frappe.PermissionError) - if isinstance(items, string_types): + if isinstance(items, str): items = frappe._dict(json.loads(items)) for item in items.get('items'): diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py index 2a71c27009f..1e616b87b22 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py +++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'fieldname': 'sales_order', diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.js b/erpnext/selling/doctype/sales_order/test_sales_order.js deleted file mode 100644 index 57ed19b6965..00000000000 --- a/erpnext/selling/doctype/sales_order/test_sales_order.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Sales Order", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Sales Order', [ - // insert a new Sales Order - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index d685fbff82b..2a0752e56aa 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1,21 +1,34 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import json import unittest + import frappe import frappe.permissions -from frappe.utils import flt, add_days, nowdate, getdate from frappe.core.doctype.user_permission.test_user_permission import create_user -from erpnext.selling.doctype.sales_order.sales_order \ - import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired -from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -from erpnext.selling.doctype.sales_order.sales_order import make_work_orders +from frappe.utils import add_days, flt, getdate, nowdate + from erpnext.controllers.accounts_controller import update_child_qty_rate -from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request +from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( + make_maintenance_schedule, +) +from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import ( + make_maintenance_visit, +) from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle +from erpnext.selling.doctype.sales_order.sales_order import ( + WarehouseRequired, + make_delivery_note, + make_material_request, + make_raw_material_request, + make_sales_invoice, + make_work_orders, +) from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + class TestSalesOrder(unittest.TestCase): @@ -162,8 +175,8 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.get("items")[0].delivered_qty, 9) # Make return deliver note, sales invoice and check quantity - from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-3, do_not_submit=True) dn1.items[0].against_sales_order = so.name @@ -444,8 +457,8 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) def test_update_child_with_precision(self): - from frappe.model.meta import get_field_precision from frappe.custom.doctype.property_setter.property_setter import make_property_setter + from frappe.model.meta import get_field_precision precision = get_field_precision(frappe.get_meta("Sales Order Item").get_field("rate")) @@ -731,9 +744,11 @@ class TestSalesOrder(unittest.TestCase): frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1) def test_drop_shipping(self): - from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier, \ - update_status as so_update_status from erpnext.buying.doctype.purchase_order.purchase_order import update_status + from erpnext.selling.doctype.sales_order.sales_order import ( + make_purchase_order_for_default_supplier, + ) + from erpnext.selling.doctype.sales_order.sales_order import update_status as so_update_status # make items po_item = make_item("_Test Item for Drop Shipping", {"is_stock_item": 1, "delivered_by_supplier": 1}) @@ -815,8 +830,10 @@ class TestSalesOrder(unittest.TestCase): so.cancel() def test_drop_shipping_partial_order(self): - from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier, \ - update_status as so_update_status + from erpnext.selling.doctype.sales_order.sales_order import ( + make_purchase_order_for_default_supplier, + ) + from erpnext.selling.doctype.sales_order.sales_order import update_status as so_update_status # make items po_item1 = make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1}) @@ -869,7 +886,9 @@ class TestSalesOrder(unittest.TestCase): def test_drop_shipping_full_for_default_suppliers(self): """Test if multiple POs are generated in one go against different default suppliers.""" - from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier + from erpnext.selling.doctype.sales_order.sales_order import ( + make_purchase_order_for_default_supplier, + ) if not frappe.db.exists("Item", "_Test Item for Drop Shipping 1"): make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1}) @@ -906,6 +925,38 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(purchase_orders[0].supplier, '_Test Supplier') self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1') + def test_product_bundles_in_so_are_replaced_with_bundle_items_in_po(self): + """ + Tests if the the Product Bundles in the Items table of Sales Orders are replaced with + their child items(from the Packed Items table) on creating a Purchase Order from it. + """ + from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order + + product_bundle = make_item("_Test Product Bundle", {"is_stock_item": 0}) + make_item("_Test Bundle Item 1", {"is_stock_item": 1}) + make_item("_Test Bundle Item 2", {"is_stock_item": 1}) + + make_product_bundle("_Test Product Bundle", + ["_Test Bundle Item 1", "_Test Bundle Item 2"]) + + so_items = [ + { + "item_code": product_bundle.item_code, + "warehouse": "", + "qty": 2, + "rate": 400, + "delivered_by_supplier": 1, + "supplier": '_Test Supplier' + } + ] + + so = make_sales_order(item_list=so_items) + + purchase_order = make_purchase_order(so.name, selected_items=so_items) + + self.assertEqual(purchase_order.items[0].item_code, "_Test Bundle Item 1") + self.assertEqual(purchase_order.items[1].item_code, "_Test Bundle Item 2") + def test_reserved_qty_for_closing_so(self): bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, fields=["reserved_qty"]) @@ -1043,8 +1094,7 @@ class TestSalesOrder(unittest.TestCase): }] }) so.submit() - from erpnext.manufacturing.doctype.work_order.test_work_order import \ - make_wo_order_test_record + from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record work_order = make_wo_order_test_record(item=item.item_code, qty=1, do_not_save=True) work_order.fg_warehouse = "_Test Warehouse - _TC" @@ -1052,8 +1102,9 @@ class TestSalesOrder(unittest.TestCase): work_order.submit() make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1) item_serial_no = frappe.get_doc("Serial No", {"item_code": item.item_code}) - from erpnext.manufacturing.doctype.work_order.work_order import \ - make_stock_entry as make_production_stock_entry + from erpnext.manufacturing.doctype.work_order.work_order import ( + make_stock_entry as make_production_stock_entry, + ) se = frappe.get_doc(make_production_stock_entry(work_order.name, "Manufacture", 1)) se.submit() reserved_serial_no = se.get("items")[2].serial_no @@ -1085,8 +1136,9 @@ class TestSalesOrder(unittest.TestCase): si = make_sales_invoice(so.name) si.update_stock = 0 si.submit() - from erpnext.accounts.doctype.sales_invoice.sales_invoice import \ - make_delivery_note as make_delivery_note_from_invoice + from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + make_delivery_note as make_delivery_note_from_invoice, + ) dn = make_delivery_note_from_invoice(si.name) dn.save() dn.submit() @@ -1123,6 +1175,7 @@ class TestSalesOrder(unittest.TestCase): def test_cancel_sales_order_after_cancel_payment_entry(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + # make a sales order so = make_sales_order() @@ -1231,8 +1284,76 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, so.cancel) + def test_so_cancellation_after_si_submission(self): + """ + Test to check if Sales Order gets cancelled when linked Sales Invoice has been Submitted + Expected result: Sales Order should not get cancelled + """ + so = make_sales_order() + so.submit() + si = make_sales_invoice(so.name) + si.submit() + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + + def test_so_cancellation_after_dn_submission(self): + """ + Test to check if Sales Order gets cancelled when linked Delivery Note has been Submitted + Expected result: Sales Order should not get cancelled + """ + so = make_sales_order() + so.submit() + dn = make_delivery_note(so.name) + dn.submit() + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + + def test_so_cancellation_after_maintenance_schedule_submission(self): + """ + Expected result: Sales Order should not get cancelled + """ + so = make_sales_order() + so.submit() + ms = make_maintenance_schedule() + ms.items[0].sales_order = so.name + ms.submit() + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + + def test_so_cancellation_after_maintenance_visit_submission(self): + """ + Expected result: Sales Order should not get cancelled + """ + so = make_sales_order() + so.submit() + mv = make_maintenance_visit() + mv.purposes[0].prevdoc_doctype = "Sales Order" + mv.purposes[0].prevdoc_docname = so.name + mv.submit() + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + + def test_so_cancellation_after_work_order_submission(self): + """ + Expected result: Sales Order should not get cancelled + """ + from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record + + so = make_sales_order(item_code="_Test FG Item", qty=10) + so.submit() + make_wo_order_test_record(sales_order=so.name) + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): - from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( + create_payment_terms_template, + ) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice automatically_fetch_payment_terms() @@ -1332,7 +1453,6 @@ def make_sales_order_workflow(): frappe.get_doc(dict(doctype='Role', role_name='Test Junior Approver')).insert(ignore_if_duplicate=True) frappe.get_doc(dict(doctype='Role', role_name='Test Approver')).insert(ignore_if_duplicate=True) - frappe.db.commit() frappe.cache().hdel('roles', frappe.session.user) workflow = frappe.get_doc({ diff --git a/erpnext/selling/doctype/sales_order_item/__init__.py b/erpnext/selling/doctype/sales_order_item/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/sales_order_item/__init__.py +++ b/erpnext/selling/doctype/sales_order_item/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 1e5590e7489..95f6c4e96df 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -48,6 +48,7 @@ "pricing_rules", "stock_uom_rate", "is_free_item", + "grant_commission", "section_break_24", "net_rate", "net_amount", @@ -789,15 +790,23 @@ "no_copy": 1, "options": "currency", "read_only": 1 + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 01:15:05.803091", + "modified": "2021-10-05 12:27:25.014789", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.py b/erpnext/selling/doctype/sales_order_item/sales_order_item.py index 62afef3e170..441a6ac9709 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.py +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.py @@ -1,11 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe +import frappe from frappe.model.document import Document + class SalesOrderItem(Document): pass diff --git a/erpnext/selling/doctype/sales_partner_type/sales_partner_type.py b/erpnext/selling/doctype/sales_partner_type/sales_partner_type.py index 68d289f0189..0a07073af05 100644 --- a/erpnext/selling/doctype/sales_partner_type/sales_partner_type.py +++ b/erpnext/selling/doctype/sales_partner_type/sales_partner_type.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class SalesPartnerType(Document): pass diff --git a/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js b/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js deleted file mode 100644 index 3ed7b46e1d9..00000000000 --- a/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Sales Partner Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Sales Partner Type - () => frappe.tests.make('Sales Partner Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.py b/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.py index fb8f8b0417a..04d40898254 100644 --- a/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.py +++ b/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestSalesPartnerType(unittest.TestCase): pass diff --git a/erpnext/selling/doctype/sales_team/__init__.py b/erpnext/selling/doctype/sales_team/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/sales_team/__init__.py +++ b/erpnext/selling/doctype/sales_team/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/sales_team/sales_team.json b/erpnext/selling/doctype/sales_team/sales_team.json index 876789135c8..cac5b763ffd 100644 --- a/erpnext/selling/doctype/sales_team/sales_team.json +++ b/erpnext/selling/doctype/sales_team/sales_team.json @@ -1,247 +1,100 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-04-19 13:30:51", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2013-04-19 13:30:51", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "sales_person", + "contact_no", + "allocated_percentage", + "allocated_amount", + "commission_rate", + "incentives" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_person", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Sales Person", - "length": 0, - "no_copy": 0, - "oldfieldname": "sales_person", - "oldfieldtype": "Link", - "options": "Sales Person", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fieldname": "sales_person", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sales Person", + "oldfieldname": "sales_person", + "oldfieldtype": "Link", + "options": "Sales Person", + "print_width": "200px", + "reqd": 1, + "search_index": 1, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_no", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Contact No.", - "length": 0, - "no_copy": 0, - "oldfieldname": "contact_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fieldname": "contact_no", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "Contact No.", + "oldfieldname": "contact_no", + "oldfieldtype": "Data", + "print_width": "100px", "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allocated_percentage", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Contribution (%)", - "length": 0, - "no_copy": 0, - "oldfieldname": "allocated_percentage", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fieldname": "allocated_percentage", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Contribution (%)", + "oldfieldname": "allocated_percentage", + "oldfieldtype": "Currency", + "print_width": "100px", "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allocated_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Contribution to Net Total", - "length": 0, - "no_copy": 0, - "oldfieldname": "allocated_amount", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Contribution to Net Total", + "oldfieldname": "allocated_amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_width": "120px", + "read_only": 1, "width": "120px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "commission_rate", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Commission Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "sales_person.commission_rate", + "fetch_if_empty": 1, + "fieldname": "commission_rate", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Commission Rate", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "incentives", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Incentives", - "length": 0, - "no_copy": 0, - "oldfieldname": "incentives", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "allow_on_submit": 1, + "fieldname": "incentives", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Incentives", + "oldfieldname": "incentives", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-09-17 13:03:14.755974", - "modified_by": "Administrator", - "module": "Selling", - "name": "Sales Team", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-11-09 23:55:20.670475", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Team", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_team/sales_team.py b/erpnext/selling/doctype/sales_team/sales_team.py index 28bea254d68..d3eae3a5978 100644 --- a/erpnext/selling/doctype/sales_team/sales_team.py +++ b/erpnext/selling/doctype/sales_team/sales_team.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class SalesTeam(Document): pass diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.js b/erpnext/selling/doctype/selling_settings/selling_settings.js index d8d30515f8f..cf6fb2806ee 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.js +++ b/erpnext/selling/doctype/selling_settings/selling_settings.js @@ -6,26 +6,3 @@ frappe.ui.form.on('Selling Settings', { } }); - -frappe.tour['Selling Settings'] = [ - { - fieldname: "cust_master_name", - title: "Customer Naming By", - description: __("By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a ") + "Naming Series" + __(" choose the 'Naming Series' option."), - }, - { - fieldname: "selling_price_list", - title: "Default Selling Price List", - description: __("Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.") - }, - { - fieldname: "so_required", - title: "Sales Order Required for Sales Invoice & Delivery Note Creation", - description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice or Delivery Note without creating a Sales Order first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Sales Order' checkbox in the Customer master.") - }, - { - fieldname: "dn_required", - title: "Delivery Note Required for Sales Invoice Creation", - description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.") - } -]; diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 717fd9b92e3..27bc541d62f 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -11,24 +11,20 @@ "customer_group", "column_break_4", "territory", - "crm_settings_section", - "campaign_naming_by", - "default_valid_till", - "column_break_9", - "close_opportunity_after_days", "item_price_settings_section", "selling_price_list", + "maintain_same_rate_action", + "role_to_override_stop_action", "column_break_15", "maintain_same_sales_rate", - "maintain_same_rate_action", "editable_price_list_rate", "validate_selling_price", + "editable_bundle_item_rates", "sales_transactions_settings_section", "so_required", "dn_required", "sales_update_frequency", "column_break_5", - "role_to_override_stop_action", "allow_multiple_items", "allow_against_multiple_purchase_orders", "hide_tax_id" @@ -40,14 +36,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Customer Naming By", - "options": "Customer Name\nNaming Series" - }, - { - "fieldname": "campaign_naming_by", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Campaign Naming By", - "options": "Campaign Name\nNaming Series" + "options": "Customer Name\nNaming Series\nAuto Name" }, { "fieldname": "customer_group", @@ -70,18 +59,6 @@ "label": "Default Price List", "options": "Price List" }, - { - "default": "15", - "description": "Auto close Opportunity after the no. of days mentioned above", - "fieldname": "close_opportunity_after_days", - "fieldtype": "Int", - "label": "Close Opportunity After Days" - }, - { - "fieldname": "default_valid_till", - "fieldtype": "Data", - "label": "Default Quotation Validity Days" - }, { "fieldname": "column_break_5", "fieldtype": "Column Break" @@ -146,15 +123,14 @@ { "default": "Stop", "depends_on": "maintain_same_sales_rate", - "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.", "fieldname": "maintain_same_rate_action", "fieldtype": "Select", - "label": "Action if Same Rate is Not Maintained", + "label": "Action if Same Rate is Not Maintained Throughout Sales Cycle", "mandatory_depends_on": "maintain_same_sales_rate", "options": "Stop\nWarn" }, { - "depends_on": "eval: doc.maintain_same_rate_action == 'Stop'", + "depends_on": "eval: doc.maintain_same_sales_rate && doc.maintain_same_rate_action == 'Stop'", "fieldname": "role_to_override_stop_action", "fieldtype": "Link", "label": "Role Allowed to Override Stop Action", @@ -169,15 +145,6 @@ "fieldname": "column_break_4", "fieldtype": "Column Break" }, - { - "fieldname": "crm_settings_section", - "fieldtype": "Section Break", - "label": "CRM Settings" - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, { "fieldname": "item_price_settings_section", "fieldtype": "Section Break", @@ -191,6 +158,12 @@ "fieldname": "sales_transactions_settings_section", "fieldtype": "Section Break", "label": "Transaction Settings" + }, + { + "default": "0", + "fieldname": "editable_bundle_item_rates", + "fieldtype": "Check", + "label": "Calculate Product Bundle Price based on Child Items' Rates" } ], "icon": "fa fa-cog", @@ -198,7 +171,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-08-06 22:25:50.119458", + "modified": "2021-09-13 12:32:17.004404", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index b219e7ecce0..fb86e614b6c 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -3,18 +3,17 @@ # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -import frappe.defaults -from frappe.utils import cint -from frappe.custom.doctype.property_setter.property_setter import make_property_setter -from frappe.utils.nestedset import get_root_of +import frappe +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.model.document import Document +from frappe.utils import cint + class SellingSettings(Document): def on_update(self): self.toggle_hide_tax_id() + self.toggle_editable_rate_for_bundle_items() def validate(self): for key in ["cust_master_name", "campaign_naming_by", "customer_group", "territory", @@ -33,8 +32,7 @@ class SellingSettings(Document): make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False) - def set_default_customer_group_and_territory(self): - if not self.customer_group: - self.customer_group = get_root_of('Customer Group') - if not self.territory: - self.territory = get_root_of('Territory') + def toggle_editable_rate_for_bundle_items(self): + editable_bundle_item_rates = cint(self.editable_bundle_item_rates) + + make_property_setter("Packed Item", "rate", "read_only", not(editable_bundle_item_rates), "Check", validate_fields_for_doctype=False) diff --git a/erpnext/selling/doctype/selling_settings/test_selling_settings.py b/erpnext/selling/doctype/selling_settings/test_selling_settings.py index 961a54de0a8..fc6754a7c59 100644 --- a/erpnext/selling/doctype/selling_settings/test_selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/test_selling_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestSellingSettings(unittest.TestCase): pass diff --git a/erpnext/selling/doctype/sms_center/__init__.py b/erpnext/selling/doctype/sms_center/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/doctype/sms_center/__init__.py +++ b/erpnext/selling/doctype/sms_center/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/sms_center/sms_center.py b/erpnext/selling/doctype/sms_center/sms_center.py index 87846a84d30..d192457ee07 100644 --- a/erpnext/selling/doctype/sms_center/sms_center.py +++ b/erpnext/selling/doctype/sms_center/sms_center.py @@ -1,15 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe - -from frappe.utils import cstr -from frappe import msgprint, _ - -from frappe.model.document import Document - +from frappe import _, msgprint from frappe.core.doctype.sms_settings.sms_settings import send_sms +from frappe.model.document import Document +from frappe.utils import cstr + class SMSCenter(Document): @frappe.whitelist() diff --git a/erpnext/selling/form_tour/sales_order/sales_order.json b/erpnext/selling/form_tour/sales_order/sales_order.json new file mode 100644 index 00000000000..a81eb4a043b --- /dev/null +++ b/erpnext/selling/form_tour/sales_order/sales_order.json @@ -0,0 +1,97 @@ +{ + "creation": "2021-06-29 21:13:36.089054", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-06-29 21:13:36.089054", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Order", + "owner": "Administrator", + "reference_doctype": "Sales Order", + "save_on_complete": 1, + "steps": [ + { + "description": "Select a customer.", + "field": "", + "fieldname": "customer", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Customer", + "next_step_condition": "customer", + "parent_field": "", + "position": "Right", + "title": "Select Customer" + }, + { + "description": "You can add items here.", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "position": "Bottom", + "title": "List of items" + }, + { + "child_doctype": "Sales Order Item", + "description": "Select an item.", + "field": "", + "fieldname": "item_code", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 1, + "label": "Item Code", + "next_step_condition": "eval: doc.item_code", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Select Item" + }, + { + "child_doctype": "Sales Order Item", + "description": "Enter quantity.", + "field": "", + "fieldname": "qty", + "fieldtype": "Float", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Quantity", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Enter Quantity" + }, + { + "child_doctype": "Sales Order Item", + "description": "Enter rate of the item.", + "field": "", + "fieldname": "rate", + "fieldtype": "Currency", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Rate", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Enter Rate" + }, + { + "description": "You can add sales taxes and charges here.", + "field": "", + "fieldname": "taxes", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Sales Taxes and Charges", + "parent_field": "", + "position": "Bottom", + "title": "Add Sales Taxes and Charges" + } + ], + "title": "Sales Order" +} \ No newline at end of file diff --git a/erpnext/selling/form_tour/selling_settings/selling_settings.json b/erpnext/selling/form_tour/selling_settings/selling_settings.json new file mode 100644 index 00000000000..20c718f8c0d --- /dev/null +++ b/erpnext/selling/form_tour/selling_settings/selling_settings.json @@ -0,0 +1,65 @@ +{ + "creation": "2021-06-29 20:39:19.408763", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-06-29 20:49:01.359489", + "modified_by": "Administrator", + "module": "Selling", + "name": "Selling Settings", + "owner": "Administrator", + "reference_doctype": "Selling Settings", + "save_on_complete": 0, + "steps": [ + { + "description": "By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a Naming Series. Choose the 'Naming Series' option.", + "field": "", + "fieldname": "cust_master_name", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Customer Naming By", + "parent_field": "", + "position": "Right", + "title": "Customer Naming By" + }, + { + "description": "If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice or Delivery Note without creating a Sales Order first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Sales Order' checkbox in the Customer master.", + "field": "", + "fieldname": "so_required", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Sales Order Required for Sales Invoice & Delivery Note Creation?", + "parent_field": "", + "position": "Left", + "title": "Sales Order Required for Sales Invoice & Delivery Note Creation" + }, + { + "description": "If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.", + "field": "", + "fieldname": "dn_required", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Delivery Note Required for Sales Invoice Creation?", + "parent_field": "", + "position": "Left", + "title": "Delivery Note Required for Sales Invoice Creation" + }, + { + "description": "Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.", + "field": "", + "fieldname": "selling_price_list", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Price List", + "parent_field": "", + "position": "Right", + "title": "Default Selling Price List" + } + ], + "title": "Selling Settings" +} \ No newline at end of file diff --git a/erpnext/selling/module_onboarding/selling/selling.json b/erpnext/selling/module_onboarding/selling/selling.json index 160208ff680..c02f444b059 100644 --- a/erpnext/selling/module_onboarding/selling/selling.json +++ b/erpnext/selling/module_onboarding/selling/selling.json @@ -19,32 +19,17 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/selling", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:37.669753", + "modified": "2021-08-24 18:12:38.052434", "modified_by": "Administrator", "module": "Selling", "name": "Selling", "owner": "Administrator", "steps": [ - { - "step": "Introduction to Selling" - }, - { - "step": "Create a Customer" - }, - { - "step": "Setup your Warehouse" - }, - { - "step": "Create a Product" - }, - { - "step": "Create a Quotation" - }, - { - "step": "Create your first Sales Order" - }, { "step": "Selling Settings" + }, + { + "step": "Sales Order" } ], "subtitle": "Products, Sales, Analysis, and more.", diff --git a/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json index 5a403b06cf0..64defbfe3d3 100644 --- a/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json +++ b/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-06-01 13:16:19.731719", @@ -13,6 +12,7 @@ "name": "Create a Customer", "owner": "Administrator", "reference_document": "Customer", + "show_form_tour": 0, "show_full_form": 0, "title": "Create a Customer", "validate_action": 1 diff --git a/erpnext/selling/onboarding_step/create_a_product/create_a_product.json b/erpnext/selling/onboarding_step/create_a_product/create_a_product.json index d2068e167b7..52a5861551c 100644 --- a/erpnext/selling/onboarding_step/create_a_product/create_a_product.json +++ b/erpnext/selling/onboarding_step/create_a_product/create_a_product.json @@ -5,14 +5,14 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 18:30:02.489949", + "modified": "2020-10-14 14:53:00.133574", "modified_by": "Administrator", "name": "Create a Product", "owner": "Administrator", "reference_document": "Item", + "show_form_tour": 0, "show_full_form": 0, "title": "Create a Product", "validate_action": 1 diff --git a/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json b/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json index 27253d15b6c..6f1837e24cd 100644 --- a/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json +++ b/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-06-01 13:34:58.958641", @@ -13,6 +12,7 @@ "name": "Create a Quotation", "owner": "Administrator", "reference_document": "Quotation", + "show_form_tour": 0, "show_full_form": 1, "title": "Create a Quotation", "validate_action": 1 diff --git a/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json b/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json new file mode 100644 index 00000000000..378f295f0c0 --- /dev/null +++ b/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Create Sales Order", + "creation": "2021-06-29 21:22:54.204880", + "description": "A Sales Order is a confirmation of an order from your customer. It is also referred to as Proforma Invoice.\n\nSales Order at the heart of your sales and purchase transactions. Sales Orders are linked in Delivery Note, Sales Invoices, Material Request, and Maintenance transactions. Through Sales Order, you can track fulfillment of the overall deal towards the customer.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-06-29 21:22:54.204880", + "modified_by": "Administrator", + "name": "Create a Sales Order", + "owner": "Administrator", + "reference_document": "Sales Order", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Create a Sales Order", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json b/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json index d21c1f4954b..636453363bf 100644 --- a/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json +++ b/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json @@ -5,13 +5,13 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-06-01 13:29:13.703177", "modified_by": "Administrator", "name": "Introduction to Selling", "owner": "Administrator", + "show_form_tour": 0, "show_full_form": 0, "title": "Introduction to Selling", "validate_action": 1, diff --git a/erpnext/selling/onboarding_step/sales_order/sales_order.json b/erpnext/selling/onboarding_step/sales_order/sales_order.json new file mode 100644 index 00000000000..d616fae0660 --- /dev/null +++ b/erpnext/selling/onboarding_step/sales_order/sales_order.json @@ -0,0 +1,21 @@ +{ + "action": "Show Form Tour", + "action_label": "Let\u2019s convert your first Sales Order against a Quotation", + "creation": "2021-08-13 14:03:49.217866", + "description": "# Sales Order\n\nA Sales Order is a confirmation of an order from your customer. It is also referred to as Proforma Invoice.\n\nSales Order at the heart of your sales and purchase transactions. Sales Orders are linked in Delivery Note, Sales Invoices, Material Request, and Maintenance transactions. Through Sales Order, you can track fulfillment of the overall deal towards the customer.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-24 18:12:32.394992", + "modified_by": "Administrator", + "name": "Sales Order", + "owner": "Administrator", + "reference_document": "Sales Order", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Sales Order", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/selling/onboarding_step/selling_settings/selling_settings.json b/erpnext/selling/onboarding_step/selling_settings/selling_settings.json index 7996d7b1593..ec30fd30330 100644 --- a/erpnext/selling/onboarding_step/selling_settings/selling_settings.json +++ b/erpnext/selling/onboarding_step/selling_settings/selling_settings.json @@ -1,19 +1,21 @@ { "action": "Show Form Tour", + "action_label": "Let\u2019s walk-through Selling Settings", "creation": "2020-06-01 13:01:45.615189", + "description": "# Selling Settings\n\nCRM and Selling module\u2019s features are configurable as per your business needs. Selling Settings is the place where you can set your preferences for:\n - Customer naming and default values\n - Billing and shipping preference in sales transactions\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-06-01 13:04:14.980743", + "modified": "2021-08-24 18:11:21.264335", "modified_by": "Administrator", "name": "Selling Settings", "owner": "Administrator", "reference_document": "Selling Settings", + "show_form_tour": 0, "show_full_form": 0, - "title": "Configure Selling Settings.", + "title": "Selling Settings", "validate_action": 0 } \ No newline at end of file diff --git a/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json index 9457deee262..1e20eb0eba7 100644 --- a/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json +++ b/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json @@ -5,15 +5,15 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-07-04 12:33:16.970031", + "modified": "2020-10-14 14:53:25.538900", "modified_by": "Administrator", "name": "Setup your Warehouse", "owner": "Administrator", "path": "Tree/Warehouse", "reference_document": "Warehouse", + "show_form_tour": 0, "show_full_form": 0, "title": "Set up your Warehouse", "validate_action": 1 diff --git a/erpnext/selling/page/__init__.py b/erpnext/selling/page/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/selling/page/__init__.py +++ b/erpnext/selling/page/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 03c46bb2ae0..db5b20e3e19 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -1,12 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, json + +import json + +import frappe from frappe.utils.nestedset import get_root_of -from frappe.utils import cint -from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups + from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability +from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups + def search_by_term(search_term, warehouse, price_list): result = search_for_serial_or_batch_or_barcode_number(search_term) or {} @@ -209,7 +212,6 @@ def check_opening_entry(user): @frappe.whitelist() def create_opening_voucher(pos_profile, company, balance_details): - import json balance_details = json.loads(balance_details) new_pos_opening = frappe.get_doc({ diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 9d8338e5fed..4920584d95e 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -49,11 +49,11 @@ erpnext.PointOfSale.ItemCart = class { this.$component.append( `
      -
      Item Cart
      +
      ${__('Item Cart')}
      -
      Item
      -
      Qty
      -
      Amount
      +
      ${__('Item')}
      +
      ${__('Quantity')}
      +
      ${__('Amount')}
      @@ -78,7 +78,7 @@ erpnext.PointOfSale.ItemCart = class { make_no_items_placeholder() { this.$cart_header.css('display', 'none'); this.$cart_items_wrapper.html( - `
      No items in cart
      ` + `
      ${__('No items in cart')}
      ` ); } @@ -98,19 +98,23 @@ erpnext.PointOfSale.ItemCart = class { this.$totals_section.append( `
      - ${this.get_discount_icon()} Add Discount + ${this.get_discount_icon()} ${__('Add Discount')} +
      +
      +
      ${__('Total Items')}
      +
      0.00
      -
      Net Total
      +
      ${__("Net Total")}
      0.00
      -
      Grand Total
      +
      ${__('Grand Total')}
      0.00
      -
      Checkout
      -
      Edit Cart
      ` +
      ${__('Checkout')}
      +
      ${__('Edit Cart')}
      ` ) this.$add_discount_elem = this.$component.find(".add-discount-wrapper"); @@ -126,10 +130,10 @@ erpnext.PointOfSale.ItemCart = class { }, cols: 5, keys: [ - [ 1, 2, 3, 'Quantity' ], - [ 4, 5, 6, 'Discount' ], - [ 7, 8, 9, 'Rate' ], - [ '.', 0, 'Delete', 'Remove' ] + [ 1, 2, 3, __('Quantity') ], + [ 4, 5, 6, __('Discount') ], + [ 7, 8, 9, __('Rate') ], + [ '.', 0, __('Delete'), __('Remove') ] ], css_classes: [ [ '', '', '', 'col-span-2' ], @@ -142,13 +146,14 @@ erpnext.PointOfSale.ItemCart = class { this.$numpad_section.prepend( `
      +
      ` ) this.$numpad_section.append( - `
      Checkout
      ` + `
      ${__('Checkout')}
      ` ) } @@ -386,7 +391,7 @@ erpnext.PointOfSale.ItemCart = class { 'border': '1px dashed var(--gray-500)', 'padding': 'var(--padding-sm) var(--padding-md)' }); - me.$add_discount_elem.html(`${me.get_discount_icon()} Add Discount`); + me.$add_discount_elem.html(`${me.get_discount_icon()} ${__('Add Discount')}`); me.discount_field = undefined; } }, @@ -411,7 +416,7 @@ erpnext.PointOfSale.ItemCart = class { }); this.$add_discount_elem.html( `
      - ${this.get_discount_icon()} Additional ${String(discount).bold()}% discount applied + ${this.get_discount_icon()} ${__("Additional")} ${String(discount).bold()}% ${__("discount applied")}
      ` ); } @@ -445,7 +450,7 @@ erpnext.PointOfSale.ItemCart = class { function get_customer_description() { if (!email_id && !mobile_no) { - return `
      Click to add email / phone
      `; + return `
      ${__('Click to add email / phone')}
      `; } else if (email_id && !mobile_no) { return `
      ${email_id}
      `; } else if (mobile_no && !email_id) { @@ -470,6 +475,7 @@ erpnext.PointOfSale.ItemCart = class { if (!frm) frm = this.events.get_frm(); this.render_net_total(frm.doc.net_total); + this.render_total_item_qty(frm.doc.items); const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total; this.render_grand_total(grand_total); @@ -479,22 +485,37 @@ erpnext.PointOfSale.ItemCart = class { render_net_total(value) { const currency = this.events.get_frm().doc.currency; this.$totals_section.find('.net-total-container').html( - `
      Net Total
      ${format_currency(value, currency)}
      ` + `
      ${__('Net Total')}
      ${format_currency(value, currency)}
      ` ) this.$numpad_section.find('.numpad-net-total').html( - `
      Net Total: ${format_currency(value, currency)}
      ` + `
      ${__('Net Total')}: ${format_currency(value, currency)}
      ` + ); + } + + render_total_item_qty(items) { + var total_item_qty = 0; + items.map((item) => { + total_item_qty = total_item_qty + item.qty; + }); + + this.$totals_section.find('.item-qty-total-container').html( + `
      ${__('Total Quantity')}
      ${total_item_qty}
      ` + ); + + this.$numpad_section.find('.numpad-item-qty-total').html( + `
      ${__('Total Quantity')}: ${total_item_qty}
      ` ); } render_grand_total(value) { const currency = this.events.get_frm().doc.currency; this.$totals_section.find('.grand-total-container').html( - `
      Grand Total
      ${format_currency(value, currency)}
      ` + `
      ${__('Grand Total')}
      ${format_currency(value, currency)}
      ` ) this.$numpad_section.find('.numpad-grand-total').html( - `
      Grand Total: ${format_currency(value, currency)}
      ` + `
      ${__('Grand Total')}: ${format_currency(value, currency)}
      ` ); } @@ -502,6 +523,7 @@ erpnext.PointOfSale.ItemCart = class { if (taxes.length) { const currency = this.events.get_frm().doc.currency; const taxes_html = taxes.map(t => { + if (t.tax_amount_after_discount_amount == 0.0) return; const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; return `
      ${description}
      diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index d899c5c19b4..fb69b63f82a 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -28,7 +28,7 @@ erpnext.PointOfSale.ItemDetails = class { init_child_components() { this.$component.html( `
      -
      Item Details
      +
      ${__('Item Details')}
      @@ -201,8 +201,9 @@ erpnext.PointOfSale.ItemDetails = class { `
      ` ); } + const label = __('Auto Fetch Serial Numbers'); this.$form_container.append( - `
      Auto Fetch Serial Numbers
      ` + `
      ${label}
      ` ); this.$form_container.find('.serial_no-control').find('textarea').css('height', '6rem'); } @@ -236,7 +237,7 @@ erpnext.PointOfSale.ItemDetails = class { if (this.value) { me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => { me.item_stock_map = me.events.get_item_stock_map(); - const available_qty = me.item_stock_map[me.item_row.item_code][this.value]; + const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value]; if (available_qty === undefined) { me.events.get_available_stock(me.item_row.item_code, this.value).then(() => { // item stock map is updated now reset warehouse diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 8352b148acc..496385248c4 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -24,7 +24,7 @@ erpnext.PointOfSale.ItemSelector = class { this.wrapper.append( `
      -
      All Items
      +
      ${__('All Items')}
      diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js index e0993e2e342..a0475c70d0d 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -16,7 +16,7 @@ erpnext.PointOfSale.PastOrderList = class { this.wrapper.append( `
      -
      Recent Orders
      +
      ${__('Recent Orders')}
      diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index dd9e05a0e6d..eeb8523f19c 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -17,16 +17,16 @@ erpnext.PointOfSale.PastOrderSummary = class { this.wrapper.append( `
      - Select an invoice to load summary data + ${__('Select an invoice to load summary data')}
      -
      Items
      +
      ${__('Items')}
      -
      Totals
      +
      ${__('Totals')}
      -
      Payments
      +
      ${__('Payments')}
      @@ -82,7 +82,7 @@ erpnext.PointOfSale.PastOrderSummary = class { return `
      ${doc.customer}
      ${this.customer_email}
      -
      Sold by: ${doc.owner}
      +
      ${__('Sold by')}: ${doc.owner}
      @@ -121,7 +121,7 @@ erpnext.PointOfSale.PastOrderSummary = class { get_net_total_html(doc) { return `
      -
      Net Total
      +
      ${__('Net Total')}
      ${format_currency(doc.net_total, doc.currency)}
      `; } @@ -144,14 +144,14 @@ erpnext.PointOfSale.PastOrderSummary = class { get_grand_total_html(doc) { return `
      -
      Grand Total
      +
      ${__('Grand Total')}
      ${format_currency(doc.grand_total, doc.currency)}
      `; } get_payment_html(doc, payment) { return `
      -
      ${payment.mode_of_payment}
      +
      ${__(payment.mode_of_payment)}
      ${format_currency(payment.amount, doc.currency)}
      `; } @@ -285,8 +285,9 @@ erpnext.PointOfSale.PastOrderSummary = class { if (m.condition) { m.visible_btns.forEach(b => { const class_name = b.split(' ')[0].toLowerCase(); + const btn = __(b); this.$summary_btns.append( - `
      ${b}
      ` + `
      ${btn}
      ` ); }); } diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 63306adc6fa..b9b65591dc7 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -18,11 +18,11 @@ erpnext.PointOfSale.Payment = class { prepare_dom() { this.wrapper.append( `
      - +
      - +
      @@ -30,7 +30,7 @@ erpnext.PointOfSale.Payment = class {
      -
      Complete Order
      +
      ${__("Complete Order")}
      ` ); this.$component = this.wrapper.find('.payment-container'); @@ -253,41 +253,6 @@ erpnext.PointOfSale.Payment = class { } } - setup_listener_for_payments() { - frappe.realtime.on("process_phone_payment", (data) => { - const doc = this.events.get_frm().doc; - const { response, amount, success, failure_message } = data; - let message, title; - - if (success) { - title = __("Payment Received"); - if (amount >= doc.grand_total) { - frappe.dom.unfreeze(); - message = __("Payment of {0} received successfully.", [format_currency(amount, doc.currency, 0)]); - this.events.submit_invoice(); - cur_frm.reload_doc(); - - } else { - message = __("Payment of {0} received successfully. Waiting for other requests to complete...", [format_currency(amount, doc.currency, 0)]); - } - } else if (failure_message) { - message = failure_message; - title = __("Payment Failed"); - } - - frappe.msgprint({ "message": message, "title": title }); - }); - } - - auto_set_remaining_amount() { - const doc = this.events.get_frm().doc; - const remaining_amount = doc.grand_total - doc.paid_amount; - const current_value = this.selected_mode ? this.selected_mode.get_value() : undefined; - if (!current_value && remaining_amount > 0 && this.selected_mode) { - this.selected_mode.set_value(remaining_amount); - } - } - attach_shortcuts() { const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl'; this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`); @@ -332,6 +297,7 @@ erpnext.PointOfSale.Payment = class { this.render_payment_mode_dom(); this.make_invoice_fields_control(); this.update_totals_section(); + this.focus_on_default_mop(); } edit_cart() { @@ -413,17 +379,24 @@ erpnext.PointOfSale.Payment = class { }); this[`${mode}_control`].toggle_label(false); this[`${mode}_control`].set_value(p.amount); + }); + this.render_loyalty_points_payment_mode(); + + this.attach_cash_shortcuts(doc); + } + + focus_on_default_mop() { + const doc = this.events.get_frm().doc; + const payments = doc.payments; + payments.forEach(p => { + const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase(); if (p.default) { setTimeout(() => { this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click(); }, 500); } }); - - this.render_loyalty_points_payment_mode(); - - this.attach_cash_shortcuts(doc); } attach_cash_shortcuts(doc) { @@ -545,12 +518,12 @@ erpnext.PointOfSale.Payment = class { this.$totals.html( `
      -
      Grand Total
      +
      ${__('Grand Total')}
      ${format_currency(grand_total, currency)}
      -
      Paid Amount
      +
      ${__('Paid Amount')}
      ${format_currency(paid_amount, currency)}
      diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.py b/erpnext/selling/page/sales_funnel/sales_funnel.py index 78aaa49a668..a75108e4032 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.py +++ b/erpnext/selling/page/sales_funnel/sales_funnel.py @@ -1,12 +1,13 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe import _ -from erpnext.accounts.report.utils import convert +import frappe import pandas as pd +from frappe import _ + +from erpnext.accounts.report.utils import convert + def validate_filters(from_date, to_date, company): if from_date and to_date and (from_date >= to_date): diff --git a/erpnext/healthcare/doctype/lab_test_template/__init__.py b/erpnext/selling/print_format_field_template/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/lab_test_template/__init__.py rename to erpnext/selling/print_format_field_template/__init__.py diff --git a/erpnext/healthcare/doctype/lab_test_uom/__init__.py b/erpnext/selling/print_format_field_template/quotation_taxes/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/lab_test_uom/__init__.py rename to erpnext/selling/print_format_field_template/quotation_taxes/__init__.py diff --git a/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json b/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json new file mode 100644 index 00000000000..eaa17cedf05 --- /dev/null +++ b/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 15:48:56.416449", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Quotation", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:11:33.553722", + "modified_by": "Administrator", + "module": "Selling", + "name": "Quotation Taxes", + "owner": "Administrator", + "standard": 1, + "template": "", + "template_file": "templates/print_formats/includes/taxes_and_charges.html" +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/medical_code/__init__.py b/erpnext/selling/print_format_field_template/sales_order_taxes/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/medical_code/__init__.py rename to erpnext/selling/print_format_field_template/sales_order_taxes/__init__.py diff --git a/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json b/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json new file mode 100644 index 00000000000..2aacb440ff8 --- /dev/null +++ b/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 18:04:24.443076", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Sales Order", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:04:24.443076", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Order Taxes", + "owner": "Administrator", + "standard": 1, + "template": "", + "template_file": "templates/print_formats/includes/taxes_and_charges.html" +} \ No newline at end of file diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.py b/erpnext/selling/report/address_and_contacts/address_and_contacts.py index f295333322c..915f8dc3cfe 100644 --- a/erpnext/selling/report/address_and_contacts/address_and_contacts.py +++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.py @@ -1,11 +1,8 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from six.moves import range -from six import iteritems -import frappe +import frappe field_map = { "Contact": [ "first_name", "last_name", "phone", "mobile_no", "email_id", "is_primary_contact" ], @@ -67,7 +64,7 @@ def get_party_addresses_and_contact(party_type, party, party_group): party_details = get_party_details(party_type, party_list, "Address", party_details) party_details = get_party_details(party_type, party_list, "Contact", party_details) - for party, details in iteritems(party_details): + for party, details in party_details.items(): addresses = details.get("address", []) contacts = details.get("contact", []) if not any([addresses, contacts]): diff --git a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py index 5523bad5715..e702a51d0e7 100644 --- a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py +++ b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe.utils import flt + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index f15f63d7bb7..2426cbb0b55 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -1,12 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import calendar + import frappe from frappe import _ from frappe.utils import cint, cstr, getdate + def execute(filters=None): common_columns = [ { diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py index 6fb7666c2ce..777b02ca66d 100644 --- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py +++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py @@ -1,11 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.utils import flt -from erpnext.selling.doctype.customer.customer import get_customer_outstanding, get_credit_limit + +from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py index eb9273a5626..e5f93543209 100644 --- a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py +++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py @@ -1,13 +1,13 @@ # 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 erpnext import get_default_company from erpnext.accounts.party import get_party_details from erpnext.stock.get_item_details import get_price_list_rate_for -from frappe import _ def execute(filters=None): diff --git a/erpnext/selling/report/inactive_customers/inactive_customers.py b/erpnext/selling/report/inactive_customers/inactive_customers.py index e7aff36e828..d97e1c6dcb0 100644 --- a/erpnext/selling/report/inactive_customers/inactive_customers.py +++ b/erpnext/selling/report/inactive_customers/inactive_customers.py @@ -1,10 +1,11 @@ # 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.utils import cint from frappe import _ +from frappe.utils import cint + def execute(filters=None): if not filters: filters ={} diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 1700fc7bdd5..4a245e1f778 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -1,12 +1,13 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.utils import flt from frappe.utils.nestedset import get_descendants_of + def execute(filters=None): filters = frappe._dict(filters or {}) if filters.from_date > filters.to_date: diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py index e89c45182fd..01421e8fd0e 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py @@ -1,11 +1,12 @@ # 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 frappe.utils import flt + def execute(filters=None): columns = get_columns() data = get_data() diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py index f2518f09f8e..9c30afc5b1a 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py @@ -1,13 +1,16 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import unittest -from frappe.utils import nowdate, add_months -from erpnext.selling.report.pending_so_items_for_purchase_request.pending_so_items_for_purchase_request\ - import execute + +from frappe.utils import add_months, nowdate + from erpnext.selling.doctype.sales_order.sales_order import make_material_request from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.selling.report.pending_so_items_for_purchase_request.pending_so_items_for_purchase_request import ( + execute, +) class TestPendingSOItemsForPurchaseRequest(unittest.TestCase): diff --git a/erpnext/selling/report/quotation_trends/quotation_trends.py b/erpnext/selling/report/quotation_trends/quotation_trends.py index 968e2ff26f7..047b09081af 100644 --- a/erpnext/selling/report/quotation_trends/quotation_trends.py +++ b/erpnext/selling/report/quotation_trends/quotation_trends.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + from frappe import _ -from erpnext.controllers.trends import get_columns, get_data + +from erpnext.controllers.trends import get_columns, get_data + def execute(filters=None): if not filters: filters ={} diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index d036a1cb095..83588c34569 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -1,13 +1,14 @@ # 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 _, scrub -from frappe.utils import getdate, flt, add_to_date, add_days -from six import iteritems +from frappe.utils import add_days, add_to_date, flt, getdate + from erpnext.accounts.utils import get_fiscal_year + def execute(filters=None): return Analytics(filters).run() @@ -224,7 +225,7 @@ class Analytics(object): self.data = [] self.get_periodic_data() - for entity, period_data in iteritems(self.entity_periodic_data): + for entity, period_data in self.entity_periodic_data.items(): row = { "entity": entity, "entity_name": self.entity_names.get(entity) if hasattr(self, 'entity_names') else None @@ -293,7 +294,7 @@ class Analytics(object): return period def get_period_date_ranges(self): - from dateutil.relativedelta import relativedelta, MO + from dateutil.relativedelta import MO, relativedelta from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date) increment = { diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py index 4d81a1e4dda..8ffc5d6d0a7 100644 --- a/erpnext/selling/report/sales_analytics/test_analytics.py +++ b/erpnext/selling/report/sales_analytics/test_analytics.py @@ -1,12 +1,14 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -import frappe.defaults + import unittest -from erpnext.selling.report.sales_analytics.sales_analytics import execute + +import frappe + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.selling.report.sales_analytics.sales_analytics import execute + class TestAnalytics(unittest.TestCase): def test_sales_analytics(self): diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 00dcd69c6e6..82e5d0ce57d 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -1,11 +1,13 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + import copy + +import frappe from frappe import _ -from frappe.utils import flt, date_diff, getdate +from frappe.utils import date_diff, flt, getdate + def execute(filters=None): if not filters: @@ -70,7 +72,7 @@ def get_data(conditions, filters): `tabSales Order` so, `tabSales Order Item` soi LEFT JOIN `tabSales Invoice Item` sii - ON sii.so_detail = soi.name + ON sii.so_detail = soi.name and sii.docstatus = 1 WHERE soi.parent = so.name and so.status not in ('Stopped', 'Closed', 'On Hold') diff --git a/erpnext/selling/report/sales_order_trends/sales_order_trends.py b/erpnext/selling/report/sales_order_trends/sales_order_trends.py index de7d3f2f778..5a711712620 100644 --- a/erpnext/selling/report/sales_order_trends/sales_order_trends.py +++ b/erpnext/selling/report/sales_order_trends/sales_order_trends.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + from frappe import _ -from erpnext.controllers.trends import get_columns,get_data + +from erpnext.controllers.trends import get_columns, get_data + def execute(filters=None): if not filters: filters ={} diff --git a/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.py b/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.py index 2c49d51a920..b775907bd53 100644 --- a/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.py +++ b/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.py @@ -1,10 +1,9 @@ # 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 msgprint, _ -from frappe.utils import flt +from frappe import _, msgprint def execute(filters=None): diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py index 24ca666f6b1..a647eb4fea2 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py @@ -1,13 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt -from erpnext.accounts.utils import get_fiscal_year + +from erpnext.accounts.doctype.monthly_distribution.monthly_distribution import ( + get_periodwise_distribution_data, +) from erpnext.accounts.report.financial_statements import get_period_list -from erpnext.accounts.doctype.monthly_distribution.monthly_distribution import get_periodwise_distribution_data +from erpnext.accounts.utils import get_fiscal_year + def get_data_column(filters, partner_doctype): data = [] @@ -44,18 +47,6 @@ def get_data(filters, period_list, partner_doctype): if d.item_group not in item_groups: item_groups.append(d.item_group) - if item_groups: - child_items = [] - for item_group in item_groups: - if frappe.db.get_value("Item Group", {"name":item_group}, "is_group"): - for child_item_group in frappe.get_all("Item Group", {"parent_item_group":item_group}): - if child_item_group['name'] not in child_items: - child_items.append(child_item_group['name']) - - for item in child_items: - if item not in item_groups: - item_groups.append(item) - date_field = ("transaction_date" if filters.get('doctype') == "Sales Order" else "posting_date") diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.py index 87ed5a8ea21..f2b6a54a8a6 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.py @@ -1,9 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.item_group_wise_sales_target_variance import get_data_column + +from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.item_group_wise_sales_target_variance import ( + get_data_column, +) + def execute(filters=None): data = [] diff --git a/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.py b/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.py index f07293d8ec2..c64555bf2d3 100644 --- a/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.py +++ b/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.py @@ -1,10 +1,9 @@ # 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 msgprint, _ -from frappe.utils import flt +from frappe import _, msgprint def execute(filters=None): diff --git a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py index 9917d72af86..1542e31feff 100644 --- a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py +++ b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py @@ -1,10 +1,9 @@ # 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 msgprint, _ -from frappe.utils import flt +from frappe import _, msgprint def execute(filters=None): diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py index ea9bbab0c72..dda24662bb2 100644 --- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py +++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py @@ -1,9 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.item_group_wise_sales_target_variance import get_data_column + +from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.item_group_wise_sales_target_variance import ( + get_data_column, +) + def execute(filters=None): data = [] diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py index 41c7f765175..c621be88295 100644 --- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py +++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py @@ -1,12 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe import msgprint, _ -from frappe.utils import flt +from frappe import _, msgprint + from erpnext import get_company_currency + def execute(filters=None): if not filters: filters = {} diff --git a/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.py b/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.py index b1d89cc3fb8..eee2d42a786 100644 --- a/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.py +++ b/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.py @@ -1,9 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.item_group_wise_sales_target_variance import get_data_column + +from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.item_group_wise_sales_target_variance import ( + get_data_column, +) + def execute(filters=None): data = [] diff --git a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py index e8835001707..b7b4d3aa4ca 100644 --- a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py +++ b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py @@ -1,11 +1,13 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from erpnext import get_default_currency from frappe import _ +from erpnext import get_default_currency + + def execute(filters=None): filters = frappe._dict(filters) columns = get_columns() diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 2de57c87f18..e2e0db40449 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -63,7 +63,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran this.frm.set_query("item_code", "items", function() { return { query: "erpnext.controllers.queries.item_query", - filters: {'is_sales_item': 1} + filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer} } }); } @@ -90,10 +90,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran this.frm.toggle_display("customer_name", (this.frm.doc.customer_name && this.frm.doc.customer_name!==this.frm.doc.customer)); - if(this.frm.fields_dict.packed_items) { - var packing_list_exists = (this.frm.doc.packed_items || []).length; - this.frm.toggle_display("packing_list", packing_list_exists ? true : false); - } + this.toggle_editable_price_list_rate(); } @@ -160,25 +157,19 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran commission_rate() { this.calculate_commission(); - refresh_field("total_commission"); } total_commission() { - if(this.frm.doc.base_net_total) { - frappe.model.round_floats_in(this.frm.doc, ["base_net_total", "total_commission"]); + frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]); - if(this.frm.doc.base_net_total < this.frm.doc.total_commission) { - var msg = (__("[Error]") + " " + - __(frappe.meta.get_label(this.frm.doc.doctype, "total_commission", - this.frm.doc.name)) + " > " + - __(frappe.meta.get_label(this.frm.doc.doctype, "base_net_total", this.frm.doc.name))); - frappe.msgprint(msg); - throw msg; - } + const { amount_eligible_for_commission } = this.frm.doc; + if(!amount_eligible_for_commission) return; - this.frm.set_value("commission_rate", - flt(this.frm.doc.total_commission * 100.0 / this.frm.doc.base_net_total)); - } + this.frm.set_value( + "commission_rate", flt( + this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission + ) + ); } allocated_percentage(doc, cdt, cdn) { @@ -188,7 +179,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran sales_person.allocated_percentage = flt(sales_person.allocated_percentage, precision("allocated_percentage", sales_person)); - sales_person.allocated_amount = flt(this.frm.doc.base_net_total * + sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0, precision("allocated_amount", sales_person)); refresh_field(["allocated_amount"], sales_person); @@ -209,8 +200,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran var me = this; var item = frappe.get_doc(cdt, cdn); - if (item.serial_no && item.qty === item.serial_no.split(`\n`).length) { - return; + // check if serial nos entered are as much as qty in row + if (item.serial_no) { + let serial_nos = item.serial_no.split(`\n`).filter(sn => sn.trim()); // filter out whitespaces + if (item.qty === serial_nos.length) return; } if (item.serial_no && !item.batch_no) { @@ -250,33 +243,49 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate")); if(df && editable_price_list_rate) { - df.read_only = 0; + const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item"); + if (!this.frm.fields_dict[parent_field]) return; + + this.frm.fields_dict[parent_field].grid.update_docfield_property( + 'price_list_rate', 'read_only', 0 + ); } } calculate_commission() { - if(this.frm.fields_dict.commission_rate) { - if(this.frm.doc.commission_rate > 100) { - var msg = __(frappe.meta.get_label(this.frm.doc.doctype, "commission_rate", this.frm.doc.name)) + - " " + __("cannot be greater than 100"); - frappe.msgprint(msg); - throw msg; - } + if(!this.frm.fields_dict.commission_rate) return; - this.frm.doc.total_commission = flt(this.frm.doc.base_net_total * this.frm.doc.commission_rate / 100.0, - precision("total_commission")); + if(this.frm.doc.commission_rate > 100) { + this.frm.set_value("commission_rate", 100); + frappe.throw(`${__(frappe.meta.get_label( + this.frm.doc.doctype, "commission_rate", this.frm.doc.name + ))} ${__("cannot be greater than 100")}`); } + + this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce( + (sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0 + ) + + this.frm.doc.total_commission = flt( + this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0, + precision("total_commission") + ); + + refresh_field(["amount_eligible_for_commission", "total_commission"]); } calculate_contribution() { var me = this; $.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) { frappe.model.round_floats_in(sales_person); - if(sales_person.allocated_percentage) { - sales_person.allocated_amount = flt( - me.frm.doc.base_net_total * sales_person.allocated_percentage / 100.0, - precision("allocated_amount", sales_person)); - } + if (!sales_person.allocated_percentage) return; + + sales_person.allocated_amount = flt( + me.frm.doc.amount_eligible_for_commission + * sales_person.allocated_percentage + / 100.0, + precision("allocated_amount", sales_person) + ); }); } @@ -469,6 +478,12 @@ frappe.ui.form.on(cur_frm.doctype, { "options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail', "reqd": 1 }, + { + "fieldtype": "Table MultiSelect", + "label": __("Competitors"), + "fieldname": "competitors", + "options": "Competitor Detail" + }, { "fieldtype": "Text", "label": __("Detailed Reason"), @@ -476,27 +491,25 @@ frappe.ui.form.on(cur_frm.doctype, { }, ], primary_action: function() { - var values = dialog.get_values(); - var reasons = values["lost_reason"]; - var detailed_reason = values["detailed_reason"]; + let values = dialog.get_values(); frm.call({ doc: frm.doc, method: 'declare_enquiry_lost', args: { - 'lost_reasons_list': reasons, - 'detailed_reason': detailed_reason + 'lost_reasons_list': values.lost_reason, + 'competitors': values.competitors, + 'detailed_reason': values.detailed_reason }, callback: function(r) { dialog.hide(); frm.reload_doc(); }, }); - refresh_field("lost_reason"); }, primary_action_label: __('Declare Lost') }); dialog.show(); } -}) +}) \ No newline at end of file diff --git a/erpnext/selling/workspace/retail/retail.json b/erpnext/selling/workspace/retail/retail.json index 9d2e6cabbc3..a851ace738c 100644 --- a/erpnext/selling/workspace/retail/retail.json +++ b/erpnext/selling/workspace/retail/retail.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Point Of Sale\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings & Configurations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loyalty Program\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening & Closing\", \"col\": 4}}]", "creation": "2020-03-02 17:18:32.505616", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "retail", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Retail", "links": [ { @@ -108,15 +101,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.840988", + "modified": "2021-08-05 12:16:01.840989", "modified_by": "Administrator", "module": "Selling", "name": "Retail", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "Retail", "roles": [], diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json index 345187f93c4..db2e6bafd55 100644 --- a/erpnext/selling/workspace/selling/selling.json +++ b/erpnext/selling/workspace/selling/selling.json @@ -1,26 +1,18 @@ { - "category": "", "charts": [ { "chart_name": "Sales Order Trends", "label": "Sales Order Trends" } ], - "charts_label": "Selling ", "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Selling\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Sales Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Selling\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", "creation": "2020-01-28 11:49:12.092882", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "sell", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Selling", "links": [ { @@ -570,15 +562,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.990702", + "modified": "2021-08-05 12:16:01.990703", "modified_by": "Administrator", "module": "Selling", "name": "Selling", - "onboarding": "Selling", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], @@ -619,6 +608,5 @@ "type": "Dashboard" } ], - "shortcuts_label": "Quick Access", "title": "Selling" } \ No newline at end of file diff --git a/erpnext/setup/default_energy_point_rules.py b/erpnext/setup/default_energy_point_rules.py index 8dbccc497b7..1ce06b8c2f4 100644 --- a/erpnext/setup/default_energy_point_rules.py +++ b/erpnext/setup/default_energy_point_rules.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from frappe import _ doctype_rule_map = { diff --git a/erpnext/setup/default_success_action.py b/erpnext/setup/default_success_action.py index 827839f8b75..a1f48df672a 100644 --- a/erpnext/setup/default_success_action.py +++ b/erpnext/setup/default_success_action.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from frappe import _ doctype_list = [ diff --git a/erpnext/setup/doctype/__init__.py b/erpnext/setup/doctype/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/__init__.py +++ b/erpnext/setup/doctype/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/authorization_control/__init__.py b/erpnext/setup/doctype/authorization_control/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/authorization_control/__init__.py +++ b/erpnext/setup/doctype/authorization_control/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/authorization_control/authorization_control.py b/erpnext/setup/doctype/authorization_control/authorization_control.py index fec5c7ca95d..2a0d785520a 100644 --- a/erpnext/setup/doctype/authorization_control/authorization_control.py +++ b/erpnext/setup/doctype/authorization_control/authorization_control.py @@ -1,12 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe.utils import cstr, flt, has_common, comma_or -from frappe import session, _ +from frappe import _, session +from frappe.utils import comma_or, cstr, flt, has_common + from erpnext.utilities.transaction_base import TransactionBase + class AuthorizationControl(TransactionBase): def get_appr_user_role(self, det, doctype_name, total, based_on, condition, item, company): amt_list, appr_users, appr_roles = [], [], [] diff --git a/erpnext/setup/doctype/authorization_rule/__init__.py b/erpnext/setup/doctype/authorization_rule/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/authorization_rule/__init__.py +++ b/erpnext/setup/doctype/authorization_rule/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/authorization_rule/authorization_rule.py b/erpnext/setup/doctype/authorization_rule/authorization_rule.py index eb8e6ebe78c..e07de3b2934 100644 --- a/erpnext/setup/doctype/authorization_rule/authorization_rule.py +++ b/erpnext/setup/doctype/authorization_rule/authorization_rule.py @@ -1,13 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe - -from frappe.utils import cstr, flt -from frappe import _, msgprint - +from frappe import _ from frappe.model.document import Document +from frappe.utils import cstr, flt + class AuthorizationRule(Document): def check_duplicate_entry(self): diff --git a/erpnext/setup/doctype/authorization_rule/test_authorization_rule.py b/erpnext/setup/doctype/authorization_rule/test_authorization_rule.py index 332f1039921..7d3d5d4c4d3 100644 --- a/erpnext/setup/doctype/authorization_rule/test_authorization_rule.py +++ b/erpnext/setup/doctype/authorization_rule/test_authorization_rule.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Authorization Rule') diff --git a/erpnext/setup/doctype/brand/__init__.py b/erpnext/setup/doctype/brand/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/brand/__init__.py +++ b/erpnext/setup/doctype/brand/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/brand/brand.py b/erpnext/setup/doctype/brand/brand.py index a8d1cf8ff2d..9b91b456c34 100644 --- a/erpnext/setup/doctype/brand/brand.py +++ b/erpnext/setup/doctype/brand/brand.py @@ -1,12 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + import copy +import frappe from frappe.model.document import Document + class Brand(Document): pass diff --git a/erpnext/setup/doctype/brand/test_brand.py b/erpnext/setup/doctype/brand/test_brand.py index 25ed86ef1dd..1c71448cb8d 100644 --- a/erpnext/setup/doctype/brand/test_brand.py +++ b/erpnext/setup/doctype/brand/test_brand.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Brand') diff --git a/erpnext/setup/doctype/company/__init__.py b/erpnext/setup/doctype/company/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/company/__init__.py +++ b/erpnext/setup/doctype/company/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 56700af79e9..91f60fbd4e2 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -12,6 +12,10 @@ frappe.ui.form.on("Company", { } }); } + + frm.call('check_if_transactions_exist').then(r => { + frm.toggle_enable("default_currency", (!r.message)); + }); }, setup: function(frm) { erpnext.company.setup_queries(frm); @@ -46,43 +50,6 @@ frappe.ui.form.on("Company", { }); }, - change_abbreviation(frm) { - var dialog = new frappe.ui.Dialog({ - title: "Replace Abbr", - fields: [ - {"fieldtype": "Data", "label": "New Abbreviation", "fieldname": "new_abbr", - "reqd": 1 }, - {"fieldtype": "Button", "label": "Update", "fieldname": "update"}, - ] - }); - - dialog.fields_dict.update.$input.click(function() { - var args = dialog.get_values(); - if (!args) return; - frappe.show_alert(__("Update in progress. It might take a while.")); - return frappe.call({ - method: "erpnext.setup.doctype.company.company.enqueue_replace_abbr", - args: { - "company": frm.doc.name, - "old": frm.doc.abbr, - "new": args.new_abbr - }, - callback: function(r) { - if (r.exc) { - frappe.msgprint(__("There were errors.")); - return; - } else { - frm.set_value("abbr", args.new_abbr); - } - dialog.hide(); - frm.refresh(); - }, - btn: this - }); - }); - dialog.show(); - }, - company_name: function(frm) { if(frm.doc.__islocal) { // add missing " " arg in split method @@ -124,9 +91,6 @@ frappe.ui.form.on("Company", { frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'} - frm.toggle_enable("default_currency", (frm.doc.__onload && - !frm.doc.__onload.transactions_exist)); - if (frappe.perm.has_perm("Cost Center", 0, 'read')) { frm.add_custom_button(__('Cost Centers'), function() { frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}); @@ -164,10 +128,6 @@ frappe.ui.form.on("Company", { }, __('Manage')); } } - - frm.add_custom_button(__('Change Abbreviation'), () => { - frm.trigger('change_abbreviation'); - }, __('Manage')); } erpnext.company.set_chart_of_accounts_options(frm.doc); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index e4ee3ecea7c..63d96bf85e7 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -125,7 +125,8 @@ "label": "Abbr", "oldfieldname": "abbr", "oldfieldtype": "Data", - "reqd": 1 + "reqd": 1, + "set_only_once": 1 }, { "bold": 1, @@ -715,6 +716,15 @@ "label": "Default Payment Discount Account", "options": "Account" }, + { + "fieldname": "hr_settings_section", + "fieldtype": "Section Break", + "label": "HR & Payroll Settings" + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, { "collapsible": 1, "fieldname": "fixed_asset_defaults", @@ -731,15 +741,6 @@ "fieldname": "section_break_28", "fieldtype": "Section Break", "label": "Chart of Accounts" - }, - { - "fieldname": "hr_settings_section", - "fieldtype": "Section Break", - "label": "HR & Payroll Settings" - }, - { - "fieldname": "column_break_26", - "fieldtype": "Column Break" } ], "icon": "fa fa-building", @@ -747,10 +748,11 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2021-07-12 11:27:06.353860", + "modified": "2021-10-04 12:09:25.833133", "modified_by": "Administrator", "module": "Setup", "name": "Company", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_company", "owner": "Administrator", "permissions": [ diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 6dee2ad92a0..e7397394587 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -1,31 +1,29 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, os, json -from frappe import _ -from frappe.utils import get_timestamp -from frappe.utils import cint, today, formatdate +import json +import os + +import frappe import frappe.defaults +from frappe import _ from frappe.cache_manager import clear_defaults_cache - -from frappe.model.document import Document from frappe.contacts.address_and_contact import load_address_and_contact +from frappe.utils import cint, formatdate, get_timestamp, today from frappe.utils.nestedset import NestedSet -from past.builtins import cmp -import functools from erpnext.accounts.doctype.account.account import get_account_currency from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges + class Company(NestedSet): nsm_parent_field = 'parent_company' def onload(self): load_address_and_contact(self, "company") - self.get("__onload")["transactions_exist"] = self.check_if_transactions_exist() + @frappe.whitelist() def check_if_transactions_exist(self): exists = False for doctype in ["Sales Invoice", "Delivery Note", "Sales Order", "Quotation", @@ -387,6 +385,7 @@ class Company(NestedSet): frappe.db.sql("delete from tabEmployee where company=%s", self.name) frappe.db.sql("delete from tabDepartment where company=%s", self.name) frappe.db.sql("delete from `tabTax Withholding Account` where company=%s", self.name) + frappe.db.sql("delete from `tabTransaction Deletion Record` where company=%s", self.name) # delete tax templates frappe.db.sql("delete from `tabSales Taxes and Charges Template` where company=%s", self.name) @@ -397,44 +396,6 @@ class Company(NestedSet): if not frappe.db.get_value('GL Entry', {'company': self.name}): frappe.db.sql("delete from `tabProcess Deferred Accounting` where company=%s", self.name) -@frappe.whitelist() -def enqueue_replace_abbr(company, old, new): - kwargs = dict(queue="long", company=company, old=old, new=new) - frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs) - - -@frappe.whitelist() -def replace_abbr(company, old, new): - new = new.strip() - if not new: - frappe.throw(_("Abbr can not be blank or space")) - - frappe.only_for("System Manager") - - def _rename_record(doc): - parts = doc[0].rsplit(" - ", 1) - if len(parts) == 1 or parts[1].lower() == old.lower(): - frappe.rename_doc(dt, doc[0], parts[0] + " - " + new, force=True) - - def _rename_records(dt): - # rename is expensive so let's be economical with memory usage - doc = (d for d in frappe.db.sql("select name from `tab%s` where company=%s" % (dt, '%s'), company)) - for d in doc: - _rename_record(d) - try: - frappe.db.auto_commit_on_many_writes = 1 - for dt in ["Warehouse", "Account", "Cost Center", "Department", - "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]: - _rename_records(dt) - frappe.db.commit() - frappe.db.set_value("Company", company, "abbr", new) - - except Exception: - frappe.log_error(title=_('Abbreviation Rename Error')) - finally: - frappe.db.auto_commit_on_many_writes = 0 - - def get_name_with_abbr(name, company): company_abbr = frappe.get_cached_value('Company', company, "abbr") parts = name.split(" - ") @@ -452,7 +413,7 @@ def install_country_fixtures(company, country): frappe.get_attr(module_name)(company, False) except Exception as e: frappe.log_error() - frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(country))) + frappe.throw(_("Failed to setup defaults for country {0}. Please contact support.").format(frappe.bold(country))) def update_company_current_month_sales(company): @@ -479,8 +440,9 @@ def update_company_current_month_sales(company): def update_company_monthly_sales(company): '''Cache past year monthly sales of every company based on sales invoices''' - from frappe.utils.goal import get_monthly_results import json + + from frappe.utils.goal import get_monthly_results filter_str = "company = {0} and status != 'Draft' and docstatus=1".format(frappe.db.escape(company)) month_to_value_dict = get_monthly_results("Sales Invoice", "base_grand_total", "posting_date", filter_str, "sum") @@ -619,7 +581,7 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad return existing_address if out: - return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0] + return min(out, key=lambda x: x[1])[0] # find min by sort_key else: return None diff --git a/erpnext/setup/doctype/company/company_dashboard.py b/erpnext/setup/doctype/company/company_dashboard.py index 2d760284e5a..7cb0b1254f6 100644 --- a/erpnext/setup/doctype/company/company_dashboard.py +++ b/erpnext/setup/doctype/company/company_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'graph': True, diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index 1b7fd4fd5c8..4ee94927381 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -1,13 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + +import json +import unittest import frappe -import unittest -import json from frappe import _ from frappe.utils import random_string -from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import get_charts_for_country + +from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import ( + get_charts_for_country, +) test_ignore = ["Account", "Cost Center", "Payment Terms Template", "Salary Component", "Warehouse"] test_dependencies = ["Fiscal Year"] diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.py b/erpnext/setup/doctype/currency_exchange/currency_exchange.py index 6480f60f59c..4191935742f 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.py @@ -3,11 +3,11 @@ # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + from frappe import _, throw from frappe.model.document import Document -from frappe.utils import get_datetime_str, formatdate, nowdate, cint +from frappe.utils import cint, formatdate, get_datetime_str, nowdate + class CurrencyExchange(Document): def autoname(self): diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js deleted file mode 100644 index 19fde2e1481..00000000000 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Currency Exchange", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Currency Exchange - () => frappe.tests.make('Currency Exchange', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 4ff2dd7e0e9..2b007e9efd5 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -1,14 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, unittest -from frappe.utils import flt + +import unittest +from unittest import mock + +import frappe +from frappe.utils import cint, flt + from erpnext.setup.utils import get_exchange_rate -from frappe.utils import cint test_records = frappe.get_test_records('Currency Exchange') - def save_new_records(test_records): for record in test_records: # If both selling and buying enabled @@ -37,18 +39,45 @@ def save_new_records(test_records): curr_exchange.for_selling = record["for_selling"] curr_exchange.insert() +test_exchange_values = { + '2015-12-15': '66.999', + '2016-01-15': '65.1' +} +# Removing API call from get_exchange_rate +def patched_requests_get(*args, **kwargs): + class PatchResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def raise_for_status(self): + if self.status_code != 200: + raise frappe.DoesNotExistError + + def json(self): + return self.json_data + + if args[0] == "https://api.exchangerate.host/convert" and kwargs.get('params'): + if kwargs['params'].get('date') and kwargs['params'].get('from') and kwargs['params'].get('to'): + if test_exchange_values.get(kwargs['params']['date']): + return PatchResponse({'result': test_exchange_values[kwargs['params']['date']]}, 200) + + return PatchResponse({'result': None}, 404) + +@mock.patch('requests.get', side_effect=patched_requests_get) class TestCurrencyExchange(unittest.TestCase): def clear_cache(self): cache = frappe.cache() - key = "currency_exchange_rate:{0}:{1}".format("USD", "INR") - cache.delete(key) + for date in test_exchange_values.keys(): + key = "currency_exchange_rate_{0}:{1}:{2}".format(date, "USD", "INR") + cache.delete(key) def tearDown(self): frappe.db.set_value("Accounts Settings", None, "allow_stale", 1) self.clear_cache() - def test_exchange_rate(self): + def test_exchange_rate(self, mock_get): save_new_records(test_records) frappe.db.set_value("Accounts Settings", None, "allow_stale", 1) @@ -69,7 +98,11 @@ class TestCurrencyExchange(unittest.TestCase): self.assertFalse(exchange_rate == 60) self.assertEqual(flt(exchange_rate, 3), 66.999) - def test_exchange_rate_strict(self): + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-20", "for_buying") + self.assertFalse(exchange_rate == 60) + self.assertEqual(flt(exchange_rate, 3), 65.1) + + def test_exchange_rate_strict(self, mock_get): # strict currency settings frappe.db.set_value("Accounts Settings", None, "allow_stale", 0) frappe.db.set_value("Accounts Settings", None, "stale_days", 1) @@ -79,7 +112,7 @@ class TestCurrencyExchange(unittest.TestCase): self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") - self.assertEqual(flt(exchange_rate, 3), 67.235) + self.assertEqual(flt(exchange_rate, 3), 65.100) exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") self.assertEqual(exchange_rate, 62.9) @@ -89,7 +122,7 @@ class TestCurrencyExchange(unittest.TestCase): exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying") self.assertEqual(flt(exchange_rate, 3), 66.999) - def test_exchange_rate_strict_switched(self): + def test_exchange_rate_strict_switched(self, mock_get): # Start with allow_stale is True exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") self.assertEqual(exchange_rate, 65.1) @@ -97,7 +130,7 @@ class TestCurrencyExchange(unittest.TestCase): frappe.db.set_value("Accounts Settings", None, "allow_stale", 0) frappe.db.set_value("Accounts Settings", None, "stale_days", 1) - # Will fetch from fixer.io self.clear_cache() - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") - self.assertEqual(flt(exchange_rate, 3), 67.235) + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_buying") + self.assertFalse(exchange_rate == 65) + self.assertEqual(flt(exchange_rate, 3), 62.9) diff --git a/erpnext/setup/doctype/customer_group/__init__.py b/erpnext/setup/doctype/customer_group/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/customer_group/__init__.py +++ b/erpnext/setup/doctype/customer_group/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py index c06669b16b4..5b917265d99 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.py +++ b/erpnext/setup/doctype/customer_group/customer_group.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ - - from frappe.utils.nestedset import NestedSet, get_root_of + + class CustomerGroup(NestedSet): nsm_parent_field = 'parent_customer_group' def validate(self): diff --git a/erpnext/setup/doctype/customer_group/test_customer_group.py b/erpnext/setup/doctype/customer_group/test_customer_group.py index ec90b376cdc..f02ae097928 100644 --- a/erpnext/setup/doctype/customer_group/test_customer_group.py +++ b/erpnext/setup/doctype/customer_group/test_customer_group.py @@ -1,10 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals test_ignore = ["Price List"] import frappe + test_records = frappe.get_test_records('Customer Group') diff --git a/erpnext/setup/doctype/email_digest/__init__.py b/erpnext/setup/doctype/email_digest/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/email_digest/__init__.py +++ b/erpnext/setup/doctype/email_digest/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 6fbd4cd78cf..02f9156e196 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -1,20 +1,34 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.utils import (fmt_money, formatdate, format_time, now_datetime, - get_url_to_form, get_url_to_list, flt, get_link_to_report, add_to_date, today) + from datetime import timedelta -from dateutil.relativedelta import relativedelta -from frappe.core.doctype.user.user import STANDARD_USERS + +import frappe import frappe.desk.notifications +from dateutil.relativedelta import relativedelta +from frappe import _ +from frappe.core.doctype.user.user import STANDARD_USERS +from frappe.utils import ( + add_to_date, + flt, + fmt_money, + format_time, + formatdate, + get_link_to_report, + get_url_to_form, + get_url_to_list, + now_datetime, + today, +) + from erpnext.accounts.utils import get_balance_on, get_count_on, get_fiscal_year user_specific_content = ["calendar_events", "todo_list"] from frappe.model.document import Document + + class EmailDigest(Document): def __init__(self, *args, **kwargs): super(EmailDigest, self).__init__(*args, **kwargs) @@ -60,9 +74,6 @@ class EmailDigest(Document): reference_name = self.name, unsubscribe_message = _("Unsubscribe from this Email Digest")) - frappe.set_user(original_user) - frappe.set_user_lang(original_user) - def get_msg_html(self): """Build email digest content""" frappe.flags.ignore_account_permission = True diff --git a/erpnext/setup/doctype/email_digest/quotes.py b/erpnext/setup/doctype/email_digest/quotes.py index 5451ee1daff..fbd2d941179 100644 --- a/erpnext/setup/doctype/email_digest/quotes.py +++ b/erpnext/setup/doctype/email_digest/quotes.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import random + def get_random_quote(): quotes = [ ("Start by doing what's necessary; then do what's possible; and suddenly you are doing the impossible.", "Francis of Assisi"), diff --git a/erpnext/setup/doctype/email_digest/test_email_digest.py b/erpnext/setup/doctype/email_digest/test_email_digest.py index afe693afd2f..3fdf168a65e 100644 --- a/erpnext/setup/doctype/email_digest/test_email_digest.py +++ b/erpnext/setup/doctype/email_digest/test_email_digest.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Email Digest') diff --git a/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py index 968c51c345b..06bf6273c07 100644 --- a/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py +++ b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class EmailDigestRecipient(Document): pass diff --git a/erpnext/setup/doctype/global_defaults/__init__.py b/erpnext/setup/doctype/global_defaults/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/global_defaults/__init__.py +++ b/erpnext/setup/doctype/global_defaults/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index a0ba1efb5b4..f0b720a42e1 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + """Global Defaults""" import frappe import frappe.defaults -from frappe.utils import cint from frappe.custom.doctype.property_setter.property_setter import make_property_setter +from frappe.utils import cint keydict = { # "key in defaults": "key in Global Defaults" @@ -22,6 +22,7 @@ keydict = { from frappe.model.document import Document + class GlobalDefaults(Document): def on_update(self): diff --git a/erpnext/setup/doctype/global_defaults/test_global_defaults.js b/erpnext/setup/doctype/global_defaults/test_global_defaults.js deleted file mode 100644 index 33634eb0f88..00000000000 --- a/erpnext/setup/doctype/global_defaults/test_global_defaults.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Global Defaults", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Global Defaults - () => frappe.tests.make('Global Defaults', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/global_defaults/test_global_defaults.py b/erpnext/setup/doctype/global_defaults/test_global_defaults.py index 0495af7b414..a9d62ad30fa 100644 --- a/erpnext/setup/doctype/global_defaults/test_global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/test_global_defaults.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + class TestGlobalDefaults(unittest.TestCase): pass diff --git a/erpnext/setup/doctype/item_group/__init__.py b/erpnext/setup/doctype/item_group/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/item_group/__init__.py +++ b/erpnext/setup/doctype/item_group/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index c46b6cc9bdb..c94b3463fc8 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -1,20 +1,22 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe + import copy + +import frappe from frappe import _ -from frappe.utils import nowdate, cint, cstr +from frappe.utils import cint, cstr, nowdate from frappe.utils.nestedset import NestedSet -from frappe.website.website_generator import WebsiteGenerator from frappe.website.utils import clear_cache -from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow -from erpnext.shopping_cart.product_info import set_product_info_for_website -from erpnext.utilities.product import get_qty_in_stock +from frappe.website.website_generator import WebsiteGenerator from six.moves.urllib.parse import quote -from erpnext.shopping_cart.product_query import ProductQuery + from erpnext.shopping_cart.filters import ProductFiltersBuilder +from erpnext.shopping_cart.product_info import set_product_info_for_website +from erpnext.shopping_cart.product_query import ProductQuery +from erpnext.utilities.product import get_qty_in_stock + class ItemGroup(NestedSet, WebsiteGenerator): nsm_parent_field = 'parent_item_group' @@ -36,11 +38,11 @@ class ItemGroup(NestedSet, WebsiteGenerator): self.parent_item_group = _('All Item Groups') self.make_route() + self.validate_item_group_defaults() def on_update(self): NestedSet.on_update(self) invalidate_cache_for(self) - self.validate_name_with_item() self.validate_one_root() self.delete_child_item_groups_key() @@ -64,10 +66,6 @@ class ItemGroup(NestedSet, WebsiteGenerator): WebsiteGenerator.on_trash(self) self.delete_child_item_groups_key() - def validate_name_with_item(self): - if frappe.db.exists("Item", self.name): - frappe.throw(frappe._("An item exists with same name ({0}), please change the item group name or rename the item").format(self.name), frappe.NameError) - def get_context(self, context): context.show_search=True context.page_length = cint(frappe.db.get_single_value('Products Settings', 'products_per_page')) or 6 @@ -96,7 +94,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): filter_engine = ProductFiltersBuilder(self.name) context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_fitlers() + context.attribute_filters = filter_engine.get_attribute_filters() context.update({ "parents": get_parent_item_groups(self.parent_item_group), @@ -131,6 +129,10 @@ class ItemGroup(NestedSet, WebsiteGenerator): def delete_child_item_groups_key(self): frappe.cache().hdel("child_item_groups", self.name) + def validate_item_group_defaults(self): + from erpnext.stock.doctype.item.item import validate_item_default_company_links + validate_item_default_company_links(self.item_group_defaults) + @frappe.whitelist(allow_guest=True) def get_product_list_for_group(product_group=None, start=0, limit=10, search=None): if product_group: diff --git a/erpnext/setup/doctype/item_group/test_item_group.js b/erpnext/setup/doctype/item_group/test_item_group.js deleted file mode 100644 index ea322e23d65..00000000000 --- a/erpnext/setup/doctype/item_group/test_item_group.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Item Group", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Item Group - () => frappe.tests.make('Item Group', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/item_group/test_item_group.py b/erpnext/setup/doctype/item_group/test_item_group.py index 745b13a5bed..f6e9ed4ce59 100644 --- a/erpnext/setup/doctype/item_group/test_item_group.py +++ b/erpnext/setup/doctype/item_group/test_item_group.py @@ -1,11 +1,18 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import print_function, unicode_literals + import unittest + import frappe -from frappe.utils.nestedset import NestedSetRecursionError, NestedSetMultipleRootsError, \ - NestedSetChildExistsError, NestedSetInvalidMergeError, rebuild_tree, get_ancestors_of +from frappe.utils.nestedset import ( + NestedSetChildExistsError, + NestedSetInvalidMergeError, + NestedSetMultipleRootsError, + NestedSetRecursionError, + get_ancestors_of, + rebuild_tree, +) test_records = frappe.get_test_records('Item Group') diff --git a/erpnext/setup/doctype/item_group/test_records.json b/erpnext/setup/doctype/item_group/test_records.json index 146da87bddc..ce1d718375a 100644 --- a/erpnext/setup/doctype/item_group/test_records.json +++ b/erpnext/setup/doctype/item_group/test_records.json @@ -1,73 +1,74 @@ [ { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group", "parent_item_group": "All Item Groups", "item_group_defaults": [{ "company": "_Test Company", "buying_cost_center": "_Test Cost Center 2 - _TC", - "selling_cost_center": "_Test Cost Center 2 - _TC" + "selling_cost_center": "_Test Cost Center 2 - _TC", + "default_warehouse": "_Test Warehouse - _TC" }] - }, + }, { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group Desktops", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group Desktops", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group A", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group A", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B - 1", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B - 1", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B - 2", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B - 2", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group B - 3", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group B - 3", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C - 1", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C - 1", "parent_item_group": "_Test Item Group C" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C - 2", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C - 2", "parent_item_group": "_Test Item Group C" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group D", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group D", "parent_item_group": "All Item Groups" }, { @@ -104,4 +105,4 @@ } ] } -] \ No newline at end of file +] diff --git a/erpnext/setup/doctype/naming_series/__init__.py b/erpnext/setup/doctype/naming_series/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/naming_series/__init__.py +++ b/erpnext/setup/doctype/naming_series/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index c1f9433b411..986b4e87ff0 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -1,16 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe - -from frappe.utils import cstr, cint -from frappe import msgprint, throw, _ - +from frappe import _, msgprint, throw +from frappe.core.doctype.doctype.doctype import validate_series from frappe.model.document import Document from frappe.model.naming import parse_naming_series from frappe.permissions import get_doctypes_with_read -from frappe.core.doctype.doctype.doctype import validate_series +from frappe.utils import cint, cstr + class NamingSeriesNotSetError(frappe.ValidationError): pass @@ -79,7 +78,8 @@ class NamingSeries(Document): options = self.scrub_options_list(ol) # validate names - for i in options: self.validate_series_name(i) + for i in options: + self.validate_series_name(i) if options and self.user_must_always_select: options = [''] + options @@ -138,7 +138,7 @@ class NamingSeries(Document): def validate_series_name(self, n): import re - if not re.match("^[\w\- /.#{}]*$", n, re.UNICODE): + if not re.match(r"^[\w\- \/.#{}]+$", n, re.UNICODE): throw(_('Special Characters except "-", "#", ".", "/", "{" and "}" not allowed in naming series')) @frappe.whitelist() @@ -180,11 +180,11 @@ class NamingSeries(Document): prefix = parse_naming_series(parts) return prefix -def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True): +def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True, make_mandatory=1): from frappe.custom.doctype.property_setter.property_setter import make_property_setter if naming_series: make_property_setter(doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False) - make_property_setter(doctype, "naming_series", "reqd", 1, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "naming_series", "reqd", make_mandatory, "Check", validate_fields_for_doctype=False) # set values for mandatory try: diff --git a/erpnext/setup/doctype/naming_series/test_naming_series.js b/erpnext/setup/doctype/naming_series/test_naming_series.js deleted file mode 100644 index 22b664b2e6d..00000000000 --- a/erpnext/setup/doctype/naming_series/test_naming_series.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Naming Series", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Naming Series - () => frappe.tests.make('Naming Series', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py index 96e60936a4b..d0d2946e94a 100644 --- a/erpnext/setup/doctype/party_type/party_type.py +++ b/erpnext/setup/doctype/party_type/party_type.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.model.document import Document + class PartyType(Document): pass diff --git a/erpnext/setup/doctype/party_type/test_party_type.js b/erpnext/setup/doctype/party_type/test_party_type.js deleted file mode 100644 index c97dbc58c80..00000000000 --- a/erpnext/setup/doctype/party_type/test_party_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Party Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Party Type - () => frappe.tests.make('Party Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/party_type/test_party_type.py b/erpnext/setup/doctype/party_type/test_party_type.py index 079fe2fe3b7..a9a3db8777e 100644 --- a/erpnext/setup/doctype/party_type/test_party_type.py +++ b/erpnext/setup/doctype/party_type/test_party_type.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest # test_records = frappe.get_test_records('Party Type') diff --git a/erpnext/setup/doctype/print_heading/__init__.py b/erpnext/setup/doctype/print_heading/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/print_heading/__init__.py +++ b/erpnext/setup/doctype/print_heading/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/print_heading/print_heading.py b/erpnext/setup/doctype/print_heading/print_heading.py index 3d5cd2d6f9e..3a2f15f4b2a 100644 --- a/erpnext/setup/doctype/print_heading/print_heading.py +++ b/erpnext/setup/doctype/print_heading/print_heading.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class PrintHeading(Document): pass diff --git a/erpnext/setup/doctype/print_heading/test_print_heading.py b/erpnext/setup/doctype/print_heading/test_print_heading.py index b2be2e375e4..04de08d2697 100644 --- a/erpnext/setup/doctype/print_heading/test_print_heading.py +++ b/erpnext/setup/doctype/print_heading/test_print_heading.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Print Heading') diff --git a/erpnext/setup/doctype/quotation_lost_reason/__init__.py b/erpnext/setup/doctype/quotation_lost_reason/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/quotation_lost_reason/__init__.py +++ b/erpnext/setup/doctype/quotation_lost_reason/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.py b/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.py index 42c5a5a54fb..651705c6e5b 100644 --- a/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.py +++ b/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class QuotationLostReason(Document): pass diff --git a/erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py b/erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py index f6b30b649b5..9330ba85870 100644 --- a/erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py +++ b/erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Quotation Lost Reason') diff --git a/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py index 7bb8d02670e..dda64e9c624 100644 --- a/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py +++ b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class QuotationLostReasonDetail(Document): pass diff --git a/erpnext/setup/doctype/sales_partner/__init__.py b/erpnext/setup/doctype/sales_partner/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/sales_partner/__init__.py +++ b/erpnext/setup/doctype/sales_partner/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.py b/erpnext/setup/doctype/sales_partner/sales_partner.py index 675f9ca560b..d2ec49dd6c3 100644 --- a/erpnext/setup/doctype/sales_partner/sales_partner.py +++ b/erpnext/setup/doctype/sales_partner/sales_partner.py @@ -1,11 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe +from frappe.contacts.address_and_contact import load_address_and_contact from frappe.utils import cstr, filter_strip_join from frappe.website.website_generator import WebsiteGenerator -from frappe.contacts.address_and_contact import load_address_and_contact + class SalesPartner(WebsiteGenerator): website = frappe._dict( diff --git a/erpnext/setup/doctype/sales_partner/test_sales_partner.py b/erpnext/setup/doctype/sales_partner/test_sales_partner.py index 4548a4e19b5..80ef3680147 100644 --- a/erpnext/setup/doctype/sales_partner/test_sales_partner.py +++ b/erpnext/setup/doctype/sales_partner/test_sales_partner.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Sales Partner') test_ignore = ["Item Group"] diff --git a/erpnext/setup/doctype/sales_person/__init__.py b/erpnext/setup/doctype/sales_person/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/sales_person/__init__.py +++ b/erpnext/setup/doctype/sales_person/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py index 19c2e5b9543..b79a566578d 100644 --- a/erpnext/setup/doctype/sales_person/sales_person.py +++ b/erpnext/setup/doctype/sales_person/sales_person.py @@ -1,13 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ from frappe.utils import flt from frappe.utils.nestedset import NestedSet, get_root_of + from erpnext import get_default_currency + class SalesPerson(NestedSet): nsm_parent_field = 'parent_sales_person' diff --git a/erpnext/setup/doctype/sales_person/sales_person_dashboard.py b/erpnext/setup/doctype/sales_person/sales_person_dashboard.py index 662008ec8db..e946406e638 100644 --- a/erpnext/setup/doctype/sales_person/sales_person_dashboard.py +++ b/erpnext/setup/doctype/sales_person/sales_person_dashboard.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_data(): return { 'heatmap': True, diff --git a/erpnext/setup/doctype/sales_person/test_sales_person.py b/erpnext/setup/doctype/sales_person/test_sales_person.py index 8313cb45084..786d2cac4da 100644 --- a/erpnext/setup/doctype/sales_person/test_sales_person.py +++ b/erpnext/setup/doctype/sales_person/test_sales_person.py @@ -1,10 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals test_dependencies = ["Employee"] import frappe + test_records = frappe.get_test_records('Sales Person') test_ignore = ["Item Group"] diff --git a/erpnext/setup/doctype/supplier_group/__init__.py b/erpnext/setup/doctype/supplier_group/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/supplier_group/__init__.py +++ b/erpnext/setup/doctype/supplier_group/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.py b/erpnext/setup/doctype/supplier_group/supplier_group.py index 9d84f9097b5..381e1250c82 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.py +++ b/erpnext/setup/doctype/supplier_group/supplier_group.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + import frappe from frappe.utils.nestedset import NestedSet, get_root_of + class SupplierGroup(NestedSet): nsm_parent_field = 'parent_supplier_group' diff --git a/erpnext/setup/doctype/supplier_group/test_supplier_group.js b/erpnext/setup/doctype/supplier_group/test_supplier_group.js deleted file mode 100644 index 976dd2cc4f3..00000000000 --- a/erpnext/setup/doctype/supplier_group/test_supplier_group.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Supplier Group", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Supplier Group - () => frappe.tests.make('Supplier Group', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/supplier_group/test_supplier_group.py b/erpnext/setup/doctype/supplier_group/test_supplier_group.py index 0e3d23d6bd3..283b3bfec3c 100644 --- a/erpnext/setup/doctype/supplier_group/test_supplier_group.py +++ b/erpnext/setup/doctype/supplier_group/test_supplier_group.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import frappe + test_records = frappe.get_test_records('Supplier Group') diff --git a/erpnext/setup/doctype/target_detail/__init__.py b/erpnext/setup/doctype/target_detail/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/target_detail/__init__.py +++ b/erpnext/setup/doctype/target_detail/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/target_detail/target_detail.py b/erpnext/setup/doctype/target_detail/target_detail.py index 633be45d20b..e27f5b6020d 100644 --- a/erpnext/setup/doctype/target_detail/target_detail.py +++ b/erpnext/setup/doctype/target_detail/target_detail.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class TargetDetail(Document): pass diff --git a/erpnext/setup/doctype/terms_and_conditions/__init__.py b/erpnext/setup/doctype/terms_and_conditions/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/terms_and_conditions/__init__.py +++ b/erpnext/setup/doctype/terms_and_conditions/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py index 5b00ccbdbb3..658f286f7c3 100644 --- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py +++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py @@ -1,15 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + +import json + import frappe from frappe import _, throw -import json from frappe.model.document import Document -from frappe.utils.jinja import validate_template from frappe.utils import cint +from frappe.utils.jinja import validate_template -from six import string_types class TermsandConditions(Document): def validate(self): @@ -20,7 +20,7 @@ class TermsandConditions(Document): @frappe.whitelist() def get_terms_and_conditions(template_name, doc): - if isinstance(doc, string_types): + if isinstance(doc, str): doc = json.loads(doc) terms_and_conditions = frappe.get_doc("Terms and Conditions", template_name) diff --git a/erpnext/setup/doctype/terms_and_conditions/test_terms_and_conditions.py b/erpnext/setup/doctype/terms_and_conditions/test_terms_and_conditions.py index 6fea78f46ae..ca9e6c1aef8 100644 --- a/erpnext/setup/doctype/terms_and_conditions/test_terms_and_conditions.py +++ b/erpnext/setup/doctype/terms_and_conditions/test_terms_and_conditions.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Terms and Conditions') diff --git a/erpnext/setup/doctype/territory/__init__.py b/erpnext/setup/doctype/territory/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/territory/__init__.py +++ b/erpnext/setup/doctype/territory/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/territory/territory.py b/erpnext/setup/doctype/territory/territory.py index 7eefe77495c..4c47d829e98 100644 --- a/erpnext/setup/doctype/territory/territory.py +++ b/erpnext/setup/doctype/territory/territory.py @@ -1,13 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import flt -from frappe import _ +import frappe +from frappe import _ +from frappe.utils import flt from frappe.utils.nestedset import NestedSet, get_root_of + class Territory(NestedSet): nsm_parent_field = 'parent_territory' diff --git a/erpnext/setup/doctype/territory/test_territory.py b/erpnext/setup/doctype/territory/test_territory.py index efe00c5a30b..a18b7bf70ef 100644 --- a/erpnext/setup/doctype/territory/test_territory.py +++ b/erpnext/setup/doctype/territory/test_territory.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('Territory') test_ignore = ["Item Group"] diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py index 933a8c3bed8..095c3d0b6fb 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe -import unittest + class TestTransactionDeletionRecord(unittest.TestCase): def setUp(self): diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 8a491554801..83ce042cde0 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals -from frappe.utils import cint + import frappe -from frappe.model.document import Document from frappe import _ from frappe.desk.notifications import clear_notifications +from frappe.model.document import Document +from frappe.utils import cint + class TransactionDeletionRecord(Document): def validate(self): diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py index 2176cb10deb..92ca8a2ac73 100644 --- a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + # import frappe from frappe.model.document import Document + class TransactionDeletionRecordItem(Document): pass diff --git a/erpnext/setup/doctype/uom/__init__.py b/erpnext/setup/doctype/uom/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/doctype/uom/__init__.py +++ b/erpnext/setup/doctype/uom/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/doctype/uom/test_uom.py b/erpnext/setup/doctype/uom/test_uom.py index 330d30358d9..feb43293079 100644 --- a/erpnext/setup/doctype/uom/test_uom.py +++ b/erpnext/setup/doctype/uom/test_uom.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import frappe + test_records = frappe.get_test_records('UOM') diff --git a/erpnext/setup/doctype/uom/uom.json b/erpnext/setup/doctype/uom/uom.json index 3a4e7f6dc4b..844a11f1397 100644 --- a/erpnext/setup/doctype/uom/uom.json +++ b/erpnext/setup/doctype/uom/uom.json @@ -1,164 +1,82 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:uom_name", - "beta": 0, "creation": "2013-01-10 16:34:24", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "enabled", + "uom_name", + "must_be_whole_number" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "uom_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "UOM Name", - "length": 0, - "no_copy": 0, "oldfieldname": "uom_name", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "Check this to disallow fractions. (for Nos)", "fieldname": "must_be_whole_number", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Must be Whole Number", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Must be Whole Number" + }, + { + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-compass", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-29 06:35:56.143361", + "links": [], + "modified": "2021-10-18 14:07:43.722144", "modified_by": "Administrator", "module": "Setup", "name": "UOM", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Item Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock Manager" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock User" } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 1, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file diff --git a/erpnext/setup/doctype/uom/uom.py b/erpnext/setup/doctype/uom/uom.py index 404b84b1134..ddb512a0852 100644 --- a/erpnext/setup/doctype/uom/uom.py +++ b/erpnext/setup/doctype/uom/uom.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class UOM(Document): pass diff --git a/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js b/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js deleted file mode 100644 index afcf74ccb47..00000000000 --- a/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: UOM Conversion Factor", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new UOM Conversion Factor - () => frappe.tests.make('UOM Conversion Factor', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.py b/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.py index 04596efbca6..5683b5bc30d 100644 --- a/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.py +++ b/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import unittest + class TestUOMConversionFactor(unittest.TestCase): pass diff --git a/erpnext/setup/doctype/uom_conversion_factor/uom_conversion_factor.py b/erpnext/setup/doctype/uom_conversion_factor/uom_conversion_factor.py index 3566c537c6c..12642fe7e25 100644 --- a/erpnext/setup/doctype/uom_conversion_factor/uom_conversion_factor.py +++ b/erpnext/setup/doctype/uom_conversion_factor/uom_conversion_factor.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals + from frappe.model.document import Document + class UOMConversionFactor(Document): pass diff --git a/erpnext/setup/doctype/website_item_group/website_item_group.py b/erpnext/setup/doctype/website_item_group/website_item_group.py index e416b509b98..87fcb984530 100644 --- a/erpnext/setup/doctype/website_item_group/website_item_group.py +++ b/erpnext/setup/doctype/website_item_group/website_item_group.py @@ -3,10 +3,9 @@ # For license information, please see license.txt -from __future__ import unicode_literals -import frappe from frappe.model.document import Document + class WebsiteItemGroup(Document): pass diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index bbee74cafb4..86c9b3f178d 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -1,18 +1,18 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import print_function, unicode_literals import frappe -from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import DEFAULT_MAPPERS -from .default_success_action import get_default_success_action from frappe import _ -from frappe.utils import cint -from frappe.installer import update_site_config -from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to +from frappe.installer import update_site_config +from frappe.utils import cint + +from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import DEFAULT_MAPPERS from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules -from six import iteritems + +from .default_success_action import get_default_success_action default_mail_footer = """
      Sent via ERPNext
      """ @@ -172,12 +172,12 @@ def add_non_standard_user_types(): user_types = get_user_types_data() user_type_limit = {} - for user_type, data in iteritems(user_types): + for user_type, data in user_types.items(): user_type_limit.setdefault(frappe.scrub(user_type), 10) update_site_config('user_type_doctype_limit', user_type_limit) - for user_type, data in iteritems(user_types): + for user_type, data in user_types.items(): create_custom_role(data) create_user_type(user_type, data) @@ -227,7 +227,7 @@ def create_user_type(user_type, data): doc.save(ignore_permissions=True) def create_role_permissions_for_doctype(doc, data): - for doctype, perms in iteritems(data.get('doctypes')): + for doctype, perms in data.get('doctypes').items(): args = {'document_type': doctype} for perm in perms: args[perm] = 1 diff --git a/erpnext/setup/page/__init__.py b/erpnext/setup/page/__init__.py index baffc488252..e69de29bb2d 100644 --- a/erpnext/setup/page/__init__.py +++ b/erpnext/setup/page/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 34af093a231..14b79510c12 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1195,7 +1195,7 @@ "*": { "item_tax_templates": [ { - "title": "GST 9%", + "title": "GST 18%", "taxes": [ { "tax_type": { @@ -2116,6 +2116,10 @@ }, "Saudi Arabia": { + "KSA VAT 15%": { + "account_name": "VAT 15%", + "tax_rate": 15.00 + }, "KSA VAT 5%": { "account_name": "VAT 5%", "tax_rate": 5.00 diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py index 9ce64eb9d92..6cb15b2a46c 100644 --- a/erpnext/setup/setup_wizard/data/dashboard_charts.py +++ b/erpnext/setup/setup_wizard/data/dashboard_charts.py @@ -1,8 +1,8 @@ -from __future__ import unicode_literals -from frappe import _ -import frappe import json +import frappe + + def get_company_for_dashboards(): company = frappe.defaults.get_defaults().company if company: diff --git a/erpnext/setup/setup_wizard/data/industry_type.py b/erpnext/setup/setup_wizard/data/industry_type.py index 4fa9f8abb16..ecd8b001991 100644 --- a/erpnext/setup/setup_wizard/data/industry_type.py +++ b/erpnext/setup/setup_wizard/data/industry_type.py @@ -1,6 +1,6 @@ -from __future__ import unicode_literals from frappe import _ + def get_industry_types(): return [ _('Accounting'), diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py index 4833d93c4a7..358b9218312 100644 --- a/erpnext/setup/setup_wizard/operations/company_setup.py +++ b/erpnext/setup/setup_wizard/operations/company_setup.py @@ -1,12 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cstr, getdate from .default_website import website_maker -from erpnext.accounts.doctype.account.account import RootNotEditable def create_fiscal_year_and_company(args): if (args.get('fy_start_date')): diff --git a/erpnext/setup/setup_wizard/operations/default_website.py b/erpnext/setup/setup_wizard/operations/default_website.py index 38b5c1470e7..c11910b584c 100644 --- a/erpnext/setup/setup_wizard/operations/default_website.py +++ b/erpnext/setup/setup_wizard/operations/default_website.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe +import frappe from frappe import _ from frappe.utils import nowdate + class website_maker(object): def __init__(self, args): self.args = args diff --git a/erpnext/setup/setup_wizard/operations/defaults_setup.py b/erpnext/setup/setup_wizard/operations/defaults_setup.py index 6dd0fb1403f..e4b1fa26ae0 100644 --- a/erpnext/setup/setup_wizard/operations/defaults_setup.py +++ b/erpnext/setup/setup_wizard/operations/defaults_setup.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cstr, getdate @@ -62,6 +61,13 @@ def set_default_settings(args): hr_settings.emp_created_by = "Naming Series" hr_settings.leave_approval_notification_template = _("Leave Approval Notification") hr_settings.leave_status_notification_template = _("Leave Status Notification") + + hr_settings.send_interview_reminder = 1 + hr_settings.interview_reminder_template = _("Interview Reminder") + hr_settings.remind_before = "00:15:00" + + hr_settings.send_interview_feedback_reminder = 1 + hr_settings.feedback_reminder_notification_template = _("Interview Feedback Reminder") hr_settings.save() def set_no_copy_fields_in_variant_settings(): diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index cd49a180529..98f91198853 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -1,18 +1,21 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, os, json +import json +import os +import frappe from frappe import _ +from frappe.desk.doctype.global_search_settings.global_search_settings import ( + update_global_search_doctypes, +) from frappe.desk.page.setup_wizard.setup_wizard import make_records from frappe.utils import cstr, getdate -from frappe.desk.doctype.global_search_settings.global_search_settings import update_global_search_doctypes +from frappe.utils.nestedset import rebuild_tree from erpnext.accounts.doctype.account.account import RootNotEditable from erpnext.regional.address_template.setup import set_up_address_templates -from frappe.utils.nestedset import rebuild_tree default_lead_sources = ["Existing Customer", "Reference", "Advertisement", "Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing", @@ -198,7 +201,6 @@ def install(country=None): {'doctype': "Party Type", "party_type": "Student", "account_type": "Receivable"}, {'doctype': "Party Type", "party_type": "Donor", "account_type": "Receivable"}, - {'doctype': "Opportunity Type", "name": "Hub"}, {'doctype': "Opportunity Type", "name": _("Sales")}, {'doctype': "Opportunity Type", "name": _("Support")}, {'doctype': "Opportunity Type", "name": _("Maintenance")}, @@ -260,16 +262,26 @@ def install(country=None): base_path = frappe.get_app_path("erpnext", "hr", "doctype") response = frappe.read_file(os.path.join(base_path, "leave_application/leave_application_email_template.html")) - records += [{'doctype': 'Email Template', 'name': _("Leave Approval Notification"), 'response': response,\ + records += [{'doctype': 'Email Template', 'name': _("Leave Approval Notification"), 'response': response, 'subject': _("Leave Approval Notification"), 'owner': frappe.session.user}] - records += [{'doctype': 'Email Template', 'name': _("Leave Status Notification"), 'response': response,\ + records += [{'doctype': 'Email Template', 'name': _("Leave Status Notification"), 'response': response, 'subject': _("Leave Status Notification"), 'owner': frappe.session.user}] + response = frappe.read_file(os.path.join(base_path, "interview/interview_reminder_notification_template.html")) + + records += [{'doctype': 'Email Template', 'name': _('Interview Reminder'), 'response': response, + 'subject': _('Interview Reminder'), 'owner': frappe.session.user}] + + response = frappe.read_file(os.path.join(base_path, "interview/interview_feedback_reminder_template.html")) + + records += [{'doctype': 'Email Template', 'name': _('Interview Feedback Reminder'), 'response': response, + 'subject': _('Interview Feedback Reminder'), 'owner': frappe.session.user}] + base_path = frappe.get_app_path("erpnext", "stock", "doctype") response = frappe.read_file(os.path.join(base_path, "delivery_trip/dispatch_notification_template.html")) - records += [{'doctype': 'Email Template', 'name': _("Dispatch Notification"), 'response': response,\ + records += [{'doctype': 'Email Template', 'name': _("Dispatch Notification"), 'response': response, 'subject': _("Your order is out for delivery!"), 'owner': frappe.session.user}] # Records for the Supplier Scorecard @@ -291,7 +303,6 @@ def set_more_defaults(): def update_selling_defaults(): selling_settings = frappe.get_doc("Selling Settings") - selling_settings.set_default_customer_group_and_territory() selling_settings.cust_master_name = "Customer Name" selling_settings.so_required = "No" selling_settings.dn_required = "No" @@ -313,6 +324,14 @@ def update_hr_defaults(): hr_settings.emp_created_by = "Naming Series" hr_settings.leave_approval_notification_template = _("Leave Approval Notification") hr_settings.leave_status_notification_template = _("Leave Status Notification") + + hr_settings.send_interview_reminder = 1 + hr_settings.interview_reminder_template = _("Interview Reminder") + hr_settings.remind_before = "00:15:00" + + hr_settings.send_interview_feedback_reminder = 1 + hr_settings.feedback_reminder_notification_template = _("Interview Feedback Reminder") + hr_settings.save() def update_item_variant_settings(): diff --git a/erpnext/setup/setup_wizard/operations/sample_data.py b/erpnext/setup/setup_wizard/operations/sample_data.py index c6d9f0851b1..16859947741 100644 --- a/erpnext/setup/setup_wizard/operations/sample_data.py +++ b/erpnext/setup/setup_wizard/operations/sample_data.py @@ -1,13 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + +import json +import os +import random import frappe -from frappe.utils.make_random import add_random_children import frappe.utils -import random, os, json from frappe import _ +from frappe.utils.make_random import add_random_children + def make_sample_data(domains, make_dependent = False): """Create a few opportunities, quotes, material requests, issues, todos, projects diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index faa25dfbaa2..289ffa58b87 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -1,7 +1,6 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import os import json @@ -192,7 +191,7 @@ def get_or_create_account(company_name, account): default_root_type = 'Liability' root_type = account.get('root_type', default_root_type) - existing_accounts = frappe.get_list('Account', + existing_accounts = frappe.get_all('Account', filters={ 'company': company_name, 'root_type': root_type @@ -247,7 +246,7 @@ def get_or_create_tax_group(company_name, root_type): # Create a new group account named 'Duties and Taxes' or 'Tax Assets' just # below the root account - root_account = frappe.get_list('Account', { + root_account = frappe.get_all('Account', { 'is_group': 1, 'root_type': root_type, 'company': company_name, diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index f63d2695aa3..c9ed184e04e 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -1,12 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ -from .operations import install_fixtures as fixtures, company_setup, sample_data +from .operations import company_setup +from .operations import install_fixtures as fixtures +from .operations import sample_data + def get_setup_stages(args=None): if frappe.db.sql("select name from tabCompany"): @@ -106,7 +108,7 @@ def fin(args): def make_sample_data(domains): try: sample_data.make_sample_data(domains) - except: + except Exception: # clear message if frappe.message_log: frappe.message_log.pop() diff --git a/erpnext/setup/setup_wizard/utils.py b/erpnext/setup/setup_wizard/utils.py index 4223f000a6b..f1ec50afcea 100644 --- a/erpnext/setup/setup_wizard/utils.py +++ b/erpnext/setup/setup_wizard/utils.py @@ -1,8 +1,8 @@ -from __future__ import unicode_literals -import json, os +import json +import os from frappe.desk.page.setup_wizard.setup_wizard import setup_complete -from erpnext.setup.setup_wizard import setup_wizard + def complete(): with open(os.path.join(os.path.dirname(__file__), diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index e49259e1a25..cad4c54d7db 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -1,13 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt, add_days -from frappe.utils import get_datetime_str, nowdate +from frappe.utils import add_days, flt, get_datetime_str, nowdate + from erpnext import get_default_company + def get_root_of(doctype): """Get root element of a DocType with a tree structure""" result = frappe.db.sql_list("""select name from `tab%s` @@ -52,6 +53,7 @@ def before_tests(): frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0) enable_all_roles_and_domains() + set_defaults_for_tests() frappe.db.commit() @@ -109,7 +111,7 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No value = response.json()["result"] cache.setex(name=key, time=21600, value=flt(value)) return flt(value) - except: + except Exception: frappe.log_error(title="Get Exchange Rate") frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually").format(from_currency, to_currency, transaction_date)) return 0.0 @@ -126,6 +128,14 @@ def enable_all_roles_and_domains(): [d.name for d in domains]) add_all_roles_to('Administrator') +def set_defaults_for_tests(): + from frappe.utils.nestedset import get_root_of + + selling_settings = frappe.get_single("Selling Settings") + selling_settings.customer_group = get_root_of("Customer Group") + selling_settings.territory = get_root_of("Territory") + selling_settings.save() + def insert_record(records): for r in records: diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index ef4b050ceb2..e47837f2ca4 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,31 +1,21 @@ { - "category": "", "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Projects Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"HR Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Selling Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Buying Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Support Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Shopping Cart Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Portal Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Manufacturing Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Education Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Hotel Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Domain Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Products Settings\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":4}}]", "creation": "2020-03-12 14:47:51.166455", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "setting", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "ERPNext Settings", "links": [], - "modified": "2021-08-05 12:15:59.052327", + "modified": "2021-11-05 21:32:55.323591", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], @@ -37,6 +27,14 @@ "link_to": "Projects Settings", "type": "DocType" }, + { + "color": "Grey", + "doc_view": "", + "icon": "dot-horizontal", + "label": "Naming Series", + "link_to": "Naming Series", + "type": "DocType" + }, { "icon": "accounting", "label": "Accounts Settings", @@ -125,6 +123,13 @@ "label": "Products Settings", "link_to": "Products Settings", "type": "DocType" + }, + { + "doc_view": "", + "icon": "crm", + "label": "CRM Settings", + "link_to": "CRM Settings", + "type": "DocType" } ], "title": "ERPNext Settings" diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json index cc9569f6421..4e1ccf9b94f 100644 --- a/erpnext/setup/workspace/home/home.json +++ b/erpnext/setup/workspace/home/home.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], - "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", "creation": "2020-01-23 13:46:38.833076", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "getting-started", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Home", "links": [ { @@ -278,15 +271,12 @@ "type": "Link" } ], - "modified": "2021-08-10 15:33:20.704740", + "modified": "2021-08-10 15:33:20.704741", "modified_by": "Administrator", "module": "Setup", "name": "Home", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index e9f4bd57a6a..ebbe233ca3a 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -1,17 +1,20 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe import throw, _ import frappe.defaults -from frappe.utils import cint, flt, get_fullname, cstr +from frappe import _, throw from frappe.contacts.doctype.address.address import get_address_display -from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import get_shopping_cart_settings -from frappe.utils.nestedset import get_root_of -from erpnext.accounts.utils import get_account_name -from erpnext.utilities.product import get_qty_in_stock from frappe.contacts.doctype.contact.contact import get_contact_name +from frappe.utils import cint, cstr, flt, get_fullname +from frappe.utils.nestedset import get_root_of + +from erpnext.accounts.utils import get_account_name +from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import ( + get_shopping_cart_settings, +) +from erpnext.utilities.product import get_qty_in_stock class WebsitePriceListMissingError(frappe.ValidationError): @@ -191,7 +194,9 @@ def add_new_address(doc): def create_lead_for_item_inquiry(lead, subject, message): lead = frappe.parse_json(lead) lead_doc = frappe.new_doc('Lead') - lead_doc.update(lead) + for fieldname in ("lead_name", "company_name", "email_id", "phone"): + lead_doc.set(fieldname, lead.get(fieldname)) + lead_doc.set('lead_owner', '') if not frappe.db.exists('Lead Source', 'Product Inquiry'): @@ -199,6 +204,7 @@ def create_lead_for_item_inquiry(lead, subject, message): 'doctype': 'Lead Source', 'source_name' : 'Product Inquiry' }).insert(ignore_permissions=True) + lead_doc.set('source', 'Product Inquiry') try: diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py index efed1968a14..4a755998dd4 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -3,12 +3,12 @@ # For license information, please see license.txt -from __future__ import unicode_literals + import frappe -from frappe import _, msgprint -from frappe.utils import flt +from frappe import _ from frappe.model.document import Document -from frappe.utils import get_datetime, get_datetime_str, now_datetime +from frappe.utils import flt + class ShoppingCartSetupError(frappe.ValidationError): pass diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js deleted file mode 100644 index c8485e73fa3..00000000000 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shopping Cart Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shopping Cart Settings - () => frappe.tests.make('Shopping Cart Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py index 9965e1af672..c3809b30b0f 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py @@ -3,10 +3,15 @@ # For license information, please see license.txt -from __future__ import unicode_literals -import frappe + import unittest -from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import ShoppingCartSetupError + +import frappe + +from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import ( + ShoppingCartSetupError, +) + class TestShoppingCartSettings(unittest.TestCase): def setUp(self): @@ -38,7 +43,6 @@ class TestShoppingCartSettings(unittest.TestCase): def test_tax_rule_validation(self): frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") - frappe.db.commit() cart_settings = self.get_cart_settings() cart_settings.enabled = 1 diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py index 7dfa09e2d62..ef0badc8c89 100644 --- a/erpnext/shopping_cart/filters.py +++ b/erpnext/shopping_cart/filters.py @@ -1,9 +1,9 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import frappe -from frappe import _dict + class ProductFiltersBuilder: def __init__(self, item_group=None): @@ -55,37 +55,31 @@ class ProductFiltersBuilder: return filter_data - def get_attribute_fitlers(self): + def get_attribute_filters(self): attributes = [row.attribute for row in self.doc.filter_attributes] - attribute_docs = [ - frappe.get_doc('Item Attribute', attribute) for attribute in attributes - ] - valid_attributes = [] + if not attributes: + return [] - for attr_doc in attribute_docs: - selected_attributes = [] - for attr in attr_doc.item_attribute_values: - or_filters = [] - filters= [ - ["Item Variant Attribute", "attribute", "=", attr.parent], - ["Item Variant Attribute", "attribute_value", "=", attr.attribute_value] - ] - if self.item_group: - or_filters.extend([ - ["item_group", "=", self.item_group], - ["Website Item Group", "item_group", "=", self.item_group] - ]) + result = frappe.db.sql( + """ + select + distinct attribute, attribute_value + from + `tabItem Variant Attribute` + where + attribute in %(attributes)s + and attribute_value is not null + """, + {"attributes": attributes}, + as_dict=1, + ) - if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1): - selected_attributes.append(attr) + attribute_value_map = {} + for d in result: + attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value) - if selected_attributes: - valid_attributes.append( - _dict( - item_attribute_values=selected_attributes, - name=attr_doc.name - ) - ) - - return valid_attributes + out = [] + for name, values in attribute_value_map.items(): + out.append(frappe._dict(name=name, item_attribute_values=values)) + return out diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py index 6c9e531a4d1..977f12fb9ef 100644 --- a/erpnext/shopping_cart/product_info.py +++ b/erpnext/shopping_cart/product_info.py @@ -1,13 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe + from erpnext.shopping_cart.cart import _get_cart_quotation, _set_price_list -from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings \ - import get_shopping_cart_settings, show_quantity_in_website -from erpnext.utilities.product import get_price, get_qty_in_stock, get_non_stock_item_status +from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import ( + get_shopping_cart_settings, + show_quantity_in_website, +) +from erpnext.utilities.product import get_non_stock_item_status, get_price, get_qty_in_stock + @frappe.whitelist(allow_guest=True) def get_product_info_for_website(item_code, skip_quotation_creation=False): diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index 6c92d967d0c..5cc0505aed2 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -2,8 +2,10 @@ # License: GNU General Public License v3. See license.txt import frappe + from erpnext.shopping_cart.product_info import get_product_info_for_website + class ProductQuery: """Query engine for product listing diff --git a/erpnext/shopping_cart/search.py b/erpnext/shopping_cart/search.py index 9f674dcebf8..5d2de78f7ca 100644 --- a/erpnext/shopping_cart/search.py +++ b/erpnext/shopping_cart/search.py @@ -1,9 +1,9 @@ import frappe from frappe.search.full_text_search import FullTextSearch -from whoosh.fields import TEXT, ID, KEYWORD, Schema from frappe.utils import strip_html_tags -from whoosh.qparser import MultifieldParser, FieldsPlugin, WildcardPlugin from whoosh.analysis import StemmingAnalyzer +from whoosh.fields import ID, KEYWORD, TEXT, Schema +from whoosh.qparser import FieldsPlugin, MultifieldParser, WildcardPlugin from whoosh.query import Prefix INDEX_NAME = "products" diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py index ac61aebc564..60c220a0878 100644 --- a/erpnext/shopping_cart/test_shopping_cart.py +++ b/erpnext/shopping_cart/test_shopping_cart.py @@ -1,13 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals + import unittest + import frappe -from frappe.utils import nowdate, add_months -from erpnext.shopping_cart.cart import _get_cart_quotation, update_cart, get_party +from frappe.utils import add_months, nowdate + +from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule +from erpnext.shopping_cart.cart import _get_cart_quotation, get_party, update_cart from erpnext.tests.utils import create_test_contact_and_address -from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule # test_dependencies = ['Payment Terms Template'] diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py index 0e1466fd1fa..5f0c7923814 100644 --- a/erpnext/shopping_cart/utils.py +++ b/erpnext/shopping_cart/utils.py @@ -1,11 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - import frappe -import frappe.defaults -from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import is_cart_enabled + +from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import ( + is_cart_enabled, +) + def show_cart_count(): if (is_cart_enabled() and @@ -15,10 +15,19 @@ def show_cart_count(): return False def set_cart_count(login_manager): - role, parties = check_customer_or_supplier() - if role == 'Supplier': return + # since this is run only on hooks login event + # make sure user is already a customer + # before trying to set cart count + user_is_customer = is_customer() + if not user_is_customer: + return + if show_cart_count(): from erpnext.shopping_cart.cart import set_cart_count + + # set_cart_count will try to fetch existing cart quotation + # or create one if non existent (and create a customer too) + # cart count is calculated from this quotation's items set_cart_count() def clear_cart_count(login_manager): @@ -29,13 +38,13 @@ def update_website_context(context): cart_enabled = is_cart_enabled() context["shopping_cart_enabled"] = cart_enabled -def check_customer_or_supplier(): - if frappe.session.user: +def is_customer(): + if frappe.session.user and frappe.session.user != "Guest": contact_name = frappe.get_value("Contact", {"email_id": frappe.session.user}) if contact_name: contact = frappe.get_doc('Contact', contact_name) for link in contact.links: - if link.link_doctype in ('Customer', 'Supplier'): - return link.link_doctype, link.link_name + if link.link_doctype == 'Customer': + return True - return 'Customer', None + return False diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html index 1e3d0d069a1..e560f4ad7de 100644 --- a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html +++ b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html @@ -1,7 +1,7 @@ {%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%} {%- set align_class = resolve_class({ 'text-right': align == 'Right', - 'text-centre': align == 'Center', + 'text-centre': align == 'Centre', 'text-left': align == 'Left', }) -%} @@ -15,7 +15,7 @@