Merge pull request #22086 from deepeshgarg007/rcm_develop
fix(India): Reverse charge mechanism for GST
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index d36b115..8f67858 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -11,4 +11,4 @@
- name: curl
run: |
apk add curl bash
- curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.org/repo/frappe%2Ffrappe_docker/requests
+ curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json
index c9bbbab..e7fa954 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.json
+++ b/erpnext/accounts/doctype/cost_center/cost_center.json
@@ -146,7 +146,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2020-04-29 16:09:30.025214",
+ "modified": "2020-06-17 16:09:30.025214",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 50b17ab..1f95e00 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -102,7 +102,7 @@
frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
else:
- msg = _("The value {0} is already assigned to an exisiting Item {1}.").format(
+ msg = _("The value {0} is already assigned to an existing Item {1}.").format(
frappe.bold(attribute_value), frappe.bold(item))
msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.js b/erpnext/education/doctype/fee_structure/fee_structure.js
index 7606565..f09d2ef 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.js
+++ b/erpnext/education/doctype/fee_structure/fee_structure.js
@@ -9,6 +9,14 @@
},
onload: function(frm) {
+ frm.set_query("academic_term", function() {
+ return {
+ "filters": {
+ "academic_year": frm.doc.academic_year
+ }
+ };
+ });
+
frm.set_query("receivable_account", function(doc) {
return {
filters: {
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.json b/erpnext/education/doctype/fee_structure/fee_structure.json
index 8ff6851..67e4637 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.json
+++ b/erpnext/education/doctype/fee_structure/fee_structure.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
@@ -11,8 +12,8 @@
"program",
"student_category",
"column_break_2",
- "academic_term",
"academic_year",
+ "academic_term",
"section_break_4",
"components",
"section_break_6",
@@ -157,7 +158,8 @@
],
"icon": "fa fa-flag",
"is_submittable": 1,
- "modified": "2019-05-26 09:04:17.765758",
+ "links": [],
+ "modified": "2020-06-16 15:34:57.295010",
"modified_by": "Administrator",
"module": "Education",
"name": "Fee Structure",
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 1b0c9f6..6dedaa8 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -73,10 +73,16 @@
if customer_exists:
frappe.rename_doc("Customer", old_name, customer_name)
- billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"})
- shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"})
- rename_address(billing_address, customer)
- rename_address(shipping_address, customer)
+ for address_type in ("Billing", "Shipping",):
+ try:
+ address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": address_type})
+ rename_address(address, customer)
+ except (
+ frappe.DoesNotExistError,
+ frappe.DuplicateEntryError,
+ frappe.ValidationError,
+ ):
+ pass
else:
create_address(raw_billing_data, customer, "Billing")
create_address(raw_shipping_data, customer, "Shipping")
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index 64c3b2d..25ffd28 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -8,6 +8,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_request_session
+from requests.exceptions import HTTPError
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.erpnext_integrations.utils import get_webhook_address
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
@@ -29,19 +30,24 @@
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
# url = get_shopify_url('admin/webhooks.json', self)
created_webhooks = [d.method for d in self.webhooks]
- url = get_shopify_url('admin/api/2019-04/webhooks.json', self)
+ url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
for method in webhooks:
session = get_request_session()
try:
- d = session.post(url, data=json.dumps({
+ res = session.post(url, data=json.dumps({
"webhook": {
"topic": method,
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
"format": "json"
}
}), headers=get_header(self))
- d.raise_for_status()
- self.update_webhook_table(method, d.json())
+ res.raise_for_status()
+ self.update_webhook_table(method, res.json())
+
+ except HTTPError as e:
+ error_message = res.json().get('errors', e)
+ make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
except Exception as e:
make_shopify_log(status="Warning", exception=e, rollback=True)
@@ -50,13 +56,18 @@
deleted_webhooks = []
for d in self.webhooks:
- url = get_shopify_url('admin/api/2019-04/webhooks/{0}.json'.format(d.webhook_id), self)
+ url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
try:
res = session.delete(url, headers=get_header(self))
res.raise_for_status()
deleted_webhooks.append(d)
+
+ except HTTPError as e:
+ error_message = res.json().get('errors', e)
+ make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
except Exception as e:
- frappe.log_error(message=frappe.get_traceback(), title=e)
+ frappe.log_error(message=e, title='Shopify Webhooks Issue')
for d in deleted_webhooks:
self.remove(d)
@@ -125,4 +136,3 @@
}
create_custom_fields(custom_fields)
-
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
index bde1011..f9f0bb3 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
@@ -8,7 +8,7 @@
shopify_variants_attr_list = ["option1", "option2", "option3"]
def sync_item_from_shopify(shopify_settings, item):
- url = get_shopify_url("admin/api/2019-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
+ url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
session = get_request_session()
try:
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
index 0c13b6a..3dc7c1e 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
@@ -70,6 +70,7 @@
if frappe.db.get_value('Item', item, 'is_stock_item'):
frappe.throw(_(msg))
+@frappe.whitelist()
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
fields = ['name', 'practitioner_name', 'mobile_phone']
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 279c453..d74dc24 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -699,3 +699,4 @@
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
erpnext.patches.v13_0.update_sla_enhancements
erpnext.patches.v12_0.update_address_template_for_india
+erpnext.patches.v13_0.update_issue_metrics
diff --git a/erpnext/patches/v13_0/update_issue_metrics.py b/erpnext/patches/v13_0/update_issue_metrics.py
new file mode 100644
index 0000000..6d76235
--- /dev/null
+++ b/erpnext/patches/v13_0/update_issue_metrics.py
@@ -0,0 +1,33 @@
+from __future__ import unicode_literals
+import frappe
+
+from frappe.core.doctype.communication.communication import set_avg_response_time
+from erpnext.support.doctype.issue.issue import set_resolution_time, set_user_resolution_time
+
+def execute():
+ if frappe.db.exists('DocType', 'Issue'):
+ frappe.reload_doctype('Issue')
+
+ count = 0
+ for parent in frappe.get_all('Issue', order_by='creation desc'):
+ parent_doc = frappe.get_doc('Issue', parent.name)
+
+ communication = frappe.get_all('Communication', filters={
+ 'reference_doctype': 'Issue',
+ 'reference_name': parent.name,
+ 'communication_medium': 'Email',
+ 'sent_or_received': 'Sent'
+ }, order_by = 'creation asc', limit=1)
+
+ if communication:
+ communication_doc = frappe.get_doc('Communication', communication[0].name)
+ set_avg_response_time(parent_doc, communication_doc)
+
+ if parent_doc.status in ['Closed', 'Resolved']:
+ set_resolution_time(parent_doc)
+ set_user_resolution_time(parent_doc)
+
+ # commit after every 100 records
+ count += 1
+ if count % 100 == 0:
+ frappe.db.commit()
\ No newline at end of file
diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss
index 735b417..617e916 100644
--- a/erpnext/public/scss/website.scss
+++ b/erpnext/public/scss/website.scss
@@ -81,4 +81,10 @@
.place-order-container {
text-align: right;
+}
+
+.kb-card {
+ .card-body > .card-title {
+ line-height: 1.3;
+ }
}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index ac3bc20..682dfed 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -339,6 +339,7 @@
return lp_details
+@frappe.whitelist()
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
from erpnext.controllers.queries import get_fields
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 e78d0ff..f15f63d 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
@@ -5,7 +5,7 @@
import calendar
import frappe
from frappe import _
-from frappe.utils import cint, cstr
+from frappe.utils import cint, cstr, getdate
def execute(filters=None):
common_columns = [
@@ -160,7 +160,7 @@
return columns, data, None, None, None, 1
def get_customer_stats(filters, tree_view=False):
- """ Calculates number of new and repeated customers. """
+ """ Calculates number of new and repeated customers and revenue. """
company_condition = ''
if filters.get('company'):
company_condition = ' and company=%(company)s'
@@ -174,14 +174,14 @@
filters, as_dict=1):
key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
+ new_or_repeat = 'new' if si.customer not in customers else 'repeat'
customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
- if not si.customer in customers:
- customers_in[key]['new'][0] += 1
- customers_in[key]['new'][1] += si.base_grand_total
+ # if filters.from_date <= si.posting_date.strftime('%Y-%m-%d'):
+ if getdate(filters.from_date) <= getdate(si.posting_date):
+ customers_in[key][new_or_repeat][0] += 1
+ customers_in[key][new_or_repeat][1] += si.base_grand_total
+ if new_or_repeat == 'new':
customers.append(si.customer)
- else:
- customers_in[key]['repeat'][0] += 1
- customers_in[key]['repeat'][1] += si.base_grand_total
return customers_in
diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json
index 1c1b0c3..5d3d3ac 100644
--- a/erpnext/support/doctype/support_settings/support_settings.json
+++ b/erpnext/support/doctype/support_settings/support_settings.json
@@ -1,5 +1,5 @@
{
- "actions": [],
+ "actions": "",
"creation": "2017-02-17 13:07:35.686409",
"doctype": "DocType",
"editable_grid": 1,
@@ -22,6 +22,10 @@
"post_description_key",
"post_route_key",
"post_route_string",
+ "greetings_section_section",
+ "greeting_title",
+ "column_break_19",
+ "greeting_subtitle",
"search_apis_sb",
"search_apis"
],
@@ -127,11 +131,40 @@
"fieldname": "allow_resetting_service_level_agreement",
"fieldtype": "Check",
"label": "Allow Resetting Service Level Agreement"
+ },
+ {
+ "default": "We're here to help",
+ "fieldname": "greeting_title",
+ "fieldtype": "Data",
+ "label": "Greeting Title",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "Browse help topics",
+ "fieldname": "greeting_subtitle",
+ "fieldtype": "Data",
+ "label": "Greeting Subtitle",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "greetings_section_section",
+ "fieldtype": "Section Break",
+ "label": "Greetings Section",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"issingle": 1,
"links": [],
- "modified": "2020-06-05 17:56:17.491684",
+ "modified": "2020-06-11 13:08:38.473616",
"modified_by": "Administrator",
"module": "Support",
"name": "Support Settings",
diff --git a/erpnext/www/support/__init__.py b/erpnext/www/support/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/www/support/__init__.py
diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html
new file mode 100644
index 0000000..93da503
--- /dev/null
+++ b/erpnext/www/support/index.html
@@ -0,0 +1,58 @@
+{% extends "templates/web.html" %}
+
+{% block content %}
+<section class="section section-padding-top section-padding-bottom">
+ <div class='container'>
+ <div class="hero-content">
+ <h1 class="hero-title">{{ greeting_title or _("We're here to help!") }}</h1>
+ {% if greeting_subtitle %}
+ <p class="hero-subtitle">{{ greeting_subtitle }}</p>
+ {% endif %}
+ </div>
+ </div>
+</section>
+
+{% if favorite_article_list %}
+<section class="section section-padding-top section-padding-bottom bg-light">
+ <div class='container'>
+ <h2>{{ _("Frequently Read Articles") }}</h2>
+ <div class="row">
+ {% for favorite_article in favorite_article_list %}
+ <div class="mt-4 col-12 col-sm-6 col-lg-4">
+ <div class="card card-md h-100 kb-card">
+ <div class="card-body">
+ <h6 class="card-subtitle mb-2 text-uppercase small text-muted">
+ {{ favorite_article['category'] }}</h6>
+ <h3 class="card-title">{{ favorite_article['title'] }}</h3>
+ <p class="card-text">{{ favorite_article['description'] }}</p>
+ </div>
+ <a href="{{ favorite_article['route'] }}" class="stretched-link"></a>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+</section>
+{% endif %}
+
+{% if help_article_list %}
+<section class="section section-padding-top section-padding-bottom">
+ <div class='container'>
+ <h2>{{ _("Help Articles") }}</h2>
+ <div class="row">
+ {% for item in help_article_list %}
+ <div class="mt-5 col-12 col-sm-6 col-lg-4">
+ <h5>{{ item['category'].name }}</h5>
+ <div>
+ {% for article in item['articles'] %}
+ <a href="{{ article.route }}" class="mt-2 d-block">{{ article.title }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+</section>
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/support/index.py b/erpnext/www/support/index.py
new file mode 100644
index 0000000..5d26743
--- /dev/null
+++ b/erpnext/www/support/index.py
@@ -0,0 +1,74 @@
+from __future__ import unicode_literals
+import frappe
+
+def get_context(context):
+ context.no_cache = 1
+ context.align_greeting = ''
+ setting = frappe.get_doc("Support Settings")
+
+ context.greeting_title = setting.greeting_title
+ context.greeting_subtitle = setting.greeting_subtitle
+
+ # Support content
+ favorite_articles = get_favorite_articles_by_page_view()
+ if len(favorite_articles) < 6:
+ name_list = []
+ if favorite_articles:
+ for article in favorite_articles:
+ name_list.append(article.name)
+ for record in (frappe.get_all("Help Article",
+ fields=["title", "content", "route", "category"],
+ filters={"name": ['not in', tuple(name_list)], "published": 1},
+ order_by="creation desc", limit=(6-len(favorite_articles)))):
+ favorite_articles.append(record)
+
+ context.favorite_article_list = get_favorite_articles(favorite_articles)
+ context.help_article_list = get_help_article_list()
+
+def get_favorite_articles_by_page_view():
+ return frappe.db.sql(
+ """
+ SELECT
+ t1.name as name,
+ t1.title as title,
+ t1.content as content,
+ t1.route as route,
+ t1.category as category,
+ count(t1.route) as count
+ FROM `tabHelp Article` AS t1
+ INNER JOIN
+ `tabWeb Page View` AS t2
+ ON t1.route = t2.path
+ WHERE t1.published = 1
+ GROUP BY route
+ ORDER BY count DESC
+ LIMIT 6;
+ """, as_dict=True)
+
+def get_favorite_articles(favorite_articles):
+ favorite_article_list=[]
+ for article in favorite_articles:
+ description = frappe.utils.strip_html(article.content)
+ if len(description) > 120:
+ description = description[:120] + '...'
+ favorite_article_dict = {
+ 'title': article.title,
+ 'description': description,
+ 'route': article.route,
+ 'category': article.category,
+ }
+ favorite_article_list.append(favorite_article_dict)
+ return favorite_article_list
+
+def get_help_article_list():
+ help_article_list=[]
+ category_list = frappe.get_all("Help Category", fields="name")
+ for category in category_list:
+ help_articles = frappe.get_all("Help Article", fields="*", filters={"category": category.name, "published": 1}, order_by="modified desc", limit=5)
+ if help_articles:
+ help_aricles_per_caetgory = {
+ 'category': category,
+ 'articles': help_articles,
+ }
+ help_article_list.append(help_aricles_per_caetgory)
+ return help_article_list
\ No newline at end of file