Merge pull request #25541 from rohitwaghchaure/fixed-incorrect-serial-no-set
fix: serial no changed after saving stock reconciliation
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 824b74e..84ecfb1 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -80,14 +80,29 @@
env:
TYPE: ${{ matrix.TYPE }}
- - name: Coverage
- if: matrix.TYPE == 'server'
+ - name: Coverage - Pull Request
+ if: matrix.TYPE == 'server' && github.event_name == 'pull_request'
run: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE}
- pip install coveralls==3.0.1
- pip install coverage==5.5
+ pip install coveralls==2.2.0
+ pip install coverage==4.5.4
coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
+ COVERALLS_SERVICE_NAME: github
+
+ - name: Coverage - Push
+ if: matrix.TYPE == 'server' && github.event_name == 'push'
+ run: |
+ cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
+ cd ${GITHUB_WORKSPACE}
+ pip install coveralls==2.2.0
+ pip install coverage==4.5.4
+ coveralls --service=github-actions
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
+ COVERALLS_SERVICE_NAME: github-actions
+
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 4da0605..a988d72 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.1.0'
+__version__ = '13.2.0'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json
index aa32d95..c9f15a6 100644
--- a/erpnext/accounts/doctype/party_account/party_account.json
+++ b/erpnext/accounts/doctype/party_account/party_account.json
@@ -1,87 +1,39 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2014-08-29 16:02:39.740505",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2014-08-29 16:02:39.740505",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "field_order": [
+ "company",
+ "account"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-07-11 03:28:03.348246",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Party Account",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-07 18:13:08.833822",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Party Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index cf6ec18..6635128 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -114,7 +114,7 @@
'party_type': self.party_type,
'voucher_type': voucher_type,
'account': self.receivable_payable_account
- }, as_dict=1, debug=1)
+ }, as_dict=1)
def add_payment_entries(self, entries):
self.set('payments', [])
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 9ea616f..aa0c53e 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -22,7 +22,43 @@
});
if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime());
- if (frm.doc.docstatus === 1) set_html_data(frm);
+
+ frappe.realtime.on('closing_process_complete', async function(data) {
+ await frm.reload_doc();
+ if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) {
+ frappe.msgprint({
+ title: __('POS Closing Failed'),
+ message: frm.doc.error_message,
+ indicator: 'orange',
+ clear: true
+ });
+ }
+ });
+
+ set_html_data(frm);
+ },
+
+ refresh: function(frm) {
+ if (frm.doc.docstatus == 1 && frm.doc.status == 'Failed') {
+ const issue = '<a id="jump_to_error" style="text-decoration: underline;">issue</a>';
+ frm.dashboard.set_headline(
+ __('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue]));
+
+ $('#jump_to_error').on('click', (e) => {
+ e.preventDefault();
+ frappe.utils.scroll_to(
+ cur_frm.get_field("error_message").$wrapper,
+ true,
+ 30
+ );
+ });
+
+ frm.add_custom_button(__('Retry'), function () {
+ frm.call('retry', {}, () => {
+ frm.reload_doc();
+ });
+ });
+ }
},
pos_opening_entry(frm) {
@@ -61,44 +97,24 @@
refresh_fields(frm);
set_html_data(frm);
}
- })
+ });
+ },
+
+ before_save: function(frm) {
+ for (let row of frm.doc.pos_transactions) {
+ frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => {
+ cur_frm.doc.grand_total -= flt(doc.grand_total);
+ cur_frm.doc.net_total -= flt(doc.net_total);
+ cur_frm.doc.total_quantity -= flt(doc.total_qty);
+ refresh_payments(doc, cur_frm, 1);
+ refresh_taxes(doc, cur_frm, 1);
+ refresh_fields(cur_frm);
+ set_html_data(cur_frm);
+ });
+ }
}
});
-cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) {
- const removed_row = locals[cdt][cdn];
-
- if (!removed_row.pos_invoice) return;
-
- frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => {
- cur_frm.doc.grand_total -= flt(doc.grand_total);
- cur_frm.doc.net_total -= flt(doc.net_total);
- cur_frm.doc.total_quantity -= flt(doc.total_qty);
- refresh_payments(doc, cur_frm, 1);
- refresh_taxes(doc, cur_frm, 1);
- refresh_fields(cur_frm);
- set_html_data(cur_frm);
- });
-}
-
-frappe.ui.form.on('POS Invoice Reference', {
- pos_invoice(frm, cdt, cdn) {
- const added_row = locals[cdt][cdn];
-
- if (!added_row.pos_invoice) return;
-
- frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => {
- frm.doc.grand_total += flt(doc.grand_total);
- frm.doc.net_total += flt(doc.net_total);
- frm.doc.total_quantity += flt(doc.total_qty);
- refresh_payments(doc, frm);
- refresh_taxes(doc, frm);
- refresh_fields(frm);
- set_html_data(frm);
- });
- }
-})
-
frappe.ui.form.on('POS Closing Entry Detail', {
closing_amount: (frm, cdt, cdn) => {
const row = locals[cdt][cdn];
@@ -177,11 +193,13 @@
}
function set_html_data(frm) {
- frappe.call({
- method: "get_payment_reconciliation_details",
- doc: frm.doc,
- callback: (r) => {
- frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
- }
- })
+ if (frm.doc.docstatus === 1 && frm.doc.status == 'Submitted') {
+ frappe.call({
+ method: "get_payment_reconciliation_details",
+ doc: frm.doc,
+ callback: (r) => {
+ frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
+ }
+ });
+ }
}
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
index a9b91e0..4d6e4a2 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
@@ -30,6 +30,8 @@
"total_quantity",
"column_break_16",
"taxes",
+ "failure_description_section",
+ "error_message",
"section_break_14",
"amended_from"
],
@@ -195,7 +197,7 @@
"fieldtype": "Select",
"hidden": 1,
"label": "Status",
- "options": "Draft\nSubmitted\nQueued\nCancelled",
+ "options": "Draft\nSubmitted\nQueued\nFailed\nCancelled",
"print_hide": 1,
"read_only": 1
},
@@ -203,6 +205,21 @@
"fieldname": "period_details_section",
"fieldtype": "Section Break",
"label": "Period Details"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "error_message",
+ "depends_on": "error_message",
+ "fieldname": "failure_description_section",
+ "fieldtype": "Section Break",
+ "label": "Failure Description"
+ },
+ {
+ "depends_on": "error_message",
+ "fieldname": "error_message",
+ "fieldtype": "Small Text",
+ "label": "Error",
+ "read_only": 1
}
],
"is_submittable": 1,
@@ -212,7 +229,7 @@
"link_fieldname": "pos_closing_entry"
}
],
- "modified": "2021-02-01 13:47:20.722104",
+ "modified": "2021-05-05 16:59:49.723261",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
index 1065168..8252872 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -60,6 +60,10 @@
def on_cancel(self):
unconsolidate_pos_invoices(closing_entry=self)
+ @frappe.whitelist()
+ def retry(self):
+ consolidate_pos_invoices(closing_entry=self)
+
def update_opening_entry(self, for_cancel=False):
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
opening_entry.pos_closing_entry = self.name if not for_cancel else None
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js
index 20fd610..cffeb4d 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js
@@ -8,6 +8,7 @@
"Draft": "red",
"Submitted": "blue",
"Queued": "orange",
+ "Failed": "red",
"Cancelled": "red"
};
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 6d2cffc..bc78743 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -13,8 +13,7 @@
from frappe.utils.scheduler import is_scheduler_inactive
from frappe.core.page.background_jobs.background_jobs import get_info
import json
-
-from six import iteritems
+import six
class POSInvoiceMergeLog(Document):
def validate(self):
@@ -235,11 +234,11 @@
return pos_invoice_customer_map
-def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
- invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices()
+def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
+ invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices()
invoice_by_customer = get_invoice_customer_map(invoices)
- if len(invoices) >= 5 and closing_entry:
+ if len(invoices) >= 10 and closing_entry:
closing_entry.set_status(update=True, status='Queued')
enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
else:
@@ -252,51 +251,83 @@
pluck='name'
)
- if len(merge_logs) >= 5:
+ if len(merge_logs) >= 10:
closing_entry.set_status(update=True, status='Queued')
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
else:
cancel_merge_logs(merge_logs, closing_entry)
-def create_merge_logs(invoice_by_customer, closing_entry={}):
- for customer, invoices in iteritems(invoice_by_customer):
- merge_log = frappe.new_doc('POS Invoice Merge Log')
- merge_log.posting_date = getdate(closing_entry.get('posting_date'))
- merge_log.customer = customer
- merge_log.pos_closing_entry = closing_entry.get('name', None)
+def create_merge_logs(invoice_by_customer, closing_entry=None):
+ try:
+ for customer, invoices in six.iteritems(invoice_by_customer):
+ merge_log = frappe.new_doc('POS Invoice Merge Log')
+ merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
+ merge_log.customer = customer
+ merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
- merge_log.set('pos_invoices', invoices)
- merge_log.save(ignore_permissions=True)
- merge_log.submit()
+ merge_log.set('pos_invoices', invoices)
+ merge_log.save(ignore_permissions=True)
+ merge_log.submit()
- if closing_entry:
- closing_entry.set_status(update=True, status='Submitted')
- closing_entry.update_opening_entry()
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Submitted')
+ closing_entry.db_set('error_message', '')
+ closing_entry.update_opening_entry()
-def cancel_merge_logs(merge_logs, closing_entry={}):
- for log in merge_logs:
- merge_log = frappe.get_doc('POS Invoice Merge Log', log)
- merge_log.flags.ignore_permissions = True
- merge_log.cancel()
+ except Exception:
+ frappe.db.rollback()
+ message_log = frappe.message_log.pop()
+ error_message = safe_load_json(message_log)
- if closing_entry:
- closing_entry.set_status(update=True, status='Cancelled')
- closing_entry.update_opening_entry(for_cancel=True)
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Failed')
+ closing_entry.db_set('error_message', error_message)
+ raise
-def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None):
+ finally:
+ frappe.db.commit()
+ frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user})
+
+def cancel_merge_logs(merge_logs, closing_entry=None):
+ try:
+ for log in merge_logs:
+ merge_log = frappe.get_doc('POS Invoice Merge Log', log)
+ merge_log.flags.ignore_permissions = True
+ merge_log.cancel()
+
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Cancelled')
+ closing_entry.db_set('error_message', '')
+ closing_entry.update_opening_entry(for_cancel=True)
+
+ except Exception:
+ frappe.db.rollback()
+ message_log = frappe.message_log.pop()
+ error_message = safe_load_json(message_log)
+
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Submitted')
+ closing_entry.db_set('error_message', error_message)
+ raise
+
+ finally:
+ frappe.db.commit()
+ frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user})
+
+def enqueue_job(job, **kwargs):
check_scheduler_status()
+ closing_entry = kwargs.get('closing_entry') or {}
+
job_name = closing_entry.get("name")
if not job_already_enqueued(job_name):
enqueue(
job,
+ **kwargs,
queue="long",
timeout=10000,
event="processing_merge_logs",
job_name=job_name,
- closing_entry=closing_entry,
- invoice_by_customer=invoice_by_customer,
- merge_logs=merge_logs,
now=frappe.conf.developer_mode or frappe.flags.in_test
)
@@ -314,4 +345,14 @@
def job_already_enqueued(job_name):
enqueued_jobs = [d.get("job_name") for d in get_info()]
if job_name in enqueued_jobs:
- return True
\ No newline at end of file
+ return True
+
+def safe_load_json(message):
+ JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
+
+ try:
+ json_message = json.loads(message).get('message')
+ except JSONDecodeError:
+ json_message = message
+
+ return json_message
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_search_fields/__init__.py b/erpnext/accounts/doctype/pos_search_fields/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/__init__.py
diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json
new file mode 100644
index 0000000..a627f5b
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "creation": "2021-04-19 14:56:06.652327",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "field",
+ "fieldname"
+ ],
+ "fields": [
+ {
+ "fieldname": "fieldname",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Fieldname"
+ },
+ {
+ "fieldname": "field",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Field"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-21 11:12:54.632093",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Search Fields",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py
new file mode 100644
index 0000000..720ea77
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class POSSearchFields(Document):
+ pass
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
index 3625393..9003af5 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.js
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -1,9 +1,17 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
+let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor'];
+let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series",
+ "default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series",
+ "serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account",
+ "deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail",
+ "web_long_description", "hub_sync_id"]
+
frappe.ui.form.on('POS Settings', {
onload: function(frm) {
frm.trigger("get_invoice_fields");
+ frm.trigger("add_search_options");
},
get_invoice_fields: function(frm) {
@@ -21,6 +29,38 @@
);
});
+ },
+
+ add_search_options: function(frm) {
+ frappe.model.with_doctype("Item", () => {
+ var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
+ if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) {
+ return [d.label];
+ } else {
+ return null;
+ }
+ });
+
+ fields.unshift('');
+ frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields);
+ });
+
+ }
+});
+
+frappe.ui.form.on("POS Search Fields", {
+ field: function(frm, doctype, name) {
+ var doc = frappe.get_doc(doctype, name);
+ var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
+ if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) {
+ return d;
+ } else {
+ return null;
+ }
+ })[0];
+
+ doc.fieldname = df.fieldname;
+ frm.refresh_field("fields");
}
});
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json
index 3539588..962eb94 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.json
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json
@@ -5,7 +5,8 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "invoice_fields"
+ "invoice_fields",
+ "pos_search_fields"
],
"fields": [
{
@@ -13,11 +14,17 @@
"fieldtype": "Table",
"label": "POS Field",
"options": "POS Field"
+ },
+ {
+ "fieldname": "pos_search_fields",
+ "fieldtype": "Table",
+ "label": "POS Search Fields",
+ "options": "POS Search Fields"
}
],
"issingle": 1,
"links": [],
- "modified": "2020-06-01 15:46:41.478928",
+ "modified": "2021-04-19 14:56:24.465218",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Settings",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index e61cde8..f58c8f4 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -514,6 +514,28 @@
}
},
+ refresh: function(frm) {
+ frm.events.add_custom_buttons(frm);
+ },
+
+ add_custom_buttons: function(frm) {
+ if (frm.doc.per_received < 100) {
+ frm.add_custom_button(__('Purchase Receipt'), () => {
+ frm.events.make_purchase_receipt(frm);
+ }, __('Create'));
+ }
+
+ if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
+ frm.add_custom_button(__('Purchase Receipt'), () => {
+ frappe.route_options = {
+ 'purchase_invoice': frm.doc.name
+ }
+
+ frappe.set_route("List", "Purchase Receipt", "List")
+ }, __('View'));
+ }
+ },
+
onload: function(frm) {
if(frm.doc.__onload && frm.is_new()) {
if(frm.doc.supplier) {
@@ -539,5 +561,13 @@
update_stock: function(frm) {
hide_fields(frm.doc);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
+ },
+
+ make_purchase_receipt: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
+ frm: frm,
+ freeze_message: __("Creating Purchase Receipt ...")
+ })
}
})
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 2d5760b..24e67fe 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -163,7 +163,8 @@
"to_date",
"column_break_114",
"auto_repeat",
- "update_auto_repeat_reference"
+ "update_auto_repeat_reference",
+ "per_received"
],
"fields": [
{
@@ -1364,6 +1365,15 @@
"print_hide": 1,
"print_width": "50px",
"width": "50px"
+ },
+ {
+ "fieldname": "per_received",
+ "fieldtype": "Percent",
+ "hidden": 1,
+ "label": "Per Received",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 5c4e32e..83e9f75 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1207,3 +1207,41 @@
def on_doctype_update():
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
+
+@frappe.whitelist()
+def make_purchase_receipt(source_name, target_doc=None):
+ def update_item(obj, target, source_parent):
+ target.qty = flt(obj.qty) - flt(obj.received_qty)
+ target.received_qty = flt(obj.qty) - flt(obj.received_qty)
+ target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
+ target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
+ target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
+ flt(obj.rate) * flt(source_parent.conversion_rate)
+
+ doc = get_mapped_doc("Purchase Invoice", source_name, {
+ "Purchase Invoice": {
+ "doctype": "Purchase Receipt",
+ "validation": {
+ "docstatus": ["=", 1],
+ }
+ },
+ "Purchase Invoice Item": {
+ "doctype": "Purchase Receipt Item",
+ "field_map": {
+ "name": "purchase_invoice_item",
+ "parent": "purchase_invoice",
+ "bom": "bom",
+ "purchase_order": "purchase_order",
+ "po_detail": "purchase_order_item",
+ "material_request": "material_request",
+ "material_request_item": "material_request_item"
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
+ },
+ "Purchase Taxes and Charges": {
+ "doctype": "Purchase Taxes and Charges"
+ }
+ }, target_doc)
+
+ return doc
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 96ad0fd..10e1c73 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -607,6 +607,7 @@
"oldfieldname": "purchase_order",
"oldfieldtype": "Link",
"options": "Purchase Order",
+ "print_hide": 1,
"read_only": 1,
"search_index": 1
},
@@ -853,7 +854,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 00:59:52.614805",
+ "modified": "2021-03-30 09:02:39.256602",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 4461f29..4de8773 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1111,7 +1111,7 @@
if not item.serial_no:
continue
- for serial_no in item.serial_no.split("\n"):
+ for serial_no in get_serial_nos(item.serial_no):
if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json
index e80df2a..c4e4be7 100644
--- a/erpnext/accounts/doctype/subscription/subscription.json
+++ b/erpnext/accounts/doctype/subscription/subscription.json
@@ -36,6 +36,7 @@
"additional_discount_percentage",
"additional_discount_amount",
"sb_3",
+ "submit_invoice",
"invoices",
"accounting_dimensions_section",
"cost_center",
@@ -45,9 +46,7 @@
{
"allow_on_submit": 1,
"fieldname": "cb_1",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "status",
@@ -55,97 +54,73 @@
"label": "Status",
"no_copy": 1,
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "subscription_period",
"fieldtype": "Section Break",
- "label": "Subscription Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Subscription Period"
},
{
"fieldname": "cancelation_date",
"fieldtype": "Date",
"label": "Cancelation Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "trial_period_start",
"fieldtype": "Date",
"label": "Trial Period Start Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"depends_on": "eval:doc.trial_period_start",
"fieldname": "trial_period_end",
"fieldtype": "Date",
"label": "Trial Period End Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "current_invoice_start",
"fieldtype": "Date",
"label": "Current Invoice Start Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "current_invoice_end",
"fieldtype": "Date",
"label": "Current Invoice End Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "0",
"description": "Number of days that the subscriber has to pay invoices generated by this subscription",
"fieldname": "days_until_due",
"fieldtype": "Int",
- "label": "Days Until Due",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Days Until Due"
},
{
"default": "0",
"fieldname": "cancel_at_period_end",
"fieldtype": "Check",
- "label": "Cancel At End Of Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Cancel At End Of Period"
},
{
"default": "0",
"fieldname": "generate_invoice_at_period_start",
"fieldtype": "Check",
- "label": "Generate Invoice At Beginning Of Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Generate Invoice At Beginning Of Period"
},
{
"allow_on_submit": 1,
"fieldname": "sb_4",
"fieldtype": "Section Break",
- "label": "Plans",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Plans"
},
{
"allow_on_submit": 1,
@@ -153,84 +128,62 @@
"fieldtype": "Table",
"label": "Plans",
"options": "Subscription Plan Detail",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
"fieldname": "sb_1",
"fieldtype": "Section Break",
- "label": "Taxes",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Taxes"
},
{
"fieldname": "sb_2",
"fieldtype": "Section Break",
- "label": "Discounts",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Discounts"
},
{
"fieldname": "apply_additional_discount",
"fieldtype": "Select",
"label": "Apply Additional Discount On",
- "options": "\nGrand Total\nNet Total",
- "show_days": 1,
- "show_seconds": 1
+ "options": "\nGrand Total\nNet Total"
},
{
"fieldname": "cb_2",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Percent",
- "label": "Additional DIscount Percentage",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional DIscount Percentage"
},
{
"collapsible": 1,
"fieldname": "additional_discount_amount",
"fieldtype": "Currency",
- "label": "Additional DIscount Amount",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional DIscount Amount"
},
{
"depends_on": "eval:doc.invoices",
"fieldname": "sb_3",
"fieldtype": "Section Break",
- "label": "Invoices",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Invoices"
},
{
"collapsible": 1,
"fieldname": "invoices",
"fieldtype": "Table",
"label": "Invoices",
- "options": "Subscription Invoice",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Subscription Invoice"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
- "label": "Accounting Dimensions",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "party_type",
@@ -238,9 +191,7 @@
"label": "Party Type",
"options": "DocType",
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "party",
@@ -249,27 +200,21 @@
"label": "Party",
"options": "party_type",
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"depends_on": "eval:doc.party_type === 'Customer'",
"fieldname": "sales_tax_template",
"fieldtype": "Link",
"label": "Sales Taxes and Charges Template",
- "options": "Sales Taxes and Charges Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Sales Taxes and Charges Template"
},
{
"depends_on": "eval:doc.party_type === 'Supplier'",
"fieldname": "purchase_tax_template",
"fieldtype": "Link",
"label": "Purchase Taxes and Charges Template",
- "options": "Purchase Taxes and Charges Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Purchase Taxes and Charges Template"
},
{
"default": "0",
@@ -277,55 +222,49 @@
"fieldname": "follow_calendar_months",
"fieldtype": "Check",
"label": "Follow Calendar Months",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"default": "0",
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
"fieldname": "generate_new_invoices_past_due_date",
"fieldtype": "Check",
- "label": "Generate New Invoices Past Due Date",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Generate New Invoices Past Due Date"
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"label": "Subscription End Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Subscription Start Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
- "options": "Cost Center",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Cost Center"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
- "options": "Company",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Company"
+ },
+ {
+ "default": "1",
+ "fieldname": "submit_invoice",
+ "fieldtype": "Check",
+ "label": "Submit Invoice Automatically"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-02-09 15:44:20.024789",
+ "modified": "2021-04-19 15:24:27.550797",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 826044a..7c4ff73 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -276,7 +276,7 @@
frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
if billing_info[0]['billing_interval'] != 'Month':
- frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months')
+ frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months'))
def after_insert(self):
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
@@ -383,7 +383,9 @@
invoice.flags.ignore_mandatory = True
invoice.save()
- invoice.submit()
+
+ if self.submit_invoice:
+ invoice.submit()
return invoice
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 85bff10..f1717c5 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -171,7 +171,7 @@
else:
allowance = .5
- if abs(debit_credit_diff) >= allowance:
+ if abs(debit_credit_diff) > allowance:
frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
.format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 444b40e..db605f7 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -364,7 +364,7 @@
payment_terms_details = frappe.db.sql("""
select
si.name, si.party_account_currency, si.currency, si.conversion_rate,
- ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
+ ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
from `tab{0}` si, `tabPayment Schedule` ps
where
si.name = ps.parent and
@@ -394,7 +394,7 @@
"due_date": d.due_date,
"invoiced": invoiced,
"invoice_grand_total": row.invoiced,
- "payment_term": d.description,
+ "payment_term": d.description or d.payment_term,
"paid": d.paid_amount + d.discounted_amount,
"credit_note": 0.0,
"outstanding": invoiced - d.paid_amount - d.discounted_amount
diff --git a/erpnext/accounts/report/billed_items_to_be_received/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
new file mode 100644
index 0000000..e1fccb6
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports['Billed Items To Be Received'] = {
+ 'filters': [
+ {
+ 'label': __('Company'),
+ 'fieldname': 'company',
+ 'fieldtype': 'Link',
+ 'options': 'Company',
+ 'reqd': 1,
+ 'default': frappe.defaults.get_default('Company')
+ },
+ {
+ 'label': __('As on Date'),
+ 'fieldname': 'posting_date',
+ 'fieldtype': 'Date',
+ 'reqd': 1,
+ 'default': get_today()
+ },
+ {
+ 'label': __('Purchase Invoice'),
+ 'fieldname': 'purchase_invoice',
+ 'fieldtype': 'Link',
+ 'options': 'Purchase Invoice'
+ }
+ ]
+};
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json
new file mode 100644
index 0000000..de09b33
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json
@@ -0,0 +1,39 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-30 09:35:38.683028",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-03-31 08:48:30.944429",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Billed Items To Be Received",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Purchase Invoice",
+ "report_name": "Billed Items To Be Received",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Auditor"
+ },
+ {
+ "role": "Stock User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
new file mode 100644
index 0000000..2ce5d50
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
@@ -0,0 +1,107 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ data = get_data(filters) or []
+ columns = get_columns()
+
+ return columns, data
+
+def get_data(report_filters):
+ filters = get_report_filters(report_filters)
+ fields = get_report_fields()
+
+ return frappe.get_all('Purchase Invoice',
+ fields= fields, filters=filters)
+
+def get_report_filters(report_filters):
+ filters = [['Purchase Invoice','company','=',report_filters.get('company')],
+ ['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
+ ['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
+
+ if report_filters.get('purchase_invoice'):
+ filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
+
+ return filters
+
+def get_report_fields():
+ fields = []
+ for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
+ fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
+
+ for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
+ fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
+
+ return fields
+
+def get_columns():
+ return [
+ {
+ 'label': _('Purchase Invoice'),
+ 'fieldname': 'name',
+ 'fieldtype': 'Link',
+ 'options': 'Purchase Invoice',
+ 'width': 170
+ },
+ {
+ 'label': _('Supplier'),
+ 'fieldname': 'supplier',
+ 'fieldtype': 'Link',
+ 'options': 'Supplier',
+ 'width': 120
+ },
+ {
+ 'label': _('Posting Date'),
+ 'fieldname': 'posting_date',
+ 'fieldtype': 'Date',
+ 'width': 100
+ },
+ {
+ 'label': _('Item Code'),
+ 'fieldname': 'item_code',
+ 'fieldtype': 'Link',
+ 'options': 'Item',
+ 'width': 100
+ },
+ {
+ 'label': _('Item Name'),
+ 'fieldname': 'item_name',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ {
+ 'label': _('UOM'),
+ 'fieldname': 'uom',
+ 'fieldtype': 'Link',
+ 'options': 'UOM',
+ 'width': 100
+ },
+ {
+ 'label': _('Invoiced Qty'),
+ 'fieldname': 'qty',
+ 'fieldtype': 'Float',
+ 'width': 100
+ },
+ {
+ 'label': _('Received Qty'),
+ 'fieldname': 'received_qty',
+ 'fieldtype': 'Float',
+ 'width': 100
+ },
+ {
+ 'label': _('Rate'),
+ 'fieldname': 'rate',
+ 'fieldtype': 'Currency',
+ 'width': 100
+ },
+ {
+ 'label': _('Amount'),
+ 'fieldname': 'amount',
+ 'fieldtype': 'Currency',
+ 'width': 100
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
index 0947922..1363b53 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -2,118 +2,128 @@
// For license information, please see license.txt
/* eslint-disable */
-frappe.query_reports["Consolidated Financial Statement"] = {
- "filters": [
- {
- "fieldname":"company",
- "label": __("Company"),
- "fieldtype": "Link",
- "options": "Company",
- "default": frappe.defaults.get_user_default("Company"),
- "reqd": 1
- },
- {
- "fieldname":"filter_based_on",
- "label": __("Filter Based On"),
- "fieldtype": "Select",
- "options": ["Fiscal Year", "Date Range"],
- "default": ["Fiscal Year"],
- "reqd": 1,
- on_change: function() {
- let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
- frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
- frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
- frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
- frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
+frappe.require("assets/erpnext/js/financial_statements.js", function() {
+ frappe.query_reports["Consolidated Financial Statement"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"filter_based_on",
+ "label": __("Filter Based On"),
+ "fieldtype": "Select",
+ "options": ["Fiscal Year", "Date Range"],
+ "default": ["Fiscal Year"],
+ "reqd": 1,
+ on_change: function() {
+ let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
+ frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
+ frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
+ frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
+ frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
- frappe.query_report.refresh();
+ frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"period_start_date",
+ "label": __("Start Date"),
+ "fieldtype": "Date",
+ "hidden": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname":"period_end_date",
+ "label": __("End Date"),
+ "fieldtype": "Date",
+ "hidden": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname":"from_fiscal_year",
+ "label": __("Start Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"to_fiscal_year",
+ "label": __("End Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"finance_book",
+ "label": __("Finance Book"),
+ "fieldtype": "Link",
+ "options": "Finance Book"
+ },
+ {
+ "fieldname":"report",
+ "label": __("Report"),
+ "fieldtype": "Select",
+ "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
+ "default": "Balance Sheet",
+ "reqd": 1
+ },
+ {
+ "fieldname": "presentation_currency",
+ "label": __("Currency"),
+ "fieldtype": "Select",
+ "options": erpnext.get_presentation_currency_list(),
+ "default": frappe.defaults.get_user_default("Currency")
+ },
+ {
+ "fieldname":"accumulated_in_group_company",
+ "label": __("Accumulated Values in Group Company"),
+ "fieldtype": "Check",
+ "default": 0
+ },
+ {
+ "fieldname": "include_default_book_entries",
+ "label": __("Include Default Book Entries"),
+ "fieldtype": "Check",
+ "default": 1
}
- },
- {
- "fieldname":"period_start_date",
- "label": __("Start Date"),
- "fieldtype": "Date",
- "hidden": 1,
- "reqd": 1
- },
- {
- "fieldname":"period_end_date",
- "label": __("End Date"),
- "fieldtype": "Date",
- "hidden": 1,
- "reqd": 1
- },
- {
- "fieldname":"from_fiscal_year",
- "label": __("Start Year"),
- "fieldtype": "Link",
- "options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1
- },
- {
- "fieldname":"to_fiscal_year",
- "label": __("End Year"),
- "fieldtype": "Link",
- "options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1
- },
- {
- "fieldname":"finance_book",
- "label": __("Finance Book"),
- "fieldtype": "Link",
- "options": "Finance Book"
- },
- {
- "fieldname":"report",
- "label": __("Report"),
- "fieldtype": "Select",
- "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
- "default": "Balance Sheet",
- "reqd": 1
- },
- {
- "fieldname": "presentation_currency",
- "label": __("Currency"),
- "fieldtype": "Select",
- "options": erpnext.get_presentation_currency_list(),
- "default": frappe.defaults.get_user_default("Currency")
- },
- {
- "fieldname":"accumulated_in_group_company",
- "label": __("Accumulated Values in Group Company"),
- "fieldtype": "Check",
- "default": 0
- },
- {
- "fieldname": "include_default_book_entries",
- "label": __("Include Default Book Entries"),
- "fieldtype": "Check",
- "default": 1
- }
- ],
- "formatter": function(value, row, column, data, default_formatter) {
- value = default_formatter(value, row, column, data);
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ if (data && column.fieldname=="account") {
+ value = data.account_name || value;
+
+ column.link_onclick =
+ "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
+ column.is_tree = true;
+ }
- if (!data.parent_account) {
- value = $(`<span>${value}</span>`);
+ value = default_formatter(value, row, column, data);
- var $value = $(value).css("font-weight", "bold");
+ if (!data.parent_account) {
+ value = $(`<span>${value}</span>`);
- value = $value.wrap("<p></p>").parent().html();
- }
- return value;
- },
- onload: function() {
- let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+ var $value = $(value).css("font-weight", "bold");
- frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
- var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
- frappe.query_report.set_filter_value({
- period_start_date: fy.year_start_date,
- period_end_date: fy.year_end_date
+ value = $value.wrap("<p></p>").parent().html();
+ }
+ return value;
+ },
+ onload: function() {
+ let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+
+ frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
+ var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
+ frappe.query_report.set_filter_value({
+ period_start_date: fy.year_start_date,
+ period_end_date: fy.year_end_date
+ });
});
- });
+ }
}
-}
+});
\ No newline at end of file
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 0c4a422..094f5db 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -329,8 +329,9 @@
has_value = False
total = 0
row = frappe._dict({
- "account_name": _(d.account_name),
- "account": _(d.account_name),
+ "account_name": ('%s - %s' %(_(d.account_number), _(d.account_name))
+ if d.account_number else _(d.account_name)),
+ "account": _(d.name),
"parent_account": _(d.parent_account),
"indent": flt(d.indent),
"year_start_date": start_date,
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 3c4f908..42f4472 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -435,6 +435,35 @@
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 5)
+ def test_purchase_order_invoice_receipt_workflow(self):
+ from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt
+
+ po = create_purchase_order()
+ pi = make_pi_from_po(po.name)
+
+ pi.submit()
+
+ pr = make_purchase_receipt(pi.name)
+ pr.submit()
+
+ pi.load_from_db()
+
+ self.assertEquals(pi.per_received, 100.00)
+ self.assertEquals(pi.items[0].qty, pi.items[0].received_qty)
+
+ po.load_from_db()
+
+ self.assertEquals(po.per_received, 100.00)
+ self.assertEquals(po.per_billed, 100.00)
+
+ pr.cancel()
+
+ pi.load_from_db()
+ pi.cancel()
+
+ po.load_from_db()
+ po.cancel()
+
def test_make_purchase_invoice(self):
po = create_purchase_order(do_not_submit=True)
diff --git a/erpnext/change_log/v13/v13_2_0.md b/erpnext/change_log/v13/v13_2_0.md
new file mode 100644
index 0000000..eb9499d
--- /dev/null
+++ b/erpnext/change_log/v13/v13_2_0.md
@@ -0,0 +1,56 @@
+# Version 13.2.0 Release Notes
+
+### Features & Enhancements
+
+- Employee Hours Utilization Report ([#25209](https://github.com/frappe/erpnext/pull/25209))
+- Delayed Tasks Summary Report ([#25024](https://github.com/frappe/erpnext/pull/25024))
+- Project Profitability Report ([#24944](https://github.com/frappe/erpnext/pull/24944))
+- Timer in LMS Quiz ([#24246](https://github.com/frappe/erpnext/pull/24246))
+- Role to allow over billing, delivery, receipt ([#24854](https://github.com/frappe/erpnext/pull/24854))
+- Auto calculate distance for e-way bill generations ([#25480](https://github.com/frappe/erpnext/pull/25480))
+- Add total available stock field in PO ([#24878](https://github.com/frappe/erpnext/pull/24878))
+- Refactored Setup Taxes and Charges ([#24805](https://github.com/frappe/erpnext/pull/24805))
+- Inpatient Occupancy Table Editable for Healthcare Admin ([#24989](https://github.com/frappe/erpnext/pull/24989))
+- Added Disable Rounded Total in sales transactions ([#25362](https://github.com/frappe/erpnext/pull/25362))
+
+
+### Fixes
+
+- Incorrect GL Entry validation ([#25474](https://github.com/frappe/erpnext/pull/25474))
+- Cannot create item variants ([#25433](https://github.com/frappe/erpnext/pull/25433))
+- Leave policy in leave allocation ([#25334](https://github.com/frappe/erpnext/pull/25334))
+- Let Administrator delete company transactions ([#25300](https://github.com/frappe/erpnext/pull/25300))
+- Display reconcile tool when closing balance 0 ([#25417](https://github.com/frappe/erpnext/pull/25417))
+- Bulk Salary Structure Assignment ([#25389](https://github.com/frappe/erpnext/pull/25389))
+- Payment amount showing in foreign currency ([#25518](https://github.com/frappe/erpnext/pull/25518))
+- Commit changes to shipment status in database ([#25374](https://github.com/frappe/erpnext/pull/25374))
+- Add amend perm for loan and system manager for loan doctypes ([#25393](https://github.com/frappe/erpnext/pull/25393))
+- Cashier query in POS Opening/Closing Entry ([#25398](https://github.com/frappe/erpnext/pull/25398))
+- Apply single transaction threshold on net_total instead of supplier credit amount ([#25243](https://github.com/frappe/erpnext/pull/25243))
+- Update allocated amount after paid amount is changed in PE ([#25528](https://github.com/frappe/erpnext/pull/25528))
+- Remove non-standard module cards from Home Workspace ([#25391](https://github.com/frappe/erpnext/pull/25391))
+- Cannot scan spacebar character in pos ([#25479](https://github.com/frappe/erpnext/pull/25479))
+- Permission error after submitting exchange rate revaluation ([#25432](https://github.com/frappe/erpnext/pull/25432))
+- Equality check instead of assignment in cart ([#25372](https://github.com/frappe/erpnext/pull/25372))
+- Disable auto naming of customer during import ([#25152](https://github.com/frappe/erpnext/pull/25152))
+- Additional Salary component amount not getting set ([#25355](https://github.com/frappe/erpnext/pull/25355))
+- Round off values near to zero ([#25304](https://github.com/frappe/erpnext/pull/25304))
+- Allow to cancel loan with cancelled repayment entry ([#25508](https://github.com/frappe/erpnext/pull/25508))
+- Currency symbol in bank transaction list view ([#25336](https://github.com/frappe/erpnext/pull/25336))
+- Incorrect batch picked in subcontracted purchase receipt ([#25186](https://github.com/frappe/erpnext/pull/25186))
+- Issue in project custom status ([#25452](https://github.com/frappe/erpnext/pull/25452))
+- Shipment pickup_to, pickup_from functionality. ([#25359](https://github.com/frappe/erpnext/pull/25359))
+- Stock ledger entry created against draft stock entry ([#25539](https://github.com/frappe/erpnext/pull/25539))
+- Ageing errors in PSOA ([#25529](https://github.com/frappe/erpnext/pull/25529))
+- Permission error while adding weekly holidays ([#25450](https://github.com/frappe/erpnext/pull/25450))
+- Filter for employees in salary slip ([#25360](https://github.com/frappe/erpnext/pull/25360))
+- Backward compatibility for GSTR-1 report ([#25444](https://github.com/frappe/erpnext/pull/25444))
+- Incorrect incoming rate for the sales return ([#25145](https://github.com/frappe/erpnext/pull/25145))
+- POS print receipt ([#25328](https://github.com/frappe/erpnext/pull/25328))
+- Laboratory Module patch ([#25431](https://github.com/frappe/erpnext/pull/25431))
+- Performance: fetching exchange rate on every line item slows down PO ([#25345](https://github.com/frappe/erpnext/pull/25345))
+- Presentation currency in statement of accounts ([#25367](https://github.com/frappe/erpnext/pull/25367))
+- Serial No not updated correctly via Inter Company Stock Transfer ([#25006](https://github.com/frappe/erpnext/pull/25006))
+- Ignore Customer Group Perm on All Products page ([#25396](https://github.com/frappe/erpnext/pull/25396))
+- Change subcontracted item display ([#25425](https://github.com/frappe/erpnext/pull/25425))
+- Add company validation for e-invoicing ([#25348](https://github.com/frappe/erpnext/pull/25348))
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index b686dc0..3f2d339 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -838,9 +838,10 @@
if not self.get("items"):
return
- earliest_schedule_date = min([d.schedule_date for d in self.get("items")])
- if earliest_schedule_date:
- self.schedule_date = earliest_schedule_date
+ if any(d.schedule_date for d in self.get("items")):
+ # Select earliest schedule_date.
+ self.schedule_date = min(d.schedule_date for d in self.get("items")
+ if d.schedule_date is not None)
if self.schedule_date:
for d in self.get('items'):
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 5276da9..4bb6138 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -98,6 +98,7 @@
["Draft", None],
["Submitted", "eval:self.docstatus == 1"],
["Queued", "eval:self.status == 'Queued'"],
+ ["Failed", "eval:self.status == 'Failed'"],
["Cancelled", "eval:self.docstatus == 2"],
]
}
diff --git a/erpnext/hr/doctype/designation/designation.json b/erpnext/hr/doctype/designation/designation.json
index 4c3888b..bab6b90 100644
--- a/erpnext/hr/doctype/designation/designation.json
+++ b/erpnext/hr/doctype/designation/designation.json
@@ -182,6 +182,10 @@
"share": 1,
"submit": 0,
"write": 1
+ },
+ {
+ "read": 1,
+ "role": "Sales User"
}
],
"quick_entry": 1,
@@ -191,4 +195,4 @@
"track_changes": 0,
"track_seen": 0,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js
index 5037ceb..fa4b06a 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -34,7 +34,7 @@
};
});
- frm.set_query('salary_component', function(doc) {
+ frm.set_query('salary_component', function() {
return {
filters: {
"type": "Deduction"
@@ -44,48 +44,49 @@
},
refresh: function(frm) {
- if (frm.doc.docstatus===1
- && (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount))
- && frappe.model.can_create("Payment Entry")) {
+ if (frm.doc.docstatus === 1 &&
+ (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) &&
+ frappe.model.can_create("Payment Entry")) {
frm.add_custom_button(__('Payment'),
- function() { frm.events.make_payment_entry(frm); }, __('Create'));
- }
- else if (
- frm.doc.docstatus === 1
- && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount)
- && frappe.model.can_create("Expense Claim")
+ function () {
+ frm.events.make_payment_entry(frm);
+ }, __('Create'));
+ } else if (
+ frm.doc.docstatus === 1 &&
+ flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) &&
+ frappe.model.can_create("Expense Claim")
) {
frm.add_custom_button(
__("Expense Claim"),
- function() {
+ function () {
frm.events.make_expense_claim(frm);
},
__('Create')
);
}
- if (frm.doc.docstatus === 1
- && (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
+ if (frm.doc.docstatus === 1 &&
+ (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
- if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){
- frm.add_custom_button(__("Return"), function() {
+ if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) {
+ frm.add_custom_button(__("Return"), function() {
frm.trigger('make_return_entry');
}, __('Create'));
- }else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
- frm.add_custom_button(__("Deduction from salary"), function() {
+ } else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) {
+ frm.add_custom_button(__("Deduction from salary"), function() {
frm.events.make_deduction_via_additional_salary(frm);
}, __('Create'));
}
}
},
- make_deduction_via_additional_salary: function(frm){
+ make_deduction_via_additional_salary: function(frm) {
frappe.call({
method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary",
args: {
doc: frm.doc
},
- callback: function (r){
+ callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
@@ -94,7 +95,7 @@
make_payment_entry: function(frm) {
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
- if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
+ if (frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry";
}
return frappe.call({
@@ -148,11 +149,11 @@
});
},
- employee: function (frm) {
+ employee: function(frm) {
if (frm.doc.employee) {
frappe.run_serially([
- () => frm.trigger('get_employee_currency'),
- () => frm.trigger('get_pending_amount')
+ () => frm.trigger('get_employee_currency'),
+ () => frm.trigger('get_pending_amount')
]);
}
},
@@ -199,7 +200,7 @@
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
- frm.set_df_property("exchange_rate", "description", "" );
+ frm.set_df_property("exchange_rate", "description", "");
}
frm.refresh_fields();
}
@@ -215,8 +216,8 @@
callback: function(r) {
frm.set_value("exchange_rate", flt(r.message));
frm.set_df_property('exchange_rate', 'hidden', 0);
- frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
- + " = [?] " + company_currency);
+ frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
+ " = [?] " + company_currency);
}
});
}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
index c3b4a3a..2f493e2 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
@@ -4,10 +4,10 @@
def get_data():
return {
'fieldname': 'employee_advance',
- 'non_standard_fieldnames': {
- 'Payment Entry': 'reference_name',
- 'Journal Entry': 'reference_name'
- },
+ 'non_standard_fieldnames': {
+ 'Payment Entry': 'reference_name',
+ 'Journal Entry': 'reference_name'
+ },
'transactions': [
{
'items': ['Expense Claim']
diff --git a/erpnext/hr/doctype/employee_referral/__init__.py b/erpnext/hr/doctype/employee_referral/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/__init__.py
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js
new file mode 100644
index 0000000..9c99bbb
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Employee Referral", {
+ refresh: function(frm) {
+ if (frm.doc.docstatus === 1 && frm.doc.status === "Pending") {
+ frm.add_custom_button(__("Reject Employee Referral"), function() {
+ frappe.confirm(
+ __("Are you sure you want to reject the Employee Referral?"),
+ function() {
+ frm.doc.status = "Rejected";
+ frm.dirty();
+ frm.save_or_update();
+ },
+ function() {
+ window.close();
+ }
+ );
+ });
+
+ frm.add_custom_button(__("Create Job Applicant"), function() {
+ frm.events.create_job_applicant(frm);
+ }).addClass("btn-primary");
+ }
+
+ // To check whether Payment is done or not
+ if (frm.doc.docstatus === 1 && frm.doc.status === "Accepted") {
+ frappe.db.get_list("Additional Salary", {
+ filters: {
+ ref_docname: cur_frm.doc.name,
+ docstatus: 1
+ },
+ fields: ["count(name) as additional_salary_count"]
+ }).then((data) => {
+
+ let additional_salary_count = data[0].additional_salary_count;
+
+ if (frm.doc.is_applicable_for_referral_bonus && !additional_salary_count) {
+ frm.add_custom_button(__("Create Additional Salary"), function() {
+ frm.events.create_additional_salary(frm);
+ }).addClass("btn-primary");
+ }
+ });
+ }
+
+
+
+ },
+ create_job_applicant: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.hr.doctype.employee_referral.employee_referral.create_job_applicant",
+ frm: frm
+ });
+ },
+
+ create_additional_salary: function(frm) {
+ frappe.call({
+ method: "erpnext.hr.doctype.employee_referral.employee_referral.create_additional_salary",
+ args: {
+ doc: frm.doc
+ },
+ callback: function (r) {
+ var doclist = frappe.model.sync(r.message);
+ frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+ }
+ });
+ },
+});
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.json b/erpnext/hr/doctype/employee_referral/employee_referral.json
new file mode 100644
index 0000000..bfd404b
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.json
@@ -0,0 +1,294 @@
+{
+ "actions": [],
+ "autoname": "format:HR-REF-{####}",
+ "creation": "2021-03-23 14:54:45.047051",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "first_name",
+ "last_name",
+ "full_name",
+ "email",
+ "contact_no",
+ "resume",
+ "resume_link",
+ "column_break_6",
+ "date",
+ "status",
+ "for_designation",
+ "current_employer",
+ "current_job_title",
+ "referrer_details_section",
+ "referrer",
+ "referrer_name",
+ "column_break_14",
+ "is_applicable_for_referral_bonus",
+ "referral_payment_status",
+ "department",
+ "additional_information_section",
+ "qualification_reason",
+ "work_references",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "label": "First Name ",
+ "reqd": 1
+ },
+ {
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "label": "Last Name",
+ "reqd": 1
+ },
+ {
+ "fieldname": "full_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Full Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "contact_no",
+ "fieldtype": "Data",
+ "in_standard_filter": 1,
+ "label": "Contact No.",
+ "options": "Phone"
+ },
+ {
+ "fieldname": "current_employer",
+ "fieldtype": "Data",
+ "label": "Current Employer "
+ },
+ {
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "in_standard_filter": 1,
+ "label": "Date",
+ "reqd": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Pending\nIn Process\nAccepted\nRejected",
+ "permlevel": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "current_job_title",
+ "fieldtype": "Data",
+ "label": "Current Job Title"
+ },
+ {
+ "fieldname": "resume",
+ "fieldtype": "Attach",
+ "label": "Resume"
+ },
+ {
+ "fieldname": "referrer_details_section",
+ "fieldtype": "Section Break",
+ "label": "Referrer Details"
+ },
+ {
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
+ {
+ "fieldname": "additional_information_section",
+ "fieldtype": "Section Break",
+ "label": "Additional Information "
+ },
+ {
+ "fieldname": "work_references",
+ "fieldtype": "Text Editor",
+ "label": "Work References"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Employee Referral",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "for_designation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "For Designation ",
+ "options": "Designation",
+ "reqd": 1
+ },
+ {
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Email",
+ "options": "Email",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "is_applicable_for_referral_bonus",
+ "fieldtype": "Check",
+ "label": "Is Applicable for Referral Bonus"
+ },
+ {
+ "fieldname": "qualification_reason",
+ "fieldtype": "Text Editor",
+ "label": "Why is this Candidate Qualified for this Position?"
+ },
+ {
+ "fieldname": "referrer",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Referrer",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "referrer.employee_name",
+ "fieldname": "referrer_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Referrer Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "resume_link",
+ "fieldtype": "Data",
+ "label": "Resume Link"
+ },
+ {
+ "fieldname": "referral_payment_status",
+ "fieldtype": "Select",
+ "label": "Referral Bonus Payment Status",
+ "options": "\nUnpaid\nPaid",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-04-26 21:21:38.094086",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Referral",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Employee",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Employee",
+ "share": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "full_name"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py
new file mode 100644
index 0000000..45d6872
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import get_link_to_form
+from frappe.model.document import Document
+
+class EmployeeReferral(Document):
+ def validate(self):
+ self.set_full_name()
+ self.set_referral_bonus_payment_status()
+
+ def set_full_name(self):
+ self.full_name = " ".join(filter(None, [self.first_name, self.last_name]))
+
+ def set_referral_bonus_payment_status(self):
+ if not self.is_applicable_for_referral_bonus:
+ self.referral_payment_status = ""
+ else:
+ if not self.referral_payment_status:
+ self.referral_payment_status = "Unpaid"
+
+
+@frappe.whitelist()
+def create_job_applicant(source_name, target_doc=None):
+ emp_ref = frappe.get_doc("Employee Referral", source_name)
+ #just for Api call if some set status apart from default Status
+ status = emp_ref.status
+ if emp_ref.status in ["Pending", "In process"]:
+ status = "Open"
+
+ job_applicant = frappe.new_doc("Job Applicant")
+ job_applicant.employee_referral = emp_ref.name
+ job_applicant.status = status
+ job_applicant.applicant_name = emp_ref.full_name
+ job_applicant.email_id = emp_ref.email
+ job_applicant.phone_number = emp_ref.contact_no
+ job_applicant.resume_attachment = emp_ref.resume
+ job_applicant.resume_link = emp_ref.resume_link
+ job_applicant.save()
+
+ frappe.msgprint(_("Job Applicant {0} created successfully.").format(
+ get_link_to_form("Job Applicant", job_applicant.name)),
+ title=_("Success"), indicator="green")
+
+ emp_ref.db_set("status", "In Process")
+
+ return job_applicant
+
+
+@frappe.whitelist()
+def create_additional_salary(doc):
+ import json
+ from six import string_types
+
+ if isinstance(doc, string_types):
+ doc = frappe._dict(json.loads(doc))
+
+ if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}):
+ additional_salary = frappe.new_doc("Additional Salary")
+ additional_salary.employee = doc.referrer
+ additional_salary.company = frappe.db.get_value("Employee", doc.referrer, "company")
+ additional_salary.overwrite_salary_structure_amount = 0
+ additional_salary.ref_doctype = doc.doctype
+ additional_salary.ref_docname = doc.name
+
+ return additional_salary
+
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
new file mode 100644
index 0000000..afa2a1f
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
@@ -0,0 +1,15 @@
+from __future__ import unicode_literals
+
+def get_data():
+ return {
+ 'fieldname': 'employee_referral',
+ 'non_standard_fieldnames': {
+ 'Additional Salary': 'ref_docname'
+ },
+ 'transactions': [
+ {
+ 'items': ['Job Applicant', 'Additional Salary']
+ },
+
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_list.js b/erpnext/hr/doctype/employee_referral/employee_referral_list.js
new file mode 100644
index 0000000..7533ab6
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_list.js
@@ -0,0 +1,14 @@
+frappe.listview_settings['Employee Referral'] = {
+ add_fields: ["status"],
+ get_indicator: function (doc) {
+ if (doc.status == "Pending") {
+ return [__(doc.status), "grey", "status,=," + doc.status];
+ } else if (doc.status == "In Process") {
+ return [__(doc.status), "orange", "status,=," + doc.status];
+ } else if (doc.status == "Accepted") {
+ return [__(doc.status), "green", "status,=," + doc.status];
+ } else if (doc.status == "Rejected") {
+ return [__(doc.status), "red", "status,=," + doc.status];
+ }
+ },
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
new file mode 100644
index 0000000..a674f39
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+from frappe.utils import today
+from erpnext.hr.doctype.designation.test_designation import create_designation
+from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary
+from erpnext.hr.doctype.employee.test_employee import make_employee
+import unittest
+
+class TestEmployeeReferral(unittest.TestCase):
+ def test_workflow_and_status_sync(self):
+ emp_ref = create_employee_referral()
+
+ #Check Initial status
+ self.assertTrue(emp_ref.status, "Pending")
+
+ job_applicant = create_job_applicant(emp_ref.name)
+
+
+ #Check status sync
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "In Process")
+
+ job_applicant.reload()
+ job_applicant.status = "Rejected"
+ job_applicant.save()
+
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "Rejected")
+
+ job_applicant.reload()
+ job_applicant.status = "Accepted"
+ job_applicant.save()
+
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "Accepted")
+
+
+ # Check for Referral reference in additional salary
+
+ add_sal = create_additional_salary(emp_ref)
+ self.assertTrue(add_sal.ref_docname, emp_ref.name)
+
+
+def create_employee_referral():
+ emp_ref = frappe.new_doc("Employee Referral")
+ emp_ref.first_name = "Mahesh"
+ emp_ref.last_name = "Singh"
+ emp_ref.email = "a@b.c"
+ emp_ref.date = today()
+ emp_ref.for_designation = create_designation().name
+ emp_ref.referrer = make_employee("testassetmovemp@example.com", company="_Test Company")
+ emp_ref.is_applicable_for_employee_referral_compensation = 1
+ emp_ref.save()
+ emp_ref.submit()
+
+ return emp_ref
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json
index f44d830..7af20988 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.json
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.json
@@ -1,626 +1,177 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "HR-EMP-SEP-.YYYY.-.#####",
- "beta": 0,
- "creation": "2018-05-10 02:29:16.740490",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "HR-EMP-SEP-.YYYY.-.#####",
+ "creation": "2018-05-10 02:29:16.740490",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "employee",
+ "employee_name",
+ "department",
+ "designation",
+ "employee_grade",
+ "column_break_7",
+ "company",
+ "boarding_status",
+ "resignation_letter_date",
+ "project",
+ "table_for_activity",
+ "employee_separation_template",
+ "activities",
+ "notify_users_by_email",
+ "section_break_14",
+ "exit_interview",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee",
- "length": 0,
- "no_copy": 0,
- "options": "Employee",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.employee_name",
- "fieldname": "employee_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Employee Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.resignation_letter_date",
- "fieldname": "resignation_letter_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Resignation Letter Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "boarding_status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "\nPending\nIn Process\nCompleted",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "label": "Employee",
+ "options": "Employee",
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_bulk_edit": 0,
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_in_quick_entry": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "notify_users_by_email",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Notify users by email",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_7",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee_separation_template",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee Separation Template",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Separation Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.company",
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Employee Name",
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "project",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Project",
- "length": 0,
- "no_copy": 0,
- "options": "Project",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.resignation_letter_date",
+ "fieldname": "resignation_letter_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Resignation Letter Date",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.department",
- "fieldname": "department",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Department",
- "length": 0,
- "no_copy": 0,
- "options": "Department",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "fieldname": "boarding_status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "\nPending\nIn Process\nCompleted",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.designation",
- "fieldname": "designation",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Designation",
- "length": 0,
- "no_copy": 0,
- "options": "Designation",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "notify_users_by_email",
+ "fieldtype": "Check",
+ "label": "Notify users by email"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.grade",
- "fieldname": "employee_grade",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee Grade",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Grade",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "table_for_activity",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "employee_separation_template",
+ "fieldtype": "Link",
+ "label": "Employee Separation Template",
+ "options": "Employee Separation Template"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "activities",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Activities",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Boarding Activity",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_14",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "exit_interview",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Exit Interview Summary",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Employee Separation",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fetch_from": "employee.designation",
+ "fieldname": "designation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Designation",
+ "options": "Designation",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "employee.grade",
+ "fieldname": "employee_grade",
+ "fieldtype": "Link",
+ "label": "Employee Grade",
+ "options": "Employee Grade",
+ "read_only": 1
+ },
+ {
+ "fieldname": "table_for_activity",
+ "fieldtype": "Section Break",
+ "label": "Separation Activities"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "activities",
+ "fieldtype": "Table",
+ "label": "Activities",
+ "options": "Employee Boarding Activity"
+ },
+ {
+ "fieldname": "section_break_14",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "exit_interview",
+ "fieldtype": "Text Editor",
+ "label": "Exit Interview Summary"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Employee Separation",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-08-03 16:15:39.025898",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Employee Separation",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-04-28 15:58:36.020196",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Separation",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "employee_name",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "employee_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index 2fa114d..713fcf5 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -18,7 +18,7 @@
'activity_name': 'Deactivate Employee',
'role': 'HR User'
})
- separation.status = 'Pending'
+ separation.boarding_status = 'Pending'
separation.insert()
separation.submit()
self.assertEqual(separation.docstatus, 1)
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json
index 1360fd1..bcea5f5 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.json
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.json
@@ -18,6 +18,7 @@
"job_title",
"source",
"source_name",
+ "employee_referral",
"applicant_rating",
"section_break_6",
"notes",
@@ -152,13 +153,20 @@
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
+ },
+ {
+ "fieldname": "employee_referral",
+ "fieldtype": "Link",
+ "label": "Employee Referral",
+ "options": "Employee Referral",
+ "read_only": 1
}
],
"icon": "fa fa-user",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-09-18 12:39:02.557563",
+ "modified": "2021-03-24 15:51:11.117517",
"modified_by": "Administrator",
"module": "HR",
"name": "Job Applicant",
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index a6aef04..0594ba3 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -28,10 +28,21 @@
if self.email_id:
validate_email_address(self.email_id, True)
+ if self.employee_referral:
+ self.set_status_for_employee_referral()
+
if not self.applicant_name and self.email_id:
guess = self.email_id.split('@')[0]
self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')])
+ def set_status_for_employee_referral(self):
+ emp_ref = frappe.get_doc("Employee Referral", self.employee_referral)
+ if self.status in ["Open", "Replied", "Hold"]:
+ emp_ref.db_set("status", "In Process")
+ elif self.status in ["Accepted", "Rejected"]:
+ emp_ref.db_set("status", self.status)
+
+
def check_email_id_is_unique(self):
if self.email_id:
names = frappe.db.sql_list("""select name from `tabJob Applicant`
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 190eb4f..2540b3d 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -32,13 +32,15 @@
project_name += self.job_applicant
else:
project_name += self.employee
+
project = frappe.get_doc({
"doctype": "Project",
"project_name": project_name,
"expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
"department": self.department,
"company": self.company
- }).insert(ignore_permissions=True)
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
+
self.db_set("project", project.name)
self.db_set("boarding_status", "Pending")
self.reload()
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index f4b56a0..c5201c2 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -521,6 +521,15 @@
"type": "Link"
},
{
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Referral",
+ "link_to": "Employee Referral",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -814,7 +823,7 @@
"type": "Link"
}
],
- "modified": "2021-03-24 17:35:21.483297",
+ "modified": "2021-04-26 13:36:15.413819",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 20b44a1..230475f 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -71,7 +71,6 @@
frappe.throw(_("Repay From Salary can be selected only for term loans"))
def make_repayment_schedule(self):
-
if not self.repayment_start_date:
frappe.throw(_("Repayment Start Date is mandatory for term loans"))
@@ -79,10 +78,9 @@
payment_date = self.repayment_start_date
balance_amount = self.loan_amount
while(balance_amount > 0):
- interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
+ interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12*100))
principal_amount = self.monthly_repayment_amount - interest_amount
- balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount)
-
+ balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
if balance_amount < 0:
principal_amount += balance_amount
balance_amount = 0.0
@@ -196,7 +194,8 @@
posting_date = getdate()
amounts = calculate_amounts(loan, posting_date)
- pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest']
+ pending_amount = amounts['pending_principal_amount'] + amounts['unaccrued_interest'] + \
+ amounts['interest_amount'] + amounts['penalty_amount']
loan_type = frappe.get_value('Loan', loan, 'loan_type')
write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount')
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 6f8da31..fae6f86 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -56,25 +56,25 @@
def test_loan(self):
loan = frappe.get_doc("Loan", {"applicant":self.applicant1})
self.assertEquals(loan.monthly_repayment_amount, 15052)
- self.assertEquals(loan.total_interest_payable, 21034)
- self.assertEquals(loan.total_payment, 301034)
+ self.assertEquals(flt(loan.total_interest_payable, 0), 21034)
+ self.assertEquals(flt(loan.total_payment, 0), 301034)
schedule = loan.repayment_schedule
self.assertEqual(len(schedule), 20)
- for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
- self.assertEqual(schedule[idx].principal_amount, principal_amount)
- self.assertEqual(schedule[idx].interest_amount, interest_amount)
- self.assertEqual(schedule[idx].balance_loan_amount, balance_loan_amount)
+ for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227080], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
+ self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount)
+ self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount)
+ self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount)
loan.repayment_method = "Repay Fixed Amount per Period"
loan.monthly_repayment_amount = 14000
loan.save()
self.assertEquals(len(loan.repayment_schedule), 22)
- self.assertEquals(loan.total_interest_payable, 22712)
- self.assertEquals(loan.total_payment, 302712)
+ self.assertEquals(flt(loan.total_interest_payable, 0), 22712)
+ self.assertEquals(flt(loan.total_payment, 0), 302712)
def test_loan_with_security(self):
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 728eadf..3d99b1f 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -435,7 +435,6 @@
@frappe.whitelist()
def calculate_amounts(against_loan, posting_date, payment_type=''):
-
amounts = {
'penalty_amount': 0.0,
'interest_amount': 0.0,
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index de9f6e3..82d223c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -774,3 +774,7 @@
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
erpnext.patches.v13_0.update_shipment_status
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
+erpnext.patches.v12_0.add_ewaybill_validity_field
+erpnext.patches.v13_0.germany_make_custom_fields
+erpnext.patches.v13_0.germany_fill_debtor_creditor_number
+erpnext.patches.v13_0.set_pos_closing_as_failed
diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
new file mode 100644
index 0000000..87d98f1
--- /dev/null
+++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
@@ -0,0 +1,16 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+ depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill')
+ ]
+ }
+ create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
index af1f6e7..77a23cf 100644
--- a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
+++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
@@ -22,5 +22,7 @@
frappe.delete_doc("Page", "bank-reconciliation", force=1)
+ frappe.reload_doc('accounts', 'doctype', 'bank_transaction')
+
rename_field("Bank Transaction", "debit", "deposit")
rename_field("Bank Transaction", "credit", "withdrawal")
diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
new file mode 100644
index 0000000..11e1e9b
--- /dev/null
+++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+ """Move account number into the new custom field debtor_creditor_number.
+
+ German companies used to use a dedicated payable/receivable account for
+ every party to mimick party accounts in the external accounting software
+ "DATEV". This is no longer necessary. The reference ID for DATEV will be
+ stored in a new custom field "debtor_creditor_number".
+ """
+ company_list = frappe.get_all('Company', filters={'country': 'Germany'})
+
+ for company in company_list:
+ party_account_list = frappe.get_all('Party Account', filters={'company': company.name}, fields=['name', 'account', 'debtor_creditor_number'])
+ for party_account in party_account_list:
+ if (not party_account.account) or party_account.debtor_creditor_number:
+ # account empty or debtor_creditor_number already filled
+ continue
+
+ account_number = frappe.db.get_value('Account', party_account.account, 'account_number')
+ if not account_number:
+ continue
+
+ frappe.db.set_value('Party Account', party_account.name, 'debtor_creditor_number', account_number)
+ frappe.db.set_value('Party Account', party_account.name, 'account', '')
diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py
new file mode 100644
index 0000000..41ab945
--- /dev/null
+++ b/erpnext/patches/v13_0/germany_make_custom_fields.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from erpnext.regional.germany.setup import make_custom_fields
+
+
+def execute():
+ """Execute the make_custom_fields method for german companies.
+
+ It is usually run once at setup of a new company. Since it's new, run it
+ once for existing companies as well.
+ """
+ company_list = frappe.get_all('Company', filters = {'country': 'Germany'})
+ if not company_list:
+ return
+
+ make_custom_fields()
diff --git a/erpnext/patches/v13_0/set_pos_closing_as_failed.py b/erpnext/patches/v13_0/set_pos_closing_as_failed.py
new file mode 100644
index 0000000..1c576db
--- /dev/null
+++ b/erpnext/patches/v13_0/set_pos_closing_as_failed.py
@@ -0,0 +1,7 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc('accounts', 'doctype', 'pos_closing_entry')
+
+ frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'")
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index 13b6c05..ebeddf9 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -13,12 +13,19 @@
if self.ref_doctype == "Employee Advance" and self.ref_docname:
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
+ self.update_employee_referral()
+
+ def on_cancel(self):
+ self.update_employee_referral(cancel=True)
+
def validate(self):
self.validate_dates()
self.validate_salary_structure()
self.validate_recurring_additional_salary_overlap()
+ self.validate_employee_referral()
+
if self.amount < 0:
- frappe.throw(_("Amount should not be less than zero."))
+ frappe.throw(_("Amount should not be less than zero"))
def validate_salary_structure(self):
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
@@ -70,6 +77,27 @@
if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date):
frappe.throw(_("Payroll date can not be greater than employee's relieving date."))
+ def validate_employee_referral(self):
+ if self.ref_doctype == "Employee Referral":
+ referral_details = frappe.db.get_value("Employee Referral", self.ref_docname,
+ ["is_applicable_for_referral_bonus", "status"], as_dict=1)
+
+ if not referral_details.is_applicable_for_referral_bonus:
+ frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format(
+ self.ref_docname))
+
+ if self.type == "Deduction":
+ frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus."))
+
+ if referral_details.status != "Accepted":
+ frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
+ frappe.bold("Accepted")))
+
+ def update_employee_referral(self, cancel=False):
+ if self.ref_doctype == "Employee Referral":
+ status = "Unpaid" if cancel else "Paid"
+ frappe.db.set_value("Employee Referral", self.ref_docname, "referral_payment_status", status)
+
def get_amount(self, sal_start_date, sal_end_date):
start_date = getdate(sal_start_date)
end_date = getdate(sal_end_date)
@@ -110,8 +138,7 @@
for d in additional_salary_list:
if d.overwrite:
if d.component in components_to_overwrite:
- frappe.throw(_("Multiple Additional Salaries with overwrite "
- "property exist for Salary Component {0} between {1} and {2}.").format(
+ frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format(
frappe.bold(d.component), start_date, end_date), title=_("Error"))
components_to_overwrite.append(d.component)
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index e00bd87..d5c20dc 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -16,11 +16,11 @@
onload: function(frm) {
let help_button = $(`<a class = 'control-label'>
- Condition and Formula Help
+ ${__("Condition and Formula Help")}
</a>`).click(()=>{
let d = new frappe.ui.Dialog({
- title: 'Condition and Formula Help',
+ title: __('Condition and Formula Help'),
fields: [
{
fieldname: 'msg_wrapper',
diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py
index ea7f1ab..2c7bb49 100644
--- a/erpnext/projects/report/project_summary/project_summary.py
+++ b/erpnext/projects/report/project_summary/project_summary.py
@@ -131,25 +131,25 @@
{
"value": avg_completion,
"indicator": "Green" if avg_completion > 50 else "Red",
- "label": "Average Completion",
+ "label": _("Average Completion"),
"datatype": "Percent",
},
{
"value": total,
"indicator": "Blue",
- "label": "Total Tasks",
+ "label": _("Total Tasks"),
"datatype": "Int",
},
{
"value": completed,
"indicator": "Green",
- "label": "Completed Tasks",
+ "label": _("Completed Tasks"),
"datatype": "Int",
},
{
"value": total_overdue,
"indicator": "Green" if total_overdue == 0 else "Red",
- "label": "Overdue Tasks",
+ "label": _("Overdue Tasks"),
"datatype": "Int",
}
]
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index a2b95cb..0af8da7 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -562,7 +562,7 @@
weight_uom: item.weight_uom,
manufacturer: item.manufacturer,
stock_uom: item.stock_uom,
- pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
+ pos_profile: cint(me.frm.doc.is_pos) ? me.frm.doc.pos_profile : '',
cost_center: item.cost_center,
tax_category: me.frm.doc.tax_category,
item_tax_template: item.item_tax_template,
@@ -1379,12 +1379,12 @@
update_payment_schedule_grid_labels: function(company_currency) {
const me = this;
- if (this.frm.fields_dict["payment_schedule"]) {
+ if (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length > 0) {
this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"],
company_currency, "payment_schedule");
this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"],
this.frm.doc.currency, "payment_schedule");
-
+
var schedule_grid = this.frm.fields_dict["payment_schedule"].grid;
$.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) {
if (frappe.meta.get_docfield(schedule_grid.doctype, fname))
@@ -2034,7 +2034,7 @@
if(r.message && !r.exc) {
me.frm.set_value("payment_schedule", r.message);
const company_currency = me.get_company_currency();
- this.update_payment_schedule_grid_labels(company_currency);
+ me.update_payment_schedule_grid_labels(company_currency);
}
}
})
diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py
index ac1f543..c1fa6e4 100644
--- a/erpnext/regional/germany/setup.py
+++ b/erpnext/regional/germany/setup.py
@@ -1,11 +1,24 @@
import os
import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def setup(company=None, patch=True):
+ make_custom_fields()
add_custom_roles_for_reports()
+def make_custom_fields():
+ custom_fields = {
+ 'Party Account': [
+ dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number',
+ fieldtype='Data', insert_after='account', translatable=0)
+ ]
+ }
+
+ create_custom_fields(custom_fields)
+
+
def add_custom_roles_for_reports():
"""Add Access Control to UAE VAT 201."""
if not frappe.db.get_value('Custom Role', dict(report='DATEV')):
@@ -16,4 +29,4 @@
dict(role='Accounts User'),
dict(role='Accounts Manager')
]
- )).insert()
\ No newline at end of file
+ )).insert()
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
index f138a80..826d51f 100644
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -56,10 +56,10 @@
)
if not six.PY2:
- data = data.encode('latin_1')
+ data = data.encode('latin_1', errors='replace')
header = get_header(filters, csv_class)
- header = ';'.join(header).encode('latin_1')
+ header = ';'.join(header).encode('latin_1', errors='replace')
# 1st Row: Header with meta data
# 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 699441b..b4e7a88 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -71,13 +71,14 @@
def raise_document_name_too_long_error():
title = _('Document ID Too Long')
- msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ')
- msg += _('document id {} exceed 16 letters. ').format(bold(_('should not')))
+ msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice')
+ msg += ', '
+ msg += _('document id {} exceed 16 letters.').format(bold(_('should not')))
msg += '<br><br>'
- msg += _('You must {} your {} in order to have document id of {} length 16. ').format(
+ msg += _('You must {} your {} in order to have document id of {} length 16.').format(
bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
)
- msg += _('Please account for ammended documents too. ')
+ msg += _('Please account for ammended documents too.')
frappe.throw(msg, title=title)
def read_json(name):
@@ -847,6 +848,7 @@
res = self.make_request('post', self.generate_ewaybill_url, headers, data)
if res.get('success'):
self.invoice.ewaybill = res.get('result').get('EwbNo')
+ self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill')
self.invoice.eway_bill_cancelled = 0
self.invoice.update(args)
self.invoice.flags.updater_reference = {
@@ -944,6 +946,7 @@
self.invoice.irn = res.get('Irn')
self.invoice.ewaybill = res.get('EwbNo')
+ self.invoice.eway_bill_validity = res.get('EwbValidTill')
self.invoice.ack_no = res.get('AckNo')
self.invoice.ack_date = res.get('AckDt')
self.invoice.signed_einvoice = dec_signed_invoice
@@ -960,6 +963,7 @@
'label': _('IRN Generated')
}
self.update_invoice()
+
def attach_qrcode_image(self):
qrcode = self.invoice.signed_qr_code
doctype = self.invoice.doctype
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 9ded8da..b12e152 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -422,6 +422,9 @@
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+ dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+ depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'),
+
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index cbc9478..a5ca7ee 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -3,9 +3,9 @@
Provide a report and downloadable CSV according to the German DATEV format.
- Query report showing only the columns that contain data, formatted nicely for
- dispay to the user.
+ dispay to the user.
- CSV download functionality `download_datev_csv` that provides a CSV file with
- all required columns. Used to import the data into the DATEV Software.
+ all required columns. Used to import the data into the DATEV Software.
"""
from __future__ import unicode_literals
@@ -88,6 +88,32 @@
"fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 2",
"width": 150
+ },
+ {
+ "label": "Beleginfo - Art 3",
+ "fieldname": "Beleginfo - Art 3",
+ "fieldtype": "Link",
+ "options": "DocType",
+ "width": 100
+ },
+ {
+ "label": "Beleginfo - Inhalt 3",
+ "fieldname": "Beleginfo - Inhalt 3",
+ "fieldtype": "Dynamic Link",
+ "options": "Beleginfo - Art 3",
+ "width": 150
+ },
+ {
+ "label": "Beleginfo - Art 4",
+ "fieldname": "Beleginfo - Art 4",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": "Beleginfo - Inhalt 4",
+ "fieldname": "Beleginfo - Inhalt 4",
+ "fieldtype": "Data",
+ "width": 150
}
]
@@ -120,10 +146,8 @@
validate_fiscal_year(from_date, to_date, company)
if not frappe.db.exists('DATEV Settings', filters.get('company')):
- frappe.log_error(_('Please create {} for Company {}.').format(
- '<a href="desk#List/DATEV%20Settings/List">{}</a>'.format(_('DATEV Settings')),
- frappe.bold(filters.get('company'))
- ))
+ msg = 'Please create DATEV Settings for Company {}'.format(filters.get('company'))
+ frappe.log_error(msg, title='DATEV Settings missing')
return False
return True
@@ -169,7 +193,11 @@
gl.voucher_type as 'Beleginfo - Art 1',
gl.voucher_no as 'Beleginfo - Inhalt 1',
gl.against_voucher_type as 'Beleginfo - Art 2',
- gl.against_voucher as 'Beleginfo - Inhalt 2'
+ gl.against_voucher as 'Beleginfo - Inhalt 2',
+ gl.party_type as 'Beleginfo - Art 3',
+ gl.party as 'Beleginfo - Inhalt 3',
+ case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4',
+ par.debtor_creditor_number as 'Beleginfo - Inhalt 4'
FROM `tabGL Entry` gl
@@ -177,6 +205,19 @@
left join `tabAccount` acc
on gl.account = acc.name
+ left join `tabCustomer` cus
+ on gl.party_type = 'Customer'
+ and gl.party = cus.name
+
+ left join `tabSupplier` sup
+ on gl.party_type = 'Supplier'
+ and gl.party = sup.name
+
+ left join `tabParty Account` par
+ on par.parent = gl.party
+ and par.parenttype = gl.party_type
+ and par.company = %(company)s
+
WHERE gl.company = %(company)s
AND DATE(gl.posting_date) >= %(from_date)s
AND DATE(gl.posting_date) <= %(to_date)s
@@ -196,40 +237,56 @@
return frappe.db.sql("""
SELECT
- acc.account_number as 'Konto',
- CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
- CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
- CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
- CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
+ par.debtor_creditor_number as 'Konto',
+ CASE cus.customer_type
+ WHEN 'Company' THEN cus.customer_name
+ ELSE null
+ END as 'Name (Adressatentyp Unternehmen)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN TRIM(SUBSTR(cus.customer_name, LOCATE(' ', cus.customer_name)))
+ ELSE null
+ END as 'Name (Adressatentyp natürl. Person)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(cus.customer_name, ' ', 1), ' ', -1)
+ ELSE null
+ END as 'Vorname (Adressatentyp natürl. Person)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN '1'
+ WHEN 'Company' THEN '2'
+ ELSE '0'
+ END as 'Adressatentyp',
adr.address_line1 as 'Straße',
adr.pincode as 'Postleitzahl',
adr.city as 'Ort',
UPPER(country.code) as 'Land',
adr.address_line2 as 'Adresszusatz',
- con.email_id as 'E-Mail',
- coalesce(con.mobile_no, con.phone) as 'Telefon',
+ adr.email_id as 'E-Mail',
+ adr.phone as 'Telefon',
+ adr.fax as 'Fax',
cus.website as 'Internet',
cus.tax_id as 'Steuernummer'
- FROM `tabParty Account` par
+ FROM `tabCustomer` cus
- left join `tabAccount` acc
- on acc.name = par.account
+ left join `tabParty Account` par
+ on par.parent = cus.name
+ and par.parenttype = 'Customer'
+ and par.company = %(company)s
- left join `tabCustomer` cus
- on cus.name = par.parent
+ left join `tabDynamic Link` dyn_adr
+ on dyn_adr.link_name = cus.name
+ and dyn_adr.link_doctype = 'Customer'
+ and dyn_adr.parenttype = 'Address'
left join `tabAddress` adr
- on adr.name = cus.customer_primary_address
+ on adr.name = dyn_adr.parent
+ and adr.is_primary_address = '1'
left join `tabCountry` country
on country.name = adr.country
- left join `tabContact` con
- on con.name = cus.customer_primary_contact
-
- WHERE par.company = %(company)s
- AND par.parenttype = 'Customer'""", filters, as_dict=1)
+ WHERE adr.is_primary_address = '1'
+ """, filters, as_dict=1)
def get_suppliers(filters):
@@ -242,35 +299,48 @@
return frappe.db.sql("""
SELECT
- acc.account_number as 'Konto',
- CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
- CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
- CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
- CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
+ par.debtor_creditor_number as 'Konto',
+ CASE sup.supplier_type
+ WHEN 'Company' THEN sup.supplier_name
+ ELSE null
+ END as 'Name (Adressatentyp Unternehmen)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN TRIM(SUBSTR(sup.supplier_name, LOCATE(' ', sup.supplier_name)))
+ ELSE null
+ END as 'Name (Adressatentyp natürl. Person)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(sup.supplier_name, ' ', 1), ' ', -1)
+ ELSE null
+ END as 'Vorname (Adressatentyp natürl. Person)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN '1'
+ WHEN 'Company' THEN '2'
+ ELSE '0'
+ END as 'Adressatentyp',
adr.address_line1 as 'Straße',
adr.pincode as 'Postleitzahl',
adr.city as 'Ort',
UPPER(country.code) as 'Land',
adr.address_line2 as 'Adresszusatz',
- con.email_id as 'E-Mail',
- coalesce(con.mobile_no, con.phone) as 'Telefon',
+ adr.email_id as 'E-Mail',
+ adr.phone as 'Telefon',
+ adr.fax as 'Fax',
sup.website as 'Internet',
sup.tax_id as 'Steuernummer',
case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis'
- FROM `tabParty Account` par
+ FROM `tabSupplier` sup
- left join `tabAccount` acc
- on acc.name = par.account
-
- left join `tabSupplier` sup
- on sup.name = par.parent
+ left join `tabParty Account` par
+ on par.parent = sup.name
+ and par.parenttype = 'Supplier'
+ and par.company = %(company)s
left join `tabDynamic Link` dyn_adr
on dyn_adr.link_name = sup.name
and dyn_adr.link_doctype = 'Supplier'
and dyn_adr.parenttype = 'Address'
-
+
left join `tabAddress` adr
on adr.name = dyn_adr.parent
and adr.is_primary_address = '1'
@@ -278,17 +348,8 @@
left join `tabCountry` country
on country.name = adr.country
- left join `tabDynamic Link` dyn_con
- on dyn_con.link_name = sup.name
- and dyn_con.link_doctype = 'Supplier'
- and dyn_con.parenttype = 'Contact'
-
- left join `tabContact` con
- on con.name = dyn_con.parent
- and con.is_primary_contact = '1'
-
- WHERE par.company = %(company)s
- AND par.parenttype = 'Supplier'""", filters, as_dict=1)
+ WHERE adr.is_primary_address = '1'
+ """, filters, as_dict=1)
def get_account_names(filters):
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 49ca942..51d86ff 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -490,7 +490,7 @@
outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0
# Outstanding based on Sales Order
- outstanding_based_on_so = 0.0
+ outstanding_based_on_so = 0
# if credit limit check is bypassed at sales order level,
# we should not consider outstanding Sales Orders, when customer credit balance report is run
@@ -501,9 +501,11 @@
where customer=%s and docstatus = 1 and company=%s
and per_billed < 100 and status != 'Closed'""", (customer, company))
- outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0
+ outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0
# Outstanding based on Delivery Note, which are not created against Sales Order
+ outstanding_based_on_dn = 0
+
unmarked_delivery_note_items = frappe.db.sql("""select
dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
@@ -515,15 +517,29 @@
and ifnull(dn_item.against_sales_invoice, '') = ''
""", (customer, company), as_dict=True)
- outstanding_based_on_dn = 0.0
+ if not unmarked_delivery_note_items:
+ return outstanding_based_on_gle + outstanding_based_on_so
+
+ si_amounts = frappe.db.sql("""
+ SELECT
+ dn_detail, sum(amount) from `tabSales Invoice Item`
+ WHERE
+ docstatus = 1
+ and dn_detail in ({})
+ GROUP BY dn_detail""".format(", ".join(
+ frappe.db.escape(dn_item.name)
+ for dn_item in unmarked_delivery_note_items
+ ))
+ )
+
+ si_amounts = {si_item[0]: si_item[1] for si_item in si_amounts}
for dn_item in unmarked_delivery_note_items:
- si_amount = frappe.db.sql("""select sum(amount)
- from `tabSales Invoice Item`
- where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0]
+ dn_amount = flt(dn_item.amount)
+ si_amount = flt(si_amounts.get(dn_item.name))
- if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total:
- outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \
+ if dn_amount > si_amount and dn_item.base_net_total:
+ outstanding_based_on_dn += ((dn_amount - si_amount)
/ dn_item.base_net_total) * dn_item.base_grand_total
return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn
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 062cba1..750a1a6 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -23,7 +23,7 @@
if search_value:
data = search_serial_or_batch_or_barcode_number(search_value)
-
+
item_code = data.get("item_code") if data.get("item_code") else search_value
serial_no = data.get("serial_no") if data.get("serial_no") else ""
batch_no = data.get("batch_no") if data.get("batch_no") else ""
@@ -31,7 +31,7 @@
if data:
item_info = frappe.db.get_value(
- "Item", data.get("item_code"),
+ "Item", data.get("item_code"),
["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"]
, as_dict=1)
item_info.setdefault('serial_no', serial_no)
@@ -139,8 +139,24 @@
if serial_no or batch_no or barcode:
return "item.name = {0}".format(frappe.db.escape(item_code))
- return """(item.name like {item_code}
- or item.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%'))
+ return make_condition(item_code)
+
+def make_condition(item_code):
+ condition = "("
+ condition += """item.name like {item_code}
+ or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%'))
+ condition += add_search_fields_condition(item_code)
+ condition += ")"
+
+ return condition
+
+def add_search_fields_condition(item_code):
+ condition = ''
+ search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname'])
+ if search_fields:
+ for field in search_fields:
+ condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%'))
+ return condition
def get_item_group_condition(pos_profile):
cond = "and 1=1"
@@ -257,4 +273,4 @@
elif fieldname == 'mobile_no':
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
frappe.db.set_value('Customer', customer, 'mobile_no', value)
- contact_doc.save()
\ No newline at end of file
+ contact_doc.save()
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index 709fe57..9384ae5 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -81,13 +81,24 @@
const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
+ let qty_to_display = actual_qty;
+
+ if (Math.round(qty_to_display) > 999) {
+ qty_to_display = Math.round(qty_to_display)/1000;
+ qty_to_display = qty_to_display.toFixed(1) + 'K';
+ }
+
function get_item_image_html() {
if (!me.hide_images && item_image) {
- return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
+ return `<div class="flex" style="margin: 8px; justify-content: flex-end">
+ <span class="indicator-pill whitespace-nowrap ${indicator_color}" id="text">${qty_to_display}</span></div>
+ <div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
</div>`;
} else {
- return `<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
+ return `<div class="flex" style="margin: 8px; justify-content: flex-end">
+ <span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span></div>
+ <div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
}
}
@@ -95,13 +106,12 @@
`<div class="item-wrapper"
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
- title="Avaiable Qty: ${actual_qty}">
+ title="${item.item_name}">
${get_item_image_html()}
<div class="item-detail">
<div class="item-name">
- <span class="indicator ${indicator_color}"></span>
${frappe.ellipsis(item.item_name, 18)}
</div>
<div class="item-rate">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
@@ -316,4 +326,4 @@
toggle_component(show) {
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index ac55fdf..8c97322 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -50,8 +50,12 @@
recipients = list(filter(lambda r: r in valid_users,
self.recipient_list.split("\n")))
+ original_user = frappe.session.user
+
if recipients:
for user_id in recipients:
+ frappe.set_user(user_id)
+ frappe.set_user_lang(user_id)
msg_for_this_recipient = self.get_msg_html()
if msg_for_this_recipient:
frappe.sendmail(
@@ -62,6 +66,9 @@
reference_name = self.name,
unsubscribe_message = _("Unsubscribe from this Email Digest"))
+ frappe.set_user(original_user)
+ frappe.set_user_lang(original_user)
+
def get_msg_html(self):
"""Build email digest content"""
frappe.flags.ignore_account_permission = True
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 2079cf8..8aec893 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -46,9 +46,6 @@
}, __("View"));
}
- if (!frm.doc.is_fixed_asset) {
- erpnext.item.make_dashboard(frm);
- }
if (frm.doc.is_fixed_asset) {
frm.trigger('is_fixed_asset');
@@ -96,6 +93,10 @@
erpnext.item.edit_prices_button(frm);
erpnext.item.toggle_attributes(frm);
+
+ if (!frm.doc.is_fixed_asset) {
+ erpnext.item.make_dashboard(frm);
+ }
frm.add_custom_button(__('Duplicate'), function() {
var new_item = frappe.model.copy_doc(frm.doc);
@@ -473,11 +474,15 @@
me.multiple_variant_dialog.get_primary_btn().html(__('Create Variants'));
me.multiple_variant_dialog.disable_primary_action();
} else {
+
let no_of_combinations = lengths.reduce((a, b) => a * b, 1);
- me.multiple_variant_dialog.get_primary_btn()
- .html(__(
- `Make ${no_of_combinations} Variant${no_of_combinations === 1 ? '' : 's'}`
- ));
+ let msg;
+ if (no_of_combinations === 1) {
+ msg = __("Make {0} Variant", [no_of_combinations]);
+ } else {
+ msg = __("Make {0} Variants", [no_of_combinations]);
+ }
+ me.multiple_variant_dialog.get_primary_btn().html(msg);
me.multiple_variant_dialog.enable_primary_action();
}
}
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 7dfc5da..92c8d21 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -433,13 +433,21 @@
if (doc.material_request_type == "Customer Provided") {
return{
query: "erpnext.controllers.queries.item_query",
- filters:{ 'customer': me.frm.doc.customer }
+ filters:{
+ 'customer': me.frm.doc.customer,
+ 'is_stock_item':1
+ }
}
- } else if (doc.material_request_type != "Manufacture") {
+ } else if (doc.material_request_type == "Purchase") {
return{
query: "erpnext.controllers.queries.item_query",
filters: {'is_purchase_item': 1}
}
+ } else {
+ return{
+ query: "erpnext.controllers.queries.item_query",
+ filters: {'is_stock_item': 1}
+ }
}
});
},
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 4d1a514..befdad9 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -73,6 +73,34 @@
})
}, __('Create'));
}
+
+ frm.events.add_custom_buttons(frm);
+ },
+
+ add_custom_buttons: function(frm) {
+ if (frm.doc.docstatus == 0) {
+ frm.add_custom_button(__('Purchase Invoice'), function () {
+ if (!frm.doc.supplier) {
+ frappe.throw({
+ title: __("Mandatory"),
+ message: __("Please Select a Supplier")
+ });
+ }
+ erpnext.utils.map_current_doc({
+ method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
+ source_doctype: "Purchase Invoice",
+ target: frm,
+ setters: {
+ supplier: frm.doc.supplier,
+ },
+ get_query_filters: {
+ docstatus: 1,
+ per_received: ["<", 100],
+ company: frm.doc.company
+ }
+ })
+ }, __("Get Items From"));
+ }
},
company: function(frm) {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index d8d8310..61e60f3 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -53,7 +53,20 @@
'target_ref_field': 'stock_qty',
'source_field': 'stock_qty',
'percent_join_field': 'material_request'
+ },
+ {
+ 'source_dt': 'Purchase Receipt Item',
+ 'target_dt': 'Purchase Invoice Item',
+ 'join_field': 'purchase_invoice_item',
+ 'target_field': 'received_qty',
+ 'target_parent_dt': 'Purchase Invoice',
+ 'target_parent_field': 'per_received',
+ 'target_ref_field': 'qty',
+ 'source_field': 'received_qty',
+ 'percent_join_field': 'purchase_invoice',
+ 'overflow_type': 'receipt'
}]
+
if cint(self.is_return):
self.status_updater.extend([
{
@@ -514,7 +527,9 @@
def update_billing_status(self, update_modified=True):
updated_pr = [self.name]
for d in self.get("items"):
- if d.purchase_order_item:
+ if d.purchase_invoice and d.purchase_invoice_item:
+ d.db_set('billed_amt', d.amount, update_modified=update_modified)
+ elif d.purchase_order_item:
updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified)
for pr in set(updated_pr):
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index efe3642..82cc98e 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -72,16 +72,18 @@
"warehouse",
"rejected_warehouse",
"from_warehouse",
- "purchase_order",
"material_request",
+ "purchase_order",
+ "purchase_invoice",
"column_break_40",
"is_fixed_asset",
"asset_location",
"asset_category",
"schedule_date",
"quality_inspection",
- "purchase_order_item",
"material_request_item",
+ "purchase_order_item",
+ "purchase_invoice_item",
"purchase_receipt_item",
"delivery_note_item",
"putaway_rule",
@@ -937,7 +939,21 @@
"fieldname": "base_rate_with_margin",
"fieldtype": "Currency",
"label": "Rate With Margin (Company Currency)",
- "options": "Company:company:default_currency",
+ "options": "Company:company:default_currency"
+ },
+ {
+ "fieldname": "purchase_invoice",
+ "fieldtype": "Link",
+ "label": "Purchase Invoice",
+ "options": "Purchase Invoice",
+ "read_only": 1
+ },
+ {
+ "fieldname": "purchase_invoice_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Purchase Invoice Item",
+ "no_copy": 1,
"print_hide": 1,
"read_only": 1
}
@@ -945,7 +961,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 00:59:14.360847",
+ "modified": "2021-03-29 04:17:00.336298",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 2029b07..7e216d6 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -469,7 +469,7 @@
def submit(self):
if len(self.items) > 100:
msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"))
- self.queue_action('submit')
+ self.queue_action('submit', timeout=2000)
else:
self._submit()
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 3fc1df7..3832415 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -470,7 +470,9 @@
item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out)
item_group = item_group_doc.parent_item_group
-def _get_item_tax_template(args, taxes, out={}, for_validate=False):
+def _get_item_tax_template(args, taxes, out=None, for_validate=False):
+ if out is None:
+ out = {}
taxes_with_validity = []
taxes_with_no_validity = []
@@ -935,8 +937,8 @@
def get_company_total_stock(item_code, company):
return frappe.db.sql("""SELECT sum(actual_qty) from
(`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
- WHERE `tabWarehouse`.company = '{0}' and `tabBin`.item_code = '{1}'"""
- .format(company, item_code))[0][0]
+ WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""",
+ (company, item_code))[0][0]
@frappe.whitelist()
def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):
diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
index 087c12e..01927c2 100644
--- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
+++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
@@ -70,7 +70,7 @@
return frappe.db.sql("""
select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty
from `tabStock Ledger Entry`
- where docstatus < 2 and ifnull(batch_no, '') != '' %s
+ where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s
group by voucher_no, batch_no, item_code, warehouse
order by item_code, warehouse""" %
conditions, as_dict=1)
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 6dfede4..bbd73e9 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -165,7 +165,7 @@
select
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
- sle.item_code as name, sle.voucher_no, sle.stock_value
+ sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no
from
`tabStock Ledger Entry` sle force index (posting_sort_index)
where sle.docstatus < 2 %s %s
@@ -193,7 +193,7 @@
qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)]
- if d.voucher_type == "Stock Reconciliation":
+ if d.voucher_type == "Stock Reconciliation" and not d.batch_no:
qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty)
else:
qty_diff = flt(d.actual_qty)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 985901f..bbfcb7a 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -416,7 +416,7 @@
frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate)
# Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount
- stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no)
+ stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True)
stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False)
stock_entry.db_update()
for d in stock_entry.items:
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 0af3d90..034d3eb 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -172,7 +172,7 @@
bin_obj.flags.ignore_permissions = 1
bin_obj.insert()
else:
- bin_obj = frappe.get_cached_doc('Bin', bin)
+ bin_obj = frappe.get_doc('Bin', bin, for_update=True)
bin_obj.flags.ignore_permissions = True
return bin_obj
diff --git a/requirements.txt b/requirements.txt
index 377fd7d..f1ffeb8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-frappe
+# frappe # https://github.com/frappe/frappe is installed during bench-init
gocardless-pro~=1.22.0
googlemaps # used in ERPNext, but dependency is defined in Frappe
pandas~=1.1.5