Merge branch 'version-13-beta-pre-release' of https://github.com/frappe/erpnext into version-13-beta-pre-release
This commit is contained in:
commit
290f02acdb
4
.github/helper/documentation.py
vendored
4
.github/helper/documentation.py
vendored
@ -21,8 +21,8 @@ def docs_link_exists(body):
|
||||
if word.startswith('http') and uri_validator(word):
|
||||
parsed_url = urlparse(word)
|
||||
if parsed_url.netloc == "github.com":
|
||||
_, org, repo, _type, ref = parsed_url.path.split('/')
|
||||
if org == "frappe" and repo in docs_repos:
|
||||
parts = parsed_url.path.split('/')
|
||||
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
|
||||
return True
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.0.0-beta.6'
|
||||
__version__ = '13.0.0-beta.9'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
@ -132,16 +132,10 @@ def allow_regional(fn):
|
||||
|
||||
return caller
|
||||
|
||||
def get_last_membership():
|
||||
def get_last_membership(member):
|
||||
'''Returns last membership if exists'''
|
||||
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
|
||||
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
|
||||
dict(member=member, paid=1), order_by='to_date desc', limit=1)
|
||||
|
||||
return last_membership and last_membership[0]
|
||||
|
||||
def is_member():
|
||||
'''Returns true if the user is still a member'''
|
||||
last_membership = get_last_membership()
|
||||
if last_membership and getdate(last_membership.to_date) > getdate():
|
||||
return True
|
||||
return False
|
||||
if last_membership:
|
||||
return last_membership[0]
|
||||
|
@ -910,98 +910,8 @@
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"Passiva": {
|
||||
"Passiva - Verbindlichkeiten": {
|
||||
"root_type": "Liability",
|
||||
"A - Eigenkapital": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"I - Gezeichnetes Kapital": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gezeichnetes Kapital": {
|
||||
"account_type": "Equity",
|
||||
"account_number": "2900"
|
||||
},
|
||||
"Ausstehende Einlagen auf das gezeichnete Kapital": {
|
||||
"account_number": "2910",
|
||||
"is_group": 1
|
||||
}
|
||||
},
|
||||
"II - Kapitalr\u00fccklage": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Kapitalr\u00fccklage": {
|
||||
"account_number": "2920"
|
||||
}
|
||||
},
|
||||
"III - Gewinnr\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"1 - gesetzliche R\u00fccklage": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gesetzliche R\u00fccklage": {
|
||||
"account_number": "2930"
|
||||
}
|
||||
},
|
||||
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1
|
||||
},
|
||||
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||
"account_number": "2950"
|
||||
}
|
||||
},
|
||||
"4 - andere Gewinnr\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
||||
"is_group": 1,
|
||||
"Gewinnr\u00fccklagen (BilMoG)": {
|
||||
"account_number": "2963"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
||||
"account_number": "2964"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
||||
"account_number": "2965"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
||||
"account_number": "2966"
|
||||
}
|
||||
},
|
||||
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2967"
|
||||
},
|
||||
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2968"
|
||||
},
|
||||
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2969"
|
||||
}
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"IV - Gewinnvortrag/Verlustvortrag": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gewinnvortrag vor Verwendung": {
|
||||
"account_number": "2970"
|
||||
},
|
||||
"Verlustvortrag vor Verwendung": {
|
||||
"account_number": "2978"
|
||||
}
|
||||
},
|
||||
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1
|
||||
},
|
||||
"Einlagen stiller Gesellschafter": {
|
||||
"account_number": "9295"
|
||||
}
|
||||
},
|
||||
"B - R\u00fcckstellungen": {
|
||||
"is_group": 1,
|
||||
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
||||
@ -1618,6 +1528,143 @@
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"Passiva - Eigenkapital": {
|
||||
"root_type": "Equity",
|
||||
"A - Eigenkapital": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"I - Gezeichnetes Kapital": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gezeichnetes Kapital": {
|
||||
"account_number": "2900",
|
||||
"account_type": "Equity"
|
||||
},
|
||||
"Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
|
||||
"account_number": "2901"
|
||||
},
|
||||
"Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
|
||||
"account_number": "2902"
|
||||
},
|
||||
"Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
|
||||
"account_number": "2903"
|
||||
},
|
||||
"R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||
"account_number": "2906"
|
||||
},
|
||||
"Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||
"account_number": "2907"
|
||||
},
|
||||
"Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
|
||||
"account_number": "2908"
|
||||
},
|
||||
"Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
|
||||
"account_number": "2910"
|
||||
}
|
||||
},
|
||||
"II - Kapitalr\u00fccklage": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Kapitalr\u00fccklage": {
|
||||
"account_number": "2920"
|
||||
},
|
||||
"Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
|
||||
"account_number": "2925"
|
||||
},
|
||||
"Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
|
||||
"account_number": "2926"
|
||||
},
|
||||
"Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
|
||||
"account_number": "2927"
|
||||
},
|
||||
"Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
|
||||
"account_number": "2928"
|
||||
},
|
||||
"Nachschusskapital (Gegenkonto 1299)": {
|
||||
"account_number": "2929"
|
||||
}
|
||||
},
|
||||
"III - Gewinnr\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"1 - gesetzliche R\u00fccklage": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gesetzliche R\u00fccklage": {
|
||||
"account_number": "2930"
|
||||
}
|
||||
},
|
||||
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||
"account_number": "2935"
|
||||
}
|
||||
},
|
||||
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||
"account_number": "2950"
|
||||
}
|
||||
},
|
||||
"4 - andere Gewinnr\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Andere Gewinnr\u00fccklagen": {
|
||||
"account_number": "2960"
|
||||
},
|
||||
"Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
|
||||
"account_number": "2961"
|
||||
},
|
||||
"Eigenkapitalanteil von Wertaufholungen": {
|
||||
"account_number": "2962"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
||||
"is_group": 1,
|
||||
"Gewinnr\u00fccklagen (BilMoG)": {
|
||||
"account_number": "2963"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
||||
"account_number": "2964"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
||||
"account_number": "2965"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
||||
"account_number": "2966"
|
||||
}
|
||||
},
|
||||
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2967"
|
||||
},
|
||||
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2968"
|
||||
},
|
||||
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2969"
|
||||
}
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"IV - Gewinnvortrag/Verlustvortrag": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gewinnvortrag vor Verwendung": {
|
||||
"account_number": "2970"
|
||||
},
|
||||
"Verlustvortrag vor Verwendung": {
|
||||
"account_number": "2978"
|
||||
}
|
||||
},
|
||||
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1
|
||||
},
|
||||
"Einlagen stiller Gesellschafter": {
|
||||
"account_number": "9295"
|
||||
}
|
||||
}
|
||||
},
|
||||
"1 - Umsatzerl\u00f6se": {
|
||||
"root_type": "Income",
|
||||
"is_group": 1,
|
||||
|
@ -254,7 +254,8 @@ def create_account(**kwargs):
|
||||
account_name = kwargs.get('account_name'),
|
||||
account_type = kwargs.get('account_type'),
|
||||
parent_account = kwargs.get('parent_account'),
|
||||
company = kwargs.get('company')
|
||||
company = kwargs.get('company'),
|
||||
account_currency = kwargs.get('account_currency')
|
||||
))
|
||||
|
||||
account.save()
|
||||
|
@ -2,7 +2,6 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Accounting Dimension', {
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.set_query('document_type', () => {
|
||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||
|
@ -203,7 +203,7 @@ def get_dimension_with_children(doctype, dimension):
|
||||
return all_dimensions
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_dimension_filters():
|
||||
def get_dimensions(with_cost_center_and_project=False):
|
||||
dimension_filters = frappe.db.sql("""
|
||||
SELECT label, fieldname, document_type
|
||||
FROM `tabAccounting Dimension`
|
||||
@ -214,6 +214,18 @@ def get_dimension_filters():
|
||||
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
||||
WHERE c.parent = p.name""", as_dict=1)
|
||||
|
||||
if with_cost_center_and_project:
|
||||
dimension_filters.extend([
|
||||
{
|
||||
'fieldname': 'cost_center',
|
||||
'document_type': 'Cost Center'
|
||||
},
|
||||
{
|
||||
'fieldname': 'project',
|
||||
'document_type': 'Project'
|
||||
}
|
||||
])
|
||||
|
||||
default_dimensions_map = {}
|
||||
for dimension in default_dimensions:
|
||||
default_dimensions_map.setdefault(dimension.company, {})
|
||||
|
@ -11,37 +11,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import d
|
||||
|
||||
class TestAccountingDimension(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
|
||||
dimension = frappe.get_doc({
|
||||
"doctype": "Accounting Dimension",
|
||||
"document_type": "Department",
|
||||
}).insert()
|
||||
else:
|
||||
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
||||
dimension1.disabled = 0
|
||||
dimension1.save()
|
||||
|
||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
|
||||
dimension1 = frappe.get_doc({
|
||||
"doctype": "Accounting Dimension",
|
||||
"document_type": "Location",
|
||||
})
|
||||
|
||||
dimension1.append("dimension_defaults", {
|
||||
"company": "_Test Company",
|
||||
"reference_document": "Location",
|
||||
"default_dimension": "Block 1",
|
||||
"mandatory_for_bs": 1
|
||||
})
|
||||
|
||||
dimension1.insert()
|
||||
dimension1.save()
|
||||
else:
|
||||
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
|
||||
dimension1.disabled = 0
|
||||
dimension1.save()
|
||||
create_dimension()
|
||||
|
||||
def test_dimension_against_sales_invoice(self):
|
||||
si = create_sales_invoice(do_not_save=1)
|
||||
@ -101,6 +71,38 @@ class TestAccountingDimension(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
disable_dimension()
|
||||
|
||||
def create_dimension():
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
|
||||
frappe.get_doc({
|
||||
"doctype": "Accounting Dimension",
|
||||
"document_type": "Department",
|
||||
}).insert()
|
||||
else:
|
||||
dimension = frappe.get_doc("Accounting Dimension", "Department")
|
||||
dimension.disabled = 0
|
||||
dimension.save()
|
||||
|
||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
|
||||
dimension1 = frappe.get_doc({
|
||||
"doctype": "Accounting Dimension",
|
||||
"document_type": "Location",
|
||||
})
|
||||
|
||||
dimension1.append("dimension_defaults", {
|
||||
"company": "_Test Company",
|
||||
"reference_document": "Location",
|
||||
"default_dimension": "Block 1",
|
||||
"mandatory_for_bs": 1
|
||||
})
|
||||
|
||||
dimension1.insert()
|
||||
dimension1.save()
|
||||
else:
|
||||
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
|
||||
dimension1.disabled = 0
|
||||
dimension1.save()
|
||||
|
||||
def disable_dimension():
|
||||
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
||||
|
@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Accounting Dimension Filter', {
|
||||
refresh: function(frm, cdt, cdn) {
|
||||
if (frm.doc.accounting_dimension) {
|
||||
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
|
||||
}
|
||||
|
||||
let help_content =
|
||||
`<table class="table table-bordered" style="background-color: #f9f9f9;">
|
||||
<tr><td>
|
||||
<p>
|
||||
<i class="fa fa-hand-right"></i>
|
||||
{{__('Note: On checking Is Mandatory the accounting dimension will become mandatory against that specific account for all accounting transactions')}}
|
||||
</p>
|
||||
</td></tr>
|
||||
</table>`;
|
||||
|
||||
frm.set_df_property('dimension_filter_help', 'options', help_content);
|
||||
},
|
||||
onload: function(frm) {
|
||||
frm.set_query('applicable_on_account', 'accounts', function() {
|
||||
return {
|
||||
filters: {
|
||||
'company': frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frappe.db.get_list('Accounting Dimension',
|
||||
{fields: ['document_type']}).then((res) => {
|
||||
let options = ['Cost Center', 'Project'];
|
||||
|
||||
res.forEach((dimension) => {
|
||||
options.push(dimension.document_type);
|
||||
});
|
||||
|
||||
frm.set_df_property('accounting_dimension', 'options', options);
|
||||
});
|
||||
|
||||
frm.trigger('setup_filters');
|
||||
},
|
||||
|
||||
setup_filters: function(frm) {
|
||||
let filters = {};
|
||||
|
||||
if (frm.doc.accounting_dimension) {
|
||||
frappe.model.with_doctype(frm.doc.accounting_dimension, function() {
|
||||
if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
|
||||
filters['is_group'] = 0;
|
||||
}
|
||||
|
||||
if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) {
|
||||
filters['company'] = frm.doc.company;
|
||||
}
|
||||
|
||||
frm.set_query('dimension_value', 'dimensions', function() {
|
||||
return {
|
||||
filters: filters
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
accounting_dimension: function(frm) {
|
||||
frm.clear_table("dimensions");
|
||||
let row = frm.add_child("dimensions");
|
||||
row.accounting_dimension = frm.doc.accounting_dimension;
|
||||
frm.refresh_field("dimensions");
|
||||
frm.trigger('setup_filters');
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Allowed Dimension', {
|
||||
dimensions_add: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
row.accounting_dimension = frm.doc.accounting_dimension;
|
||||
frm.refresh_field("dimensions");
|
||||
}
|
||||
});
|
@ -0,0 +1,134 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "format:{accounting_dimension}-{#####}",
|
||||
"creation": "2020-11-08 18:28:11.906146",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"accounting_dimension",
|
||||
"disabled",
|
||||
"column_break_2",
|
||||
"company",
|
||||
"allow_or_restrict",
|
||||
"section_break_4",
|
||||
"accounts",
|
||||
"column_break_6",
|
||||
"dimensions",
|
||||
"section_break_10",
|
||||
"dimension_filter_help"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "accounting_dimension",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Accounting Dimension",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "allow_or_restrict",
|
||||
"fieldtype": "Select",
|
||||
"label": "Allow Or Restrict Dimension",
|
||||
"options": "Allow\nRestrict",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"label": "Applicable On Account",
|
||||
"options": "Applicable On Account",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.accounting_dimension",
|
||||
"fieldname": "dimensions",
|
||||
"fieldtype": "Table",
|
||||
"label": "Applicable Dimension",
|
||||
"options": "Allowed Dimension",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_filter_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Dimension Filter Help",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-16 15:27:23.659285",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Dimension Filter",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright, (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AccountingDimensionFilter(Document):
|
||||
def validate(self):
|
||||
self.validate_applicable_accounts()
|
||||
|
||||
def validate_applicable_accounts(self):
|
||||
accounts = frappe.db.sql(
|
||||
"""
|
||||
SELECT a.applicable_on_account as account
|
||||
FROM `tabApplicable On Account` a, `tabAccounting Dimension Filter` d
|
||||
WHERE d.name = a.parent
|
||||
and d.name != %s
|
||||
and d.accounting_dimension = %s
|
||||
""", (self.name, self.accounting_dimension), as_dict=1)
|
||||
|
||||
account_list = [d.account for d in accounts]
|
||||
|
||||
for account in self.get('accounts'):
|
||||
if account.applicable_on_account in account_list:
|
||||
frappe.throw(_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
|
||||
account.idx, frappe.bold(account.applicable_on_account), frappe.bold(self.accounting_dimension)))
|
||||
|
||||
def get_dimension_filter_map():
|
||||
filters = frappe.db.sql("""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a, `tabAllowed Dimension` d,
|
||||
`tabAccounting Dimension Filter` p
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
AND p.name = d.parent
|
||||
""", as_dict=1)
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
f.fieldname = scrub(f.accounting_dimension)
|
||||
|
||||
build_map(dimension_filter_map, f.fieldname, f.applicable_on_account, f.dimension_value,
|
||||
f.allow_or_restrict, f.is_mandatory)
|
||||
|
||||
return dimension_filter_map
|
||||
|
||||
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
||||
map_object.setdefault((dimension, account), {
|
||||
'allowed_dimensions': [],
|
||||
'is_mandatory': is_mandatory,
|
||||
'allow_or_restrict': allow_or_restrict
|
||||
})
|
||||
map_object[(dimension, account)]['allowed_dimensions'].append(filter_value)
|
@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
|
||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||
|
||||
class TestAccountingDimensionFilter(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_dimension()
|
||||
create_accounting_dimension_filter()
|
||||
self.invoice_list = []
|
||||
|
||||
def test_allowed_dimension_validation(self):
|
||||
si = create_sales_invoice(do_not_save=1)
|
||||
si.items[0].cost_center = 'Main - _TC'
|
||||
si.department = 'Accounts - _TC'
|
||||
si.location = 'Block 1'
|
||||
si.save()
|
||||
|
||||
self.assertRaises(InvalidAccountDimensionError, si.submit)
|
||||
self.invoice_list.append(si)
|
||||
|
||||
def test_mandatory_dimension_validation(self):
|
||||
si = create_sales_invoice(do_not_save=1)
|
||||
si.department = ''
|
||||
si.location = 'Block 1'
|
||||
|
||||
# Test with no department for Sales Account
|
||||
si.items[0].department = ''
|
||||
si.items[0].cost_center = '_Test Cost Center 2 - _TC'
|
||||
si.save()
|
||||
|
||||
self.assertRaises(MandatoryAccountDimensionError, si.submit)
|
||||
self.invoice_list.append(si)
|
||||
|
||||
def tearDown(self):
|
||||
disable_dimension_filter()
|
||||
disable_dimension()
|
||||
|
||||
for si in self.invoice_list:
|
||||
si.load_from_db()
|
||||
if si.docstatus == 1:
|
||||
si.cancel()
|
||||
|
||||
def create_accounting_dimension_filter():
|
||||
if not frappe.db.get_value('Accounting Dimension Filter',
|
||||
{'accounting_dimension': 'Cost Center'}):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Accounting Dimension Filter',
|
||||
'accounting_dimension': 'Cost Center',
|
||||
'allow_or_restrict': 'Allow',
|
||||
'company': '_Test Company',
|
||||
'accounts': [{
|
||||
'applicable_on_account': 'Sales - _TC',
|
||||
}],
|
||||
'dimensions': [{
|
||||
'accounting_dimension': 'Cost Center',
|
||||
'dimension_value': '_Test Cost Center 2 - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
else:
|
||||
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
|
||||
doc.disabled = 0
|
||||
doc.save()
|
||||
|
||||
if not frappe.db.get_value('Accounting Dimension Filter',
|
||||
{'accounting_dimension': 'Department'}):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Accounting Dimension Filter',
|
||||
'accounting_dimension': 'Department',
|
||||
'allow_or_restrict': 'Allow',
|
||||
'company': '_Test Company',
|
||||
'accounts': [{
|
||||
'applicable_on_account': 'Sales - _TC',
|
||||
'is_mandatory': 1
|
||||
}],
|
||||
'dimensions': [{
|
||||
'accounting_dimension': 'Department',
|
||||
'dimension_value': 'Accounts - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
else:
|
||||
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
|
||||
doc.disabled = 0
|
||||
doc.save()
|
||||
|
||||
def disable_dimension_filter():
|
||||
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
|
||||
doc.disabled = 1
|
||||
doc.save()
|
||||
|
||||
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
|
||||
doc.disabled = 1
|
||||
doc.save()
|
@ -21,6 +21,7 @@
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"automatically_fetch_payment_terms",
|
||||
"delete_linked_ledger_entries",
|
||||
"deferred_accounting_settings_section",
|
||||
"automatically_process_deferred_accounting_entry",
|
||||
"book_deferred_entries_based_on",
|
||||
@ -219,6 +220,12 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Book Deferred Entries Based On",
|
||||
"options": "Days\nMonths"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "delete_linked_ledger_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -226,7 +233,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-13 11:32:52.268826",
|
||||
"modified": "2021-01-05 13:04:00.118892",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
@ -254,4 +261,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-08 18:22:36.001131",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"accounting_dimension",
|
||||
"dimension_value"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "accounting_dimension",
|
||||
"fieldtype": "Link",
|
||||
"label": "Accounting Dimension",
|
||||
"options": "DocType",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_value",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"options": "accounting_dimension",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-23 09:56:19.744200",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Allowed Dimension",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AllowedDimension(Document):
|
||||
pass
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-08 18:20:00.944449",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"applicable_on_account",
|
||||
"is_mandatory"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "applicable_on_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Accounts",
|
||||
"options": "Account",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"fieldname": "is_mandatory",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Mandatory",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-22 19:55:13.324136",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Applicable On Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ApplicableOnAccount(Document):
|
||||
pass
|
@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.provide('erpnext.integrations');
|
||||
|
||||
frappe.ui.form.on('Bank', {
|
||||
onload: function(frm) {
|
||||
@ -20,7 +21,12 @@ frappe.ui.form.on('Bank', {
|
||||
frm.set_df_property('address_and_contact', 'hidden', 0);
|
||||
frappe.contacts.render_address_and_contact(frm);
|
||||
}
|
||||
},
|
||||
if (frm.doc.plaid_access_token) {
|
||||
frm.add_custom_button(__('Refresh Plaid Link'), () => {
|
||||
new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -40,4 +46,79 @@ let add_fields_to_mapping_table = function (frm) {
|
||||
frm.doc.name).options = options;
|
||||
|
||||
frm.fields_dict.bank_transaction_mapping.grid.refresh();
|
||||
};
|
||||
};
|
||||
|
||||
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||
constructor(access_token) {
|
||||
this.access_token = access_token;
|
||||
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
|
||||
this.init_config();
|
||||
}
|
||||
|
||||
async init_config() {
|
||||
this.plaid_env = await frappe.db.get_single_value('Plaid Settings', 'plaid_env');
|
||||
this.token = await this.get_link_token_for_update();
|
||||
this.init_plaid();
|
||||
}
|
||||
|
||||
async get_link_token_for_update() {
|
||||
const token = frappe.xcall(
|
||||
'erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update',
|
||||
{ access_token: this.access_token }
|
||||
)
|
||||
if (!token) {
|
||||
frappe.throw(__('Cannot retrieve link token for update. Check Error Log for more information'));
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
init_plaid() {
|
||||
const me = this;
|
||||
me.loadScript(me.plaidUrl)
|
||||
.then(() => {
|
||||
me.onScriptLoaded(me);
|
||||
})
|
||||
.then(() => {
|
||||
if (me.linkHandler) {
|
||||
me.linkHandler.open();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
me.onScriptError(error);
|
||||
});
|
||||
}
|
||||
|
||||
loadScript(src) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (document.querySelector("script[src='" + src + "']")) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const el = document.createElement('script');
|
||||
el.type = 'text/javascript';
|
||||
el.async = true;
|
||||
el.src = src;
|
||||
el.addEventListener('load', resolve);
|
||||
el.addEventListener('error', reject);
|
||||
el.addEventListener('abort', reject);
|
||||
document.head.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
onScriptLoaded(me) {
|
||||
me.linkHandler = Plaid.create({
|
||||
env: me.plaid_env,
|
||||
token: me.token,
|
||||
onSuccess: me.plaid_success
|
||||
});
|
||||
}
|
||||
|
||||
onScriptError(error) {
|
||||
frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
plaid_success(token, response) {
|
||||
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
|
||||
}
|
||||
};
|
||||
|
@ -1,24 +1,9 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Budget', {
|
||||
onload: function(frm) {
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frm.set_query("project", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frm.set_query("account", "accounts", function() {
|
||||
return {
|
||||
filters: {
|
||||
@ -26,16 +11,18 @@ frappe.ui.form.on('Budget', {
|
||||
report_type: "Profit and Loss",
|
||||
is_group: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("monthly_distribution", function() {
|
||||
return {
|
||||
filters: {
|
||||
fiscal_year: frm.doc.fiscal_year
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -122,8 +122,10 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
|
||||
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project=project, posting_date=nowdate())
|
||||
|
||||
self.assertRaises(BudgetError, jv.submit)
|
||||
|
||||
@ -147,8 +149,11 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
budget = make_budget(budget_against="Project")
|
||||
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
|
||||
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC",
|
||||
project=project, posting_date=nowdate())
|
||||
|
||||
self.assertRaises(BudgetError, jv.submit)
|
||||
|
||||
@ -159,10 +164,10 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
month = now_datetime().month
|
||||
if month > 10:
|
||||
month = 10
|
||||
if month > 9:
|
||||
month = 9
|
||||
|
||||
for i in range(month):
|
||||
for i in range(month+1):
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
||||
|
||||
@ -181,12 +186,14 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
budget = make_budget(budget_against="Project")
|
||||
month = now_datetime().month
|
||||
if month > 10:
|
||||
month = 10
|
||||
if month > 9:
|
||||
month = 9
|
||||
|
||||
for i in range(month):
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
for i in range(month + 1):
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
|
||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True,
|
||||
project=project)
|
||||
|
||||
self.assertTrue(frappe.db.get_value("GL Entry",
|
||||
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
|
||||
@ -289,7 +296,7 @@ def make_budget(**args):
|
||||
budget = frappe.new_doc("Budget")
|
||||
|
||||
if budget_against == "Project":
|
||||
budget.project = "_Test Project"
|
||||
budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
else:
|
||||
budget.cost_center =cost_center or "_Test Cost Center - _TC"
|
||||
|
||||
|
@ -11,8 +11,10 @@ from frappe.model.meta import get_field_precision
|
||||
from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.exceptions import InvalidAccountCurrency
|
||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
|
||||
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
|
||||
from six import iteritems
|
||||
|
||||
exclude_from_linked_with = True
|
||||
class GLEntry(Document):
|
||||
@ -39,6 +41,7 @@ class GLEntry(Document):
|
||||
if not from_repost:
|
||||
self.validate_account_details(adv_adj)
|
||||
self.validate_dimensions_for_pl_and_bs()
|
||||
self.validate_allowed_dimensions()
|
||||
|
||||
validate_frozen_account(self.account, adv_adj)
|
||||
validate_balance_type(self.account, adv_adj)
|
||||
@ -76,11 +79,9 @@ class GLEntry(Document):
|
||||
.format(self.voucher_type, self.voucher_no, self.account))
|
||||
|
||||
def validate_dimensions_for_pl_and_bs(self):
|
||||
|
||||
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
||||
|
||||
for dimension in get_checks_for_pl_and_bs_accounts():
|
||||
|
||||
if account_type == "Profit and Loss" \
|
||||
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
|
||||
if not self.get(dimension.fieldname):
|
||||
@ -93,6 +94,25 @@ class GLEntry(Document):
|
||||
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
|
||||
.format(dimension.label, self.account))
|
||||
|
||||
def validate_allowed_dimensions(self):
|
||||
dimension_filter_map = get_dimension_filter_map()
|
||||
for key, value in iteritems(dimension_filter_map):
|
||||
dimension = key[0]
|
||||
account = key[1]
|
||||
|
||||
if self.account == account:
|
||||
if value['is_mandatory'] and not self.get(dimension):
|
||||
frappe.throw(_("{0} is mandatory for account {1}").format(
|
||||
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), MandatoryAccountDimensionError)
|
||||
|
||||
if value['allow_or_restrict'] == 'Allow':
|
||||
if self.get(dimension) and self.get(dimension) not in value['allowed_dimensions']:
|
||||
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
|
||||
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
|
||||
else:
|
||||
if self.get(dimension) and self.get(dimension) in value['allowed_dimensions']:
|
||||
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
|
||||
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
|
||||
|
||||
def check_pl_account(self):
|
||||
if self.is_opening=='Yes' and \
|
||||
@ -137,9 +157,10 @@ class GLEntry(Document):
|
||||
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
|
||||
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
|
||||
|
||||
if self.cost_center and _check_is_group():
|
||||
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
|
||||
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||
if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
|
||||
and self.cost_center and _check_is_group():
|
||||
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
|
||||
self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||
|
||||
def validate_party(self):
|
||||
validate_party_frozen_disabled(self.party_type, self.party)
|
||||
@ -149,7 +170,7 @@ class GLEntry(Document):
|
||||
account_currency = get_account_currency(self.account)
|
||||
|
||||
if not self.account_currency:
|
||||
self.account_currency = company_currency
|
||||
self.account_currency = account_currency or company_currency
|
||||
|
||||
if account_currency != self.account_currency:
|
||||
frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")
|
||||
|
@ -20,7 +20,8 @@ def get_data():
|
||||
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
|
||||
},
|
||||
{
|
||||
'items': ['Item']
|
||||
'label': _('Stock'),
|
||||
'items': ['Item Groups', 'Item']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -120,6 +120,8 @@ frappe.ui.form.on("Journal Entry", {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
voucher_type: function(frm){
|
||||
@ -197,6 +199,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
this.load_defaults();
|
||||
this.setup_queries();
|
||||
this.setup_balance_formatter();
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
onload_post_render: function() {
|
||||
@ -222,15 +225,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
return erpnext.journal_entry.account_query(me.frm);
|
||||
});
|
||||
|
||||
me.frm.set_query("cost_center", "accounts", function(doc, cdt, cdn) {
|
||||
return {
|
||||
filters: {
|
||||
company: me.frm.doc.company,
|
||||
is_group: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
|
||||
@ -406,6 +400,8 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
}
|
||||
}
|
||||
cur_frm.cscript.update_totals(doc);
|
||||
|
||||
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'accounts');
|
||||
},
|
||||
|
||||
});
|
||||
|
@ -6,14 +6,18 @@ import frappe, erpnext, json
|
||||
from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint, get_link_to_form
|
||||
from frappe import msgprint, _, scrub
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.utils import get_balance_on, get_account_currency
|
||||
from erpnext.accounts.utils import get_balance_on, get_stock_accounts, get_stock_and_account_balance, \
|
||||
get_account_currency, check_if_stock_and_account_balance_synced
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
|
||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
|
||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting \
|
||||
import get_party_account_based_on_invoice_discounting
|
||||
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
|
||||
|
||||
from six import string_types, iteritems
|
||||
|
||||
class StockAccountInvalidTransaction(frappe.ValidationError): pass
|
||||
|
||||
class JournalEntry(AccountsController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JournalEntry, self).__init__(*args, **kwargs)
|
||||
@ -46,6 +50,7 @@ class JournalEntry(AccountsController):
|
||||
self.validate_empty_accounts_table()
|
||||
self.set_account_and_party_balance()
|
||||
self.validate_inter_company_accounts()
|
||||
self.validate_stock_accounts()
|
||||
if not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
@ -57,6 +62,8 @@ class JournalEntry(AccountsController):
|
||||
self.update_expense_claim()
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
check_if_stock_and_account_balance_synced(self.posting_date,
|
||||
self.company, self.doctype, self.name)
|
||||
|
||||
def on_cancel(self):
|
||||
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
||||
@ -95,6 +102,16 @@ class JournalEntry(AccountsController):
|
||||
if account_currency == previous_account_currency:
|
||||
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
|
||||
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
|
||||
|
||||
def validate_stock_accounts(self):
|
||||
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
|
||||
for account in stock_accounts:
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
|
||||
self.posting_date, self.company)
|
||||
|
||||
if account_bal == stock_bal:
|
||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||
.format(account), StockAccountInvalidTransaction)
|
||||
|
||||
def update_inter_company_jv(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
|
@ -6,7 +6,7 @@ import unittest, frappe
|
||||
from frappe.utils import flt, nowdate
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.exceptions import InvalidAccountCurrency
|
||||
from erpnext.accounts.general_ledger import StockAccountInvalidTransaction
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
|
||||
|
||||
class TestJournalEntry(unittest.TestCase):
|
||||
def test_journal_entry_with_against_jv(self):
|
||||
@ -84,25 +84,31 @@ class TestJournalEntry(unittest.TestCase):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
stock_account = get_inventory_account(company)
|
||||
|
||||
from erpnext.accounts.utils import get_stock_and_account_balance
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
|
||||
diff = flt(account_bal) - flt(stock_bal)
|
||||
|
||||
if not diff:
|
||||
diff = 100
|
||||
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.company = company
|
||||
jv.posting_date = nowdate()
|
||||
jv.append("accounts", {
|
||||
"account": stock_account,
|
||||
"cost_center": "Main - TCP1",
|
||||
"debit_in_account_currency": 100
|
||||
"debit_in_account_currency": 0 if diff > 0 else abs(diff),
|
||||
"credit_in_account_currency": diff if diff > 0 else 0
|
||||
})
|
||||
|
||||
jv.append("accounts", {
|
||||
"account": "Stock Adjustment - TCP1",
|
||||
"credit_in_account_currency": 100,
|
||||
"cost_center": "Main - TCP1",
|
||||
"debit_in_account_currency": diff if diff > 0 else 0,
|
||||
"credit_in_account_currency": 0 if diff > 0 else abs(diff)
|
||||
})
|
||||
jv.insert()
|
||||
|
||||
from erpnext.accounts.utils import get_stock_and_account_balance
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
|
||||
|
||||
if account_bal == stock_bal:
|
||||
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
|
||||
frappe.db.rollback()
|
||||
@ -154,7 +160,7 @@ class TestJournalEntry(unittest.TestCase):
|
||||
self.assertFalse(gle)
|
||||
|
||||
def test_reverse_journal_entry(self):
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
"Sales - _TC", 100, exchange_rate=50, save=False)
|
||||
|
||||
@ -293,15 +299,20 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
def test_jv_with_project(self):
|
||||
from erpnext.projects.doctype.project.test_project import make_project
|
||||
project = make_project({
|
||||
'project_name': 'Journal Entry Project',
|
||||
'project_template_name': 'Test Project Template',
|
||||
'start_date': '2020-01-01'
|
||||
})
|
||||
|
||||
if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
|
||||
project = make_project({
|
||||
'project_name': 'Journal Entry Project',
|
||||
'project_template_name': 'Test Project Template',
|
||||
'start_date': '2020-01-01'
|
||||
})
|
||||
project_name = project.name
|
||||
else:
|
||||
project_name = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
|
||||
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
||||
for d in jv.accounts:
|
||||
d.project = project.project_name
|
||||
d.project = project_name
|
||||
jv.voucher_type = "Bank Entry"
|
||||
jv.multi_currency = 0
|
||||
jv.cheque_no = "112233"
|
||||
@ -311,10 +322,10 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
expected_values = {
|
||||
"_Test Cash - _TC": {
|
||||
"project": project.project_name
|
||||
"project": project_name
|
||||
},
|
||||
"_Test Bank - _TC": {
|
||||
"project": project.project_name
|
||||
"project": project_name
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Loyalty Program', {
|
||||
setup: function(frm) {
|
||||
var help_content =
|
||||
@ -46,20 +48,17 @@ frappe.ui.form.on('Loyalty Program', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_value("company", frappe.defaults.get_user_default("Company"));
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
|
||||
frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
|
||||
}
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
}
|
||||
});
|
||||
|
@ -36,6 +36,8 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
||||
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
||||
frm.page.set_indicator(__('In Progress'), 'orange');
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
@ -100,6 +102,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
||||
}
|
||||
})
|
||||
}
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
invoice_type: function(frm) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Payment Entry', {
|
||||
onload: function(frm) {
|
||||
@ -8,6 +9,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
setup: function(frm) {
|
||||
@ -88,15 +91,6 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("cost_center", "deductions", function() {
|
||||
return {
|
||||
filters: {
|
||||
"is_group": 0,
|
||||
"company": frm.doc.company
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("reference_doctype", "references", function() {
|
||||
if (frm.doc.party_type=="Customer") {
|
||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||
@ -167,6 +161,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
company: function(frm) {
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
contact_person: function(frm) {
|
||||
@ -401,6 +396,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
set_account_currency_and_balance: function(frm, account, currency_field,
|
||||
balance_field, callback_function) {
|
||||
|
||||
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
if (frm.doc.posting_date && account) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
|
||||
@ -427,6 +424,14 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
if(!frm.doc.paid_amount && frm.doc.received_amount)
|
||||
frm.events.received_amount(frm);
|
||||
|
||||
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
|
||||
&& frm.doc.paid_amount != frm.doc.received_amount) {
|
||||
if (company_currency != frm.doc.paid_from_account_currency &&
|
||||
frm.doc.payment_type == "Pay") {
|
||||
frm.doc.paid_amount = frm.doc.received_amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
() => {
|
||||
|
@ -8,7 +8,7 @@ from frappe import _
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
|
||||
get_dimension_filters)
|
||||
get_dimensions)
|
||||
|
||||
class PeriodClosingVoucher(AccountsController):
|
||||
def validate(self):
|
||||
@ -58,7 +58,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
for dimension in accounting_dimensions:
|
||||
dimension_fields.append('t1.{0}'.format(dimension))
|
||||
|
||||
dimension_filters, default_dimensions = get_dimension_filters()
|
||||
dimension_filters, default_dimensions = get_dimensions()
|
||||
|
||||
pl_accounts = self.get_pl_balances(dimension_fields)
|
||||
|
||||
|
@ -266,6 +266,8 @@ class POSInvoice(SalesInvoice):
|
||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||
if not self.pos_profile:
|
||||
pos_profile = get_pos_profile(self.company) or {}
|
||||
if not pos_profile:
|
||||
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
|
||||
self.pos_profile = pos_profile.get('name')
|
||||
|
||||
profile = {}
|
||||
|
@ -57,6 +57,8 @@ frappe.ui.form.on('POS Profile', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
@ -67,6 +69,7 @@ frappe.ui.form.on('POS Profile', {
|
||||
|
||||
company: function(frm) {
|
||||
frm.trigger("toggle_display_account_head");
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
toggle_display_account_head: function(frm) {
|
||||
|
@ -345,9 +345,13 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||
if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
|
||||
or (pricing_rule.margin_type == 'Percentage')):
|
||||
item_details.margin_type = pricing_rule.margin_type
|
||||
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||
item_details.has_margin = True
|
||||
|
||||
if pricing_rule.apply_multiple_pricing_rules and item_details.margin_rate_or_amount is not None:
|
||||
item_details.margin_rate_or_amount += pricing_rule.margin_rate_or_amount
|
||||
else:
|
||||
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||
|
||||
if pricing_rule.rate_or_discount == 'Rate':
|
||||
pricing_rule_rate = 0.0
|
||||
if pricing_rule.currency == args.currency:
|
||||
|
@ -164,7 +164,15 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
||||
frappe.throw(_("Invalid {0}").format(args.get(field)))
|
||||
|
||||
parent_groups = frappe.db.sql_list("""select name from `tab%s`
|
||||
where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||
where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||
|
||||
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||
root_name = frappe.db.get_list(parenttype,
|
||||
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
|
||||
|
||||
if root_name and root_name[0][0]:
|
||||
parent_groups.append(root_name[0][0])
|
||||
|
||||
if parent_groups:
|
||||
if allow_blank: parent_groups.append('')
|
||||
|
@ -26,6 +26,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
company: function() {
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
onload: function() {
|
||||
this._super();
|
||||
|
||||
@ -41,6 +46,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
||||
this.frm.trigger('supplier');
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(doc) {
|
||||
@ -268,8 +275,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
|
||||
supplier: function() {
|
||||
var me = this;
|
||||
if(this.frm.updating_party_details)
|
||||
|
||||
// Do not update if inter company reference is there as the details will already be updated
|
||||
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
|
||||
return;
|
||||
|
||||
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
|
||||
{
|
||||
posting_date: this.frm.doc.posting_date,
|
||||
@ -498,7 +508,7 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
||||
frappe.ui.form.on("Purchase Invoice", {
|
||||
setup: function(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
'Purchase Invoice': 'Debit Note',
|
||||
'Purchase Invoice': 'Return / Debit Note',
|
||||
'Payment Entry': 'Payment'
|
||||
}
|
||||
|
||||
@ -511,15 +521,6 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
|
@ -57,8 +57,8 @@
|
||||
"set_warehouse",
|
||||
"rejected_warehouse",
|
||||
"col_break_warehouse",
|
||||
"set_from_warehouse",
|
||||
"is_subcontracted",
|
||||
"supplier_warehouse",
|
||||
"items_section",
|
||||
"update_stock",
|
||||
"scan_barcode",
|
||||
@ -515,6 +515,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "update_stock",
|
||||
"description": "Sets 'Accepted Warehouse' in each row of the items table.",
|
||||
"fieldname": "set_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Set Accepted Warehouse",
|
||||
@ -543,17 +544,6 @@
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"fieldname": "items_section",
|
||||
"fieldtype": "Section Break",
|
||||
@ -1232,7 +1222,9 @@
|
||||
"fieldname": "inter_company_invoice_reference",
|
||||
"fieldtype": "Link",
|
||||
"label": "Inter Company Invoice Reference",
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -1356,13 +1348,25 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Represents Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.update_stock && (doc.is_subcontracted==\"Yes\" || doc.is_internal_supplier)",
|
||||
"description": "Sets 'From Warehouse' in each row of the items table.",
|
||||
"fieldname": "set_from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Set From Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-11 12:46:12.796378",
|
||||
"modified": "2020-12-26 20:49:03.305063",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -443,7 +443,7 @@ class PurchaseInvoice(BuyingController):
|
||||
else:
|
||||
self.stock_received_but_not_billed = None
|
||||
self.expenses_included_in_valuation = None
|
||||
|
||||
|
||||
self.negative_expense_to_be_booked = 0.0
|
||||
gl_entries = []
|
||||
|
||||
@ -457,7 +457,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
gl_entries = make_regional_gl_entries(gl_entries, self)
|
||||
|
||||
|
||||
gl_entries = merge_similar_entries(gl_entries)
|
||||
|
||||
self.make_payment_gl_entries(gl_entries)
|
||||
@ -480,7 +480,7 @@ class PurchaseInvoice(BuyingController):
|
||||
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
|
||||
if grand_total and not self.is_internal_transfer():
|
||||
# Didnot use base_grand_total to book rounding loss gle
|
||||
# Did not use base_grand_total to book rounding loss gle
|
||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||
self.precision("grand_total"))
|
||||
gl_entries.append(
|
||||
@ -511,8 +511,8 @@ class PurchaseInvoice(BuyingController):
|
||||
voucher_wise_stock_value = {}
|
||||
if self.update_stock:
|
||||
for d in frappe.get_all('Stock Ledger Entry',
|
||||
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
|
||||
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
|
||||
fields = ["voucher_detail_no", "stock_value_difference", "warehouse"], filters={'voucher_no': self.name}):
|
||||
voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
|
||||
|
||||
valuation_tax_accounts = [d.account_head for d in self.get("taxes")
|
||||
if d.category in ('Valuation', 'Total and Valuation')
|
||||
@ -563,16 +563,17 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
else:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": warehouse_debit_amount,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
if not self.is_internal_transfer():
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": warehouse_debit_amount,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
|
||||
# Amount added through landed-cost-voucher
|
||||
if landed_cost_entries:
|
||||
@ -582,7 +583,8 @@ class PurchaseInvoice(BuyingController):
|
||||
"against": item.expense_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(amount),
|
||||
"credit": flt(amount["base_amount"]),
|
||||
"credit_in_account_currency": flt(amount["amount"]),
|
||||
"project": item.project or self.project
|
||||
}, item=item))
|
||||
|
||||
@ -624,13 +626,14 @@ class PurchaseInvoice(BuyingController):
|
||||
if expense_booked_in_pr:
|
||||
expense_account = service_received_but_not_billed_account
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": amount,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item))
|
||||
if not self.is_internal_transfer():
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": amount,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item))
|
||||
|
||||
# If asset is bought through this document and not linked to PR
|
||||
if self.update_stock and item.landed_cost_voucher_amount:
|
||||
@ -795,10 +798,10 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
# Stock ledger value is not matching with the warehouse amount
|
||||
if (self.update_stock and voucher_wise_stock_value.get(item.name) and
|
||||
warehouse_debit_amount != flt(voucher_wise_stock_value.get(item.name), net_amt_precision)):
|
||||
warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)):
|
||||
|
||||
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
|
||||
stock_amount = flt(voucher_wise_stock_value.get(item.name), net_amt_precision)
|
||||
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
|
||||
stock_adjustment_amt = warehouse_debit_amount - stock_amount
|
||||
|
||||
gl_entries.append(
|
||||
@ -999,10 +1002,10 @@ class PurchaseInvoice(BuyingController):
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
|
||||
self.update_project()
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
|
@ -426,32 +426,37 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_total_purchase_cost_for_project(self):
|
||||
make_project({'project_name':'_Test Project'})
|
||||
if not frappe.db.exists("Project", {"project_name": "_Test Project for Purchase"}):
|
||||
project = make_project({'project_name':'_Test Project for Purchase'})
|
||||
else:
|
||||
project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
|
||||
|
||||
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
|
||||
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
|
||||
from `tabPurchase Invoice Item`
|
||||
where project = '{0}'
|
||||
and docstatus=1""".format(project.name))
|
||||
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
|
||||
|
||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project="_Test Project")
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
|
||||
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||
existing_purchase_cost + 15000)
|
||||
|
||||
pi1 = make_purchase_invoice(qty=10, project="_Test Project")
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
pi1 = make_purchase_invoice(qty=10, project=project.name)
|
||||
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||
existing_purchase_cost + 15500)
|
||||
|
||||
pi1.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||
existing_purchase_cost + 15000)
|
||||
|
||||
pi.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
|
||||
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost)
|
||||
|
||||
def test_return_purchase_invoice_with_perpetual_inventory(self):
|
||||
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||
|
||||
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
|
||||
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
|
||||
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||
|
||||
@ -860,17 +865,17 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
})
|
||||
|
||||
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
|
||||
pi.items[0].project = item_project.project_name
|
||||
pi.project = project.project_name
|
||||
pi.items[0].project = item_project.name
|
||||
pi.project = project.name
|
||||
|
||||
pi.submit()
|
||||
|
||||
expected_values = {
|
||||
"Creditors - _TC": {
|
||||
"project": project.project_name
|
||||
"project": project.name
|
||||
},
|
||||
"_Test Account Cost for Goods Sold - _TC": {
|
||||
"project": item_project.project_name
|
||||
"project": item_project.name
|
||||
}
|
||||
}
|
||||
|
||||
@ -1026,7 +1031,7 @@ def make_purchase_invoice_against_cost_center(**args):
|
||||
pi.is_return = args.is_return
|
||||
pi.credit_to = args.return_against or "Creditors - _TC"
|
||||
pi.is_subcontracted = args.is_subcontracted or "No"
|
||||
if args.supplier_warehouse:
|
||||
if args.supplier_warehouse:
|
||||
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||
|
||||
pi.append("items", {
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-05-22 12:43:10",
|
||||
"doctype": "DocType",
|
||||
@ -87,6 +88,7 @@
|
||||
"po_detail",
|
||||
"purchase_receipt",
|
||||
"pr_detail",
|
||||
"sales_invoice_item",
|
||||
"item_weight_details",
|
||||
"weight_per_unit",
|
||||
"total_weight",
|
||||
@ -553,8 +555,8 @@
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Brand",
|
||||
"print_hide": 1,
|
||||
"options": "Brand"
|
||||
"options": "Brand",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_group",
|
||||
@ -562,9 +564,9 @@
|
||||
"fieldname": "item_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Item Group",
|
||||
"options": "Item Group",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"options": "Item Group"
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges",
|
||||
@ -759,10 +761,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
|
||||
"fieldname": "from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Supplier Warehouse",
|
||||
"label": "From Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@ -779,11 +782,20 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Sales Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2020-08-20 11:48:01.398356",
|
||||
"links": [],
|
||||
"modified": "2020-12-26 17:20:36.415791",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
@ -791,4 +803,4 @@
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
||||
erpnext.setup_einvoice_actions('Sales Invoice')
|
||||
|
||||
frappe.ui.form.on("Sales Invoice", {
|
||||
setup: function(frm) {
|
||||
|
@ -5,14 +5,17 @@
|
||||
cur_frm.pformat.print_heading = 'Invoice';
|
||||
|
||||
{% include 'erpnext/selling/sales_common.js' %};
|
||||
|
||||
|
||||
frappe.provide("erpnext.accounts");
|
||||
|
||||
|
||||
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
|
||||
setup: function(doc) {
|
||||
this.setup_posting_date_time_check();
|
||||
this._super(doc);
|
||||
},
|
||||
company: function() {
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
},
|
||||
onload: function() {
|
||||
var me = this;
|
||||
this._super();
|
||||
@ -33,6 +36,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
erpnext.queries.setup_warehouse_query(this.frm);
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(doc, dt, dn) {
|
||||
@ -126,16 +130,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
|
||||
this.set_default_print_format();
|
||||
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
|
||||
frappe.model.with_doc("Customer", me.frm.doc.customer, function() {
|
||||
var customer = frappe.model.get_doc("Customer", me.frm.doc.customer);
|
||||
var internal = customer.is_internal_customer;
|
||||
var disabled = customer.disabled;
|
||||
if (internal == 1 && disabled == 0) {
|
||||
me.frm.add_custom_button("Inter Company Invoice", function() {
|
||||
me.make_inter_company_invoice();
|
||||
}, __('Create'));
|
||||
}
|
||||
});
|
||||
let internal = me.frm.doc.is_internal_customer;
|
||||
if (internal) {
|
||||
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Invoice" :
|
||||
"Inter Company Purchase Invoice";
|
||||
|
||||
me.frm.add_custom_button(button_label, function() {
|
||||
me.make_inter_company_invoice();
|
||||
}, __('Create'));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -571,15 +574,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("unrealized_profit_loss_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
@ -592,7 +586,7 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Sales Return',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
'Payment Request': 'Payment Request',
|
||||
'Payment Entry': 'Payment'
|
||||
},
|
||||
|
@ -60,6 +60,8 @@
|
||||
"ignore_pricing_rule",
|
||||
"sec_warehouse",
|
||||
"set_warehouse",
|
||||
"column_break_55",
|
||||
"set_target_warehouse",
|
||||
"items_section",
|
||||
"update_stock",
|
||||
"scan_barcode",
|
||||
@ -1969,13 +1971,24 @@
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_55",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.is_internal_customer && doc.update_stock",
|
||||
"fieldname": "set_target_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Set Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-11 12:48:31.769958",
|
||||
"modified": "2020-12-25 22:57:32.555067",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -4,9 +4,9 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
import frappe.defaults
|
||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
|
||||
from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
|
||||
from frappe import _, msgprint, throw
|
||||
from erpnext.accounts.party import get_party_account, get_due_date
|
||||
from erpnext.accounts.party import get_party_account, get_due_date, get_party_details
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
@ -21,6 +21,8 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
||||
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
|
||||
from erpnext.healthcare.utils import manage_invoice_submit_cancel
|
||||
|
||||
@ -179,7 +181,10 @@ class SalesInvoice(SellingController):
|
||||
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
@ -232,9 +237,9 @@ class SalesInvoice(SellingController):
|
||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||
|
||||
def before_cancel(self):
|
||||
super(SalesInvoice, self).before_cancel()
|
||||
self.update_time_sheet(None)
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
super(SalesInvoice, self).on_cancel()
|
||||
|
||||
@ -261,10 +266,10 @@ class SalesInvoice(SellingController):
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
|
||||
@ -549,7 +554,12 @@ class SalesInvoice(SellingController):
|
||||
self.against_income_account = ','.join(against_acc)
|
||||
|
||||
def add_remarks(self):
|
||||
if not self.remarks: self.remarks = 'No Remarks'
|
||||
if not self.remarks:
|
||||
if self.po_no and self.po_date:
|
||||
self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
|
||||
formatdate(self.po_date))
|
||||
else:
|
||||
self.remarks = _("No Remarks")
|
||||
|
||||
def validate_auto_set_posting_time(self):
|
||||
# Don't auto set the posting date and time if invoice is amended
|
||||
@ -1529,7 +1539,7 @@ def validate_inter_company_transaction(doc, doctype):
|
||||
details = get_inter_company_details(doc, doctype)
|
||||
price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
|
||||
valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
|
||||
if not valid_price_list:
|
||||
if not valid_price_list and not doc.is_internal_transfer():
|
||||
frappe.throw(_("Selected Price List should have buying and selling fields checked."))
|
||||
|
||||
party = details.get("party")
|
||||
@ -1552,6 +1562,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||
source_doc = frappe.get_doc(doctype, source_name)
|
||||
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
|
||||
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
|
||||
source_document_warehouse_field = 'target_warehouse'
|
||||
target_document_warehouse_field = 'from_warehouse'
|
||||
else:
|
||||
@ -1565,6 +1576,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
set_purchase_references(target)
|
||||
|
||||
def update_details(source_doc, target_doc, source_parent):
|
||||
target_doc.inter_company_invoice_reference = source_doc.name
|
||||
@ -1572,19 +1584,38 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
|
||||
target_doc.company = details.get("company")
|
||||
target_doc.supplier = details.get("party")
|
||||
target_doc.is_internal_supplier = 1
|
||||
target_doc.ignore_pricing_rule = 1
|
||||
target_doc.buying_price_list = source_doc.selling_price_list
|
||||
|
||||
# Invert Addresses
|
||||
update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
|
||||
update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
|
||||
|
||||
if currency:
|
||||
target_doc.currency = currency
|
||||
|
||||
update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
|
||||
doctype=target_doc.doctype, party_address=target_doc.supplier_address,
|
||||
company_address=target_doc.shipping_address)
|
||||
|
||||
else:
|
||||
currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
|
||||
target_doc.company = details.get("company")
|
||||
target_doc.customer = details.get("party")
|
||||
target_doc.selling_price_list = source_doc.buying_price_list
|
||||
|
||||
update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
|
||||
update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
|
||||
update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
|
||||
|
||||
if currency:
|
||||
target_doc.currency = currency
|
||||
|
||||
update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
|
||||
doctype=target_doc.doctype, party_address=target_doc.customer_address,
|
||||
company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
|
||||
|
||||
item_field_map = {
|
||||
"doctype": target_doctype + " Item",
|
||||
"field_no_map": [
|
||||
@ -1592,25 +1623,33 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
"expense_account",
|
||||
"cost_center",
|
||||
"warehouse"
|
||||
]
|
||||
],
|
||||
"field_map": {
|
||||
'rate': 'rate',
|
||||
}
|
||||
}
|
||||
|
||||
if source_doc.get('update_stock'):
|
||||
item_field_map.update({
|
||||
'field_map': {
|
||||
source_document_warehouse_field: target_document_warehouse_field,
|
||||
'batch_no': 'batch_no',
|
||||
'serial_no': 'serial_no'
|
||||
}
|
||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||
item_field_map["field_map"].update({
|
||||
"name": target_detail_field,
|
||||
})
|
||||
|
||||
if source_doc.get('update_stock'):
|
||||
item_field_map["field_map"].update({
|
||||
source_document_warehouse_field: target_document_warehouse_field,
|
||||
'batch_no': 'batch_no',
|
||||
'serial_no': 'serial_no'
|
||||
})
|
||||
|
||||
doclist = get_mapped_doc(doctype, source_name, {
|
||||
doctype: {
|
||||
"doctype": target_doctype,
|
||||
"postprocess": update_details,
|
||||
"set_target_warehouse": "set_from_warehouse",
|
||||
"field_no_map": [
|
||||
"taxes_and_charges"
|
||||
"taxes_and_charges",
|
||||
"set_warehouse",
|
||||
"shipping_address"
|
||||
]
|
||||
},
|
||||
doctype +" Item": item_field_map
|
||||
@ -1619,6 +1658,110 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
|
||||
return doclist
|
||||
|
||||
def set_purchase_references(doc):
|
||||
# add internal PO or PR links if any
|
||||
if doc.is_internal_transfer():
|
||||
if doc.doctype == 'Purchase Receipt':
|
||||
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
|
||||
|
||||
if so_item_map:
|
||||
pd_item_map, parent_child_map, warehouse_map = \
|
||||
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
|
||||
|
||||
update_pr_items(doc, so_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||
|
||||
elif doc.doctype == 'Purchase Invoice':
|
||||
dn_item_map, so_item_map = get_sales_invoice_details(doc.inter_company_invoice_reference)
|
||||
# First check for Purchase receipt
|
||||
if list(dn_item_map.values()):
|
||||
pd_item_map, parent_child_map, warehouse_map = \
|
||||
get_pd_details('Purchase Receipt Item', dn_item_map, 'delivery_note_item')
|
||||
|
||||
update_pi_items(doc, 'pr_detail', 'purchase_receipt',
|
||||
dn_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||
|
||||
if list(so_item_map.values()):
|
||||
pd_item_map, parent_child_map, warehouse_map = \
|
||||
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
|
||||
|
||||
update_pi_items(doc, 'po_detail', 'purchase_order',
|
||||
so_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||
|
||||
def update_pi_items(doc, detail_field, parent_field, sales_item_map,
|
||||
purchase_item_map, parent_child_map, warehouse_map):
|
||||
for item in doc.get('items'):
|
||||
item.set(detail_field, purchase_item_map.get(sales_item_map.get(item.sales_invoice_item)))
|
||||
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
|
||||
if doc.update_stock:
|
||||
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
|
||||
|
||||
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
|
||||
for item in doc.get('items'):
|
||||
item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
|
||||
def get_delivery_note_details(internal_reference):
|
||||
so_item_map = {}
|
||||
|
||||
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
|
||||
filters={'parent': internal_reference})
|
||||
|
||||
for d in si_item_details:
|
||||
so_item_map.setdefault(d.name, d.so_detail)
|
||||
|
||||
return so_item_map
|
||||
|
||||
def get_sales_invoice_details(internal_reference):
|
||||
dn_item_map = {}
|
||||
so_item_map = {}
|
||||
|
||||
si_item_details = frappe.get_all('Sales Invoice Item', fields=['name', 'so_detail',
|
||||
'dn_detail'], filters={'parent': internal_reference})
|
||||
|
||||
for d in si_item_details:
|
||||
if d.dn_detail:
|
||||
dn_item_map.setdefault(d.name, d.dn_detail)
|
||||
if d.so_detail:
|
||||
so_item_map.setdefault(d.name, d.so_detail)
|
||||
|
||||
return dn_item_map, so_item_map
|
||||
|
||||
def get_pd_details(doctype, sd_detail_map, sd_detail_field):
|
||||
pd_item_map = {}
|
||||
accepted_warehouse_map = {}
|
||||
parent_child_map = {}
|
||||
|
||||
pd_item_details = frappe.get_all(doctype,
|
||||
fields=[sd_detail_field, 'name', 'warehouse', 'parent'], filters={sd_detail_field: ('in', list(sd_detail_map.values()))})
|
||||
|
||||
for d in pd_item_details:
|
||||
pd_item_map.setdefault(d.get(sd_detail_field), d.name)
|
||||
parent_child_map.setdefault(d.get(sd_detail_field), d.parent)
|
||||
accepted_warehouse_map.setdefault(d.get(sd_detail_field), d.warehouse)
|
||||
|
||||
return pd_item_map, parent_child_map, accepted_warehouse_map
|
||||
|
||||
def update_taxes(doc, party=None, party_type=None, company=None, doctype=None, party_address=None,
|
||||
company_address=None, shipping_address_name=None, master_doctype=None):
|
||||
# Update Party Details
|
||||
party_details = get_party_details(party=party, party_type=party_type, company=company,
|
||||
doctype=doctype, party_address=party_address, company_address=company_address,
|
||||
shipping_address=shipping_address_name)
|
||||
|
||||
# Update taxes and charges if any
|
||||
doc.taxes_and_charges = party_details.get('taxes_and_charges')
|
||||
doc.set('taxes', party_details.get('taxes'))
|
||||
|
||||
def update_address(doc, address_field, address_display_field, address_name):
|
||||
doc.set(address_field, address_name)
|
||||
fetch_values = get_fetch_values(doc.doctype, address_field, address_name)
|
||||
|
||||
for key, value in fetch_values.items():
|
||||
doc.set(key, value)
|
||||
|
||||
doc.set(address_display_field, get_address_display(doc.get(address_field)))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_programs(customer):
|
||||
''' sets applicable loyalty program to the customer or returns a list of applicable programs '''
|
||||
@ -1694,6 +1837,7 @@ def get_mode_of_payment_info(mode_of_payment, company):
|
||||
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
|
||||
(company, mode_of_payment), as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_dunning(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
|
||||
|
@ -22,6 +22,7 @@ from erpnext.regional.india.utils import get_ewb_data
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def make(self):
|
||||
@ -688,7 +689,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertTrue(gle)
|
||||
|
||||
def test_pos_gl_entry_with_perpetual_inventory(self):
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
||||
@ -745,7 +746,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(pos_return.get('payments')[0].amount, -1000)
|
||||
|
||||
def test_pos_change_amount(self):
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||
@ -1573,17 +1574,17 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
})
|
||||
|
||||
sales_invoice = create_sales_invoice(do_not_save=1)
|
||||
sales_invoice.items[0].project = item_project.project_name
|
||||
sales_invoice.project = project.project_name
|
||||
sales_invoice.items[0].project = item_project.name
|
||||
sales_invoice.project = project.name
|
||||
|
||||
sales_invoice.submit()
|
||||
|
||||
expected_values = {
|
||||
"Debtors - _TC": {
|
||||
"project": project.project_name
|
||||
"project": project.name
|
||||
},
|
||||
"Sales - _TC": {
|
||||
"project": item_project.project_name
|
||||
"project": item_project.name
|
||||
}
|
||||
}
|
||||
|
||||
@ -1770,148 +1771,85 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(target_doc.company, "_Test Company 1")
|
||||
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
||||
|
||||
# def test_internal_transfer_gl_entry(self):
|
||||
# ## Create internal transfer account
|
||||
# account = create_account(account_name="Unrealized Profit",
|
||||
# parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
|
||||
def test_internal_transfer_gl_entry(self):
|
||||
## Create internal transfer account
|
||||
account = create_account(account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
|
||||
|
||||
# frappe.db.set_value('Company', '_Test Company with perpetual inventory',
|
||||
# 'unrealized_profit_loss_account', account)
|
||||
frappe.db.set_value('Company', '_Test Company with perpetual inventory',
|
||||
'unrealized_profit_loss_account', account)
|
||||
|
||||
# customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
|
||||
# "_Test Company with perpetual inventory")
|
||||
customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
|
||||
"_Test Company with perpetual inventory")
|
||||
|
||||
# create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
|
||||
# "_Test Company with perpetual inventory")
|
||||
create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
|
||||
"_Test Company with perpetual inventory")
|
||||
|
||||
# si = create_sales_invoice(
|
||||
# company = "_Test Company with perpetual inventory",
|
||||
# customer = customer,
|
||||
# debit_to = "Debtors - TCP1",
|
||||
# warehouse = "Stores - TCP1",
|
||||
# income_account = "Sales - TCP1",
|
||||
# expense_account = "Cost of Goods Sold - TCP1",
|
||||
# cost_center = "Main - TCP1",
|
||||
# currency = "INR",
|
||||
# do_not_save = 1
|
||||
# )
|
||||
si = create_sales_invoice(
|
||||
company = "_Test Company with perpetual inventory",
|
||||
customer = customer,
|
||||
debit_to = "Debtors - TCP1",
|
||||
warehouse = "Stores - TCP1",
|
||||
income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1",
|
||||
cost_center = "Main - TCP1",
|
||||
currency = "INR",
|
||||
do_not_save = 1
|
||||
)
|
||||
|
||||
# si.selling_price_list = "_Test Price List Rest of the World"
|
||||
# si.update_stock = 1
|
||||
# si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
||||
# add_taxes(si)
|
||||
# si.save()
|
||||
# si.submit()
|
||||
si.selling_price_list = "_Test Price List Rest of the World"
|
||||
si.update_stock = 1
|
||||
si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
||||
add_taxes(si)
|
||||
si.save()
|
||||
|
||||
# target_doc = make_inter_company_transaction("Sales Invoice", si.name)
|
||||
# target_doc.company = '_Test Company with perpetual inventory'
|
||||
# target_doc.items[0].warehouse = 'Finished Goods - TCP1'
|
||||
# add_taxes(target_doc)
|
||||
# target_doc.save()
|
||||
# target_doc.submit()
|
||||
rate = 0.0
|
||||
for d in si.get('items'):
|
||||
rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": si.posting_date,
|
||||
"posting_time": si.posting_time,
|
||||
"qty": -1 * flt(d.get('stock_qty')),
|
||||
"serial_no": d.serial_no,
|
||||
"company": si.company,
|
||||
"voucher_type": 'Sales Invoice',
|
||||
"voucher_no": si.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||
}, raise_error_if_no_rate=False)
|
||||
|
||||
# si_gl_entries = [
|
||||
# ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
|
||||
# ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
|
||||
# ]
|
||||
rate = flt(rate, 2)
|
||||
|
||||
# check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
|
||||
si.submit()
|
||||
|
||||
# pi_gl_entries = [
|
||||
# ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
|
||||
# ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
|
||||
# ]
|
||||
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
|
||||
target_doc.company = '_Test Company with perpetual inventory'
|
||||
target_doc.items[0].warehouse = 'Finished Goods - TCP1'
|
||||
add_taxes(target_doc)
|
||||
target_doc.save()
|
||||
target_doc.submit()
|
||||
|
||||
# check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
||||
tax_amount = flt(rate * (12/100), 2)
|
||||
si_gl_entries = [
|
||||
["_Test Account Excise Duty - TCP1", 0.0, tax_amount, nowdate()],
|
||||
["Unrealized Profit - TCP1", tax_amount, 0.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
|
||||
|
||||
pi_gl_entries = [
|
||||
["_Test Account Excise Duty - TCP1", tax_amount , 0.0, nowdate()],
|
||||
["Unrealized Profit - TCP1", 0.0, tax_amount, nowdate()]
|
||||
]
|
||||
|
||||
# Sale and Purchase both should be at valuation rate
|
||||
self.assertEqual(si.items[0].rate, rate)
|
||||
self.assertEqual(target_doc.items[0].rate, rate)
|
||||
|
||||
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
||||
|
||||
def test_eway_bill_json(self):
|
||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||
address = frappe.get_doc({
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_title": "_Test Address for Eway bill",
|
||||
"address_type": "Billing",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 1,
|
||||
"phone": "+91 0000000000",
|
||||
"gstin": "27AAECE4835E1ZR",
|
||||
"gst_state": "Maharashtra",
|
||||
"gst_state_number": "27",
|
||||
"pincode": "401108"
|
||||
}).insert()
|
||||
|
||||
address.append("links", {
|
||||
"link_doctype": "Company",
|
||||
"link_name": "_Test Company"
|
||||
})
|
||||
|
||||
address.save()
|
||||
|
||||
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
|
||||
address = frappe.get_doc({
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_title": "_Test Customer-Address for Eway bill",
|
||||
"address_type": "Shipping",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 1,
|
||||
"phone": "+91 0000000000",
|
||||
"gst_state": "Maharashtra",
|
||||
"gst_state_number": "27",
|
||||
"pincode": "410038"
|
||||
}).insert()
|
||||
|
||||
address.append("links", {
|
||||
"link_doctype": "Customer",
|
||||
"link_name": "_Test Customer"
|
||||
})
|
||||
|
||||
address.save()
|
||||
|
||||
gst_settings = frappe.get_doc("GST Settings")
|
||||
|
||||
gst_account = frappe.get_all(
|
||||
"GST Account",
|
||||
fields=["cgst_account", "sgst_account", "igst_account"],
|
||||
filters = {"company": "_Test Company"})
|
||||
|
||||
if not gst_account:
|
||||
gst_settings.append("gst_accounts", {
|
||||
"company": "_Test Company",
|
||||
"cgst_account": "CGST - _TC",
|
||||
"sgst_account": "SGST - _TC",
|
||||
"igst_account": "IGST - _TC",
|
||||
})
|
||||
|
||||
gst_settings.save()
|
||||
|
||||
si = create_sales_invoice(do_not_save =1, rate = '60000')
|
||||
|
||||
si.distance = 2000
|
||||
si.company_address = "_Test Address for Eway bill-Billing"
|
||||
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
||||
si.vehicle_no = "KA12KA1234"
|
||||
si.gst_category = "Registered Regular"
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "CGST - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "CGST @ 9.0",
|
||||
"rate": 9
|
||||
})
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "SGST - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "SGST @ 9.0",
|
||||
"rate": 9
|
||||
})
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
|
||||
si.submit()
|
||||
|
||||
@ -1928,6 +1866,197 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
||||
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||
country = frappe.flags.country
|
||||
frappe.flags.country = 'India'
|
||||
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
self.assertRaises(frappe.ValidationError, si.submit)
|
||||
|
||||
si.irn = 'test_irn'
|
||||
si.submit()
|
||||
|
||||
# reset
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
si.naming_series = 'INV-2020-.#####'
|
||||
si.items = []
|
||||
si.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"uom": "Nos",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 2000,
|
||||
"rate": 12,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
})
|
||||
si.append("items", {
|
||||
"item_code": "_Test Item 2",
|
||||
"uom": "Nos",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 420,
|
||||
"rate": 15,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
})
|
||||
si.discount_amount = 100
|
||||
si.save()
|
||||
|
||||
einvoice = make_einvoice(si)
|
||||
|
||||
total_item_ass_value = 0
|
||||
total_item_cgst_value = 0
|
||||
total_item_sgst_value = 0
|
||||
total_item_igst_value = 0
|
||||
total_item_value = 0
|
||||
|
||||
for item in einvoice['ItemList']:
|
||||
total_item_ass_value += item['AssAmt']
|
||||
total_item_cgst_value += item['CgstAmt']
|
||||
total_item_sgst_value += item['SgstAmt']
|
||||
total_item_igst_value += item['IgstAmt']
|
||||
total_item_value += item['TotItemVal']
|
||||
|
||||
self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
|
||||
self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
|
||||
|
||||
value_details = einvoice['ValDtls']
|
||||
|
||||
self.assertEqual(einvoice['Version'], '1.1')
|
||||
self.assertEqual(value_details['AssVal'], total_item_ass_value)
|
||||
self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
|
||||
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
||||
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
||||
|
||||
calculated_invoice_value = \
|
||||
value_details['AssVal'] + value_details['CgstVal'] \
|
||||
+ value_details['SgstVal'] + value_details['IgstVal'] \
|
||||
+ value_details['OthChrg'] - value_details['Discount']
|
||||
|
||||
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
|
||||
|
||||
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
||||
self.assertTrue(einvoice['EwbDtls'])
|
||||
|
||||
def make_test_address_for_ewaybill():
|
||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||
address = frappe.get_doc({
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_title": "_Test Address for Eway bill",
|
||||
"address_type": "Billing",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 1,
|
||||
"phone": "+910000000000",
|
||||
"gstin": "27AAECE4835E1ZR",
|
||||
"gst_state": "Maharashtra",
|
||||
"gst_state_number": "27",
|
||||
"pincode": "401108"
|
||||
}).insert()
|
||||
|
||||
address.append("links", {
|
||||
"link_doctype": "Company",
|
||||
"link_name": "_Test Company"
|
||||
})
|
||||
|
||||
address.save()
|
||||
|
||||
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
|
||||
address = frappe.get_doc({
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_title": "_Test Customer-Address for Eway bill",
|
||||
"address_type": "Shipping",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 1,
|
||||
"phone": "+910000000000",
|
||||
"gstin": "27AACCM7806M1Z3",
|
||||
"gst_state": "Maharashtra",
|
||||
"gst_state_number": "27",
|
||||
"pincode": "410038"
|
||||
}).insert()
|
||||
|
||||
address.append("links", {
|
||||
"link_doctype": "Customer",
|
||||
"link_name": "_Test Customer"
|
||||
})
|
||||
|
||||
address.save()
|
||||
|
||||
def make_test_transporter_for_ewaybill():
|
||||
if not frappe.db.exists('Supplier', '_Test Transporter'):
|
||||
frappe.get_doc({
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Transporter",
|
||||
"country": "India",
|
||||
"supplier_group": "_Test Supplier Group",
|
||||
"supplier_type": "Company",
|
||||
"is_transporter": 1
|
||||
}).insert()
|
||||
|
||||
def make_sales_invoice_for_ewaybill():
|
||||
make_test_address_for_ewaybill()
|
||||
make_test_transporter_for_ewaybill()
|
||||
|
||||
gst_settings = frappe.get_doc("GST Settings")
|
||||
|
||||
gst_account = frappe.get_all(
|
||||
"GST Account",
|
||||
fields=["cgst_account", "sgst_account", "igst_account"],
|
||||
filters = {"company": "_Test Company"}
|
||||
)
|
||||
|
||||
if not gst_account:
|
||||
gst_settings.append("gst_accounts", {
|
||||
"company": "_Test Company",
|
||||
"cgst_account": "CGST - _TC",
|
||||
"sgst_account": "SGST - _TC",
|
||||
"igst_account": "IGST - _TC",
|
||||
})
|
||||
|
||||
gst_settings.save()
|
||||
|
||||
si = create_sales_invoice(do_not_save=1, rate='60000')
|
||||
|
||||
si.distance = 2000
|
||||
si.company_address = "_Test Address for Eway bill-Billing"
|
||||
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
||||
si.vehicle_no = "KA12KA1234"
|
||||
si.gst_category = "Registered Regular"
|
||||
si.mode_of_transport = 'Road'
|
||||
si.transporter = '_Test Transporter'
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "CGST - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "CGST @ 9.0",
|
||||
"rate": 9
|
||||
})
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "SGST - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "SGST @ 9.0",
|
||||
"rate": 9
|
||||
})
|
||||
|
||||
return si
|
||||
|
||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
||||
from `tabGL Entry`
|
||||
|
@ -565,11 +565,12 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: parent.is_internal_customer && parent.update_stock",
|
||||
"fieldname": "target_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Customer Warehouse (Optional)",
|
||||
"label": "Target Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1
|
||||
@ -815,7 +816,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-23 19:59:04.879322",
|
||||
"modified": "2020-12-26 17:25:04.090630",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
@ -34,6 +34,9 @@ def valdiate_taxes_and_charges_template(doc):
|
||||
|
||||
validate_disabled(doc)
|
||||
|
||||
# Validate with existing taxes and charges template for unique tax category
|
||||
validate_for_tax_category(doc)
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, doc)
|
||||
@ -41,3 +44,7 @@ def valdiate_taxes_and_charges_template(doc):
|
||||
def validate_disabled(doc):
|
||||
if doc.is_default and doc.disabled:
|
||||
frappe.throw(_("Disabled template must not be default template"))
|
||||
|
||||
def validate_for_tax_category(doc):
|
||||
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
|
||||
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
|
||||
|
@ -1,16 +1,18 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on('Shipping Rule', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
frappe.provide('erpnext.accounts.dimensions');
|
||||
|
||||
frappe.ui.form.on('Shipping Rule', {
|
||||
onload: function(frm) {
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.set_query("account", function() {
|
||||
return {
|
||||
filters: {
|
||||
|
@ -446,7 +446,7 @@ class Subscription(Document):
|
||||
if not self.generate_invoice_at_period_start:
|
||||
return False
|
||||
|
||||
if self.is_new_subscription():
|
||||
if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
|
||||
return True
|
||||
|
||||
# Check invoice dates and make sure it doesn't have outstanding invoices
|
||||
|
@ -5,15 +5,11 @@ from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_stock_and_account_balance
|
||||
from frappe.model.meta import get_field_precision
|
||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||
|
||||
|
||||
class ClosedAccountingPeriod(frappe.ValidationError): pass
|
||||
class StockAccountInvalidTransaction(frappe.ValidationError): pass
|
||||
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
|
||||
|
||||
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
|
||||
if gl_map:
|
||||
@ -131,10 +127,6 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
|
||||
for entry in gl_map:
|
||||
make_entry(entry, adv_adj, update_outstanding, from_repost)
|
||||
|
||||
if not from_repost:
|
||||
validate_account_for_perpetual_inventory(gl_map)
|
||||
|
||||
|
||||
def make_entry(args, adv_adj, update_outstanding, from_repost=False):
|
||||
gle = frappe.new_doc("GL Entry")
|
||||
gle.update(args)
|
||||
@ -144,63 +136,9 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
|
||||
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
|
||||
gle.submit()
|
||||
|
||||
# check against budget
|
||||
if not from_repost:
|
||||
validate_expense_against_budget(args)
|
||||
|
||||
def validate_account_for_perpetual_inventory(gl_map):
|
||||
if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
|
||||
account_list = [gl_entries.account for gl_entries in gl_map]
|
||||
|
||||
aii_accounts = [d.name for d in frappe.get_all("Account",
|
||||
filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})]
|
||||
|
||||
for account in account_list:
|
||||
if account not in aii_accounts:
|
||||
continue
|
||||
|
||||
# Always use current date to get stock and account balance as there can future entries for
|
||||
# other items
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
|
||||
gl_map[0].posting_date, gl_map[0].company)
|
||||
|
||||
if gl_map[0].voucher_type=="Journal Entry":
|
||||
# In case of Journal Entry, there are no corresponding SL entries,
|
||||
# hence deducting currency amount
|
||||
account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit)
|
||||
if account_bal == stock_bal:
|
||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||
.format(account), StockAccountInvalidTransaction)
|
||||
|
||||
elif abs(account_bal - stock_bal) > 0.1:
|
||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
||||
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
|
||||
|
||||
diff = flt(stock_bal - account_bal, precision)
|
||||
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses on {3}.").format(
|
||||
stock_bal, account_bal, frappe.bold(account), gl_map[0].posting_date)
|
||||
error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
|
||||
stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
|
||||
|
||||
db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
|
||||
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
|
||||
|
||||
journal_entry_args = {
|
||||
'accounts':[
|
||||
{'account': account, db_or_cr_warehouse_account : abs(diff)},
|
||||
{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff)}
|
||||
]
|
||||
}
|
||||
|
||||
frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
|
||||
raise_exception=StockValueAndAccountBalanceOutOfSync,
|
||||
title=_('Values Out Of Sync'),
|
||||
primary_action={
|
||||
'label': _('Make Journal Entry'),
|
||||
'client_action': 'erpnext.route_to_adjustment_jv',
|
||||
'args': journal_entry_args
|
||||
})
|
||||
|
||||
def validate_cwip_accounts(gl_map):
|
||||
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
|
||||
|
||||
|
162
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal file
162
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal file
@ -0,0 +1,162 @@
|
||||
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
|
||||
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
||||
|
||||
<div class="page-break">
|
||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||
{% if letter_head and not no_letterhead %}
|
||||
<div class="letter-head">{{ letter_head }}</div>
|
||||
{% endif %}
|
||||
<div class="print-heading">
|
||||
<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
|
||||
</div>
|
||||
</div>
|
||||
{% if print_settings.repeat_header_footer %}
|
||||
<div id="footer-html" class="visible-pdf">
|
||||
{% if not no_letterhead and footer %}
|
||||
<div class="letter-head-footer">
|
||||
{{ footer }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-center small page-number visible-pdf">
|
||||
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||
<h5 class="font-bold" style="margin-left: 15px; margin-top: 0px;">1. Transaction Details</h5>
|
||||
<div class="col-xs-8 column-break">
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>IRN</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Ack. No</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Ack. Date</label></div>
|
||||
<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Category</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Document Type</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Document No</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-4 column-break">
|
||||
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||
<h5 class="font-bold" style="margin-left: 15px; margin-bottom: 0px;">2. Party Details</h5>
|
||||
{%- set seller = einvoice.SellerDtls -%}
|
||||
<div class="col-xs-6 column-break">
|
||||
<h5 style="margin-bottom: 5px;">Seller</h5>
|
||||
<p>{{ seller.Gstin }}</p>
|
||||
<p>{{ seller.LglNm }}</p>
|
||||
<p>{{ seller.Addr1 }}</p>
|
||||
{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
|
||||
<p>{{ seller.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
|
||||
|
||||
{%- if einvoice.ShipDtls -%}
|
||||
{%- set shipping = einvoice.ShipDtls -%}
|
||||
<h5 style="margin-bottom: 5px;">Shipping</h5>
|
||||
<p>{{ shipping.Gstin }}</p>
|
||||
<p>{{ shipping.LglNm }}</p>
|
||||
<p>{{ shipping.Addr1 }}</p>
|
||||
{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
|
||||
<p>{{ shipping.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- set buyer = einvoice.BuyerDtls -%}
|
||||
<div class="col-xs-6 column-break">
|
||||
<h5 style="margin-bottom: 5px;">Buyer</h5>
|
||||
<p>{{ buyer.Gstin }}</p>
|
||||
<p>{{ buyer.LglNm }}</p>
|
||||
<p>{{ buyer.Addr1 }}</p>
|
||||
{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
|
||||
<p>{{ buyer.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<h5 class="font-bold" style="margin-bottom: 0px;">3. Item Details</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left" style="width: 3%;">Sr. No.</th>
|
||||
<th class="text-left">Item</th>
|
||||
<th class="text-left" style="width: 10%;">HSN Code</th>
|
||||
<th class="text-left" style="width: 5%;">Qty</th>
|
||||
<th class="text-left" style="width: 5%;">UOM</th>
|
||||
<th class="text-left">Rate</th>
|
||||
<th class="text-left" style="width: 5%;">Discount</th>
|
||||
<th class="text-left">Taxable Amount</th>
|
||||
<th class="text-left" style="width: 7%;">Tax Rate</th>
|
||||
<th class="text-left" style="width: 5%;">Other Charges</th>
|
||||
<th class="text-left">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in einvoice.ItemList %}
|
||||
<tr>
|
||||
<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
|
||||
<td class="text-left">{{ item.PrdDesc }}</td>
|
||||
<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
|
||||
<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
|
||||
<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
|
||||
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
|
||||
<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
|
||||
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Taxable Amount</th>
|
||||
<th class="text-left">CGST</th>
|
||||
<th class="text-left"">SGST</th>
|
||||
<th class="text-left">IGST</th>
|
||||
<th class="text-left">CESS</th>
|
||||
<th class="text-left" style="width: 10%;">State CESS</th>
|
||||
<th class="text-left">Discount</th>
|
||||
<th class="text-left" style="width: 10%;">Other Charges</th>
|
||||
<th class="text-left" style="width: 10%;">Round Off</th>
|
||||
<th class="text-left">Total Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- set value_details = einvoice.ValDtls -%}
|
||||
<tr>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"align_labels_right": 1,
|
||||
"creation": "2020-10-10 18:01:21.032914",
|
||||
"custom_format": 0,
|
||||
"default_print_language": "en-US",
|
||||
"disabled": 1,
|
||||
"doc_type": "Sales Invoice",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font": "Default",
|
||||
"html": "",
|
||||
"idx": 0,
|
||||
"line_breaks": 1,
|
||||
"modified": "2020-10-23 19:54:40.634936",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST E-Invoice",
|
||||
"owner": "Administrator",
|
||||
"print_format_builder": 0,
|
||||
"print_format_type": "Jinja",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 1,
|
||||
"standard": "Yes"
|
||||
}
|
@ -47,21 +47,22 @@ def get_data(filters):
|
||||
|
||||
for d in gl_entries:
|
||||
asset_data = assets_details.get(d.against_voucher)
|
||||
if not asset_data.get("accumulated_depreciation_amount"):
|
||||
asset_data.accumulated_depreciation_amount = d.debit
|
||||
else:
|
||||
asset_data.accumulated_depreciation_amount += d.debit
|
||||
if asset_data:
|
||||
if not asset_data.get("accumulated_depreciation_amount"):
|
||||
asset_data.accumulated_depreciation_amount = d.debit
|
||||
else:
|
||||
asset_data.accumulated_depreciation_amount += d.debit
|
||||
|
||||
row = frappe._dict(asset_data)
|
||||
row.update({
|
||||
"depreciation_amount": d.debit,
|
||||
"depreciation_date": d.posting_date,
|
||||
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
|
||||
flt(row.accumulated_depreciation_amount)),
|
||||
"depreciation_entry": d.voucher_no
|
||||
})
|
||||
row = frappe._dict(asset_data)
|
||||
row.update({
|
||||
"depreciation_amount": d.debit,
|
||||
"depreciation_date": d.posting_date,
|
||||
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
|
||||
flt(row.accumulated_depreciation_amount)),
|
||||
"depreciation_entry": d.voucher_no
|
||||
})
|
||||
|
||||
data.append(row)
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
@ -49,12 +49,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
elif d.po_detail:
|
||||
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
|
||||
|
||||
expense_account = d.expense_account or aii_account_map.get(d.company)
|
||||
expense_account = d.unrealized_profit_loss_account or d.expense_account \
|
||||
or aii_account_map.get(d.company)
|
||||
|
||||
row = {
|
||||
'item_code': d.item_code,
|
||||
'item_name': item_record.item_name,
|
||||
'item_group': item_record.item_group,
|
||||
'item_name': item_record.item_name if item_record else d.item_name,
|
||||
'item_group': item_record.item_group if item_record else d.item_group,
|
||||
'description': d.description,
|
||||
'invoice': d.parent,
|
||||
'posting_date': d.posting_date,
|
||||
@ -315,7 +316,9 @@ def get_items(filters, additional_query_columns):
|
||||
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
|
||||
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
|
||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||
`tabPurchase Invoice`.unrealized_profit_loss_account,
|
||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
|
||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
||||
|
@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
'company': d.company,
|
||||
'sales_order': d.sales_order,
|
||||
'delivery_note': d.delivery_note,
|
||||
'income_account': d.income_account,
|
||||
'income_account': d.unrealized_profit_loss_account or d.income_account,
|
||||
'cost_center': d.cost_center,
|
||||
'stock_qty': d.stock_qty,
|
||||
'stock_uom': d.stock_uom
|
||||
@ -379,6 +379,7 @@ def get_items(filters, additional_query_columns):
|
||||
select
|
||||
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||
`tabSales Invoice`.unrealized_profit_loss_account,
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||
|
@ -59,23 +59,111 @@ def validate_filters(filters):
|
||||
|
||||
def get_columns(filters):
|
||||
return [
|
||||
_("Payment Document") + ":: 100",
|
||||
_("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140",
|
||||
_("Party Type") + "::100",
|
||||
_("Party") + ":Dynamic Link/Party Type:140",
|
||||
_("Posting Date") + ":Date:100",
|
||||
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"),
|
||||
_("Invoice Posting Date") + ":Date:130",
|
||||
_("Payment Due Date") + ":Date:130",
|
||||
_("Debit") + ":Currency:120",
|
||||
_("Credit") + ":Currency:120",
|
||||
_("Remarks") + "::150",
|
||||
_("Age") +":Int:40",
|
||||
"0-30:Currency:100",
|
||||
"30-60:Currency:100",
|
||||
"60-90:Currency:100",
|
||||
_("90-Above") + ":Currency:100",
|
||||
_("Delay in payment (Days)") + "::150"
|
||||
{
|
||||
"fieldname": "payment_document",
|
||||
"label": _("Payment Document Type"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_entry",
|
||||
"label": _("Payment Document"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "payment_document",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"label": _("Party Type"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
"label": _("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"label": _("Posting Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "invoice",
|
||||
"label": _("Invoice"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"fieldname": "invoice_posting_date",
|
||||
"label": _("Invoice Posting Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "due_date",
|
||||
"label": _("Payment Due Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "debit",
|
||||
"label": _("Debit"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
"fieldname": "credit",
|
||||
"label": _("Credit"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
"fieldname": "remarks",
|
||||
"label": _("Remarks"),
|
||||
"fieldtype": "Data",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "age",
|
||||
"label": _("Age"),
|
||||
"fieldtype": "Int",
|
||||
"width": 50
|
||||
},
|
||||
{
|
||||
"fieldname": "range1",
|
||||
"label": "0-30",
|
||||
"fieldtype": "Currency",
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
"fieldname": "range2",
|
||||
"label": "30-60",
|
||||
"fieldtype": "Currency",
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
"fieldname": "range3",
|
||||
"label": "60-90",
|
||||
"fieldtype": "Currency",
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
"fieldname": "range4",
|
||||
"label": _("90 Above"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
"fieldname": "delay_in_payment",
|
||||
"label": _("Delay in payment (Days)"),
|
||||
"fieldtype": "Int",
|
||||
"width": 100
|
||||
}
|
||||
]
|
||||
|
||||
def get_conditions(filters):
|
||||
|
@ -14,13 +14,15 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
if not filters: filters = {}
|
||||
|
||||
invoice_list = get_invoices(filters, additional_query_columns)
|
||||
columns, expense_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
|
||||
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts \
|
||||
= get_columns(invoice_list, additional_table_columns)
|
||||
|
||||
if not invoice_list:
|
||||
msgprint(_("No record found"))
|
||||
return columns, invoice_list
|
||||
|
||||
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
||||
invoice_expense_map, expense_accounts)
|
||||
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||
@ -52,10 +54,17 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
# map expense values
|
||||
base_net_total = 0
|
||||
for expense_acc in expense_accounts:
|
||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||
if inv.is_internal_supplier and inv.company == inv.represents_company:
|
||||
expense_amount = 0
|
||||
else:
|
||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||
base_net_total += expense_amount
|
||||
row.append(expense_amount)
|
||||
|
||||
# Add amount in unrealized account
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
row.append(flt(internal_invoice_map.get((inv.name, account))))
|
||||
|
||||
# net total
|
||||
row.append(base_net_total or inv.base_net_total)
|
||||
|
||||
@ -96,7 +105,8 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
"width": 80
|
||||
}
|
||||
]
|
||||
expense_accounts = tax_accounts = expense_columns = tax_columns = []
|
||||
expense_accounts = tax_accounts = expense_columns = tax_columns = unrealized_profit_loss_accounts = \
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
expense_accounts = frappe.db.sql_list("""select distinct expense_account
|
||||
@ -112,17 +122,25 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
and parent in (%s) order by account_head""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
|
||||
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||
order by unrealized_profit_loss_account""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||
|
||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
||||
unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
|
||||
|
||||
for account in tax_accounts:
|
||||
if account not in expense_accounts:
|
||||
tax_columns.append(account + ":Currency/currency:120")
|
||||
|
||||
columns = columns + expense_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \
|
||||
columns = columns + expense_columns + unrealized_profit_loss_account_columns + \
|
||||
[_("Net Total") + ":Currency/currency:120"] + tax_columns + \
|
||||
[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
|
||||
_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
|
||||
|
||||
return columns, expense_accounts, tax_accounts
|
||||
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
@ -199,6 +217,19 @@ def get_invoice_expense_map(invoice_list):
|
||||
|
||||
return invoice_expense_map
|
||||
|
||||
def get_internal_invoice_map(invoice_list):
|
||||
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
||||
base_net_total as amount from `tabPurchase Invoice` where name in (%s)
|
||||
and is_internal_supplier = 1 and company = represents_company""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
||||
|
||||
internal_invoice_map = {}
|
||||
for d in unrealized_amount_details:
|
||||
if d.unrealized_profit_loss_account:
|
||||
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
|
||||
|
||||
return internal_invoice_map
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||
tax_details = frappe.db.sql("""
|
||||
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
||||
|
@ -15,13 +15,14 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
||||
if not filters: filters = frappe._dict({})
|
||||
|
||||
invoice_list = get_invoices(filters, additional_query_columns)
|
||||
columns, income_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
|
||||
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(invoice_list, additional_table_columns)
|
||||
|
||||
if not invoice_list:
|
||||
msgprint(_("No record found"))
|
||||
return columns, invoice_list
|
||||
|
||||
invoice_income_map = get_invoice_income_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
||||
invoice_income_map, income_accounts)
|
||||
#Cost Center & Warehouse Map
|
||||
@ -70,12 +71,22 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
||||
# map income values
|
||||
base_net_total = 0
|
||||
for income_acc in income_accounts:
|
||||
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
|
||||
if inv.is_internal_customer and inv.company == inv.represents_company:
|
||||
income_amount = 0
|
||||
else:
|
||||
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
|
||||
|
||||
base_net_total += income_amount
|
||||
row.update({
|
||||
frappe.scrub(income_acc): income_amount
|
||||
})
|
||||
|
||||
# Add amount in unrealized account
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
row.update({
|
||||
frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account)))
|
||||
})
|
||||
|
||||
# net total
|
||||
row.update({'net_total': base_net_total or inv.base_net_total})
|
||||
|
||||
@ -230,6 +241,8 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tax_accounts = []
|
||||
income_columns = []
|
||||
tax_columns = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
income_accounts = frappe.db.sql_list("""select distinct income_account
|
||||
@ -243,12 +256,18 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
and parent in (%s) order by account_head""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||
from `tabSales Invoice` where docstatus = 1 and name in (%s)
|
||||
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||
order by unrealized_profit_loss_account""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||
|
||||
for account in income_accounts:
|
||||
income_columns.append({
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": 'currency',
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
})
|
||||
|
||||
@ -258,15 +277,24 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": 'currency',
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
})
|
||||
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
unrealized_profit_loss_account_columns.append({
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
})
|
||||
|
||||
net_total_column = [{
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": 'currency',
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
}]
|
||||
|
||||
@ -301,9 +329,10 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
}
|
||||
]
|
||||
|
||||
columns = columns + income_columns + net_total_column + tax_columns + total_columns
|
||||
columns = columns + income_columns + unrealized_profit_loss_account_columns + \
|
||||
net_total_column + tax_columns + total_columns
|
||||
|
||||
return columns, income_accounts, tax_accounts
|
||||
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
@ -368,7 +397,8 @@ def get_invoices(filters, additional_query_columns):
|
||||
return frappe.db.sql("""
|
||||
select name, posting_date, debit_to, project, customer,
|
||||
customer_name, owner, remarks, territory, tax_id, customer_group,
|
||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount {0}
|
||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
||||
is_internal_customer, represents_company, company {0}
|
||||
from `tabSales Invoice`
|
||||
where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') %
|
||||
conditions, filters, as_dict=1)
|
||||
@ -385,6 +415,19 @@ def get_invoice_income_map(invoice_list):
|
||||
|
||||
return invoice_income_map
|
||||
|
||||
def get_internal_invoice_map(invoice_list):
|
||||
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
||||
base_net_total as amount from `tabSales Invoice` where name in (%s)
|
||||
and is_internal_customer = 1 and company = represents_company""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
||||
|
||||
internal_invoice_map = {}
|
||||
for d in unrealized_amount_details:
|
||||
if d.unrealized_profit_loss_account:
|
||||
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
|
||||
|
||||
return internal_invoice_map
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||
tax_details = frappe.db.sql("""select parent, account_head,
|
||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
||||
|
@ -12,11 +12,12 @@ from frappe.utils import formatdate, get_number_format_info
|
||||
from six import iteritems
|
||||
# imported to enable erpnext.accounts.utils.get_account_currency
|
||||
from erpnext.accounts.doctype.account.account import get_account_currency
|
||||
from frappe.model.meta import get_field_precision
|
||||
|
||||
from erpnext.stock.utils import get_stock_value_on
|
||||
from erpnext.stock import get_warehouse_account_map
|
||||
|
||||
|
||||
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
|
||||
class FiscalYearError(frappe.ValidationError): pass
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -585,24 +586,6 @@ def fix_total_debit_credit():
|
||||
(dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
|
||||
(d.diff, d.voucher_type, d.voucher_no))
|
||||
|
||||
def get_stock_and_account_balance(account=None, posting_date=None, company=None):
|
||||
if not posting_date: posting_date = nowdate()
|
||||
|
||||
warehouse_account = get_warehouse_account_map(company)
|
||||
|
||||
account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
|
||||
|
||||
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
|
||||
if wh_details.account == account and not wh_details.is_group]
|
||||
|
||||
total_stock_value = 0.0
|
||||
for warehouse in related_warehouses:
|
||||
value = get_stock_value_on(warehouse, posting_date)
|
||||
total_stock_value += value
|
||||
|
||||
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
|
||||
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
|
||||
|
||||
def get_currency_precision():
|
||||
precision = cint(frappe.db.get_default("currency_precision"))
|
||||
if not precision:
|
||||
@ -903,12 +886,6 @@ def get_coa(doctype, parent, is_root, chart=None):
|
||||
|
||||
return accounts
|
||||
|
||||
def get_stock_accounts(company):
|
||||
return frappe.get_all("Account", filters = {
|
||||
"account_type": "Stock",
|
||||
"company": company
|
||||
})
|
||||
|
||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
||||
warehouse_account=None, company=None):
|
||||
def _delete_gl_entries(voucher_type, voucher_no):
|
||||
@ -983,4 +960,90 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle):
|
||||
if not account_existed:
|
||||
matched = False
|
||||
break
|
||||
return matched
|
||||
return matched
|
||||
|
||||
def check_if_stock_and_account_balance_synced(posting_date, company, voucher_type=None, voucher_no=None):
|
||||
if not cint(erpnext.is_perpetual_inventory_enabled(company)):
|
||||
return
|
||||
|
||||
accounts = get_stock_accounts(company, voucher_type, voucher_no)
|
||||
stock_adjustment_account = frappe.db.get_value("Company", company, "stock_adjustment_account")
|
||||
|
||||
for account in accounts:
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
|
||||
posting_date, company)
|
||||
|
||||
if abs(account_bal - stock_bal) > 0.1:
|
||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
||||
currency=frappe.get_cached_value('Company', company, "default_currency"))
|
||||
|
||||
diff = flt(stock_bal - account_bal, precision)
|
||||
|
||||
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format(
|
||||
stock_bal, account_bal, frappe.bold(account), posting_date)
|
||||
error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\
|
||||
.format(frappe.bold(diff), frappe.bold(posting_date))
|
||||
|
||||
frappe.msgprint(
|
||||
msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
|
||||
raise_exception=StockValueAndAccountBalanceOutOfSync,
|
||||
title=_('Values Out Of Sync'),
|
||||
primary_action={
|
||||
'label': _('Make Journal Entry'),
|
||||
'client_action': 'erpnext.route_to_adjustment_jv',
|
||||
'args': get_journal_entry(account, stock_adjustment_account, diff)
|
||||
})
|
||||
|
||||
def get_stock_accounts(company, voucher_type=None, voucher_no=None):
|
||||
stock_accounts = [d.name for d in frappe.db.get_all("Account", {
|
||||
"account_type": "Stock",
|
||||
"company": company,
|
||||
"is_group": 0
|
||||
})]
|
||||
if voucher_type and voucher_no:
|
||||
if voucher_type == "Journal Entry":
|
||||
stock_accounts = [d.account for d in frappe.db.get_all("Journal Entry Account", {
|
||||
"parent": voucher_no,
|
||||
"account": ["in", stock_accounts]
|
||||
}, "account")]
|
||||
|
||||
else:
|
||||
stock_accounts = [d.account for d in frappe.db.get_all("GL Entry", {
|
||||
"voucher_type": voucher_type,
|
||||
"voucher_no": voucher_no,
|
||||
"account": ["in", stock_accounts]
|
||||
}, "account")]
|
||||
|
||||
return stock_accounts
|
||||
|
||||
def get_stock_and_account_balance(account=None, posting_date=None, company=None):
|
||||
if not posting_date: posting_date = nowdate()
|
||||
|
||||
warehouse_account = get_warehouse_account_map(company)
|
||||
|
||||
account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
|
||||
|
||||
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
|
||||
if wh_details.account == account and not wh_details.is_group]
|
||||
|
||||
total_stock_value = 0.0
|
||||
for warehouse in related_warehouses:
|
||||
value = get_stock_value_on(warehouse, posting_date)
|
||||
total_stock_value += value
|
||||
|
||||
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
|
||||
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
|
||||
|
||||
def get_journal_entry(account, stock_adjustment_account, amount):
|
||||
db_or_cr_warehouse_account =('credit_in_account_currency' if amount < 0 else 'debit_in_account_currency')
|
||||
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if amount < 0 else 'credit_in_account_currency')
|
||||
|
||||
return {
|
||||
'accounts':[{
|
||||
'account': account,
|
||||
db_or_cr_warehouse_account: abs(amount)
|
||||
}, {
|
||||
'account': stock_adjustment_account,
|
||||
db_or_cr_stock_adjustment_account : abs(amount)
|
||||
}]
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class CropCycle(Document):
|
||||
|
||||
def import_disease_tasks(self, disease, start_date):
|
||||
disease_doc = frappe.get_doc('Disease', disease)
|
||||
self.create_task(disease_doc.treatment_task, self.name, start_date)
|
||||
self.create_task(disease_doc.treatment_task, self.project, start_date)
|
||||
|
||||
def create_project(self, period, crop_tasks):
|
||||
project = frappe.get_doc({
|
||||
|
@ -71,4 +71,4 @@ def check_task_creation():
|
||||
|
||||
|
||||
def check_project_creation():
|
||||
return True if frappe.db.exists('Project', 'Basil from seed 2017') else False
|
||||
return True if frappe.db.exists('Project', {'project_name': 'Basil from seed 2017'}) else False
|
||||
|
@ -2,6 +2,7 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.asset");
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Asset', {
|
||||
onload: function(frm) {
|
||||
@ -32,13 +33,11 @@ frappe.ui.form.on('Asset', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"company": frm.doc.company,
|
||||
}
|
||||
};
|
||||
});
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
setup: function(frm) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Asset Value Adjustment', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch('company', 'cost_center', 'cost_center');
|
||||
@ -13,11 +15,19 @@ frappe.ui.form.on('Asset Value Adjustment', {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(frm.is_new() && frm.doc.asset) {
|
||||
frm.trigger("set_current_asset_value");
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
asset: function(frm) {
|
||||
frm.trigger("set_current_asset_value");
|
||||
},
|
||||
|
@ -13,17 +13,14 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
|
||||
class AssetValueAdjustment(Document):
|
||||
def validate(self):
|
||||
self.validate_date()
|
||||
self.set_difference_amount()
|
||||
self.set_current_asset_value()
|
||||
self.set_difference_amount()
|
||||
|
||||
def on_submit(self):
|
||||
self.make_depreciation_entry()
|
||||
self.reschedule_depreciations(self.new_asset_value)
|
||||
|
||||
def on_cancel(self):
|
||||
if self.journal_entry:
|
||||
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
|
||||
|
||||
self.reschedule_depreciations(self.current_asset_value)
|
||||
|
||||
def validate_date(self):
|
||||
|
@ -2,7 +2,7 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.provide("erpnext.buying");
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
{% include 'erpnext/public/js/controllers/buying.js' %};
|
||||
|
||||
frappe.ui.form.on("Purchase Order", {
|
||||
@ -30,6 +30,10 @@ frappe.ui.form.on("Purchase Order", {
|
||||
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
set_schedule_date(frm);
|
||||
if (!frm.doc.transaction_date){
|
||||
@ -39,6 +43,8 @@ frappe.ui.form.on("Purchase Order", {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
}
|
||||
});
|
||||
|
||||
@ -58,8 +64,8 @@ frappe.ui.form.on("Purchase Order Item", {
|
||||
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
|
||||
setup: function() {
|
||||
this.frm.custom_make_buttons = {
|
||||
'Purchase Receipt': 'Receipt',
|
||||
'Purchase Invoice': 'Invoice',
|
||||
'Purchase Receipt': 'Purchase Receipt',
|
||||
'Purchase Invoice': 'Purchase Invoice',
|
||||
'Stock Entry': 'Material to Supplier',
|
||||
'Payment Entry': 'Payment',
|
||||
}
|
||||
@ -158,16 +164,16 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
|
||||
if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
|
||||
let me = this;
|
||||
frappe.model.with_doc("Supplier", me.frm.doc.supplier, () => {
|
||||
let supplier = frappe.model.get_doc("Supplier", me.frm.doc.supplier);
|
||||
let internal = supplier.is_internal_supplier;
|
||||
let disabled = supplier.disabled;
|
||||
if (internal === 1 && disabled === 0) {
|
||||
me.frm.add_custom_button("Inter Company Order", function() {
|
||||
me.make_inter_company_order(me.frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
});
|
||||
let internal = me.frm.doc.is_internal_supplier;
|
||||
if (internal) {
|
||||
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Sales Order" :
|
||||
"Inter Company Sales Order";
|
||||
|
||||
me.frm.add_custom_button(button_label, function() {
|
||||
me.make_inter_company_order(me.frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,7 +353,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
make_purchase_receipt: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
|
||||
frm: cur_frm
|
||||
frm: cur_frm,
|
||||
freeze_message: __("Creating Purchase Receipt ...")
|
||||
})
|
||||
},
|
||||
|
||||
@ -374,7 +381,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
material_request_type: "Purchase",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 99.99],
|
||||
per_ordered: ["<", 100],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
|
@ -134,6 +134,8 @@
|
||||
"ref_sq",
|
||||
"column_break_74",
|
||||
"party_account_currency",
|
||||
"is_internal_supplier",
|
||||
"represents_company",
|
||||
"inter_company_order_reference"
|
||||
],
|
||||
"fields": [
|
||||
@ -1101,13 +1103,28 @@
|
||||
{
|
||||
"fieldname": "items_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "supplier.is_internal_supplier",
|
||||
"fieldname": "is_internal_supplier",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Internal Supplier"
|
||||
},
|
||||
{
|
||||
"fetch_from": "supplier.represents_company",
|
||||
"fieldname": "represents_company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-03 16:46:44.229351",
|
||||
"modified": "2021-01-20 22:07:23.487138",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -123,8 +123,8 @@ class PurchaseOrder(BuyingController):
|
||||
if self.is_subcontracted == "Yes":
|
||||
for item in self.items:
|
||||
if not item.bom:
|
||||
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}"\
|
||||
.format(item.item_code, item.idx)))
|
||||
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}")
|
||||
.format(item.item_code, item.idx))
|
||||
|
||||
def get_schedule_dates(self):
|
||||
for d in self.get('items'):
|
||||
|
@ -224,7 +224,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
material_request_type: "Purchase",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 99.99],
|
||||
per_ordered: ["<", 100],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
@ -280,7 +280,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
material_request_type: "Purchase",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 99.99]
|
||||
per_ordered: ["<", 100]
|
||||
}
|
||||
});
|
||||
dialog.hide();
|
||||
|
@ -52,7 +52,10 @@ class Supplier(TransactionBase):
|
||||
self.validate_internal_supplier()
|
||||
|
||||
def validate_internal_supplier(self):
|
||||
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
|
||||
internal_supplier = frappe.db.get_value("Supplier",
|
||||
{"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
|
||||
|
||||
if internal_supplier:
|
||||
frappe.throw(_("Internal Supplier for company {0} already exists").format(
|
||||
frappe.bold(self.represents_company)))
|
||||
|
||||
|
@ -44,7 +44,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
||||
material_request_type: "Purchase",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 99.99],
|
||||
per_ordered: ["<", 100],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
|
@ -75,62 +75,70 @@ frappe.query_reports["Purchase Analytics"] = {
|
||||
return Object.assign(options, {
|
||||
checkboxColumn: true,
|
||||
events: {
|
||||
onCheckRow: function(data) {
|
||||
onCheckRow: function (data) {
|
||||
if (!data) return;
|
||||
|
||||
const data_doctype = $(
|
||||
data[2].html
|
||||
)[0].attributes.getNamedItem("data-doctype").value;
|
||||
const tree_type = frappe.query_report.filters[0].value;
|
||||
if (data_doctype != tree_type) return;
|
||||
|
||||
row_name = data[2].content;
|
||||
length = data.length;
|
||||
|
||||
var tree_type = frappe.query_report.filters[0].value;
|
||||
|
||||
if(tree_type == "Supplier" || tree_type == "Item") {
|
||||
row_values = data.slice(4,length-1).map(function (column) {
|
||||
return column.content;
|
||||
})
|
||||
}
|
||||
else {
|
||||
row_values = data.slice(3,length-1).map(function (column) {
|
||||
return column.content;
|
||||
})
|
||||
if (tree_type == "Supplier") {
|
||||
row_values = data
|
||||
.slice(4, length - 1)
|
||||
.map(function (column) {
|
||||
return column.content;
|
||||
});
|
||||
} else if (tree_type == "Item") {
|
||||
row_values = data
|
||||
.slice(5, length - 1)
|
||||
.map(function (column) {
|
||||
return column.content;
|
||||
});
|
||||
} else {
|
||||
row_values = data
|
||||
.slice(3, length - 1)
|
||||
.map(function (column) {
|
||||
return column.content;
|
||||
});
|
||||
}
|
||||
|
||||
entry = {
|
||||
'name':row_name,
|
||||
'values':row_values
|
||||
}
|
||||
entry = {
|
||||
name: row_name,
|
||||
values: row_values,
|
||||
};
|
||||
|
||||
let raw_data = frappe.query_report.chart.data;
|
||||
let new_datasets = raw_data.datasets;
|
||||
|
||||
var found = false;
|
||||
|
||||
for(var i=0; i < new_datasets.length;i++){
|
||||
if(new_datasets[i].name == row_name){
|
||||
found = true;
|
||||
new_datasets.splice(i,1);
|
||||
break;
|
||||
let element_found = new_datasets.some((element, index, array)=>{
|
||||
if(element.name == row_name){
|
||||
array.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if(!found){
|
||||
if (!element_found) {
|
||||
new_datasets.push(entry);
|
||||
}
|
||||
|
||||
let new_data = {
|
||||
labels: raw_data.labels,
|
||||
datasets: new_datasets
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
frappe.query_report.chart.update(new_data)
|
||||
},500)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
frappe.query_report.chart.draw(true);
|
||||
}, 1000)
|
||||
datasets: new_datasets,
|
||||
};
|
||||
chart_options = {
|
||||
data: new_data,
|
||||
type: "line",
|
||||
};
|
||||
frappe.query_report.render_chart(chart_options);
|
||||
|
||||
frappe.query_report.raw_chart_data = new_data;
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,10 @@ def update_last_purchase_rate(doc, is_submit):
|
||||
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
|
||||
|
||||
# update last purchsae rate
|
||||
if last_purchase_rate:
|
||||
frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
|
||||
(flt(last_purchase_rate), d.item_code))
|
||||
frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate))
|
||||
|
||||
|
||||
|
||||
|
||||
def validate_for_items(doc):
|
||||
items = []
|
||||
|
151
erpnext/change_log/v13/v13_0_0-beta_6.md
Normal file
151
erpnext/change_log/v13/v13_0_0-beta_6.md
Normal file
@ -0,0 +1,151 @@
|
||||
### Version 13.0.0 Beta 6 Release Notes
|
||||
|
||||
#### Features and Enhancements
|
||||
|
||||
- GST E-invoicing for India ([#23455](https://github.com/frappe/erpnext/pull/23455))
|
||||
- Multi-currency payroll ([#23519](https://github.com/frappe/erpnext/pull/23519))
|
||||
- Allow back-dated stock transactions and repost item costing via background job ([#24183](https://github.com/frappe/erpnext/pull/24183))
|
||||
- Introduced telephony feature using Twillio ([#24032](https://github.com/frappe/erpnext/pull/24032))
|
||||
- Shipment Doctype ([#22914](https://github.com/frappe/erpnext/pull/22914))
|
||||
- Leave policy assignment ([#23112](https://github.com/frappe/erpnext/pull/23112))
|
||||
- UAE VAT 201 Report ([#23447](https://github.com/frappe/erpnext/pull/23447))
|
||||
- Return tracking in PR/DN ([#22859](https://github.com/frappe/erpnext/pull/22859))
|
||||
- Close Production Plan ([#23728](https://github.com/frappe/erpnext/pull/23728))
|
||||
- Quality Inspection on Job Card ([#23964](https://github.com/frappe/erpnext/pull/23964))
|
||||
- Inpatient Medication Orders Script Report ([#23984](https://github.com/frappe/erpnext/pull/23984))
|
||||
- Leave type with partial payment ([#23173](https://github.com/frappe/erpnext/pull/23173))
|
||||
- Formula based Quality Inspection ([#23916](https://github.com/frappe/erpnext/pull/23916))
|
||||
- Link to Material Requests in Tools section for RFQ and Supplier Quotation ([#23429](https://github.com/frappe/erpnext/pull/23429))
|
||||
- Hide images & auto add item checkbox ([#24102](https://github.com/frappe/erpnext/pull/24102))
|
||||
- In reports get item details from Item instead of the transactions ([#24082](https://github.com/frappe/erpnext/pull/24082))
|
||||
- sales order status filter added for production plan ([#23805](https://github.com/frappe/erpnext/pull/23805))
|
||||
- Button to create Stock Entry for Drug Shortage ([#24012](https://github.com/frappe/erpnext/pull/24012))
|
||||
- Sync old shopify orders ([#23841](https://github.com/frappe/erpnext/pull/23841))
|
||||
- Added column cost center in Accounts Receivable report ([#23835](https://github.com/frappe/erpnext/pull/23835))
|
||||
- Added jinja templating in Contract Template ([#24046](https://github.com/frappe/erpnext/pull/24046))
|
||||
- Consider Holiday List in Student Leave Application and Attendance ([#23388](https://github.com/frappe/erpnext/pull/23388))
|
||||
- Make account number length configurable ([#23845](https://github.com/frappe/erpnext/pull/23845))
|
||||
- Add communication channel to communication medium ([#23793](https://github.com/frappe/erpnext/pull/23793))
|
||||
|
||||
#### Fixes
|
||||
|
||||
- Loan disbursement amount validation ([#24000](https://github.com/frappe/erpnext/pull/24000))
|
||||
- Making company address read-only in delivery note ([#23890](https://github.com/frappe/erpnext/pull/23890))
|
||||
- BOM stock report color showing always red ([#23994](https://github.com/frappe/erpnext/pull/23994))
|
||||
- Added filter for customer field in Issue ([#24051](https://github.com/frappe/erpnext/pull/24051))
|
||||
- Added project link in timesheet form ([#23764](https://github.com/frappe/erpnext/pull/23764))
|
||||
- Update integrations desk page ([#23767](https://github.com/frappe/erpnext/pull/23767))
|
||||
- Place of supply change on address change ([#23941](https://github.com/frappe/erpnext/pull/23941))
|
||||
- TDS calculation, skip invoices with "Apply Tax Withholding Amount" has disabled ([#23672](https://github.com/frappe/erpnext/pull/23672))
|
||||
- Auto fetch serial nos with modified conversion factor ([#23854](https://github.com/frappe/erpnext/pull/23854))
|
||||
- Default cost center in item master not set in stock entry ([#23877](https://github.com/frappe/erpnext/pull/23877))
|
||||
- Incorrect de-link serial no and batch ([#23947](https://github.com/frappe/erpnext/pull/23947))
|
||||
- Accounting for internal transfer invoices within same company ([#24021](https://github.com/frappe/erpnext/pull/24021))
|
||||
- Multiple pricing rule with margin type as Percentage is not working ([#24205](https://github.com/frappe/erpnext/pull/24205))
|
||||
- Added Purchase Order to Global Search ([#24055](https://github.com/frappe/erpnext/pull/24055))
|
||||
- Cannot expand row in update items dialog ([#23839](https://github.com/frappe/erpnext/pull/23839))
|
||||
- Maintain stock can't be changed it there is product bundle ([#23989](https://github.com/frappe/erpnext/pull/23989))
|
||||
- SO to PO Mapping Issue ([#23820](https://github.com/frappe/erpnext/pull/23820))
|
||||
- Asset with value zero doesn't show up in fixed asset register ([#24091](https://github.com/frappe/erpnext/pull/24091))
|
||||
- Cannot save customer email & phone ([#23797](https://github.com/frappe/erpnext/pull/23797))
|
||||
- Incorrect balance value in stock balance report ([#24048](https://github.com/frappe/erpnext/pull/24048))
|
||||
- Payment Terms not fetched in Purchase Invoice from Purchase Receipt ([#23735](https://github.com/frappe/erpnext/pull/23735))
|
||||
- Fix for LMS Sign Up link ([#23743](https://github.com/frappe/erpnext/pull/23743))
|
||||
- Incorrect stock quantity if 'Allow Multiple Material Consumption… ([#24116](https://github.com/frappe/erpnext/pull/24116))
|
||||
- Added wrong absent days calculation in salary slip ([#23897](https://github.com/frappe/erpnext/pull/23897))
|
||||
- Purchase receipt to purchase invoice bill date mapping ([#23967](https://github.com/frappe/erpnext/pull/23967))
|
||||
- Overriding po ([#24022](https://github.com/frappe/erpnext/pull/24022))
|
||||
- Do not cancel reference document on Quality Inspection cancellation ([#24198](https://github.com/frappe/erpnext/pull/24198))
|
||||
- Get formatted value in 'taxes' print template ([#24035](https://github.com/frappe/erpnext/pull/24035))
|
||||
- Don't overrule Item Price via Pricing Rule Rate if 0 ([#23636](https://github.com/frappe/erpnext/pull/23636))
|
||||
- Job card error handling for operations field ([#23991](https://github.com/frappe/erpnext/pull/23991))
|
||||
- Validation for journal entry with 0 debit and credit values ([#23975](https://github.com/frappe/erpnext/pull/23975))
|
||||
- Check if customer exists in product listing ([#24030](https://github.com/frappe/erpnext/pull/24030))
|
||||
- Asset finance book posting date fix ([#23778](https://github.com/frappe/erpnext/pull/23778))
|
||||
- Same source and target tables in Status Updater's update query ([#24110](https://github.com/frappe/erpnext/pull/24110))
|
||||
- Asset finance book depreciation posting date fix ([#23833](https://github.com/frappe/erpnext/pull/23833))
|
||||
- Ignore exception during leave ledger creation from patch ([#24005](https://github.com/frappe/erpnext/pull/24005))
|
||||
- Added link of bank reconciliation and clearance in accounting desk page ([#23850](https://github.com/frappe/erpnext/pull/23850))
|
||||
- Sales invoice add button from sales order dashboard ([#24077](https://github.com/frappe/erpnext/pull/24077))
|
||||
- Incorrect calculation for consumed qty for subcontract item ([#23257](https://github.com/frappe/erpnext/pull/23257))
|
||||
- Incorrect required_qty in Production Planning Report ([#24074](https://github.com/frappe/erpnext/pull/24074))
|
||||
- Email digest user not found ([#23949](https://github.com/frappe/erpnext/pull/23949))
|
||||
- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24115](https://github.com/frappe/erpnext/pull/24115))
|
||||
- Added TDS Payable account number and an error message ([#24065](https://github.com/frappe/erpnext/pull/24065))
|
||||
- Override field_map for job card gantt ([#24155](https://github.com/frappe/erpnext/pull/24155))
|
||||
- Old shopify order syncing date ([#23990](https://github.com/frappe/erpnext/pull/23990))
|
||||
- Shipping chanrges not sync in erpnext from shopify ([#24114](https://github.com/frappe/erpnext/pull/24114))
|
||||
- GSTR B2C report ([#24039](https://github.com/frappe/erpnext/pull/24039))
|
||||
- Ignore cancelled entries in stock balance report ([#23757](https://github.com/frappe/erpnext/pull/23757))
|
||||
- Stock ageing report not working ([#23923](https://github.com/frappe/erpnext/pull/23923))
|
||||
- Incorrect assign to in Maintenance Schedule ([#23831](https://github.com/frappe/erpnext/pull/23831))
|
||||
- Improve UX of DATEV report ([#23892](https://github.com/frappe/erpnext/pull/23892))
|
||||
- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765))
|
||||
- dDouble exception in payroll ([#24078](https://github.com/frappe/erpnext/pull/24078))
|
||||
- Make asset dashboard charts public ([#23751](https://github.com/frappe/erpnext/pull/23751))
|
||||
- Don't copy terms and discount from SO to PO ([#23903](https://github.com/frappe/erpnext/pull/23903))
|
||||
- Ignore doctypes on company transaction delete ([#23864](https://github.com/frappe/erpnext/pull/23864))
|
||||
- Error handling in Upload Attendance ([#23907](https://github.com/frappe/erpnext/pull/23907))
|
||||
- Tax template update on customer address change ([#24160](https://github.com/frappe/erpnext/pull/24160))
|
||||
- Not able to save bom ([#23910](https://github.com/frappe/erpnext/pull/23910))
|
||||
- Enable Allow Auto Repeat for standard doctypes having auto_repeat field ([#23776](https://github.com/frappe/erpnext/pull/23776))
|
||||
- Place of Supply fix in Sales Invoices ([#23785](https://github.com/frappe/erpnext/pull/23785))
|
||||
- Opening invoices in GSTR-1 report ([#24117](https://github.com/frappe/erpnext/pull/24117))
|
||||
- Add check for allowing access to european region ([#23770](https://github.com/frappe/erpnext/pull/23770))
|
||||
- Partial serial no return issue ([#24208](https://github.com/frappe/erpnext/pull/24208))
|
||||
- Multiple pos issues ([#23347](https://github.com/frappe/erpnext/pull/23347))
|
||||
- Import taxjar globally in the taxjar_integration module ([#24027](https://github.com/frappe/erpnext/pull/24027))
|
||||
- Payroll attendance error ([#23887](https://github.com/frappe/erpnext/pull/23887))
|
||||
- Cannot add items to cart ([#23796](https://github.com/frappe/erpnext/pull/23796))
|
||||
- Show tax amount in base currencies ([#24069](https://github.com/frappe/erpnext/pull/24069))
|
||||
- Loan application link on creating loan ([#23937](https://github.com/frappe/erpnext/pull/23937))
|
||||
- Added shipment link in delivery note dashboard ([#24210](https://github.com/frappe/erpnext/pull/24210))
|
||||
- POS item search includes non stock items ([#23914](https://github.com/frappe/erpnext/pull/23914))
|
||||
- Paid amount in Sales Invoice POS return resets to 0 ([#24057](https://github.com/frappe/erpnext/pull/24057))
|
||||
- Remove check for exempt_from_sales_tax ([#23870](https://github.com/frappe/erpnext/pull/23870))
|
||||
- Fiscal year can be shorter than 12 months ([#23838](https://github.com/frappe/erpnext/pull/23838))
|
||||
- Loan repayment type option remove ([#23582](https://github.com/frappe/erpnext/pull/23582))
|
||||
- Make contract template editable ([#23891](https://github.com/frappe/erpnext/pull/23891))
|
||||
- Item wise tax calculation ([#23744](https://github.com/frappe/erpnext/pull/23744))
|
||||
- Enabling track changes for stock settings ([#23982](https://github.com/frappe/erpnext/pull/23982))
|
||||
- Added link of bank reconciliation and clearance in accounting desk page ([#23809](https://github.com/frappe/erpnext/pull/23809))
|
||||
- Location data on Asset to use command(make_demo) ([#23825](https://github.com/frappe/erpnext/pull/23825))
|
||||
- Handle Account and Item None not found in Opening Invoice Creation Tool ([#23559](https://github.com/frappe/erpnext/pull/23559))
|
||||
- List index out of range on including UOM ([#23814](https://github.com/frappe/erpnext/pull/23814))
|
||||
- Showing error for wrong filters. ([#23726](https://github.com/frappe/erpnext/pull/23726))
|
||||
- Multiple subcontracting issues ([#23662](https://github.com/frappe/erpnext/pull/23662))
|
||||
- Sequence id override with workstation column ([#23810](https://github.com/frappe/erpnext/pull/23810))
|
||||
- Replaced formatdate -> format_date ([#23849](https://github.com/frappe/erpnext/pull/23849))
|
||||
- Test Payment Based on Leave Application (Travis) ([#24044](https://github.com/frappe/erpnext/pull/24044))
|
||||
- Leave policy dashboard fix and roles ([#24170](https://github.com/frappe/erpnext/pull/24170))
|
||||
- Budget test cases ([#23801](https://github.com/frappe/erpnext/pull/23801))
|
||||
- Handle for custom field IFSC code in Bank remittance report. ([#23905](https://github.com/frappe/erpnext/pull/23905))
|
||||
- Scan barcode does not update barcode item field in sales order ([#24090](https://github.com/frappe/erpnext/pull/24090))
|
||||
- Item price duplicate checking ([#23408](https://github.com/frappe/erpnext/pull/23408))
|
||||
- Tax template update on supplier change for India ([#24060](https://github.com/frappe/erpnext/pull/24060))
|
||||
- Consumed qty logic for subcontracted raw materials ([#23314](https://github.com/frappe/erpnext/pull/23314))
|
||||
- Finance book not getting added in journal Entry of asset value adjustment ([#24100](https://github.com/frappe/erpnext/pull/24100))
|
||||
- Fixed home desk page ([#24075](https://github.com/frappe/erpnext/pull/24075))
|
||||
- po_detail field has no value for subcontracted stock entry ([#23777](https://github.com/frappe/erpnext/pull/23777))
|
||||
- Set proper state code in ewaybill JSON when GST category is SEZ ([#23953](https://github.com/frappe/erpnext/pull/23953))
|
||||
- Copying po no when mapping doc ([#23729](https://github.com/frappe/erpnext/pull/23729))
|
||||
- Duplicate items validation for POS Invoice when allow multiple items is disabled ([#23896](https://github.com/frappe/erpnext/pull/23896))
|
||||
- POS register shows cancelled documents ([#23747](https://github.com/frappe/erpnext/pull/23747))
|
||||
- Subscription test case ([#23763](https://github.com/frappe/erpnext/pull/23763))
|
||||
- BOM stock report color issue ([#23980](https://github.com/frappe/erpnext/pull/23980))
|
||||
- Handle the "no leave_allocation found" case ([#23922](https://github.com/frappe/erpnext/pull/23922))
|
||||
- Filters for tax templates ([#23998](https://github.com/frappe/erpnext/pull/23998))
|
||||
- Do not allow Company as accounting dimension ([#23749](https://github.com/frappe/erpnext/pull/23749))
|
||||
- Validation for duplicate Tax Category ([#23978](https://github.com/frappe/erpnext/pull/23978))
|
||||
- Therapy plan and session fixes ([#23817](https://github.com/frappe/erpnext/pull/23817))
|
||||
- Correcting description field in taxes and charges for accounts with account number + account name ([#23836](https://github.com/frappe/erpnext/pull/23836))
|
||||
- Pricing rule with transaction not working for additional product ([#24053](https://github.com/frappe/erpnext/pull/24053))
|
||||
- Keyerror 'sourced_by_supplier' ([#24038](https://github.com/frappe/erpnext/pull/24038))
|
||||
- Validation for membership ([#23934](https://github.com/frappe/erpnext/pull/23934))
|
||||
- Inpatient Medication Order and Entry fixes ([#23799](https://github.com/frappe/erpnext/pull/23799))
|
||||
- Avoid using SQL query to get fiscal year dates ([#24050](https://github.com/frappe/erpnext/pull/24050))
|
||||
- Auto Statewise gst tax template ([#23832](https://github.com/frappe/erpnext/pull/23832))
|
||||
- POS profile has no attr 'show_only_available_items' ([#23758](https://github.com/frappe/erpnext/pull/23758))
|
||||
- On save sequence id column override with workstation ([#23812](https://github.com/frappe/erpnext/pull/23812))
|
||||
- Multiple pricing rules are not working on selling side ([#22711](https://github.com/frappe/erpnext/pull/22711))
|
||||
- Salary slip popup error ([#24192](https://github.com/frappe/erpnext/pull/24192))
|
@ -75,6 +75,9 @@ class AccountsController(TransactionBase):
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
|
||||
self.validate_date_with_fiscal_year()
|
||||
self.validate_inter_company_reference()
|
||||
|
||||
self.set_incoming_rate()
|
||||
|
||||
if self.meta.get_field("currency"):
|
||||
self.calculate_taxes_and_totals()
|
||||
@ -110,9 +113,21 @@ class AccountsController(TransactionBase):
|
||||
self.set_inter_company_account()
|
||||
|
||||
validate_regional(self)
|
||||
|
||||
validate_einvoice_fields(self)
|
||||
|
||||
if self.doctype != 'Material Request':
|
||||
apply_pricing_rule_on_transaction(self)
|
||||
|
||||
def before_cancel(self):
|
||||
validate_einvoice_fields(self)
|
||||
|
||||
def on_trash(self):
|
||||
# delete sl and gl entries on deletion of transaction
|
||||
if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
|
||||
frappe.db.sql("delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
|
||||
frappe.db.sql("delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
|
||||
|
||||
def validate_deferred_start_and_end_date(self):
|
||||
for d in self.items:
|
||||
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
|
||||
@ -200,6 +215,17 @@ class AccountsController(TransactionBase):
|
||||
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
|
||||
self.meta.get_label(date_field), self)
|
||||
|
||||
def validate_inter_company_reference(self):
|
||||
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||
return
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
|
||||
or self.get('inter_company_order_reference')):
|
||||
msg = _("Internal Sale or Delivery Reference missing. ")
|
||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||
|
||||
def validate_due_date(self):
|
||||
if self.get('is_pos'): return
|
||||
|
||||
@ -442,8 +468,10 @@ class AccountsController(TransactionBase):
|
||||
account_currency = get_account_currency(gl_dict.account)
|
||||
|
||||
if gl_dict.account and self.doctype not in ["Journal Entry",
|
||||
"Period Closing Voucher", "Payment Entry"]:
|
||||
"Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
|
||||
self.validate_account_currency(gl_dict.account, account_currency)
|
||||
|
||||
if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]:
|
||||
set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
|
||||
self.company_currency)
|
||||
|
||||
@ -956,9 +984,9 @@ class AccountsController(TransactionBase):
|
||||
It will an internal transfer if its an internal customer and representation
|
||||
company is same as billing company
|
||||
"""
|
||||
if self.doctype == 'Sales Invoice':
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Sales Order'):
|
||||
internal_party_field = 'is_internal_customer'
|
||||
else:
|
||||
elif self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||
internal_party_field = 'is_internal_supplier'
|
||||
|
||||
if self.get(internal_party_field) and (self.represents_company == self.company):
|
||||
@ -1518,3 +1546,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
@erpnext.allow_regional
|
||||
def validate_regional(doc):
|
||||
pass
|
||||
|
||||
@erpnext.allow_regional
|
||||
def validate_einvoice_fields(doc):
|
||||
pass
|
||||
|
@ -44,7 +44,6 @@ class BuyingController(StockController):
|
||||
self.validate_items()
|
||||
self.set_qty_as_per_stock_uom()
|
||||
self.validate_stock_or_nonstock_items()
|
||||
self.update_tax_category_for_internal_transfer()
|
||||
self.validate_warehouse()
|
||||
self.validate_from_warehouse()
|
||||
self.set_supplier_address()
|
||||
@ -100,11 +99,6 @@ class BuyingController(StockController):
|
||||
msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
|
||||
self.update_tax_category(msg)
|
||||
|
||||
def update_tax_category_for_internal_transfer(self):
|
||||
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
|
||||
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
|
||||
self.update_tax_category(msg)
|
||||
|
||||
def update_tax_category(self, msg):
|
||||
tax_for_valuation = [d for d in self.get("taxes")
|
||||
if d.category in ["Valuation", "Valuation and Total"]]
|
||||
@ -224,6 +218,48 @@ class BuyingController(StockController):
|
||||
else:
|
||||
item.valuation_rate = 0.0
|
||||
|
||||
def set_incoming_rate(self):
|
||||
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
|
||||
return
|
||||
|
||||
ref_doctype_map = {
|
||||
"Purchase Order": "Sales Order Item",
|
||||
"Purchase Receipt": "Delivery Note Item",
|
||||
"Purchase Invoice": "Sales Invoice Item",
|
||||
}
|
||||
|
||||
ref_doctype = ref_doctype_map.get(self.doctype)
|
||||
items = self.get("items")
|
||||
for d in items:
|
||||
if not cint(self.get("is_return")):
|
||||
# Get outgoing rate based on original item cost based on valuation method
|
||||
|
||||
if not d.get(frappe.scrub(ref_doctype)):
|
||||
outgoing_rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.get('from_warehouse'),
|
||||
"posting_date": self.get('posting_date') or self.get('transation_date'),
|
||||
"posting_time": self.get('posting_time'),
|
||||
"qty": -1 * flt(d.get('stock_qty')),
|
||||
"serial_no": d.get('serial_no'),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||
}, raise_error_if_no_rate=False)
|
||||
|
||||
rate = flt(outgoing_rate * d.conversion_factor, d.precision('rate'))
|
||||
else:
|
||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), 'rate')
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if rate != d.rate:
|
||||
d.rate = rate
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
|
||||
.format(d.idx), alert=1)
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
supplied_items_cost = 0.0
|
||||
for d in self.get("supplied_items"):
|
||||
@ -241,9 +277,9 @@ class BuyingController(StockController):
|
||||
if rate > 0:
|
||||
d.rate = rate
|
||||
|
||||
d.amount = flt(d.consumed_qty) * flt(d.rate)
|
||||
d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount"))
|
||||
supplied_items_cost += flt(d.amount)
|
||||
|
||||
|
||||
return supplied_items_cost
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
@ -336,7 +372,7 @@ class BuyingController(StockController):
|
||||
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
||||
|
||||
consumed_qty = raw_material_data.get('qty', 0)
|
||||
consumed_serial_nos = raw_material_data.get('serial_nos', '')
|
||||
consumed_serial_nos = raw_material_data.get('serial_no', '')
|
||||
consumed_batch_nos = raw_material_data.get('batch_nos', '')
|
||||
|
||||
transferred_qty = raw_material.qty
|
||||
@ -559,6 +595,8 @@ class BuyingController(StockController):
|
||||
from_warehouse_sle = self.get_sl_entries(d, {
|
||||
"actual_qty": -1 * pr_qty,
|
||||
"warehouse": d.from_warehouse,
|
||||
"outgoing_rate": d.rate,
|
||||
"recalculate_rate": 1,
|
||||
"dependant_sle_voucher_detail_no": d.name
|
||||
})
|
||||
|
||||
|
@ -493,6 +493,41 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
'company': filters.get("company", "")
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
|
||||
dimension_filters = get_dimension_filter_map()
|
||||
dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
|
||||
query_filters = []
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
if meta.is_tree:
|
||||
query_filters.append(['is_group', '=', 0])
|
||||
|
||||
if meta.has_field('company'):
|
||||
query_filters.append(['company', '=', filters.get('company')])
|
||||
|
||||
if txt:
|
||||
query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt])
|
||||
|
||||
if dimension_filters:
|
||||
if dimension_filters['allow_or_restrict'] == 'Allow':
|
||||
query_selector = 'in'
|
||||
else:
|
||||
query_selector = 'not in'
|
||||
|
||||
if len(dimension_filters['allowed_dimensions']) == 1:
|
||||
dimensions = tuple(dimension_filters['allowed_dimensions'] * 2)
|
||||
else:
|
||||
dimensions = tuple(dimension_filters['allowed_dimensions'])
|
||||
|
||||
query_filters.append(['name', query_selector, dimensions])
|
||||
|
||||
output = frappe.get_all(doctype, filters=query_filters)
|
||||
result = [d.name for d in output]
|
||||
|
||||
return [(d,) for d in set(result)]
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
@ -620,6 +655,34 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """
|
||||
select name
|
||||
from `tabHealthcare Service Unit`
|
||||
where
|
||||
is_group = 0
|
||||
and company = {company}
|
||||
and name like {txt}""".format(
|
||||
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||
|
||||
if filters and filters.get('inpatient_record'):
|
||||
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
|
||||
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
|
||||
|
||||
# if the patient is admitted, then appointments should be allowed against the admission service unit,
|
||||
# inspite of it being an Inpatient Occupancy service unit
|
||||
if service_unit:
|
||||
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
|
||||
else:
|
||||
query += " and allow_appointments = 1"
|
||||
else:
|
||||
query += " and allow_appointments = 1"
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
@ -204,21 +204,25 @@ def get_already_returned_items(doc):
|
||||
return items
|
||||
|
||||
def get_returned_qty_map_for_row(row_name, doctype):
|
||||
if doctype == "POS Invoice": return {}
|
||||
|
||||
child_doctype = doctype + " Item"
|
||||
reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail"
|
||||
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
|
||||
|
||||
fields = [
|
||||
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
|
||||
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
|
||||
]
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
if doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
fields += [
|
||||
"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
|
||||
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
|
||||
"sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)
|
||||
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype)
|
||||
]
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
|
||||
|
||||
data = frappe.db.get_list(doctype,
|
||||
fields = fields,
|
||||
filters = [
|
||||
@ -231,6 +235,7 @@ def get_returned_qty_map_for_row(row_name, doctype):
|
||||
|
||||
def make_return_doc(doctype, source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
company = frappe.db.get_value("Delivery Note", source_name, "company")
|
||||
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
|
||||
|
||||
@ -290,6 +295,12 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = -1 * source_doc.qty
|
||||
|
||||
if source_doc.serial_no:
|
||||
returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
|
||||
serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
|
||||
if serial_nos:
|
||||
target_doc.serial_no = '\n'.join(serial_nos)
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
||||
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
|
||||
@ -305,16 +316,19 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.purchase_receipt_item = source_doc.name
|
||||
|
||||
elif doctype == "Purchase Invoice":
|
||||
target_doc.received_qty = -1 * source_doc.received_qty
|
||||
target_doc.rejected_qty = -1 * source_doc.rejected_qty
|
||||
target_doc.qty = -1* source_doc.qty
|
||||
target_doc.stock_qty = -1 * source_doc.stock_qty
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
||||
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
|
||||
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
|
||||
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
|
||||
target_doc.purchase_order = source_doc.purchase_order
|
||||
target_doc.purchase_receipt = source_doc.purchase_receipt
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
target_doc.po_detail = source_doc.po_detail
|
||||
target_doc.pr_detail = source_doc.pr_detail
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
target_doc.price_list_rate = 0
|
||||
|
||||
elif doctype == "Delivery Note":
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
||||
@ -330,12 +344,17 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
elif doctype == "Sales Invoice" or doctype == "POS Invoice":
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
|
||||
|
||||
target_doc.sales_order = source_doc.sales_order
|
||||
target_doc.delivery_note = source_doc.delivery_note
|
||||
target_doc.so_detail = source_doc.so_detail
|
||||
target_doc.dn_detail = source_doc.dn_detail
|
||||
target_doc.expense_account = source_doc.expense_account
|
||||
target_doc.sales_invoice_item = source_doc.name
|
||||
target_doc.price_list_rate = 0
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
|
||||
@ -406,4 +425,22 @@ def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, ite
|
||||
if reference_voucher_detail_no:
|
||||
filters["voucher_detail_no"] = reference_voucher_detail_no
|
||||
|
||||
return filters
|
||||
return filters
|
||||
|
||||
def get_returned_serial_nos(child_doc, parent_doc):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
return_ref_field = frappe.scrub(child_doc.doctype)
|
||||
if child_doc.doctype == "Delivery Note Item":
|
||||
return_ref_field = "dn_detail"
|
||||
|
||||
serial_nos = []
|
||||
|
||||
fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
|
||||
|
||||
filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1],
|
||||
[child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]]
|
||||
|
||||
for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters):
|
||||
serial_nos.extend(get_serial_nos(row.serial_no))
|
||||
|
||||
return serial_nos
|
@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form
|
||||
from frappe.utils import cint, flt, cstr, get_link_to_form, nowtime
|
||||
from frappe import _, throw
|
||||
from erpnext.stock.get_item_details import get_bin_details
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
@ -49,7 +49,6 @@ class SellingController(StockController):
|
||||
self.set_customer_address()
|
||||
self.validate_for_duplicate_items()
|
||||
self.validate_target_warehouse()
|
||||
self.set_incoming_rate()
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
|
||||
@ -191,7 +190,7 @@ class SellingController(StockController):
|
||||
for it in self.get("items"):
|
||||
if not it.item_code:
|
||||
continue
|
||||
|
||||
|
||||
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
|
||||
last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1)
|
||||
if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||
@ -233,7 +232,7 @@ class SellingController(StockController):
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'dn_detail': d.get("dn_detail"),
|
||||
'incoming_rate': p.incoming_rate
|
||||
'incoming_rate': p.get("incoming_rate")
|
||||
}))
|
||||
else:
|
||||
il.append(frappe._dict({
|
||||
@ -252,7 +251,7 @@ class SellingController(StockController):
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'dn_detail': d.get("dn_detail"),
|
||||
'incoming_rate': d.incoming_rate
|
||||
'incoming_rate': d.get("incoming_rate")
|
||||
}))
|
||||
return il
|
||||
|
||||
@ -312,7 +311,7 @@ class SellingController(StockController):
|
||||
sales_order.update_reserved_qty(so_item_rows)
|
||||
|
||||
def set_incoming_rate(self):
|
||||
if self.doctype not in ("Delivery Note", "Sales Invoice"):
|
||||
if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
|
||||
return
|
||||
|
||||
items = self.get("items") + (self.get("packed_items") or [])
|
||||
@ -322,15 +321,26 @@ class SellingController(StockController):
|
||||
d.incoming_rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1*flt(d.qty),
|
||||
"serial_no": d.serial_no,
|
||||
"posting_date": self.get('posting_date') or self.get('transaction_date'),
|
||||
"posting_time": self.get('posting_time') or nowtime(),
|
||||
"qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
|
||||
"serial_no": d.get('serial_no'),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||
}, raise_error_if_no_rate=False)
|
||||
|
||||
# For internal transfers use incoming rate as the valuation rate
|
||||
if self.is_internal_transfer():
|
||||
rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate'))
|
||||
if d.rate != rate:
|
||||
d.rate = rate
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
|
||||
.format(d.idx), alert=1)
|
||||
|
||||
elif self.get("return_against"):
|
||||
# Get incoming rate of return entry from reference document
|
||||
# based on original item cost as per valuation method
|
||||
@ -391,7 +401,7 @@ class SellingController(StockController):
|
||||
})
|
||||
if item_row.warehouse:
|
||||
sle.dependant_sle_voucher_detail_no = item_row.name
|
||||
|
||||
|
||||
return sle
|
||||
|
||||
def set_po_nos(self, for_validate=False):
|
||||
|
@ -6,7 +6,8 @@ import frappe, erpnext
|
||||
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
|
||||
from frappe import _
|
||||
import frappe.defaults
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from collections import defaultdict
|
||||
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.stock.stock_ledger import get_valuation_rate
|
||||
@ -23,6 +24,8 @@ class StockController(AccountsController):
|
||||
self.validate_inspection()
|
||||
self.validate_serialized_batch()
|
||||
self.validate_customer_provided_item()
|
||||
self.validate_internal_transfer()
|
||||
self.validate_putaway_capacity()
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
if self.docstatus == 2:
|
||||
@ -72,6 +75,7 @@ class StockController(AccountsController):
|
||||
warehouse_with_no_account = []
|
||||
precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
|
||||
for item_row in voucher_details:
|
||||
|
||||
sle_list = sle_map.get(item_row.name)
|
||||
if sle_list:
|
||||
for sle in sle_list:
|
||||
@ -216,7 +220,7 @@ class StockController(AccountsController):
|
||||
""", (self.doctype, self.name), as_dict=True)
|
||||
|
||||
for sle in stock_ledger_entries:
|
||||
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||
return stock_ledger
|
||||
|
||||
def make_batches(self, warehouse_field):
|
||||
@ -391,6 +395,84 @@ class StockController(AccountsController):
|
||||
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
||||
d.allow_zero_valuation_rate = 1
|
||||
|
||||
def validate_internal_transfer(self):
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
|
||||
and self.is_internal_transfer():
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
|
||||
def validate_in_transit_warehouses(self):
|
||||
if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note':
|
||||
for item in self.get('items'):
|
||||
if not item.target_warehouse:
|
||||
frappe.throw(_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx))
|
||||
|
||||
if (self.doctype == 'Purchase Invoice' and self.get('update_stock')) or self.doctype == 'Purchase Receipt':
|
||||
for item in self.get('items'):
|
||||
if not item.from_warehouse:
|
||||
frappe.throw(_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx))
|
||||
|
||||
def validate_multi_currency(self):
|
||||
if self.currency != self.company_currency:
|
||||
frappe.throw(_("Internal transfers can only be done in company's default currency"))
|
||||
|
||||
def validate_packed_items(self):
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'):
|
||||
frappe.throw(_("Packed Items cannot be transferred internally"))
|
||||
|
||||
def validate_putaway_capacity(self):
|
||||
# if over receipt is attempted while 'apply putaway rule' is disabled
|
||||
# and if rule was applied on the transaction, validate it.
|
||||
from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
|
||||
valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
|
||||
"Stock Reconciliation")
|
||||
|
||||
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
|
||||
valid_doctype = False
|
||||
|
||||
if valid_doctype:
|
||||
rule_map = defaultdict(dict)
|
||||
for item in self.get("items"):
|
||||
warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
|
||||
rule = frappe.db.get_value("Putaway Rule",
|
||||
{
|
||||
"item_code": item.get("item_code"),
|
||||
"warehouse": item.get(warehouse_field)
|
||||
},
|
||||
["name", "disable"], as_dict=True)
|
||||
if rule:
|
||||
if rule.get("disabled"): continue # dont validate for disabled rule
|
||||
|
||||
if self.doctype == "Stock Reconciliation":
|
||||
stock_qty = flt(item.qty)
|
||||
else:
|
||||
stock_qty = flt(item.transfer_qty) if self.doctype == "Stock Entry" else flt(item.stock_qty)
|
||||
|
||||
rule_name = rule.get("name")
|
||||
if not rule_map[rule_name]:
|
||||
rule_map[rule_name]["warehouse"] = item.get(warehouse_field)
|
||||
rule_map[rule_name]["item"] = item.get("item_code")
|
||||
rule_map[rule_name]["qty_put"] = 0
|
||||
rule_map[rule_name]["capacity"] = get_available_putaway_capacity(rule_name)
|
||||
rule_map[rule_name]["qty_put"] += flt(stock_qty)
|
||||
|
||||
for rule, values in rule_map.items():
|
||||
if flt(values["qty_put"]) > flt(values["capacity"]):
|
||||
message = self.prepare_over_receipt_message(rule, values)
|
||||
frappe.throw(msg=message, title=_("Over Receipt"))
|
||||
|
||||
def prepare_over_receipt_message(self, rule, values):
|
||||
message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
|
||||
.format(
|
||||
frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
|
||||
frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
|
||||
)
|
||||
message += "<br><br>"
|
||||
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
|
||||
message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||
return message
|
||||
|
||||
def repost_future_sle_and_gle(self):
|
||||
args = frappe._dict({
|
||||
"posting_date": self.posting_date,
|
||||
@ -402,6 +484,14 @@ class StockController(AccountsController):
|
||||
|
||||
if check_if_future_sle_exists(args):
|
||||
create_repost_item_valuation_entry(args)
|
||||
elif not is_reposting_pending():
|
||||
check_if_stock_and_account_balance_synced(self.posting_date,
|
||||
self.company, self.doctype, self.name)
|
||||
|
||||
def is_reposting_pending():
|
||||
return frappe.db.exists("Repost Item Valuation",
|
||||
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
||||
|
||||
|
||||
def check_if_future_sle_exists(args):
|
||||
sl_entries = frappe.db.get_all("Stock Ledger Entry",
|
||||
|
@ -10,6 +10,7 @@ from erpnext.controllers.accounts_controller import validate_conversion_rate, \
|
||||
validate_taxes_and_charges, validate_inclusive_tax
|
||||
from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
|
||||
|
||||
class calculate_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
@ -758,3 +759,35 @@ def get_rounded_tax_amount(itemised_tax, precision):
|
||||
for taxes in itemised_tax.values():
|
||||
for tax_account in taxes:
|
||||
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
|
||||
|
||||
class init_landed_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
self.doc = doc
|
||||
self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
|
||||
self.set_account_currency()
|
||||
self.set_exchange_rate()
|
||||
self.set_amounts_in_company_currency()
|
||||
|
||||
def set_account_currency(self):
|
||||
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||
for d in self.doc.get(self.tax_field):
|
||||
if not d.account_currency:
|
||||
account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
|
||||
d.account_currency = account_currency or company_currency
|
||||
|
||||
def set_exchange_rate(self):
|
||||
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||
for d in self.doc.get(self.tax_field):
|
||||
if d.account_currency == company_currency:
|
||||
d.exchange_rate = 1
|
||||
elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date:
|
||||
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
|
||||
account_currency=d.account_currency, company=self.doc.company)
|
||||
|
||||
if not d.exchange_rate:
|
||||
frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
|
||||
|
||||
def set_amounts_in_company_currency(self):
|
||||
for d in self.doc.get(self.tax_field):
|
||||
d.amount = flt(d.amount, d.precision("amount"))
|
||||
d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
|
@ -6,6 +6,7 @@ import unittest
|
||||
|
||||
from erpnext.stock.doctype.item.test_item import set_item_variant_settings
|
||||
from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
|
||||
from erpnext.stock.doctype.quality_inspection.test_quality_inspection import create_quality_inspection_parameter
|
||||
|
||||
from six import string_types
|
||||
|
||||
@ -56,6 +57,8 @@ def make_quality_inspection_template():
|
||||
|
||||
qc = frappe.new_doc("Quality Inspection Template")
|
||||
qc.quality_inspection_template_name = qc_template
|
||||
|
||||
create_quality_inspection_parameter("Moisture")
|
||||
qc.append('item_quality_inspection_parameter', {
|
||||
"specification": "Moisture",
|
||||
"value": "< 5%",
|
||||
|
@ -126,7 +126,7 @@ class Appointment(Document):
|
||||
add_assignemnt({
|
||||
'doctype': self.doctype,
|
||||
'name': self.name,
|
||||
'assign_to': existing_assignee
|
||||
'assign_to': [existing_assignee]
|
||||
})
|
||||
return
|
||||
if self._assign:
|
||||
@ -139,7 +139,7 @@ class Appointment(Document):
|
||||
add_assignemnt({
|
||||
'doctype': self.doctype,
|
||||
'name': self.name,
|
||||
'assign_to': agent
|
||||
'assign_to': [agent]
|
||||
})
|
||||
break
|
||||
|
||||
|
@ -8,12 +8,12 @@
|
||||
"is_mandatory": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-05-14 17:38:27.496696",
|
||||
"modified": "2021-01-21 15:28:52.483839",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Opportunity",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Opportunity",
|
||||
"show_full_form": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Opportunity",
|
||||
"validate_action": 1
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
frappe.ui.form.on('Fee Schedule', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account');
|
||||
@ -8,6 +9,10 @@ frappe.ui.form.on('Fee Schedule', {
|
||||
frm.add_fetch('fee_structure', 'cost_center', 'cost_center');
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.set_query('receivable_account', function(doc) {
|
||||
return {
|
||||
@ -50,6 +55,8 @@ frappe.ui.form.on('Fee Schedule', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Fee Structure', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch('company', 'default_receivable_account', 'receivable_account');
|
||||
@ -8,6 +10,10 @@ frappe.ui.form.on('Fee Structure', {
|
||||
frm.add_fetch('company', 'cost_center', 'cost_center');
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.set_query('academic_term', function() {
|
||||
return {
|
||||
@ -35,6 +41,8 @@ frappe.ui.form.on('Fee Structure', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on("Fees", {
|
||||
setup: function(frm) {
|
||||
@ -9,15 +10,19 @@ frappe.ui.form.on("Fees", {
|
||||
frm.add_fetch("fee_structure", "cost_center", "cost_center");
|
||||
},
|
||||
|
||||
onload: function(frm){
|
||||
frm.set_query("academic_term",function(){
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.set_query("academic_term", function() {
|
||||
return{
|
||||
"filters":{
|
||||
"filters": {
|
||||
"academic_year": (frm.doc.academic_year)
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query("fee_structure",function(){
|
||||
frm.set_query("fee_structure", function() {
|
||||
return{
|
||||
"filters":{
|
||||
"academic_year": (frm.doc.academic_year)
|
||||
@ -45,6 +50,8 @@ frappe.ui.form.on("Fees", {
|
||||
if (!frm.doc.posting_date) {
|
||||
frm.doc.posting_date = frappe.datetime.get_today();
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -124,21 +124,24 @@ class ProgramEnrollment(Document):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get('program'):
|
||||
return frappe.db.sql("""select course, course_name from `tabProgram Course`
|
||||
where parent = %(program)s and course like %(txt)s {match_cond}
|
||||
order by
|
||||
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
|
||||
idx desc,
|
||||
`tabProgram Course`.course asc
|
||||
limit {start}, {page_len}""".format(
|
||||
match_cond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len), {
|
||||
"txt": "%{0}%".format(txt),
|
||||
"_txt": txt.replace('%', ''),
|
||||
"program": filters['program']
|
||||
})
|
||||
if not filters.get('program'):
|
||||
frappe.msgprint(_("Please select a Program first."))
|
||||
return []
|
||||
|
||||
return frappe.db.sql("""select course, course_name from `tabProgram Course`
|
||||
where parent = %(program)s and course like %(txt)s {match_cond}
|
||||
order by
|
||||
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
|
||||
idx desc,
|
||||
`tabProgram Course`.course asc
|
||||
limit {start}, {page_len}""".format(
|
||||
match_cond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len), {
|
||||
"txt": "%{0}%".format(txt),
|
||||
"_txt": txt.replace('%', ''),
|
||||
"program": filters['program']
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
|
@ -29,14 +29,11 @@ class PlaidConnector():
|
||||
response = self.client.Item.public_token.exchange(public_token)
|
||||
access_token = response["access_token"]
|
||||
return access_token
|
||||
|
||||
def get_link_token(self):
|
||||
|
||||
def get_token_request(self, update_mode=False):
|
||||
country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"]
|
||||
token_request = {
|
||||
args = {
|
||||
"client_name": self.client_name,
|
||||
"client_id": self.settings.plaid_client_id,
|
||||
"secret": self.settings.plaid_secret,
|
||||
"products": self.products,
|
||||
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
|
||||
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
||||
"country_codes": country_codes,
|
||||
@ -45,6 +42,20 @@ class PlaidConnector():
|
||||
}
|
||||
}
|
||||
|
||||
if update_mode:
|
||||
args["access_token"] = self.access_token
|
||||
else:
|
||||
args.update({
|
||||
"client_id": self.settings.plaid_client_id,
|
||||
"secret": self.settings.plaid_secret,
|
||||
"products": self.products,
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
def get_link_token(self, update_mode=False):
|
||||
token_request = self.get_token_request(update_mode)
|
||||
|
||||
try:
|
||||
response = self.client.LinkToken.create(token_request)
|
||||
except InvalidRequestError:
|
||||
|
@ -12,9 +12,25 @@ frappe.ui.form.on('Plaid Settings', {
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.enabled) {
|
||||
frm.add_custom_button('Link a new bank account', () => {
|
||||
frm.add_custom_button(__('Link a new bank account'), () => {
|
||||
new erpnext.integrations.plaidLink(frm);
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Sync Now"), () => {
|
||||
frappe.call({
|
||||
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
|
||||
freeze: true,
|
||||
callback: () => {
|
||||
let bank_transaction_link = '<a href="#List/Bank Transaction">Bank Transaction</a>';
|
||||
|
||||
frappe.msgprint({
|
||||
title: __("Sync Started"),
|
||||
message: __("The sync has started in the background, please check the {0} list for new records.", [bank_transaction_link]),
|
||||
alert: 1
|
||||
});
|
||||
}
|
||||
});
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -30,10 +46,18 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
this.product = ["auth", "transactions"];
|
||||
this.plaid_env = this.frm.doc.plaid_env;
|
||||
this.client_name = frappe.boot.sitename;
|
||||
this.token = await this.frm.call("get_link_token").then(resp => resp.message);
|
||||
this.token = await this.get_link_token();
|
||||
this.init_plaid();
|
||||
}
|
||||
|
||||
async get_link_token() {
|
||||
const token = await this.frm.call("get_link_token").then(resp => resp.message);
|
||||
if (!token) {
|
||||
frappe.throw(__('Cannot retrieve link token. Check Error Log for more information'));
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
init_plaid() {
|
||||
const me = this;
|
||||
me.loadScript(me.plaidUrl)
|
||||
@ -78,8 +102,8 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
}
|
||||
|
||||
onScriptError(error) {
|
||||
frappe.msgprint("There was an issue connecting to Plaid's authentication server");
|
||||
frappe.msgprint(error);
|
||||
frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
plaid_success(token, response) {
|
||||
@ -107,4 +131,4 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
});
|
||||
}, __("Select a company"), __("Continue"));
|
||||
}
|
||||
};
|
||||
};
|
@ -166,7 +166,6 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
||||
related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True)
|
||||
access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
|
||||
account_id = related_bank[0].integration_id
|
||||
|
||||
else:
|
||||
access_token = frappe.db.get_value("Bank", bank, "plaid_access_token")
|
||||
account_id = None
|
||||
@ -228,13 +227,23 @@ def new_bank_transaction(transaction):
|
||||
|
||||
def automatic_synchronization():
|
||||
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
|
||||
|
||||
if settings.enabled == 1 and settings.automatic_sync == 1:
|
||||
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
|
||||
enqueue_synchronization()
|
||||
|
||||
for plaid_account in plaid_accounts:
|
||||
frappe.enqueue(
|
||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
|
||||
bank=plaid_account.bank,
|
||||
bank_account=plaid_account.name
|
||||
)
|
||||
@frappe.whitelist()
|
||||
def enqueue_synchronization():
|
||||
plaid_accounts = frappe.get_all("Bank Account",
|
||||
filters={"integration_id": ["!=", ""]},
|
||||
fields=["name", "bank"])
|
||||
|
||||
for plaid_account in plaid_accounts:
|
||||
frappe.enqueue(
|
||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
|
||||
bank=plaid_account.bank,
|
||||
bank_account=plaid_account.name
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_link_token_for_update(access_token):
|
||||
plaid = PlaidConnector(access_token)
|
||||
return plaid.get_link_token(update_mode=True)
|
||||
|
@ -6,3 +6,5 @@ class PartyFrozen(frappe.ValidationError): pass
|
||||
class InvalidAccountCurrency(frappe.ValidationError): pass
|
||||
class InvalidCurrency(frappe.ValidationError): pass
|
||||
class PartyDisabled(frappe.ValidationError):pass
|
||||
class InvalidAccountDimensionError(frappe.ValidationError): pass
|
||||
class MandatoryAccountDimensionError(frappe.ValidationError): pass
|
||||
|
@ -100,7 +100,6 @@ class ClinicalProcedure(Document):
|
||||
allow_start = self.set_actual_qty()
|
||||
if allow_start:
|
||||
self.db_set('status', 'In Progress')
|
||||
insert_clinical_procedure_to_medical_record(self)
|
||||
return 'success'
|
||||
return 'insufficient stock'
|
||||
|
||||
@ -247,21 +246,3 @@ def make_procedure(source_name, target_doc=None):
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def insert_clinical_procedure_to_medical_record(doc):
|
||||
subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
|
||||
if doc.practitioner:
|
||||
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
|
||||
if subject and doc.notes:
|
||||
subject += '<br/>' + doc.notes
|
||||
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.start_date
|
||||
medical_record.reference_doctype = 'Clinical Procedure'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
@ -17,6 +17,9 @@
|
||||
"enable_free_follow_ups",
|
||||
"max_visits",
|
||||
"valid_days",
|
||||
"inpatient_settings_section",
|
||||
"allow_discharge_despite_unbilled_services",
|
||||
"do_not_bill_inpatient_encounters",
|
||||
"healthcare_service_items",
|
||||
"inpatient_visit_charge_item",
|
||||
"op_consulting_charge_item",
|
||||
@ -302,11 +305,28 @@
|
||||
"fieldname": "enable_free_follow_ups",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Free Follow-ups"
|
||||
},
|
||||
{
|
||||
"fieldname": "inpatient_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Inpatient Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_discharge_despite_unbilled_services",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Discharge Despite Unbilled Healthcare Services"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "do_not_bill_inpatient_encounters",
|
||||
"fieldtype": "Check",
|
||||
"label": "Do Not Bill Patient Encounters for Inpatients"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-08 15:17:21.543218",
|
||||
"modified": "2021-01-13 09:04:35.877700",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Healthcare Settings",
|
||||
|
@ -5,6 +5,7 @@ frappe.ui.form.on('Inpatient Medication Entry', {
|
||||
refresh: function(frm) {
|
||||
// Ignore cancellation of doctype on cancel all
|
||||
frm.ignore_doctypes_on_cancel_all = ['Stock Entry'];
|
||||
frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide();
|
||||
|
||||
frm.set_query('item_code', () => {
|
||||
return {
|
||||
|
@ -139,7 +139,6 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Inpatient Medication Orders",
|
||||
"options": "Inpatient Medication Entry Detail",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -180,7 +179,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-03 13:22:37.820707",
|
||||
"modified": "2021-01-11 12:37:46.749659",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Inpatient Medication Entry",
|
||||
|
@ -15,8 +15,6 @@ class InpatientMedicationEntry(Document):
|
||||
self.validate_medication_orders()
|
||||
|
||||
def get_medication_orders(self):
|
||||
self.validate_datetime_filters()
|
||||
|
||||
# pull inpatient medication orders based on selected filters
|
||||
orders = get_pending_medication_orders(self)
|
||||
|
||||
@ -27,22 +25,6 @@ class InpatientMedicationEntry(Document):
|
||||
self.set('medication_orders', [])
|
||||
frappe.msgprint(_('No pending medication orders found for selected criteria'))
|
||||
|
||||
def validate_datetime_filters(self):
|
||||
if self.from_date and self.to_date:
|
||||
self.validate_from_to_dates('from_date', 'to_date')
|
||||
|
||||
if self.from_date and getdate(self.from_date) > getdate():
|
||||
frappe.throw(_('From Date cannot be after the current date.'))
|
||||
|
||||
if self.to_date and getdate(self.to_date) > getdate():
|
||||
frappe.throw(_('To Date cannot be after the current date.'))
|
||||
|
||||
if self.from_time and self.from_time > nowtime():
|
||||
frappe.throw(_('From Time cannot be after the current time.'))
|
||||
|
||||
if self.to_time and self.to_time > nowtime():
|
||||
frappe.throw(_('To Time cannot be after the current time.'))
|
||||
|
||||
def add_mo_to_table(self, orders):
|
||||
# Add medication orders in the child table
|
||||
self.set('medication_orders', [])
|
||||
@ -282,7 +264,7 @@ def get_filters(entry):
|
||||
|
||||
def get_current_healthcare_service_unit(inpatient_record):
|
||||
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
|
||||
if ip_record.inpatient_occupancies:
|
||||
if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies:
|
||||
return ip_record.inpatient_occupancies[-1].service_unit
|
||||
return
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
from frappe import _
|
||||
from frappe.utils import today, now_datetime, getdate, get_datetime
|
||||
from frappe.utils import today, now_datetime, getdate, get_datetime, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from frappe.desk.reportview import get_match_cond
|
||||
|
||||
@ -113,6 +113,7 @@ def schedule_inpatient(args):
|
||||
inpatient_record.status = 'Admission Scheduled'
|
||||
inpatient_record.save(ignore_permissions = True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def schedule_discharge(args):
|
||||
discharge_order = json.loads(args)
|
||||
@ -126,16 +127,19 @@ def schedule_discharge(args):
|
||||
frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status)
|
||||
frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
|
||||
|
||||
|
||||
def set_details_from_ip_order(inpatient_record, ip_order):
|
||||
for key in ip_order:
|
||||
inpatient_record.set(key, ip_order[key])
|
||||
|
||||
|
||||
def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
|
||||
for item in encounter_child:
|
||||
table = inpatient_record.append(inpatient_record_child)
|
||||
for df in table.meta.get('fields'):
|
||||
table.set(df.fieldname, item.get(df.fieldname))
|
||||
|
||||
|
||||
def check_out_inpatient(inpatient_record):
|
||||
if inpatient_record.inpatient_occupancies:
|
||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||
@ -144,54 +148,88 @@ def check_out_inpatient(inpatient_record):
|
||||
inpatient_occupancy.check_out = now_datetime()
|
||||
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
||||
|
||||
|
||||
def discharge_patient(inpatient_record):
|
||||
validate_invoiced_inpatient(inpatient_record)
|
||||
validate_inpatient_invoicing(inpatient_record)
|
||||
inpatient_record.discharge_date = today()
|
||||
inpatient_record.status = "Discharged"
|
||||
|
||||
inpatient_record.save(ignore_permissions = True)
|
||||
|
||||
def validate_invoiced_inpatient(inpatient_record):
|
||||
pending_invoices = []
|
||||
|
||||
def validate_inpatient_invoicing(inpatient_record):
|
||||
if frappe.db.get_single_value("Healthcare Settings", "allow_discharge_despite_unbilled_services"):
|
||||
return
|
||||
|
||||
pending_invoices = get_pending_invoices(inpatient_record)
|
||||
|
||||
if pending_invoices:
|
||||
message = _("Cannot mark Inpatient Record as Discharged since there are unbilled services. ")
|
||||
|
||||
formatted_doc_rows = ''
|
||||
|
||||
for doctype, docnames in pending_invoices.items():
|
||||
formatted_doc_rows += """
|
||||
<td>{0}</td>
|
||||
<td>{1}</td>
|
||||
</tr>""".format(doctype, docnames)
|
||||
|
||||
message += """
|
||||
<table class='table'>
|
||||
<thead>
|
||||
<th>{0}</th>
|
||||
<th>{1}</th>
|
||||
</thead>
|
||||
{2}
|
||||
</table>
|
||||
""".format(_("Healthcare Service"), _("Documents"), formatted_doc_rows)
|
||||
|
||||
frappe.throw(message, title=_("Unbilled Services"), is_minimizable=True, wide=True)
|
||||
|
||||
|
||||
def get_pending_invoices(inpatient_record):
|
||||
pending_invoices = {}
|
||||
if inpatient_record.inpatient_occupancies:
|
||||
service_unit_names = False
|
||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||
if inpatient_occupancy.invoiced != 1:
|
||||
if not inpatient_occupancy.invoiced:
|
||||
if service_unit_names:
|
||||
service_unit_names += ", " + inpatient_occupancy.service_unit
|
||||
else:
|
||||
service_unit_names = inpatient_occupancy.service_unit
|
||||
if service_unit_names:
|
||||
pending_invoices.append("Inpatient Occupancy (" + service_unit_names + ")")
|
||||
pending_invoices["Inpatient Occupancy"] = service_unit_names
|
||||
|
||||
docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"]
|
||||
|
||||
for doc in docs:
|
||||
doc_name_list = get_inpatient_docs_not_invoiced(doc, inpatient_record)
|
||||
doc_name_list = get_unbilled_inpatient_docs(doc, inpatient_record)
|
||||
if doc_name_list:
|
||||
pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
|
||||
|
||||
if pending_invoices:
|
||||
frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", "
|
||||
.join(pending_invoices)), title=_('Unbilled Invoices'))
|
||||
return pending_invoices
|
||||
|
||||
|
||||
def get_pending_doc(doc, doc_name_list, pending_invoices):
|
||||
if doc_name_list:
|
||||
doc_ids = False
|
||||
for doc_name in doc_name_list:
|
||||
doc_link = get_link_to_form(doc, doc_name.name)
|
||||
if doc_ids:
|
||||
doc_ids += ", "+doc_name.name
|
||||
doc_ids += ", " + doc_link
|
||||
else:
|
||||
doc_ids = doc_name.name
|
||||
doc_ids = doc_link
|
||||
if doc_ids:
|
||||
pending_invoices.append(doc + " (" + doc_ids + ")")
|
||||
pending_invoices[doc] = doc_ids
|
||||
|
||||
return pending_invoices
|
||||
|
||||
def get_inpatient_docs_not_invoiced(doc, inpatient_record):
|
||||
|
||||
def get_unbilled_inpatient_docs(doc, inpatient_record):
|
||||
return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient,
|
||||
'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
|
||||
|
||||
|
||||
def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
|
||||
inpatient_record.admitted_datetime = check_in
|
||||
inpatient_record.status = 'Admitted'
|
||||
@ -203,6 +241,7 @@ def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=N
|
||||
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted')
|
||||
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name)
|
||||
|
||||
|
||||
def transfer_patient(inpatient_record, service_unit, check_in):
|
||||
item_line = inpatient_record.append('inpatient_occupancies', {})
|
||||
item_line.service_unit = service_unit
|
||||
@ -212,6 +251,7 @@ def transfer_patient(inpatient_record, service_unit, check_in):
|
||||
|
||||
frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied")
|
||||
|
||||
|
||||
def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
||||
if inpatient_record.inpatient_occupancies:
|
||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||
@ -221,6 +261,7 @@ def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
||||
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
||||
inpatient_record.save(ignore_permissions = True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_leave_from(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
@ -8,6 +8,8 @@ import unittest
|
||||
from frappe.utils import now_datetime, today
|
||||
from frappe.utils.make_random import get_random
|
||||
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||
from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter
|
||||
from erpnext.healthcare.utils import get_encounters_to_invoice
|
||||
|
||||
class TestInpatientRecord(unittest.TestCase):
|
||||
def test_admit_and_discharge(self):
|
||||
@ -40,6 +42,60 @@ class TestInpatientRecord(unittest.TestCase):
|
||||
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
||||
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
||||
|
||||
def test_allow_discharge_despite_unbilled_services(self):
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=1)
|
||||
patient = create_patient()
|
||||
# Schedule Admission
|
||||
ip_record = create_inpatient(patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save(ignore_permissions = True)
|
||||
|
||||
# Admit
|
||||
service_unit = get_healthcare_service_unit()
|
||||
admit_patient(ip_record, service_unit, now_datetime())
|
||||
|
||||
# Discharge
|
||||
schedule_discharge(frappe.as_json({"patient": patient}))
|
||||
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||
|
||||
ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||
# Should not validate Pending Invoices
|
||||
ip_record.discharge()
|
||||
|
||||
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
||||
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
||||
|
||||
setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=0)
|
||||
|
||||
def test_do_not_bill_patient_encounters_for_inpatients(self):
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=1)
|
||||
patient = create_patient()
|
||||
# Schedule Admission
|
||||
ip_record = create_inpatient(patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save(ignore_permissions = True)
|
||||
|
||||
# Admit
|
||||
service_unit = get_healthcare_service_unit()
|
||||
admit_patient(ip_record, service_unit, now_datetime())
|
||||
|
||||
# Patient Encounter
|
||||
patient_encounter = create_patient_encounter()
|
||||
encounters = get_encounters_to_invoice(patient, "_Test Company")
|
||||
encounter_ids = [entry.reference_name for entry in encounters]
|
||||
self.assertFalse(patient_encounter.name in encounter_ids)
|
||||
|
||||
# Discharge
|
||||
schedule_discharge(frappe.as_json({"patient": patient}))
|
||||
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||
|
||||
ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||
mark_invoiced_inpatient_occupancy(ip_record)
|
||||
discharge_patient(ip_record)
|
||||
setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=0)
|
||||
|
||||
def test_validate_overlap_admission(self):
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
patient = create_patient()
|
||||
@ -63,6 +119,13 @@ def mark_invoiced_inpatient_occupancy(ip_record):
|
||||
inpatient_occupancy.invoiced = 1
|
||||
ip_record.save(ignore_permissions = True)
|
||||
|
||||
|
||||
def setup_inpatient_settings(key, value):
|
||||
settings = frappe.get_single("Healthcare Settings")
|
||||
settings.set(key, value)
|
||||
settings.save()
|
||||
|
||||
|
||||
def create_inpatient(patient):
|
||||
patient_obj = frappe.get_doc('Patient', patient)
|
||||
inpatient_record = frappe.new_doc('Inpatient Record')
|
||||
@ -78,11 +141,16 @@ def create_inpatient(patient):
|
||||
inpatient_record.scheduled_date = today()
|
||||
return inpatient_record
|
||||
|
||||
def get_healthcare_service_unit():
|
||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||
|
||||
def get_healthcare_service_unit(unit_name=None):
|
||||
if not unit_name:
|
||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||
else:
|
||||
service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name})
|
||||
|
||||
if not service_unit:
|
||||
service_unit = frappe.new_doc("Healthcare Service Unit")
|
||||
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
|
||||
service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy"
|
||||
service_unit.company = "_Test Company"
|
||||
service_unit.service_unit_type = get_service_unit_type()
|
||||
service_unit.inpatient_occupancy = 1
|
||||
@ -105,6 +173,7 @@ def get_healthcare_service_unit():
|
||||
return service_unit.name
|
||||
return service_unit
|
||||
|
||||
|
||||
def get_service_unit_type():
|
||||
service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1})
|
||||
|
||||
@ -116,6 +185,7 @@ def get_service_unit_type():
|
||||
return service_unit_type.name
|
||||
return service_unit_type
|
||||
|
||||
|
||||
def create_patient():
|
||||
patient = frappe.db.exists('Patient', '_Test IPD Patient')
|
||||
if not patient:
|
||||
|
@ -359,6 +359,7 @@
|
||||
{
|
||||
"fieldname": "normal_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Normal Test Result",
|
||||
"options": "Normal Test Result",
|
||||
"print_hide": 1
|
||||
},
|
||||
@ -380,6 +381,7 @@
|
||||
{
|
||||
"fieldname": "sensitivity_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Sensitivity Test Result",
|
||||
"options": "Sensitivity Test Result",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
@ -529,6 +531,7 @@
|
||||
{
|
||||
"fieldname": "descriptive_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Descriptive Test Result",
|
||||
"options": "Descriptive Test Result",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
@ -549,13 +552,14 @@
|
||||
{
|
||||
"fieldname": "organism_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Organism Test Result",
|
||||
"options": "Organism Test Result",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-30 18:18:38.516215",
|
||||
"modified": "2020-11-30 11:04:17.195848",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Lab Test",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user