diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index 3e372affa12..1a6dbedf560 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -93,6 +93,7 @@ def resolve_dunning(doc, state):
def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
interest_amount = 0
+ grand_total = 0
if rate_of_interest:
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
interest_amount = (interest_per_year * cint(overdue_days)) / 365
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js
index ef431d7d41a..8ec6a536269 100755
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.js
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js
@@ -31,8 +31,7 @@ frappe.ui.form.on('POS Profile', {
frm.set_query("print_format", function() {
return {
filters: [
- ['Print Format', 'doc_type', '=', 'Sales Invoice'],
- ['Print Format', 'print_format_type', '=', 'Jinja'],
+ ['Print Format', 'doc_type', '=', 'POS Invoice']
]
};
});
@@ -45,10 +44,6 @@ frappe.ui.form.on('POS Profile', {
};
});
- frm.set_query("print_format", function() {
- return { filters: { doc_type: "Sales Invoice", print_format_type: "JS"} };
- });
-
frm.set_query('company_address', function(doc) {
if(!doc.company) {
frappe.throw(__('Please set Company'));
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/__init__.py b/erpnext/accounts/doctype/process_statement_of_accounts/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
new file mode 100644
index 00000000000..e1ddeff61f7
--- /dev/null
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -0,0 +1,89 @@
+
{{ filters.party[0] }}
+{{ _("Statement of Accounts") }}
+
+
+ {{ frappe.format(filters.from_date, 'Date')}}
+ {{ _("to") }}
+ {{ frappe.format(filters.to_date, 'Date')}}
+
+
+
+
+
+ | {{ _("Date") }} |
+ {{ _("Ref") }} |
+ {{ _("Party") }} |
+ {{ _("Debit") }} |
+ {{ _("Credit") }} |
+ {{ _("Balance (Dr - Cr)") }} |
+
+
+
+ {% for row in data %}
+
+ {% if(row.posting_date) %}
+ | {{ frappe.format(row.posting_date, 'Date') }} |
+ {{ row.voucher_type }}
+ {{ row.voucher_no }} |
+
+ {% if not (filters.party or filters.account) %}
+ {{ row.party or row.account }}
+
+ {% endif %}
+
+ {{ _("Against") }}: {{ row.against }}
+ {{ _("Remarks") }}: {{ row.remarks }}
+ {% if row.bill_no %}
+ {{ _("Supplier Invoice No") }}: {{ row.bill_no }}
+ {% endif %}
+ |
+
+ {{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }} |
+
+ {{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }} |
+ {% else %}
+ |
+ |
+ {{ frappe.format(row.account, {fieldtype: "Link"}) or " " }} |
+
+ {{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
+ |
+
+ {{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
+ |
+ {% endif %}
+
+ {{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
+ |
+
+ {% endfor %}
+
+
+
+{% if aging %}
+{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}
+
+ {{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
+
+
+
+
+
+
+ | 30 Days |
+ 60 Days |
+ 90 Days |
+ 120 Days |
+
+
+
+
+ | {{ aging.range1 }} |
+ {{ aging.range2 }} |
+ {{ aging.range3 }} |
+ {{ aging.range4 }} |
+
+
+
+{% endif %}
+Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
new file mode 100644
index 00000000000..7425132c468
--- /dev/null
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
@@ -0,0 +1,132 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Process Statement Of Accounts', {
+ view_properties: function(frm) {
+ frappe.route_options = {doc_type: 'Customer'};
+ frappe.set_route("Form", "Customize Form");
+ },
+ refresh: function(frm){
+ if(!frm.doc.__islocal) {
+ frm.add_custom_button('Send Emails',function(){
+ frappe.call({
+ method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
+ args: {
+ "document_name": frm.doc.name,
+ },
+ callback: function(r) {
+ if(r && r.message) {
+ frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
+ }
+ else{
+ frappe.msgprint('No Records for these settings.')
+ }
+ }
+ });
+ });
+ frm.add_custom_button('Download',function(){
+ var url = frappe.urllib.get_full_url(
+ '/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
+ + 'document_name='+encodeURIComponent(frm.doc.name))
+ $.ajax({
+ url: url,
+ type: 'GET',
+ success: function(result) {
+ if(jQuery.isEmptyObject(result)){
+ frappe.msgprint('No Records for these settings.');
+ }
+ else{
+ window.location = url;
+ }
+ }
+ });
+ });
+ }
+ },
+ onload: function(frm) {
+ frm.set_query('currency', function(){
+ return {
+ filters: {
+ 'enabled': 1
+ }
+ }
+ });
+ if(frm.doc.__islocal){
+ frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1));
+ frm.set_value('to_date', frappe.datetime.get_today());
+ }
+ },
+ customer_collection: function(frm){
+ frm.set_value('collection_name', '');
+ if(frm.doc.customer_collection){
+ frm.get_field('collection_name').set_label(frm.doc.customer_collection);
+ }
+ },
+ frequency: function(frm){
+ if(frm.doc.frequency != ''){
+ frm.set_value('start_date', frappe.datetime.get_today());
+ }
+ else{
+ frm.set_value('start_date', '');
+ }
+ },
+ fetch_customers: function(frm){
+ if(frm.doc.collection_name){
+ frappe.call({
+ method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.fetch_customers",
+ args: {
+ 'customer_collection': frm.doc.customer_collection,
+ 'collection_name': frm.doc.collection_name,
+ 'primary_mandatory': frm.doc.primary_mandatory
+ },
+ callback: function(r) {
+ if(!r.exc) {
+ if(r.message.length){
+ frm.clear_table('customers');
+ for (const customer of r.message){
+ var row = frm.add_child('customers');
+ row.customer = customer.name;
+ row.primary_email = customer.primary_email;
+ row.billing_email = customer.billing_email;
+ }
+ frm.refresh_field('customers');
+ }
+ else{
+ frappe.msgprint('No Customers found with selected options.');
+ }
+ }
+ }
+ });
+ }
+ else {
+ frappe.throw('Enter ' + frm.doc.customer_collection + ' name.');
+ }
+ }
+});
+
+frappe.ui.form.on('Process Statement Of Accounts Customer', {
+ customer: function(frm, cdt, cdn){
+ var row = locals[cdt][cdn];
+ if (!row.customer){
+ return;
+ }
+ frappe.call({
+ method: 'erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.get_customer_emails',
+ args: {
+ 'customer_name': row.customer,
+ 'primary_mandatory': frm.doc.primary_mandatory
+ },
+ callback: function(r){
+ if(!r.exe){
+ if(r.message.length){
+ frappe.model.set_value(cdt, cdn, "primary_email", r.message[0])
+ frappe.model.set_value(cdt, cdn, "billing_email", r.message[1])
+ }
+ else {
+ return
+ }
+ }
+ }
+ })
+ }
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
new file mode 100644
index 00000000000..4be0e2ec068
--- /dev/null
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
@@ -0,0 +1,310 @@
+{
+ "actions": [],
+ "allow_workflow": 1,
+ "autoname": "Prompt",
+ "creation": "2020-05-22 16:46:18.712954",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "section_break_11",
+ "from_date",
+ "company",
+ "account",
+ "group_by",
+ "cost_center",
+ "column_break_14",
+ "to_date",
+ "finance_book",
+ "currency",
+ "project",
+ "section_break_3",
+ "customer_collection",
+ "collection_name",
+ "fetch_customers",
+ "column_break_6",
+ "primary_mandatory",
+ "column_break_17",
+ "customers",
+ "preferences",
+ "orientation",
+ "section_break_14",
+ "include_ageing",
+ "ageing_based_on",
+ "section_break_1",
+ "enable_auto_email",
+ "section_break_18",
+ "frequency",
+ "filter_duration",
+ "column_break_21",
+ "start_date",
+ "section_break_33",
+ "subject",
+ "column_break_28",
+ "cc_to",
+ "section_break_30",
+ "body",
+ "help_text"
+ ],
+ "fields": [
+ {
+ "fieldname": "frequency",
+ "fieldtype": "Select",
+ "label": "Frequency",
+ "options": "Weekly\nMonthly\nQuarterly"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "depends_on": "eval:doc.enable_auto_email == 0;",
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "label": "From Date",
+ "mandatory_depends_on": "eval:doc.frequency == '';"
+ },
+ {
+ "depends_on": "eval:doc.enable_auto_email == 0;",
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "label": "To Date",
+ "mandatory_depends_on": "eval:doc.frequency == '';"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Table MultiSelect",
+ "label": "Cost Center",
+ "options": "PSOA Cost Center"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Table MultiSelect",
+ "label": "Project",
+ "options": "PSOA Project"
+ },
+ {
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break",
+ "label": "Customers"
+ },
+ {
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break",
+ "label": "General Ledger Filters"
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_17",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "customer_collection",
+ "fieldtype": "Select",
+ "label": "Select Customers By",
+ "options": "\nCustomer Group\nTerritory\nSales Partner\nSales Person"
+ },
+ {
+ "depends_on": "eval: doc.customer_collection !== ''",
+ "fieldname": "collection_name",
+ "fieldtype": "Dynamic Link",
+ "label": "Recipient",
+ "options": "customer_collection"
+ },
+ {
+ "fieldname": "section_break_1",
+ "fieldtype": "Section Break",
+ "label": "Email Settings"
+ },
+ {
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "label": "Account",
+ "options": "Account"
+ },
+ {
+ "fieldname": "finance_book",
+ "fieldtype": "Link",
+ "label": "Finance Book",
+ "options": "Finance Book"
+ },
+ {
+ "fieldname": "preferences",
+ "fieldtype": "Section Break",
+ "label": "Print Preferences"
+ },
+ {
+ "fieldname": "orientation",
+ "fieldtype": "Select",
+ "label": "Orientation",
+ "options": "Landscape\nPortrait"
+ },
+ {
+ "default": "Today",
+ "fieldname": "start_date",
+ "fieldtype": "Date",
+ "label": "Start Date"
+ },
+ {
+ "default": "Group by Voucher (Consolidated)",
+ "fieldname": "group_by",
+ "fieldtype": "Select",
+ "label": "Group By",
+ "options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
+ },
+ {
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency"
+ },
+ {
+ "default": "0",
+ "fieldname": "include_ageing",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Include Ageing Summary"
+ },
+ {
+ "default": "Due Date",
+ "depends_on": "eval:doc.include_ageing === 1",
+ "fieldname": "ageing_based_on",
+ "fieldtype": "Select",
+ "label": "Ageing Based On",
+ "options": "Due Date\nPosting Date"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_auto_email",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Enable Auto Email"
+ },
+ {
+ "fieldname": "section_break_14",
+ "fieldtype": "Column Break",
+ "hide_border": 1
+ },
+ {
+ "depends_on": "eval: doc.enable_auto_email ==1",
+ "fieldname": "section_break_18",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "column_break_21",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval: doc.customer_collection !== ''",
+ "fieldname": "fetch_customers",
+ "fieldtype": "Button",
+ "label": "Fetch Customers",
+ "options": "fetch_customers",
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "primary_mandatory",
+ "fieldtype": "Check",
+ "label": "Send To Primary Contact"
+ },
+ {
+ "fieldname": "cc_to",
+ "fieldtype": "Link",
+ "label": "CC To",
+ "options": "User"
+ },
+ {
+ "default": "1",
+ "fieldname": "filter_duration",
+ "fieldtype": "Int",
+ "label": "Filter Duration (Months)"
+ },
+ {
+ "fieldname": "customers",
+ "fieldtype": "Table",
+ "label": "Customers",
+ "options": "Process Statement Of Accounts Customer",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_28",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_30",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "section_break_33",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "help_text",
+ "fieldtype": "HTML",
+ "label": "Help Text",
+ "options": "
\nNote
\n\n- \nYou can use Jinja tags in Subject and Body fields for dynamic values.\n
- \n All fields in this doctype are available under the doc object and all fields for the customer to whom the mail will go to is available under the customer object.\n
\n Examples
\n\n\n - Subject:
Statement Of Accounts for {{ customer.name }}
\n - Body:
\nHello {{ customer.name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.
\n
\n"
+ },
+ {
+ "fieldname": "subject",
+ "fieldtype": "Data",
+ "label": "Subject"
+ },
+ {
+ "fieldname": "body",
+ "fieldtype": "Text Editor",
+ "label": "Body"
+ }
+ ],
+ "links": [],
+ "modified": "2020-08-08 08:47:09.185728",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Process Statement Of Accounts",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
new file mode 100644
index 00000000000..d50e4a8af95
--- /dev/null
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -0,0 +1,271 @@
+# -*- 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 erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
+from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
+from frappe.core.doctype.communication.email import make
+
+from frappe.utils.print_format import report_to_pdf
+from frappe.utils.pdf import get_pdf
+from frappe.utils import today, add_days, add_months, getdate, format_date
+from frappe.utils.jinja import validate_template
+
+import copy
+from datetime import timedelta
+from frappe.www.printview import get_print_style
+
+class ProcessStatementOfAccounts(Document):
+ def validate(self):
+ if not self.subject:
+ self.subject = 'Statement Of Accounts for {{ customer.name }}'
+ if not self.body:
+ self.body = 'Hello {{ customer.name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.'
+
+ validate_template(self.subject)
+ validate_template(self.body)
+
+ if not self.customers:
+ frappe.throw(frappe._('Customers not selected.'))
+
+ if self.enable_auto_email:
+ self.to_date = self.start_date
+ self.from_date = add_months(self.to_date, -1 * self.filter_duration)
+
+
+def get_report_pdf(doc, consolidated=True):
+ statement_dict = {}
+ aging = ''
+ base_template_path = "frappe/www/printview.html"
+ template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
+
+ for entry in doc.customers:
+ if doc.include_ageing:
+ ageing_filters = frappe._dict({
+ 'company': doc.company,
+ 'report_date': doc.to_date,
+ 'ageing_based_on': doc.ageing_based_on,
+ 'range1': 30,
+ 'range2': 60,
+ 'range3': 90,
+ 'range4': 120,
+ 'customer': entry.customer
+ })
+ col1, aging = get_ageing(ageing_filters)
+ aging[0]['ageing_based_on'] = doc.ageing_based_on
+
+ tax_id = frappe.get_doc('Customer', entry.customer).tax_id
+
+ filters= frappe._dict({
+ 'from_date': doc.from_date,
+ 'to_date': doc.to_date,
+ 'company': doc.company,
+ 'finance_book': doc.finance_book if doc.finance_book else None,
+ "account": doc.account if doc.account else None,
+ 'party_type': 'Customer',
+ 'party': [entry.customer],
+ 'group_by': doc.group_by,
+ 'currency': doc.currency,
+ 'cost_center': [cc.cost_center_name for cc in doc.cost_center],
+ 'project': [p.project_name for p in doc.project],
+ 'show_opening_entries': 0,
+ 'include_default_book_entries': 0,
+ 'show_cancelled_entries': 1,
+ 'tax_id': tax_id if tax_id else None
+ })
+ col, res = get_soa(filters)
+
+ for x in [0, -2, -1]:
+ res[x]['account'] = res[x]['account'].replace("'","")
+
+ if len(res) == 3:
+ continue
+ html = frappe.render_template(template_path, \
+ {"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None})
+ html = frappe.render_template(base_template_path, {"body": html, \
+ "css": get_print_style(), "title": "Statement For " + entry.customer})
+ statement_dict[entry.customer] = html
+ if not bool(statement_dict):
+ return False
+ elif consolidated:
+ result = ''.join(list(statement_dict.values()))
+ return get_pdf(result, {'orientation': doc.orientation})
+ else:
+ for customer, statement_html in statement_dict.items():
+ statement_dict[customer]=get_pdf(statement_html, {'orientation': doc.orientation})
+ return statement_dict
+
+def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
+ fields_dict = {
+ 'Customer Group': 'customer_group',
+ 'Territory': 'territory',
+ }
+ collection = frappe.get_doc(customer_collection, collection_name)
+ selected = [customer.name for customer in frappe.get_list(customer_collection, filters=[
+ ['lft', '>=', collection.lft],
+ ['rgt', '<=', collection.rgt]
+ ],
+ fields=['name'],
+ order_by='lft asc, rgt desc'
+ )]
+ return frappe.get_list('Customer', fields=['name', 'email_id'], \
+ filters=[[fields_dict[customer_collection], 'IN', selected]])
+
+def get_customers_based_on_sales_person(sales_person):
+ lft, rgt = frappe.db.get_value("Sales Person",
+ sales_person, ["lft", "rgt"])
+ records = frappe.db.sql("""
+ select distinct parent, parenttype
+ from `tabSales Team` steam
+ where parenttype = 'Customer'
+ and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
+ """, (lft, rgt), as_dict=1)
+ sales_person_records = frappe._dict()
+ for d in records:
+ sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
+ customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
+ filters=[['name', 'in', list(sales_person_records['Customer'])]])
+ return customers
+
+def get_recipients_and_cc(customer, doc):
+ recipients = []
+ for clist in doc.customers:
+ if clist.customer == customer:
+ recipients.append(clist.billing_email)
+ if doc.primary_mandatory and clist.primary_email:
+ recipients.append(clist.primary_email)
+ cc = []
+ if doc.cc_to != '':
+ try:
+ cc=[frappe.get_value('User', doc.cc_to, 'email')]
+ except:
+ pass
+
+ return recipients, cc
+
+def get_context(customer, doc):
+ template_doc = copy.deepcopy(doc)
+ del template_doc.customers
+ template_doc.from_date = format_date(template_doc.from_date)
+ template_doc.to_date = format_date(template_doc.to_date)
+ return {
+ 'doc': template_doc,
+ 'customer': frappe.get_doc('Customer', customer),
+ 'frappe': frappe.utils
+ }
+
+@frappe.whitelist()
+def fetch_customers(customer_collection, collection_name, primary_mandatory):
+ customer_list = []
+ customers = []
+
+ if customer_collection == 'Sales Person':
+ customers = get_customers_based_on_sales_person(collection_name)
+ if not bool(customers):
+ frappe.throw('No Customers found with selected options.')
+ else:
+ if customer_collection == 'Sales Partner':
+ customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
+ filters=[['default_sales_partner', '=', collection_name]])
+ else:
+ customers = get_customers_based_on_territory_or_customer_group(customer_collection, collection_name)
+
+ for customer in customers:
+ primary_email = customer.get('email_id') or ''
+ billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False)
+
+ if billing_email == '' or (primary_email == '' and int(primary_mandatory)):
+ continue
+
+ customer_list.append({
+ 'name': customer.name,
+ 'primary_email': primary_email,
+ 'billing_email': billing_email
+ })
+ return customer_list
+
+@frappe.whitelist()
+def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
+ billing_email = frappe.db.sql("""
+ SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \
+ WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \
+ c.is_billing_contact=1 \
+ order by c.creation desc""")
+
+ if len(billing_email) == 0 or (billing_email[0][0] is None):
+ if billing_and_primary:
+ frappe.throw('No billing email found for customer: '+ customer_name)
+ else:
+ return ''
+
+ if billing_and_primary:
+ primary_email = frappe.get_value('Customer', customer_name, 'email_id')
+ if primary_email is None and int(primary_mandatory):
+ frappe.throw('No primary email found for customer: '+ customer_name)
+ return [primary_email or '', billing_email[0][0]]
+ else:
+ return billing_email[0][0] or ''
+
+@frappe.whitelist()
+def download_statements(document_name):
+ doc = frappe.get_doc('Process Statement Of Accounts', document_name)
+ report = get_report_pdf(doc)
+ if report:
+ frappe.local.response.filename = doc.name + '.pdf'
+ frappe.local.response.filecontent = report
+ frappe.local.response.type = "download"
+
+@frappe.whitelist()
+def send_emails(document_name, from_scheduler=False):
+ doc = frappe.get_doc('Process Statement Of Accounts', document_name)
+ report = get_report_pdf(doc, consolidated=False)
+
+ if report:
+ for customer, report_pdf in report.items():
+ attachments = [{
+ 'fname': customer + '.pdf',
+ 'fcontent': report_pdf
+ }]
+
+ recipients, cc = get_recipients_and_cc(customer, doc)
+ context = get_context(customer, doc)
+ subject = frappe.render_template(doc.subject, context)
+ message = frappe.render_template(doc.body, context)
+
+ frappe.enqueue(
+ queue='short',
+ method=frappe.sendmail,
+ recipients=recipients,
+ sender=frappe.session.user,
+ cc=cc,
+ subject=subject,
+ message=message,
+ now=True,
+ reference_doctype='Process Statement Of Accounts',
+ reference_name=document_name,
+ attachments=attachments
+ )
+
+ if doc.enable_auto_email and from_scheduler:
+ new_to_date = getdate(today())
+ if doc.frequency == 'Weekly':
+ new_to_date = add_days(new_to_date, 7)
+ else:
+ new_to_date = add_months(new_to_date, 1 if doc.frequency == 'Monthly' else 3)
+ new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
+ doc.add_comment('Comment', 'Emails sent on: ' + frappe.utils.format_datetime(frappe.utils.now()))
+ doc.db_set('to_date', new_to_date, commit=True)
+ doc.db_set('from_date', new_from_date, commit=True)
+ return True
+ else:
+ return False
+
+@frappe.whitelist()
+def send_auto_email():
+ selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1})
+ for entry in selected:
+ send_emails(entry.name, from_scheduler=True)
+ return True
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py
new file mode 100644
index 00000000000..30efbb36833
--- /dev/null
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py
@@ -0,0 +1,10 @@
+# -*- 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 TestProcessStatementOfAccounts(unittest.TestCase):
+ pass
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts_customer/__init__.py b/erpnext/accounts/doctype/process_statement_of_accounts_customer/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
new file mode 100644
index 00000000000..dd04dc1b3c6
--- /dev/null
+++ b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
@@ -0,0 +1,47 @@
+{
+ "actions": [],
+ "allow_workflow": 1,
+ "creation": "2020-08-03 16:35:21.852178",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "customer",
+ "billing_email",
+ "primary_email"
+ ],
+ "fields": [
+ {
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Customer",
+ "options": "Customer",
+ "reqd": 1
+ },
+ {
+ "fieldname": "primary_email",
+ "fieldtype": "Read Only",
+ "in_list_view": 1,
+ "label": "Primary Contact Email"
+ },
+ {
+ "fieldname": "billing_email",
+ "fieldtype": "Read Only",
+ "in_list_view": 1,
+ "label": "Billing Email"
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-08-03 22:55:38.875601",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Process Statement Of Accounts Customer",
+ "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/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.py b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.py
new file mode 100644
index 00000000000..1a760101dba
--- /dev/null
+++ b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.py
@@ -0,0 +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 ProcessStatementOfAccountsCustomer(Document):
+ pass
diff --git a/erpnext/accounts/doctype/psoa_cost_center/__init__.py b/erpnext/accounts/doctype/psoa_cost_center/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json
new file mode 100644
index 00000000000..e292b60d68d
--- /dev/null
+++ b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json
@@ -0,0 +1,30 @@
+{
+ "actions": [],
+ "creation": "2020-08-03 16:56:45.744905",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "cost_center_name"
+ ],
+ "fields": [
+ {
+ "fieldname": "cost_center_name",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-08-03 16:56:45.744905",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "PSOA Cost Center",
+ "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/accounts/doctype/psoa_cost_center/psoa_cost_center.py b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.py
new file mode 100644
index 00000000000..0aeef3ed3a8
--- /dev/null
+++ b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.py
@@ -0,0 +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 PSOACostCenter(Document):
+ pass
diff --git a/erpnext/accounts/doctype/psoa_project/__init__.py b/erpnext/accounts/doctype/psoa_project/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/psoa_project/psoa_project.json b/erpnext/accounts/doctype/psoa_project/psoa_project.json
new file mode 100644
index 00000000000..20a03eed96e
--- /dev/null
+++ b/erpnext/accounts/doctype/psoa_project/psoa_project.json
@@ -0,0 +1,30 @@
+{
+ "actions": [],
+ "creation": "2020-08-03 16:52:14.731978",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "project_name"
+ ],
+ "fields": [
+ {
+ "fieldname": "project_name",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-08-03 16:53:39.219736",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "PSOA Project",
+ "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/accounts/doctype/psoa_project/psoa_project.py b/erpnext/accounts/doctype/psoa_project/psoa_project.py
new file mode 100644
index 00000000000..f4a5dee9752
--- /dev/null
+++ b/erpnext/accounts/doctype/psoa_project/psoa_project.py
@@ -0,0 +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 PSOAProject(Document):
+ pass
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 2e91c8ef19f..d62e73b6ac6 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -180,7 +180,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
- "options": "ACC-PINV-.YYYY.-",
+ "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@@ -1334,7 +1334,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2020-08-03 12:46:01.411074",
+ "modified": "2020-08-03 23:20:04.466153",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1396,4 +1396,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 4dc81e90875..31613e50b04 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1,7 +1,6 @@
{
"actions": [],
"allow_import": 1,
- "allow_workflow": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:05",
"doctype": "DocType",
@@ -217,7 +216,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
- "options": "ACC-SINV-.YYYY.-",
+ "options": "ACC-SINV-.YYYY.-\nACC-SINV-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@@ -1947,7 +1946,7 @@
"idx": 181,
"is_submittable": 1,
"links": [],
- "modified": "2020-07-18 05:07:16.725974",
+ "modified": "2020-08-03 23:31:12.675040",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py
index 0e9c808608b..d825c6fd325 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py
@@ -8,7 +8,7 @@ def get_data():
'fieldname': 'taxes_and_charges',
'non_standard_fieldnames': {
'Tax Rule': 'sales_tax_template',
- 'Subscription': 'tax_template',
+ 'Subscription': 'sales_tax_template',
'Restaurant': 'default_tax_template'
},
'transactions': [
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 28a65196502..2f800bb2abd 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -611,7 +611,7 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur
cond = "posting_date <= '{0}'".format(posting_date)
if company:
- cond += "and company = '{0}'".format(company)
+ cond += "and company = {0}".format(frappe.db.escape(company))
data = frappe.db.sql(""" SELECT party, sum({0}) as amount
FROM `tabGL Entry`
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 155597e8565..fd702c74c73 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -8,6 +8,7 @@ from frappe import _
from frappe.utils import flt, getdate, cint, date_diff, formatdate
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from frappe.model.document import Document
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
class AssetValueAdjustment(Document):
def validate(self):
@@ -53,17 +54,33 @@ class AssetValueAdjustment(Document):
je.company = self.company
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
- je.append("accounts", {
+ credit_entry = {
"account": accumulated_depreciation_account,
"credit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center
- })
+ }
- je.append("accounts", {
+ debit_entry = {
"account": depreciation_expense_account,
"debit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center
- })
+ }
+
+ accounting_dimensions = get_checks_for_pl_and_bs_accounts()
+
+ for dimension in accounting_dimensions:
+ if dimension.get('mandatory_for_bs'):
+ credit_entry.update({
+ dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
+ })
+
+ if dimension.get('mandatory_for_pl'):
+ debit_entry.update({
+ dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
+ })
+
+ je.append("accounts", credit_entry)
+ je.append("accounts", debit_entry)
je.flags.ignore_permissions = True
je.submit()
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 89c38c710b4..66b5f3035db 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -479,7 +479,11 @@ class AccountsController(TransactionBase):
if d.against_order:
allocated_amount = flt(d.amount)
else:
- amount = self.rounded_total or self.grand_total
+ if self.get('party_account_currency') == self.company_currency:
+ amount = self.get('base_rounded_total') or self.base_grand_total
+ else:
+ amount = self.get('rounded_total') or self.grand_total
+
allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount)
@@ -802,10 +806,22 @@ class AccountsController(TransactionBase):
self.payment_terms_template = ''
return
+ party_account_currency = self.get('party_account_currency')
+ if not party_account_currency:
+ party_type, party = self.get_party()
+
+ if party_type and party:
+ party_account_currency = get_party_account_currency(party_type, party, self.company)
+
posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
date = self.get("due_date")
due_date = date or posting_date
- grand_total = self.get("rounded_total") or self.grand_total
+
+ if party_account_currency == self.company_currency:
+ grand_total = self.get("base_rounded_total") or self.base_grand_total
+ else:
+ grand_total = self.get("rounded_total") or self.grand_total
+
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount)
@@ -850,13 +866,25 @@ class AccountsController(TransactionBase):
def validate_payment_schedule_amount(self):
if self.doctype == 'Sales Invoice' and self.is_pos: return
+ party_account_currency = self.get('party_account_currency')
+ if not party_account_currency:
+ party_type, party = self.get_party()
+
+ if party_type and party:
+ party_account_currency = get_party_account_currency(party_type, party, self.company)
+
if self.get("payment_schedule"):
total = 0
for d in self.get("payment_schedule"):
total += flt(d.payment_amount)
- total = flt(total, self.precision("grand_total"))
- grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
+ if party_account_currency == self.company_currency:
+ total = flt(total, self.precision("base_grand_total"))
+ grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
+ else:
+ total = flt(total, self.precision("grand_total"))
+ grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
+
if self.get("total_advance"):
grand_total -= self.get("total_advance")
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index babc5bdd797..37b7e31e611 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -613,9 +613,12 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
if not taxes:
return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """)
else:
+ valid_from = filters.get('valid_from')
+ valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
+
args = {
'item_code': filters.get('item_code'),
- 'posting_date': filters.get('valid_from'),
+ 'posting_date': valid_from,
'tax_category': filters.get('tax_category'),
'company': filters.get('company')
}
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 545e2324acf..5cd5233b2e4 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -16,6 +16,7 @@
"opportunity_from",
"party_name",
"customer_name",
+ "source",
"column_break0",
"title",
"opportunity_type",
@@ -49,10 +50,9 @@
"contact_email",
"contact_mobile",
"more_info",
- "source",
+ "company",
"campaign",
"column_break1",
- "company",
"transaction_date",
"amended_from",
"lost_reasons"
@@ -344,7 +344,7 @@
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
- "label": "Source",
+ "label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text"
},
@@ -424,7 +424,7 @@
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
- "modified": "2020-07-14 16:49:15.888503",
+ "modified": "2020-08-11 17:34:35.066961",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 1b071ea1b70..e152850f170 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -119,11 +119,19 @@ class Opportunity(TransactionBase):
and q.status not in ('Lost', 'Closed')""", self.name)
def has_ordered_quotation(self):
- return frappe.db.sql("""
- select q.name
- from `tabQuotation` q, `tabQuotation Item` qi
- where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
- and q.status = 'Ordered'""", self.name)
+ if not self.with_items:
+ return frappe.get_all('Quotation',
+ {
+ 'opportunity': self.name,
+ 'status': 'Ordered',
+ 'docstatus': 1
+ }, 'name')
+ else:
+ return frappe.db.sql("""
+ select q.name
+ from `tabQuotation` q, `tabQuotation Item` qi
+ where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
+ and q.status = 'Ordered'""", self.name)
def has_lost_quotation(self):
lost_quotation = frappe.db.sql("""
@@ -330,7 +338,7 @@ def make_opportunity_from_communication(communication, ignore_communication_link
opportunity = frappe.get_doc({
"doctype": "Opportunity",
"opportunity_from": opportunity_from,
- "lead": lead
+ "party_name": lead
}).insert(ignore_permissions=True)
link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links)
diff --git a/erpnext/education/doctype/fees/test_fees.py b/erpnext/education/doctype/fees/test_fees.py
index b182992922b..eedc2ae7301 100644
--- a/erpnext/education/doctype/fees/test_fees.py
+++ b/erpnext/education/doctype/fees/test_fees.py
@@ -7,7 +7,7 @@ import frappe
import unittest
from frappe.utils import nowdate
from frappe.utils.make_random import get_random
-
+from erpnext.education.doctype.program.test_program import make_program_and_linked_courses
# test_records = frappe.get_test_records('Fees')
@@ -15,6 +15,7 @@ class TestFees(unittest.TestCase):
def test_fees(self):
student = get_random("Student")
+ program = make_program_and_linked_courses("_Test Program 1", ["_Test Course 1", "_Test Course 2"])
fee = frappe.new_doc("Fees")
fee.posting_date = nowdate()
fee.due_date = nowdate()
@@ -23,6 +24,7 @@ class TestFees(unittest.TestCase):
fee.income_account = "Sales - _TC"
fee.cost_center = "_Test Cost Center - _TC"
fee.company = "_Test Company"
+ fee.program = program.name
fee.extend("components", [
{
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
index 971e166067e..60f0f9d56d6 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
@@ -134,7 +134,7 @@ let transfer_patient_dialog = function(frm) {
{fieldtype: 'Link', label: 'Leave From', fieldname: 'leave_from', options: 'Healthcare Service Unit', reqd: 1, read_only:1},
{fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'},
{fieldtype: 'Link', label: 'Transfer To', fieldname: 'service_unit', options: 'Healthcare Service Unit', reqd: 1},
- {fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1}
+ {fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1, default: frappe.datetime.now_datetime()}
],
primary_action_label: __('Transfer'),
primary_action : function() {
@@ -147,7 +147,12 @@ let transfer_patient_dialog = function(frm) {
if(dialog.get_value('service_unit')){
service_unit = dialog.get_value('service_unit');
}
- if(!check_in){
+ if(check_in > frappe.datetime.now_datetime()){
+ frappe.msgprint({
+ title: __('Not Allowed'),
+ message: __('Check-in time cannot be greater than the current time'),
+ indicator: 'red'
+ });
return;
}
frappe.call({
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 95a836fe652..463ad6c94b4 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -322,7 +322,8 @@ scheduler_events = {
"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.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
+ "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email"
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",
diff --git a/erpnext/hr/desk_page/hr/hr.json b/erpnext/hr/desk_page/hr/hr.json
index 0fed8d322f5..895cf7290c9 100644
--- a/erpnext/hr/desk_page/hr/hr.json
+++ b/erpnext/hr/desk_page/hr/hr.json
@@ -78,7 +78,7 @@
"idx": 0,
"is_standard": 1,
"label": "HR",
- "modified": "2020-06-16 19:20:50.976045",
+ "modified": "2020-08-11 17:04:38.655417",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
@@ -88,7 +88,7 @@
"pin_to_top": 0,
"shortcuts": [
{
- "color": "#9deca2",
+ "color": "#cef6d1",
"format": "{} Active",
"label": "Employee",
"link_to": "Employee",
@@ -96,18 +96,19 @@
"type": "DocType"
},
{
- "label": "Attendance",
- "link_to": "Attendance",
- "stats_filter": "",
- "type": "DocType"
- },
- {
+ "color": "#ffe8cd",
"format": "{} Open",
"label": "Leave Application",
"link_to": "Leave Application",
"stats_filter": "{\"status\":\"Open\"}",
"type": "DocType"
},
+ {
+ "label": "Attendance",
+ "link_to": "Attendance",
+ "stats_filter": "",
+ "type": "DocType"
+ },
{
"label": "Job Applicant",
"link_to": "Job Applicant",
diff --git a/erpnext/loan_management/desk_page/loan/loan.json b/erpnext/loan_management/desk_page/loan/loan.json
index 48193b0a0d8..3bdd1ce56e7 100644
--- a/erpnext/loan_management/desk_page/loan/loan.json
+++ b/erpnext/loan_management/desk_page/loan/loan.json
@@ -3,7 +3,7 @@
{
"hidden": 0,
"label": "Loan",
- "links": "[\n {\n \"description\": \"Loan Type for interest and penalty rates\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Applications from customers and employees.\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loans provided to customers and employees.\",\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"description\": \"Loan Type for interest and penalty rates\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Applications from customers and employees.\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n { \"dependencies\": [\n \"Loan Type\"\n ],\n \"description\": \"Loans provided to customers and employees.\",\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.js b/erpnext/loan_management/doctype/loan_application/loan_application.js
index 6cf47bf85c7..b56fce1d7cb 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.js
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.js
@@ -112,16 +112,19 @@ frappe.ui.form.on('Loan Application', {
frappe.ui.form.on("Proposed Pledge", {
loan_security: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
- frappe.call({
- method: "erpnext.loan_management.doctype.loan_security_price.loan_security_price.get_loan_security_price",
- args: {
- loan_security: row.loan_security
- },
- callback: function(r) {
- frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
- frm.events.calculate_amounts(frm, cdt, cdn);
- }
- })
+
+ if (row.loan_security) {
+ frappe.call({
+ method: "erpnext.loan_management.doctype.loan_security_price.loan_security_price.get_loan_security_price",
+ args: {
+ loan_security: row.loan_security
+ },
+ callback: function(r) {
+ frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
+ frm.events.calculate_amounts(frm, cdt, cdn);
+ }
+ })
+ }
},
amount: function(frm, cdt, cdn) {
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 e6ceb551850..b56fa80c7ae 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
@@ -19,8 +19,8 @@ class LoanInterestAccrual(AccountsController):
if not self.posting_date:
self.posting_date = nowdate()
- if not self.interest_amount:
- frappe.throw(_("Interest Amount is mandatory"))
+ if not self.interest_amount and not self.payable_principal_amount:
+ frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
def on_submit(self):
@@ -39,37 +39,38 @@ class LoanInterestAccrual(AccountsController):
def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = []
- gle_map.append(
- self.get_gl_dict({
- "account": self.loan_account,
- "party_type": self.applicant_type,
- "party": self.applicant,
- "against": self.interest_income_account,
- "debit": self.interest_amount,
- "debit_in_account_currency": self.interest_amount,
- "against_voucher_type": "Loan",
- "against_voucher": self.loan,
- "remarks": _("Against Loan:") + self.loan,
- "cost_center": erpnext.get_default_cost_center(self.company),
- "posting_date": self.posting_date
- })
- )
+ if self.interest_amount:
+ gle_map.append(
+ self.get_gl_dict({
+ "account": self.loan_account,
+ "party_type": self.applicant_type,
+ "party": self.applicant,
+ "against": self.interest_income_account,
+ "debit": self.interest_amount,
+ "debit_in_account_currency": self.interest_amount,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.loan,
+ "remarks": _("Against Loan:") + self.loan,
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ "posting_date": self.posting_date
+ })
+ )
- gle_map.append(
- self.get_gl_dict({
- "account": self.interest_income_account,
- "party_type": self.applicant_type,
- "party": self.applicant,
- "against": self.loan_account,
- "credit": self.interest_amount,
- "credit_in_account_currency": self.interest_amount,
- "against_voucher_type": "Loan",
- "against_voucher": self.loan,
- "remarks": _("Against Loan:") + self.loan,
- "cost_center": erpnext.get_default_cost_center(self.company),
- "posting_date": self.posting_date
- })
- )
+ gle_map.append(
+ self.get_gl_dict({
+ "account": self.interest_income_account,
+ "party_type": self.applicant_type,
+ "party": self.applicant,
+ "against": self.loan_account,
+ "credit": self.interest_amount,
+ "credit_in_account_currency": self.interest_amount,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.loan,
+ "remarks": _("Against Loan:") + self.loan,
+ "cost_center": erpnext.get_default_cost_center(self.company),
+ "posting_date": self.posting_date
+ })
+ )
if gle_map:
make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj)
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 789c1299463..5942455919f 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -173,7 +173,7 @@
{
"fieldname": "references_section",
"fieldtype": "Section Break",
- "label": "References"
+ "label": "Payment References"
},
{
"fieldname": "reference_number",
@@ -221,7 +221,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-16 18:14:45.166754",
+ "modified": "2020-05-16 09:40:15.581165",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Repayment",
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.js b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.js
index 82837b3dac8..11c932ff1c1 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.js
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.js
@@ -22,16 +22,19 @@ frappe.ui.form.on('Loan Security Pledge', {
frappe.ui.form.on("Pledge", {
loan_security: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
- frappe.call({
- method: "erpnext.loan_management.doctype.loan_security_price.loan_security_price.get_loan_security_price",
- args: {
- loan_security: row.loan_security
- },
- callback: function(r) {
- frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
- frm.events.calculate_amounts(frm, cdt, cdn);
- }
- });
+
+ if (row.loan_security) {
+ frappe.call({
+ method: "erpnext.loan_management.doctype.loan_security_price.loan_security_price.get_loan_security_price",
+ args: {
+ loan_security: row.loan_security
+ },
+ callback: function(r) {
+ frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
+ frm.events.calculate_amounts(frm, cdt, cdn);
+ }
+ });
+ }
},
qty: function(frm, cdt, cdn) {
diff --git a/erpnext/loan_management/doctype/loan_security_type/loan_security_type.json b/erpnext/loan_management/doctype/loan_security_type/loan_security_type.json
index f46b88cbca6..871e82563af 100644
--- a/erpnext/loan_management/doctype/loan_security_type/loan_security_type.json
+++ b/erpnext/loan_management/doctype/loan_security_type/loan_security_type.json
@@ -29,6 +29,7 @@
"unique": 1
},
{
+ "description": "Haircut percentage is the percentage difference between market value of the Loan Security and the value ascribed to that Loan Security when used as collateral for that loan.",
"fieldname": "haircut",
"fieldtype": "Percent",
"label": "Haircut %"
@@ -46,13 +47,14 @@
"fieldtype": "Column Break"
},
{
+ "description": "Loan To Value Ratio expresses the ratio of the loan amount to the value of the security pledged. A loan security shortfall will be triggered if this falls below the specified value for any loan ",
"fieldname": "loan_to_value_ratio",
"fieldtype": "Percent",
"label": "Loan To Value Ratio"
}
],
"links": [],
- "modified": "2020-04-28 14:06:49.046177",
+ "modified": "2020-05-16 09:38:45.988080",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Type",
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 1dd3710cd2a..669490a4480 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -76,6 +76,7 @@
"reqd": 1
},
{
+ "description": "This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower",
"fieldname": "payment_account",
"fieldtype": "Link",
"label": "Payment Account",
@@ -83,6 +84,7 @@
"reqd": 1
},
{
+ "description": "This account is capital account which is used to allocate capital for loan disbursal account ",
"fieldname": "loan_account",
"fieldtype": "Link",
"label": "Loan Account",
@@ -94,6 +96,7 @@
"fieldtype": "Column Break"
},
{
+ "description": "This account will be used for booking loan interest accruals",
"fieldname": "interest_income_account",
"fieldtype": "Link",
"label": "Interest Income Account",
@@ -101,6 +104,7 @@
"reqd": 1
},
{
+ "description": "This account will be used for booking penalties levied due to delayed repayments",
"fieldname": "penalty_income_account",
"fieldtype": "Link",
"label": "Penalty Income Account",
@@ -109,6 +113,7 @@
},
{
"default": "0",
+ "description": "If this is not checked the loan by default will be considered as a Demand Loan",
"fieldname": "is_term_loan",
"fieldtype": "Check",
"label": "Is Term Loan"
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
index ff32dbed98a..f6486743aa3 100644
--- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
@@ -8,7 +8,7 @@ frappe.query_reports["Downtime Analysis"] = {
label: __("From Date"),
fieldname:"from_date",
fieldtype: "Datetime",
- default: frappe.datetime.add_months(frappe.datetime.now_datetime(), -1),
+ default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)),
reqd: 1
},
{
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ae20d9edbd2..49af0ba6ee8 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -687,6 +687,7 @@ execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation
+erpnext.patches.v13_0.update_old_loans
erpnext.patches.v12_0.set_serial_no_status #2020-05-21
erpnext.patches.v12_0.update_price_list_currency_in_bom
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py
new file mode 100644
index 00000000000..77239429c51
--- /dev/null
+++ b/erpnext/patches/v13_0/update_old_loans.py
@@ -0,0 +1,88 @@
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import nowdate
+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
+
+def execute():
+
+ # Create a penalty account for loan types
+
+ frappe.reload_doc('loan_management', 'doctype', 'loan_type')
+ frappe.reload_doc('loan_management', 'doctype', 'loan')
+ frappe.reload_doc('loan_management', 'doctype', 'repayment_schedule')
+ frappe.reload_doc('loan_management', 'doctype', 'process_loan_interest_accrual')
+ frappe.reload_doc('loan_management', 'doctype', 'loan_repayment')
+ frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail')
+ frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual')
+ frappe.reload_doc('accounts', 'doctype', 'gl_entry')
+
+ updated_loan_types = []
+
+ loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment',
+ 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'])
+
+ for loan in loans:
+ # Update details in Loan Types and Loan
+ loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company')
+
+ group_income_account = frappe.get_value('Account', {'company': loan.company,
+ 'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')})
+
+ if not group_income_account:
+ group_income_account = frappe.get_value('Account', {'company': loan.company,
+ 'is_group': 1, 'root_type': 'Income'})
+
+ penalty_account = create_account(company=loan.company, account_type='Income Account',
+ account_name='Penalty Account', parent_account=group_income_account)
+
+ if not loan_type_company:
+ loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type)
+ loan_type_doc.is_term_loan = 1
+ loan_type_doc.company = loan.company
+ loan_type_doc.mode_of_payment = loan.mode_of_payment
+ loan_type_doc.payment_account = loan.payment_account
+ loan_type_doc.loan_account = loan.loan_account
+ loan_type_doc.interest_income_account = loan.interest_income_account
+ loan_type_doc.penalty_income_account = penalty_account
+ loan_type_doc.submit()
+ updated_loan_types.append(loan.loan_type)
+
+ if loan.loan_type in updated_loan_types:
+ if loan.status == 'Fully Disbursed':
+ status = 'Disbursed'
+ elif loan.status == 'Repaid/Closed':
+ status = 'Closed'
+ else:
+ status = loan.status
+
+ frappe.db.set_value('Loan', loan.name, {
+ 'is_term_loan': 1,
+ 'penalty_income_account': penalty_account,
+ 'status': status
+ })
+
+ process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan.loan_type,
+ loan=loan.name)
+
+ payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date
+ FROM `tabJournal Entry` j, `tabJournal Entry Account` a
+ WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s
+ and account = %s
+ ''', (loan.name, loan.loan_account), as_dict=1)
+
+ for payment in payments:
+ repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant,
+ loan.loan_type, loan.company)
+
+ repayment_entry.amount_paid = payment.debit_in_account_currency
+ repayment_entry.posting_date = payment.posting_date
+ repayment_entry.save()
+ repayment_entry.submit()
+
+ jv = frappe.get_doc('Journal Entry', payment.name)
+ jv.flags.ignore_links = True
+ jv.cancel()
+
diff --git a/erpnext/payroll/desk_page/payroll/payroll.json b/erpnext/payroll/desk_page/payroll/payroll.json
index b5eac465c82..285e3b3a135 100644
--- a/erpnext/payroll/desk_page/payroll/payroll.json
+++ b/erpnext/payroll/desk_page/payroll/payroll.json
@@ -8,7 +8,7 @@
{
"hidden": 0,
"label": "Taxation",
- "links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]"
+ "links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]"
},
{
"hidden": 0,
@@ -38,7 +38,7 @@
"idx": 0,
"is_standard": 1,
"label": "Payroll",
- "modified": "2020-06-19 12:23:06.034046",
+ "modified": "2020-08-10 19:38:45.976209",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 554484febbb..30ea432678c 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -90,7 +90,7 @@ class PayrollEntry(Document):
cond = ''
for f in ['company', 'branch', 'department', 'designation']:
if self.get(f):
- cond += " and t1." + f + " = '" + self.get(f).replace("'", "\'") + "'"
+ cond += " and t1." + f + " = " + frappe.db.escape(self.get(f))
return cond
diff --git a/erpnext/public/js/communication.js b/erpnext/public/js/communication.js
index 5316eb45b5b..9432d421752 100644
--- a/erpnext/public/js/communication.js
+++ b/erpnext/public/js/communication.js
@@ -13,7 +13,7 @@ frappe.ui.form.on("Communication", {
frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
frm.trigger('make_issue_from_communication');
})
- }, "Make");
+ }, "Create");
}
if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 4e50f3d7f67..436a232a551 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1821,7 +1821,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
set_query_for_item_tax_template: function(doc, cdt, cdn) {
-
var item = frappe.get_doc(cdt, cdn);
if(!item.item_code) {
frappe.throw(__("Please enter Item Code to get item taxes"));
@@ -1829,7 +1828,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
let filters = {
'item_code': item.item_code,
- 'valid_from': doc.transaction_date || doc.bill_date || doc.posting_date,
+ 'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date],
'item_group': item.item_group,
}
diff --git a/erpnext/public/less/website.less b/erpnext/public/less/website.less
index 57a0a332a98..ac878de105b 100644
--- a/erpnext/public/less/website.less
+++ b/erpnext/public/less/website.less
@@ -297,6 +297,10 @@
margin-top: 30px;
}
+.item-group-slideshow {
+ margin-bottom: 1rem;
+}
+
.product-image-img {
border: 1px solid @light-border-color;
border-radius: 3px;
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 2d306ba1726..787d557e805 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -234,9 +234,6 @@ class GSTR3BReport(Document):
self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \
flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2)
- for k, v in iteritems(account_map):
- txval -= self.report_dict.get(supply_type, {}).get(supply_category, {}).get(v, 0)
-
self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2)
def set_inter_state_supply(self, inter_state_supply):
@@ -256,7 +253,7 @@ class GSTR3BReport(Document):
def get_total_taxable_value(self, doctype, reverse_charge):
return frappe._dict(frappe.db.sql("""
- select gst_category, sum(base_grand_total) as total
+ select gst_category, sum(net_total) as total
from `tab{doctype}`
where docstatus = 1 and month(posting_date) = %s
and year(posting_date) = %s and reverse_charge = %s
@@ -309,26 +306,27 @@ class GSTR3BReport(Document):
inter_state_supply_tax_mapping.setdefault(d.name, {
'place_of_supply': d.place_of_supply,
'taxable_value': d.net_total,
+ 'gst_category': d.gst_category,
'camt': 0.0,
'samt': 0.0,
'iamt': 0.0,
'csamt': 0.0
})
- if d.account_head in [d.cgst_account for d in self.account_heads]:
+ if d.account_head in [a.cgst_account for a in self.account_heads]:
inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount
- if d.account_head in [d.sgst_account for d in self.account_heads]:
+ if d.account_head in [a.sgst_account for a in self.account_heads]:
inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount
- if d.account_head in [d.igst_account for d in self.account_heads]:
+ if d.account_head in [a.igst_account for a in self.account_heads]:
inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount
- if d.account_head in [d.cess_account for d in self.account_heads]:
+ if d.account_head in [a.cess_account for a in self.account_heads]:
inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount
for key, value in iteritems(inter_state_supply_tax_mapping):
- if d.place_of_supply:
+ if value.get('place_of_supply'):
osup_det = self.report_dict["sup_details"]["osup_det"]
osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2)
osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2)
@@ -336,15 +334,15 @@ class GSTR3BReport(Document):
osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2)
osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2)
- if state_number != d.place_of_supply.split("-")[0]:
- inter_state_supply_details.setdefault((d.gst_category, d.place_of_supply), {
+ if state_number != value.get('place_of_supply').split("-")[0]:
+ inter_state_supply_details.setdefault((value.get('gst_category'), value.get('place_of_supply')), {
"txval": 0.0,
- "pos": d.place_of_supply.split("-")[0],
+ "pos": value.get('place_of_supply').split("-")[0],
"iamt": 0.0
})
- inter_state_supply_details[(d.gst_category, d.place_of_supply)]['txval'] += value['taxable_value']
- inter_state_supply_details[(d.gst_category, d.place_of_supply)]['iamt'] += value['iamt']
+ inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['txval'] += value['taxable_value']
+ inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['iamt'] += value['iamt']
return inter_state_supply_details
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 8885b88c2a4..282efe47901 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -131,6 +131,9 @@ class Gstr1Report(object):
taxable_value += abs(net_amount)
elif tax_rate:
taxable_value += abs(net_amount)
+ elif not tax_rate and self.filters.get('type_of_business') == 'EXPORT' \
+ and invoice_details.get('export_type') == "Without Payment of Tax":
+ taxable_value += abs(net_amount)
row += [tax_rate or 0, taxable_value]
diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py
index 22e30e31139..09e474dc2ef 100644
--- a/erpnext/selling/doctype/customer/customer_dashboard.py
+++ b/erpnext/selling/doctype/customer/customer_dashboard.py
@@ -12,7 +12,8 @@ def get_data():
'Payment Entry': 'party',
'Quotation': 'party_name',
'Opportunity': 'party_name',
- 'Bank Account': 'party'
+ 'Bank Account': 'party',
+ 'Subscription': 'party'
},
'dynamic_links': {
'party_name': ['Customer', 'quotation_to']
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 9f8410f40bc..83bd71d5f32 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -14,10 +14,9 @@ from six import string_types
def get_items(start, page_length, price_list, item_group, search_value="", pos_profile=None):
data = dict()
warehouse = ""
- display_items_in_stock = 0
if pos_profile:
- warehouse, display_items_in_stock = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'display_items_in_stock'])
+ warehouse = frappe.db.get_value('POS Profile', pos_profile, ['warehouse'])
if not frappe.db.exists('Item Group', item_group):
item_group = get_root_of('Item Group')
@@ -85,7 +84,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
item_price = item_prices.get(item_code) or {}
item_stock_qty = get_stock_availability(item_code, warehouse)
- if display_items_in_stock and not item_stock_qty:
+ if not item_stock_qty:
pass
else:
row = {}
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 483ef78d64c..ae5471b9000 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -35,7 +35,8 @@ erpnext.PointOfSale.Controller = class {
create_opening_voucher() {
const table_fields = [
{ fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 },
- { fieldname: "opening_amount", fieldtype: "Currency", in_list_view: 1, label: "Opening Amount", options: "company:company_currency", reqd: 1 }
+ { fieldname: "opening_amount", fieldtype: "Currency", default: 0, in_list_view: 1, label: "Opening Amount",
+ options: "company:company_currency", reqd: 1 }
];
const dialog = new frappe.ui.Dialog({
@@ -66,7 +67,7 @@ erpnext.PointOfSale.Controller = class {
frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => {
dialog.fields_dict.balance_details.df.data = [];
payment_reconciliation.forEach(pay => {
- const { mode_of_payment, closing_amount } = pay;
+ const { mode_of_payment } = pay;
dialog.fields_dict.balance_details.df.data.push({
mode_of_payment: mode_of_payment
});
@@ -152,7 +153,7 @@ erpnext.PointOfSale.Controller = class {
},
() => this.make_new_invoice(),
() => frappe.dom.unfreeze(),
- () => this.page.set_title(__('Point of Sale Beta')),
+ () => this.page.set_title(__('Point of Sale')),
]);
}
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 221044df3a7..03703fd82ef 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -242,7 +242,7 @@
{
"fieldname": "default_warehouse_for_sales_return",
"fieldtype": "Link",
- "label": "Default warehouse for Sales Return",
+ "label": "Default Warehouse for Sales Return",
"options": "Warehouse"
},
{
@@ -801,4 +801,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 66efcf8cd85..ea385c8b2a9 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -175,7 +175,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
- "options": "MAT-DN-.YYYY.-",
+ "options": "MAT-DN-.YYYY.-\nMAT-DN-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@@ -1255,7 +1255,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2020-07-18 05:13:55.580420",
+ "modified": "2020-08-03 23:18:47.739997",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 92e33ca64e3..ce54fc883f6 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -1,7 +1,6 @@
{
"actions": [],
"allow_import": 1,
- "allow_workflow": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
"doctype": "DocType",
@@ -160,7 +159,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
- "options": "MAT-PRE-.YYYY.-",
+ "options": "MAT-PRE-.YYYY.-\nMAT-PR-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@@ -1110,7 +1109,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2020-07-18 05:19:12.148115",
+ "modified": "2020-08-03 23:20:26.381024",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index 3f984536037..40a064fc768 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -4,7 +4,7 @@
{% block page_content %}
-
+
{% if slideshow %}
{% include "templates/includes/slideshow.html" %}
{% endif %}