diff --git a/erpnext/__init__.py b/erpnext/__init__.py index f40b9575632..786b9cfd164 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.2.0' +__version__ = '12.0.0-dev' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index d5a36b82599..0a72d4fa4e6 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -102,7 +102,7 @@ class Account(NestedSet): if not frappe.db.get_value("Account", {'account_name': self.account_name, 'company': ancestors[0]}, 'name'): frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) - else: + elif self.parent_account: descendants = get_descendants_of('Company', self.company) if not descendants: return parent_acc_name_map = {} diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json index ff95c5ae194..3fc109bfd67 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json @@ -2433,29 +2433,26 @@ "Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": { "account_number": "4849" }, - "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn) (Gruppe)": { - "is_group": 1, - "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": { - "account_number": "4850" - }, - "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (bei Buchgewinn)": { - "account_number": "4851" - }, - "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": { - "account_number": "4852" - }, - "Anlagenabg\u00e4nge Sachanlagen (Restbuchwert bei Buchvergewinn)": { - "account_number": "4855" - }, - "Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": { - "account_number": "4856" - }, - "Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": { - "account_number": "4857" - }, - "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": { - "account_number": "4858" - } + "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": { + "account_number": "4850" + }, + "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (bei Buchgewinn)": { + "account_number": "4851" + }, + "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": { + "account_number": "4852" + }, + "Anlagenabg\u00e4nge Sachanlagen (Restbuchwert bei Buchvergewinn)": { + "account_number": "4855" + }, + "Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": { + "account_number": "4856" + }, + "Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": { + "account_number": "4857" + }, + "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": { + "account_number": "4858" }, "Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": { "account_number": "4910", @@ -2578,20 +2575,17 @@ "Entnahme von Gegenst\u00e4nden ohne USt": { "account_number": "4605" }, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt (Gruppe)": { - "is_group": 1, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": { - "account_number": "4630" - }, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt": { - "account_number": "4637" - }, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternnehmens ohne USt (Telefon-Nutzung)": { - "account_number": "4638" - }, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": { - "account_number": "4639" - } + "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": { + "account_number": "4630" + }, + "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt": { + "account_number": "4637" + }, + "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternnehmens ohne USt (Telefon-Nutzung)": { + "account_number": "4638" + }, + "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": { + "account_number": "4639" }, "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": { "is_group": 1, @@ -2629,14 +2623,11 @@ "Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": { "account_number": "4689" }, - "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze) (Gruppe)": { - "is_group": 1, - "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": { - "account_number": "4690" - }, - "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": { - "account_number": "4695" - } + "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": { + "account_number": "4690" + }, + "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": { + "account_number": "4695" }, "Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": { "is_group": 1, @@ -2646,41 +2637,35 @@ "Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": { "account_number": "7401" }, - "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam (Gruppe)": { - "is_group": 1, - "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": { - "account_number": "7450" - }, - "Ertr\u00e4ge durch Verschmelzung und Umwandlung": { - "account_number": "7451" - }, - "Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": { - "account_number": "7452" - }, - "Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": { - "account_number": "7453" - }, - "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": { - "account_number": "7454" - } + "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": { + "account_number": "7450" }, - "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften (Gruppe)": { - "is_group": 1, - "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": { - "account_number": "7460" - }, - "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": { - "account_number": "7461" - }, - "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": { - "account_number": "7462" - }, - "Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": { - "account_number": "7463" - }, - "Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": { - "account_number": "7464" - } + "Ertr\u00e4ge durch Verschmelzung und Umwandlung": { + "account_number": "7451" + }, + "Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": { + "account_number": "7452" + }, + "Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": { + "account_number": "7453" + }, + "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": { + "account_number": "7454" + }, + "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": { + "account_number": "7460" + }, + "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": { + "account_number": "7461" + }, + "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": { + "account_number": "7462" + }, + "Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": { + "account_number": "7463" + }, + "Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": { + "account_number": "7464" } } }, @@ -2718,40 +2703,43 @@ }, "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": { "account_number": "4729" + } + }, + "Gew\u00e4hrte Skonti (Gruppe)": { + "is_group": 1, + "Gew. Skonti": { + "account_number": "4730" }, - "Gew\u00e4hrte Skonti (Gruppe)": { - "is_group": 1, - "Gew. Skonti": { - "account_number": "4730" - }, - "Gew. Skonti 7 % USt": { - "account_number": "4731" - }, - "Gew. Skonti 19 % USt": { - "account_number": "4736" - }, - "Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": { - "account_number": "4738" - }, - "Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": { - "account_number": "4741" - }, - "Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": { - "account_number": "4742" - }, - "Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": { - "account_number": "4743" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": { - "account_number": "4745" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": { - "account_number": "4746" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": { - "account_number": "4748" - } + "Gew. Skonti 7 % USt": { + "account_number": "4731" }, + "Gew. Skonti 19 % USt": { + "account_number": "4736" + }, + "Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": { + "account_number": "4738" + }, + "Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": { + "account_number": "4741" + }, + "Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": { + "account_number": "4742" + }, + "Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": { + "account_number": "4743" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": { + "account_number": "4745" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": { + "account_number": "4746" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": { + "account_number": "4748" + } + }, + "Gew\u00e4hrte Boni (Gruppe)": { + "is_group": 1, "Gew\u00e4hrte Boni 7 % USt": { "account_number": "4750" }, @@ -2864,103 +2852,79 @@ "account_number": "6398" } }, - "Versicherungen (Gruppe)": { - "is_group": 1, - "Versicherungen": { - "account_number": "6400" - }, - "Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": { - "account_number": "6405" - }, - "Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": { - "account_number": "6410" - }, - "Beitr\u00e4ge": { - "account_number": "6420" - }, - "Sonstige Abgaben": { - "account_number": "6430" - }, - "Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { - "account_number": "6436" - }, - "Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { - "account_number": "6437" - }, - "Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": { - "account_number": "6440" - }, - "Reparaturen und Instandhaltung von Bauten": { - "account_number": "6450" - }, - "Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": { - "account_number": "6460" - }, - "Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": { - "account_number": "6470" - }, - "Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": { - "account_number": "6475" - }, - "Reparaturen und Instandhaltung von anderen Anlagen": { - "account_number": "6485" - }, - "Sonstige Reparaturen und Instandhaltungen": { - "account_number": "6490" - }, - "Wartungskosten f. Hard- und Software": { - "account_number": "6495" - }, - "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": { - "account_number": "6498" - } + "Versicherungen": { + "account_number": "6400" + }, + "Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": { + "account_number": "6405" + }, + "Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": { + "account_number": "6410" + }, + "Beitr\u00e4ge": { + "account_number": "6420" + }, + "Sonstige Abgaben": { + "account_number": "6430" + }, + "Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { + "account_number": "6436" + }, + "Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { + "account_number": "6437" + }, + "Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": { + "account_number": "6440" + }, + "Reparaturen und Instandhaltung von Bauten": { + "account_number": "6450" + }, + "Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": { + "account_number": "6460" + }, + "Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": { + "account_number": "6470" + }, + "Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": { + "account_number": "6475" + }, + "Reparaturen und Instandhaltung von anderen Anlagen": { + "account_number": "6485" + }, + "Sonstige Reparaturen und Instandhaltungen": { + "account_number": "6490" + }, + "Wartungskosten f. Hard- und Software": { + "account_number": "6495" + }, + "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": { + "account_number": "6498" }, "Fahrzeugkosten (Gruppe)": { "is_group": 1, "Fahrzeugkosten": { "account_number": "6500" }, - "Kfz-Versicherungen (Gruppe)": { - "is_group": 1, - "Kfz-Versicherungen": { - "account_number": "6520" - } + "Kfz-Versicherungen": { + "account_number": "6520" }, - "Laufende Kfz-Betriebskosten (Gruppe)": { - "is_group": 1, - "Laufende Kfz-Betriebskosten": { - "account_number": "6530" - } + "Laufende Kfz-Betriebskosten": { + "account_number": "6530" }, - "Kfz-Reparaturen (Gruppe)": { - "is_group": 1, - "Kfz-Reparaturen": { - "account_number": "6540" - } + "Kfz-Reparaturen": { + "account_number": "6540" }, - "Garagenmiete (Gruppe)": { - "is_group": 1, - "Garagenmiete": { - "account_number": "6550" - } + "Garagenmiete": { + "account_number": "6550" }, - "Mietleasing Kfz (Gruppe)": { - "is_group": 1, - "Mietleasing Kfz": { - "account_number": "6560" - } + "Mietleasing Kfz": { + "account_number": "6560" }, - "Sonstige Kfz-Kosten (Gruppe)": { - "is_group": 1, - "Sonstige Kfz-Kosten": { - "account_number": "6570" - } + "Sonstige Kfz-Kosten": { + "account_number": "6570" }, - "Mautgeb\u00fchren (Gruppe)": { - "is_group": 1, - "Mautgeb\u00fchren": { - "account_number": "6580" - } + "Mautgeb\u00fchren": { + "account_number": "6580" }, "Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": { "account_number": "6590" @@ -3022,20 +2986,23 @@ "Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": { "account_number": "6645" }, - "Reisekosten Arbeitnehmer": { - "account_number": "6650" - }, - "Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": { - "account_number": "6660" - }, - "Reisekosten Arbeitnehmer Fahrtkosten": { - "account_number": "6663" - }, - "Reisekosten Arbeitnehmer Verpflegungsmehraufwand": { - "account_number": "6664" - }, - "Kilometergelderstattung Arbeitnehmer": { - "account_number": "6668" + "Reisekosten Arbeitnehmer (Gruppe)": { + "is_group": 1, + "Reisekosten Arbeitnehmer": { + "account_number": "6650" + }, + "Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": { + "account_number": "6660" + }, + "Reisekosten Arbeitnehmer Fahrtkosten": { + "account_number": "6663" + }, + "Reisekosten Arbeitnehmer Verpflegungsmehraufwand": { + "account_number": "6664" + }, + "Kilometergelderstattung Arbeitnehmer": { + "account_number": "6668" + } }, "Reisekosten Unternehmer (Gruppe)": { "is_group": 1, diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index a36f4214d7c..3c12f85f938 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -48,12 +48,6 @@ frappe.ui.form.on('Accounting Dimension', { frm.set_value('label', frm.doc.document_type); frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type)); - if (frm.is_new()){ - let row = frappe.model.add_child(frm.doc, "Accounting Dimension Detail", "dimension_defaults"); - row.reference_document = frm.doc.document_type; - frm.refresh_fields("dimension_defaults"); - } - frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => { if (r && r.document_type) { frm.set_df_property('document_type', 'description', "Document type is already set as dimension"); diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json index cf6dc7a8fa5..cf55d554fb3 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "field:label", "creation": "2019-05-04 18:13:37.002352", "doctype": "DocType", @@ -46,7 +47,8 @@ "options": "Accounting Dimension Detail" } ], - "modified": "2019-07-17 16:49:31.134385", + "links": [], + "modified": "2020-03-22 20:34:39.805728", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension", @@ -63,9 +65,20 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 } ], - "quick_entry": 1, "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 463d29c9f83..de9498e0752 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -7,7 +7,20 @@ frappe.ui.form.on('Bank', { }, refresh: function(frm) { add_fields_to_mapping_table(frm); - } + + frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' }; + + frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); + + if (frm.doc.__islocal) { + frm.set_df_property('address_and_contact', 'hidden', 1); + frappe.contacts.clear_address_and_contact(frm); + } + else { + frm.set_df_property('address_and_contact', 'hidden', 0); + frappe.contacts.render_address_and_contact(frm); + } + }, }); diff --git a/erpnext/accounts/doctype/bank/bank.json b/erpnext/accounts/doctype/bank/bank.json index 4fa0e4f29ba..99978e657da 100644 --- a/erpnext/accounts/doctype/bank/bank.json +++ b/erpnext/accounts/doctype/bank/bank.json @@ -1,224 +1,137 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:bank_name", - "beta": 0, - "creation": "2018-04-07 16:59:59.496668", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:bank_name", + "creation": "2018-04-07 16:59:59.496668", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "bank_details_section", + "bank_name", + "swift_number", + "column_break_1", + "branch_code", + "website", + "address_and_contact", + "address_html", + "column_break_13", + "contact_html", + "data_import_configuration_section", + "bank_transaction_mapping", + "section_break_4", + "plaid_access_token" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Bank Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "fieldname": "bank_name", + "fieldtype": "Data", + "label": "Bank Name", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "data_import_configuration_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Data Import Configuration", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "bank_details_section", + "fieldtype": "Section Break", + "label": "Bank Details" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_transaction_mapping", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Bank Transaction Mapping", - "length": 0, - "no_copy": 0, - "options": "Bank Transaction Mapping", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_in_quick_entry": 1, + "fieldname": "swift_number", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "SWIFT number", + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_1", + "fieldtype": "Column Break", + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "plaid_access_token", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Plaid Access Token", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "allow_in_quick_entry": 1, + "fieldname": "branch_code", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Branch Code", + "unique": 1 + }, + { + "fieldname": "address_and_contact", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-map-marker" + }, + { + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML" + }, + { + "fieldname": "website", + "fieldtype": "Data", + "label": "Website" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML" + }, + { + "collapsible": 1, + "fieldname": "data_import_configuration_section", + "fieldtype": "Section Break", + "label": "Data Import Configuration" + }, + { + "fieldname": "bank_transaction_mapping", + "fieldtype": "Table", + "label": "Bank Transaction Mapping", + "options": "Bank Transaction Mapping" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "plaid_access_token", + "fieldtype": "Data", + "hidden": 1, + "label": "Plaid Access Token", + "no_copy": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-27 16:12:13.938776", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-03-25 21:22:33.496264", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank/bank.py b/erpnext/accounts/doctype/bank/bank.py index b205d566783..41aae14362f 100644 --- a/erpnext/accounts/doctype/bank/bank.py +++ b/erpnext/accounts/doctype/bank/bank.py @@ -5,6 +5,12 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address class Bank(Document): - pass + def onload(self): + """Load address and contacts in `__onload`""" + load_address_and_contact(self) + + def on_trash(self): + delete_contact_and_address('Bank', self.name) \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index c8ae26d9f2c..aa9c434db0b 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -24,8 +24,6 @@ "iban", "column_break_12", "bank_account_no", - "branch_code", - "swift_number", "address_and_contact", "address_html", "website", @@ -145,17 +143,6 @@ "label": "Bank Account No", "length": 30 }, - { - "fieldname": "branch_code", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Branch Code" - }, - { - "fieldname": "swift_number", - "fieldtype": "Data", - "label": "SWIFT number" - }, { "fieldname": "address_and_contact", "fieldtype": "Section Break", @@ -213,7 +200,7 @@ } ], "links": [], - "modified": "2020-01-29 20:42:26.458316", + "modified": "2020-01-30 20:42:26.458316", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js index 0acbe2009f5..065d25e6c34 100644 --- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js +++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js @@ -4,8 +4,8 @@ cur_frm.add_fetch('bank_account','account','account'); cur_frm.add_fetch('bank_account','bank_account_no','bank_account_no'); cur_frm.add_fetch('bank_account','iban','iban'); -cur_frm.add_fetch('bank_account','branch_code','branch_code'); -cur_frm.add_fetch('bank_account','swift_number','swift_number'); +cur_frm.add_fetch('bank','branch_code','branch_code'); +cur_frm.add_fetch('bank','swift_number','swift_number'); frappe.ui.form.on('Bank Guarantee', { setup: function(frm) { diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index e64bc9e0c78..2214811d8b3 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -114,13 +114,13 @@ "fieldname": "debit_in_account_currency", "fieldtype": "Currency", "label": "Debit Amount in Account Currency", - "options": "currency" + "options": "account_currency" }, { "fieldname": "credit_in_account_currency", "fieldtype": "Currency", "label": "Credit Amount in Account Currency", - "options": "currency" + "options": "account_currency" }, { "fieldname": "against", @@ -250,7 +250,7 @@ "icon": "fa fa-list", "idx": 1, "in_create": 1, - "modified": "2020-02-10 04:54:57.777905", + "modified": "2020-03-28 16:22:33.766994", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 968fb605713..d2080874533 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -33,7 +33,9 @@ frappe.ui.form.on('Payment Entry', { frm.set_query("party_bank_account", function() { return { filters: { - "is_company_account":0 + "is_company_account":0, + party_type: frm.doc.party_type, + party: frm.doc.party } } }); diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index bff995ec5a5..c1559a74f21 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -839,7 +839,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fetch_from": "bank_account.branch_code", + "fetch_from": "bank.branch_code", "fieldname": "branch_code", "fieldtype": "Read Only", "hidden": 0, @@ -873,7 +873,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fetch_from": "bank_account.swift_number", + "fetch_from": "bank.swift_number", "fieldname": "swift_number", "fieldtype": "Read Only", "hidden": 0, diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 0fade8c456c..7e9211af9f4 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -317,13 +317,13 @@ def make_payment_request(**args): "payment_request_type": args.get("payment_request_type"), "currency": ref_doc.currency, "grand_total": grand_total, - "email_to": args.recipient_id or "", + "email_to": args.recipient_id or ref_doc.owner, "subject": _("Payment Request for {0}").format(args.dn), "message": gateway_account.get("message") or get_dummy_message(ref_doc), "reference_doctype": args.dt, "reference_name": args.dn, - "party_type": args.get("party_type"), - "party": args.get("party"), + "party_type": args.get("party_type") or "Customer", + "party": args.get("party") or ref_doc.customer, "bank_account": bank_account }) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 422ace64f57..8567740ee70 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -281,8 +281,8 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren existing_gle_currency = get_party_gle_currency(party_type, party, company) if existing_gle_currency and party_account_currency != existing_gle_currency: - frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") - .format(party_type, party, existing_gle_currency), InvalidAccountCurrency) + frappe.throw(_("{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.") + .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency) def validate_party_accounts(doc): companies = [] @@ -295,15 +295,13 @@ def validate_party_accounts(doc): companies.append(account.company) party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True) - existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company) if frappe.db.get_default("Company"): company_default_currency = frappe.get_cached_value('Company', frappe.db.get_default("Company"), "default_currency") else: company_default_currency = frappe.db.get_value('Company', account.company, "default_currency") - if existing_gle_currency and party_account_currency != existing_gle_currency: - frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company)) + validate_party_gle_currency(doc.doctype, doc.name, account.company, party_account_currency) if doc.get("default_currency") and party_account_currency and company_default_currency: if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency: diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 791f3f8008b..bb0d0a132a5 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -218,15 +218,15 @@ {%= __("Total") %} - {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %} + {%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %} {% if(!filters.show_future_payments) { %} - {%= format_currency(data[i]["paid"], data[i]["currency"]) %} - {%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} + {%= format_currency(data[i]["paid"], data[0]["currency"]) %} + {%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} {% } %} - {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} + {%= format_currency(data[i]["outstanding"], data[0]["currency"]) %} {% if(filters.show_future_payments) { %} {% if(report.report_name === "Accounts Receivable") { %} @@ -234,8 +234,8 @@ {%= data[i]["po_no"] %} {% } %} {%= data[i]["future_ref"] %} - {%= format_currency(data[i]["future_amount"], data[i]["currency"]) %} - {%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %} + {%= format_currency(data[i]["future_amount"], data[0]["currency"]) %} + {%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %} {% } %} {% } %} {% } else { %} @@ -256,10 +256,10 @@ {% } else { %} {%= __("Total") %} {% } %} - {%= format_currency(data[i]["invoiced"], data[i]["currency"]) %} - {%= format_currency(data[i]["paid"], data[i]["currency"]) %} - {%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} - {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} + {%= format_currency(data[i]["invoiced"], data[0]["currency"]) %} + {%= format_currency(data[i]["paid"], data[0]["currency"]) %} + {%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} + {%= format_currency(data[i]["outstanding"], data[0]["currency"]) %} {% } %} {% } %} diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 8750c2325dd..898ac13e0e4 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -131,7 +131,7 @@ def get_gl_entries(filters): gl_entries = frappe.db.sql( """ select - posting_date, account, party_type, party, + name as gl_entry, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center, project, against_voucher_type, against_voucher, account_currency, remarks, against, is_opening {select_fields} @@ -362,6 +362,12 @@ def get_columns(filters): currency = get_company_currency(company) columns = [ + { + "fieldname": "gl_entry", + "fieldtype": "Link", + "options": "GL Entry", + "hidden": 1 + }, { "label": _("Posting Date"), "fieldname": "posting_date", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index a3264a4c0f8..3111a3a7d53 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -499,7 +499,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( reference_doctype: me.frm.doctype, reference_name: me.frm.docname, content: __('Reason for hold: ')+data.reason_for_hold, - comment_email: frappe.session.user + comment_email: frappe.session.user, + comment_by: frappe.session.user_fullname }, callback: function(r) { if(!r.exc) { diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py index b3b294d5701..d0d5b73984f 100644 --- a/erpnext/buying/doctype/supplier/supplier_dashboard.py +++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py @@ -34,4 +34,4 @@ def get_data(): 'items': ['Pricing Rule'] } ] - } + } \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index 39042b8b068..16061c61ba0 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -4,15 +4,17 @@ // attach required files {% include 'erpnext/public/js/controllers/buying.js' %}; -frappe.ui.form.on('Suppier Quotation', { - setup: function(frm) { - frm.custom_make_buttons = { - 'Purchase Order': 'Purchase Order' - } - } -}); - erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.extend({ + setup: function() { + this.frm.custom_make_buttons = { + 'Purchase Order': 'Purchase Order', + 'Quotation': 'Quotation', + 'Subscription': 'Subscription' + } + + this._super(); + }, + refresh: function() { var me = this; this._super(); diff --git a/erpnext/communication/desk_page/communication/communication.json b/erpnext/communication/desk_page/communication/communication.json deleted file mode 100644 index 59318fb8cdd..00000000000 --- a/erpnext/communication/desk_page/communication/communication.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "cards": [], - "charts": [], - "creation": "2020-01-28 11:49:55.003637", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "icon": "", - "idx": 0, - "is_standard": 1, - "label": "Communication", - "modified": "2020-03-12 16:30:40.534226", - "modified_by": "Administrator", - "module": "Communication", - "name": "Communication", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [] -} \ No newline at end of file diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 8d3db8d5341..fcc9098ba10 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -672,19 +672,32 @@ class BuyingController(StockController): # If asset has to be auto created # Check for asset naming series if item_data.get('asset_naming_series'): + created_assets = [] + for qty in range(cint(d.qty)): - self.make_asset(d) - is_plural = 's' if cint(d.qty) != 1 else '' - messages.append(_('{0} Asset{2} Created for {1}').format(cint(d.qty), d.item_code, is_plural)) + asset = self.make_asset(d) + created_assets.append(asset) + + if len(created_assets) > 5: + # dont show asset form links if more than 5 assets are created + messages.append(_('{} Asset{} created for {}').format(len(created_assets), is_plural, frappe.bold(d.item_code))) + else: + assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets)) + assets_link = frappe.bold(','.join(assets_link)) + + is_plural = 's' if len(created_assets) != 1 else '' + messages.append( + _('Asset{} {assets_link} created for {}').format(is_plural, frappe.bold(d.item_code), assets_link=assets_link) + ) else: - frappe.throw(_("Row {1}: Asset Naming Series is mandatory for the auto creation for item {0}") - .format(d.item_code, d.idx)) + frappe.throw(_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}") + .format(d.idx, frappe.bold(d.item_code))) else: - messages.append(_("Assets not created for {0}. You will have to create asset manually.") - .format(d.item_code)) + messages.append(_("Assets not created for {0}. You will have to create asset manually.") + .format(frappe.bold(d.item_code))) for message in messages: - frappe.msgprint(message, title="Success") + frappe.msgprint(message, title="Success", indicator="green") def make_asset(self, row): if not row.asset_location: @@ -716,6 +729,8 @@ class BuyingController(StockController): asset.set_missing_values() asset.insert() + return asset.name + def update_fixed_asset(self, field, delete_asset = False): for d in self.get("items"): if d.is_fixed_asset: @@ -745,7 +760,7 @@ class BuyingController(StockController): asset.supplier = None if asset.docstatus == 1 and delete_asset: frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\ - Please cancel the it to continue.').format(asset.name)) + Please cancel the it to continue.').format(frappe.utils.get_link_to_form('Asset', asset.name))) asset.flags.ignore_validate_update_after_submit = True asset.flags.ignore_mandatory = True @@ -1026,4 +1041,4 @@ def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty available_batches.append({'batch': batch, 'qty': available_qty}) required_qty -= available_qty - return available_batches \ No newline at end of file + return available_batches diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index d18f8e54d8f..163ef72ee10 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +import erpnext from frappe.desk.reportview import get_match_cond, get_filters_cond from frappe.utils import nowdate, getdate from collections import defaultdict @@ -129,23 +130,26 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): }) def tax_account_query(doctype, txt, searchfield, start, page_len, filters): + company_currency = erpnext.get_company_currency(filters.get('company')) + tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount where tabAccount.docstatus!=2 and account_type in (%s) and is_group = 0 and company = %s + and account_currency = %s and `%s` LIKE %s order by idx desc, name limit %s, %s""" % - (", ".join(['%s']*len(filters.get("account_type"))), "%s", searchfield, "%s", "%s", "%s"), - tuple(filters.get("account_type") + [filters.get("company"), "%%%s%%" % txt, + (", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"), + tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len])) if not tax_accounts: tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount where tabAccount.docstatus!=2 and is_group = 0 - and company = %s and `%s` LIKE %s limit %s, %s""" - % ("%s", searchfield, "%s", "%s", "%s"), - (filters.get("company"), "%%%s%%" % txt, start, page_len)) + and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec + % ("%s", "%s", searchfield, "%s", "%s", "%s"), + (filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len)) return tax_accounts diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index bc007b146f1..5299368f426 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -171,7 +171,6 @@ "options": "Customer" }, { - "depends_on": "eval: doc.source==\"Campaign\"", "fieldname": "campaign_name", "fieldtype": "Link", "label": "Campaign Name", @@ -512,4 +511,4 @@ "sort_field": "modified", "sort_order": "DESC", "title_field": "title" -} \ No newline at end of file +} diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 0e2068a0a57..08be34e6038 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -214,7 +215,8 @@ { "fieldname": "opportunity_amount", "fieldtype": "Currency", - "label": "Opportunity Amount" + "label": "Opportunity Amount", + "options": "currency" }, { "default": "0", @@ -418,7 +420,8 @@ ], "icon": "fa fa-info-sign", "idx": 195, - "modified": "2019-09-30 12:58:37.385400", + "links": [], + "modified": "2020-03-20 12:28:45.228994", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/demo/user/fixed_asset.py b/erpnext/demo/user/fixed_asset.py index e6d16872024..dc094e1c918 100644 --- a/erpnext/demo/user/fixed_asset.py +++ b/erpnext/demo/user/fixed_asset.py @@ -6,46 +6,28 @@ from __future__ import unicode_literals import frappe from frappe.utils.make_random import get_random -from erpnext.assets.doctype.asset.asset import make_purchase_invoice, make_sales_invoice +from erpnext.assets.doctype.asset.asset import make_sales_invoice from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset + def work(): frappe.set_user(frappe.db.get_global('demo_accounts_user')) - asset_list = make_asset_purchase_entry() - - if not asset_list: - # fixed_asset.work() already run - return - # Enable booking asset depreciation entry automatically frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) - + # post depreciation entries as on today post_depreciation_entries() - + # scrap a random asset frappe.db.set_value("Company", "Wind Power LLC", "disposal_account", "Gain/Loss on Asset Disposal - WPL") - + asset = get_random_asset() scrap_asset(asset.name) - - # Sell a random asset - sell_an_asset() -def make_asset_purchase_entry(): - asset_list = frappe.get_all("Asset", filters={"purchase_invoice": ["in", ("", None)]}, - fields=["name", "item_code", "gross_purchase_amount", "company", "purchase_date"]) - - # make purchase invoice - for asset in asset_list: - pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount, - asset.company, asset.purchase_date) - pi.supplier = get_random("Supplier") - pi.save() - pi.submit() - - return asset_list + # Sell a random asset + sell_an_asset() + def sell_an_asset(): asset = get_random_asset() @@ -55,8 +37,9 @@ def sell_an_asset(): if asset.value_after_depreciation else asset.gross_purchase_amount * 0.9 si.save() si.submit() - + + def get_random_asset(): return frappe.db.sql(""" select name, item_code, value_after_depreciation, gross_purchase_amount - from `tabAsset` + from `tabAsset` where docstatus=1 and status not in ("Scrapped", "Sold") order by rand() limit 1""", as_dict=1)[0] diff --git a/erpnext/education/doctype/course/course.json b/erpnext/education/doctype/course/course.json index 68426c36be1..da10db1857a 100644 --- a/erpnext/education/doctype/course/course.json +++ b/erpnext/education/doctype/course/course.json @@ -74,7 +74,7 @@ } ], "image_field": "hero_image", - "modified": "2019-06-12 12:34:23.748157", + "modified": "2020-03-29 12:50:27.677589", "modified_by": "Administrator", "module": "Education", "name": "Course", @@ -103,6 +103,30 @@ "role": "Instructor", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Education Manager", + "share": 1, + "write": 1 } ], "restrict_to_domain": "Education", diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index c2e5685ea79..759b0d8e094 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -273,11 +273,11 @@ class TestLoan(unittest.TestCase): penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year)) - lia = frappe.get_all("Loan Interest Accrual", fields=["is_paid"], - filters={"loan": loan.name}, order_by="posting_date") + lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name') + lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name') - self.assertTrue(lia[0].get('is_paid')) - self.assertFalse(lia[1].get('is_paid')) + self.assertTrue(lia1) + self.assertTrue(lia2) def test_security_shortfall(self): pledges = [] diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 3acaee4ffbd..0051ad9dffd 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -43,8 +43,7 @@ frappe.ui.form.on("BOM", { frm.set_query("item_code", "items", function() { return { - query: "erpnext.controllers.queries.item_query", - filters: [["Item", "name", "!=", cur_frm.doc.item]] + query: "erpnext.controllers.queries.item_query" }; }); @@ -135,6 +134,7 @@ frappe.ui.form.on("BOM", { frappe.call({ method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order", args: { + bom_no: frm.doc.name, item: frm.doc.item, qty: data.qty || 0.0, project: frm.doc.project diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index f6cdb2e57cf..b3e602bdfab 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -114,10 +114,6 @@ class BOM(WebsiteGenerator): child = self.append('operations', d) child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2) - def validate_rm_item(self, item): - if (item[0]['name'] in [it.item_code for it in self.items]) and item[0]['name'] == self.item: - frappe.throw(_("BOM #{0}: Raw material cannot be same as main Item").format(self.name)) - def set_bom_material_details(self): for item in self.get("items"): self.validate_bom_currecny(item) @@ -147,7 +143,6 @@ class BOM(WebsiteGenerator): args = json.loads(args) item = self.get_item_det(args['item_code']) - self.validate_rm_item(item) args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or '' args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index bc8c22998ae..8c7876d48de 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -20,7 +20,7 @@ frappe.ui.form.on('Job Card', { } } - if (frm.doc.docstatus == 0 && frm.doc.for_quantity > frm.doc.total_completed_qty + if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity) && (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) { frm.trigger("prepare_timer_buttons"); } @@ -59,10 +59,14 @@ frappe.ui.form.on('Job Card', { let completed_time = frappe.datetime.now_datetime(); frm.trigger("hide_timer"); - frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), - fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => { - frm.events.complete_job(frm, completed_time, data.qty); - }, __("Enter Value"), __("Complete")); + if (frm.doc.for_quantity) { + frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), + fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => { + frm.events.complete_job(frm, completed_time, data.qty); + }, __("Enter Value"), __("Complete")); + } else { + frm.events.complete_job(frm, completed_time, 0); + } }).addClass("btn-primary"); } }, diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 156accee74e..7661fffa864 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -99,8 +99,7 @@ "fieldname": "for_quantity", "fieldtype": "Float", "in_list_view": 1, - "label": "Qty To Manufacture", - "reqd": 1 + "label": "Qty To Manufacture" }, { "fieldname": "wip_warehouse", @@ -122,6 +121,7 @@ "options": "Employee" }, { + "allow_bulk_edit": 1, "fieldname": "time_logs", "fieldtype": "Table", "label": "Time Logs", @@ -290,7 +290,7 @@ } ], "is_submittable": 1, - "modified": "2019-12-03 13:08:57.926201", + "modified": "2020-03-27 13:36:35.417502", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 029db1cb4e8..f8c60f2a114 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -191,12 +191,9 @@ class JobCard(Document): if not self.time_logs: frappe.throw(_("Time logs are required for job card {0}").format(self.name)) - if self.total_completed_qty <= 0.0: - frappe.throw(_("Total completed qty must be greater than zero")) - - if self.total_completed_qty != self.for_quantity: - frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})") - .format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity))) + if self.for_quantity and self.total_completed_qty != self.for_quantity: + frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})" + .format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity)))) def update_work_order(self): if not self.work_order: @@ -205,27 +202,34 @@ class JobCard(Document): for_quantity, time_in_mins = 0, 0 from_time_list, to_time_list = [], [] - for d in frappe.get_all('Job Card', - filters = {'docstatus': 1, 'operation_id': self.operation_id}): - doc = frappe.get_doc('Job Card', d.name) - for_quantity += doc.total_completed_qty - time_in_mins += doc.total_time_in_mins - for time_log in doc.time_logs: - if time_log.from_time: - from_time_list.append(time_log.from_time) - if time_log.to_time: - to_time_list.append(time_log.to_time) + data = frappe.get_all('Job Card', + fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], + filters = {"docstatus": 1, "work_order": self.work_order, + "workstation": self.workstation, "operation": self.operation}) + + if data and len(data) > 0: + for_quantity = data[0].completed_qty + time_in_mins = data[0].time_in_mins if for_quantity: + time_data = frappe.db.sql(""" + SELECT + min(from_time) as start_time, max(to_time) as end_time + FROM `tabJob Card` jc, `tabJob Card Time Log` jctl + WHERE + jctl.parent = jc.name and jc.work_order = %s + and jc.workstation = %s and jc.operation = %s and jc.docstatus = 1 + """, (self.work_order, self.workstation, self.operation), as_dict=1) + wo = frappe.get_doc('Work Order', self.work_order) for data in wo.operations: - if data.name == self.operation_id: + if data.workstation == self.workstation and data.operation == self.operation: data.completed_qty = for_quantity data.actual_operation_time = time_in_mins - data.actual_start_time = min(from_time_list) if from_time_list else None - data.actual_end_time = max(to_time_list) if to_time_list else None + data.actual_start_time = time_data[0].start_time if time_data else None + data.actual_end_time = time_data[0].end_time if time_data else None wo.flags.ignore_validate_update_after_submit = True wo.update_operation_status() diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a79ea0e14b0..358a5429d91 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -144,7 +144,7 @@ class ProductionPlan(Document): item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code)) items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description, - (qty - ordered_qty) as pending_qty + (qty - ordered_qty) * conversion_factor as pending_qty from `tabMaterial Request Item` mr_item where parent in (%s) and docstatus = 1 and qty > ordered_qty and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 98149aedefa..84bfab2f1d9 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -314,7 +314,7 @@ class WorkOrder(Document): stock_entry = frappe.db.sql("""select name from `tabStock Entry` where work_order = %s and docstatus = 1""", self.name) if stock_entry: - frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(stock_entry[0][0])) + frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(frappe.utils.get_link_to_form('Stock Entry', stock_entry[0][0]))) def update_planned_qty(self): update_bin_qty(self.production_item, self.fg_warehouse, { @@ -552,24 +552,33 @@ class WorkOrder(Document): d.db_set('transferred_qty', flt(transferred_qty), update_modified = False) def update_consumed_qty_for_required_items(self): - '''update consumed qty from submitted stock entries for that item against - the work order''' + ''' + Update consumed qty from submitted stock entries + against a work order for each stock item + ''' - for d in self.required_items: - consumed_qty = frappe.db.sql('''select sum(qty) - from `tabStock Entry` entry, `tabStock Entry Detail` detail - where + for item in self.required_items: + consumed_qty = frappe.db.sql(''' + SELECT + SUM(qty) + FROM + `tabStock Entry` entry, + `tabStock Entry Detail` detail + WHERE entry.work_order = %(name)s - and (entry.purpose = "Material Consumption for Manufacture" - or entry.purpose = "Manufacture") - and entry.docstatus = 1 - and detail.parent = entry.name - and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', { - 'name': self.name, - 'item': d.item_code - })[0][0] + AND (entry.purpose = "Material Consumption for Manufacture" + OR entry.purpose = "Manufacture") + AND entry.docstatus = 1 + AND detail.parent = entry.name + AND detail.s_warehouse IS NOT null + AND (detail.item_code = %(item)s + OR detail.original_item = %(item)s) + ''', { + 'name': self.name, + 'item': item.item_code + })[0][0] - d.db_set('consumed_qty', flt(consumed_qty), update_modified = False) + item.db_set('consumed_qty', flt(consumed_qty), update_modified=False) def make_bom(self): data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse @@ -648,7 +657,7 @@ def get_item_details(item, project = None): return res @frappe.whitelist() -def make_work_order(item, qty=0, project=None): +def make_work_order(bom_no, item, qty=0, project=None): if not frappe.has_permission("Work Order", "write"): frappe.throw(_("Not permitted"), frappe.PermissionError) @@ -657,6 +666,7 @@ def make_work_order(item, qty=0, project=None): wo_doc = frappe.new_doc("Work Order") wo_doc.production_item = item wo_doc.update(item_details) + wo_doc.bom_no = bom_no if flt(qty) > 0: wo_doc.qty = flt(qty) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b0fc7ea59c2..8aec8bddb9c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -658,4 +658,5 @@ erpnext.patches.v12_0.set_permission_einvoicing erpnext.patches.v12_0.set_published_in_hub_tracked_item erpnext.patches.v12_0.set_job_offer_applicant_email erpnext.patches.v12_0.create_irs_1099_field_united_states +erpnext.patches.v12_0.move_bank_account_swift_number_to_bank erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22 diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py new file mode 100644 index 00000000000..3c9758eb84a --- /dev/null +++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'bank', force=1) + + if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account'): + frappe.db.sql(""" + UPDATE `tabBank` b, `tabBank Account` ba + SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code + WHERE b.name = ba.bank + """) + + frappe.reload_doc('accounts', 'doctype', 'bank_account') + frappe.reload_doc('accounts', 'doctype', 'payment_request') \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 3570a0f2be4..58629634968 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -18,7 +18,7 @@ frappe.ui.form.on("Project", { }; }, onload: function (frm) { - var so = frappe.meta.get_docfield("Project", "sales_order"); + var so = frm.get_docfield("Project", "sales_order"); so.get_route_options_for_new_doc = function (field) { if (frm.is_new()) return; return { @@ -135,4 +135,4 @@ function open_form(frm, doctype, child_doctype, parentfield) { frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); -} \ No newline at end of file +} diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index fea2d5e7009..4397fe49c3c 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -4,7 +4,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ setup: function() { this._super(); - frappe.flags.hide_serial_batch_dialog = false; + frappe.flags.hide_serial_batch_dialog = true; frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); var has_margin_field = frappe.meta.has_field(cdt, 'margin_type'); @@ -165,6 +165,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ return (doc.rule_applied) ? "green" : "red"; }); } + + let batch_no_field = this.frm.get_docfield("items", "batch_no"); + if (batch_no_field) { + batch_no_field.get_route_options_for_new_doc = function(row) { + return { + "item": row.doc.item_code + } + }; + } + }, onload: function() { var me = this; @@ -352,12 +362,17 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ ['serial_no', 'batch_no', 'barcode'].forEach(field => { if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { + + let value = (row_to_modify[field] && field === "serial_no") + ? row_to_modify[field] + '\n' + data[field] : data[field]; + frappe.model.set_value(row_to_modify.doctype, - row_to_modify.name, field, data[field]); + row_to_modify.name, field, value); } }); scan_barcode_field.set_value(''); + refresh_field("items"); }); } return false; @@ -519,6 +534,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, () => me.toggle_conversion_factor(item), + () => { + if (show_batch_dialog) + return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + .then((r) => { + if(r.message.has_batch_no || r.message.has_serial_no) { + frappe.flags.hide_serial_batch_dialog = false; + } + }); + }, () => { if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog) { var d = locals[cdt][cdn]; @@ -528,7 +552,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ erpnext.show_serial_batch_selector(me.frm, d, (item) => { me.frm.script_manager.trigger('qty', item.doctype, item.name); - }); + if (!me.frm.doc.set_warehouse) + me.frm.script_manager.trigger('warehouse', item.doctype, item.name); + }, undefined, !frappe.flags.hide_serial_batch_dialog); } }, () => me.conversion_factor(doc, cdt, cdn, true), diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index 8b6de0f5179..75c5a820b46 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -1,23 +1,25 @@ frappe.provide('frappe.ui.form'); -erpnext.doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", +let default_dimensions = {}; + +let doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Shipping Rule", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Subscription", "Purchase Order", "Journal Entry", "Material Request", "Purchase Receipt", "Landed Cost Item", "Asset"]; -erpnext.child_docs = ["Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", +let child_docs = ["Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Landed Cost Item", "Asset Value Adjustment", "Opening Invoice Creation Tool Item", "Subscription Plan"]; frappe.call({ method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimension_filters", - callback: function(r){ + callback: function(r) { erpnext.dimension_filters = r.message[0]; - erpnext.default_dimensions = r.message[1]; + default_dimensions = r.message[1]; } }); -erpnext.doctypes_with_dimensions.forEach((doctype) => { +doctypes_with_dimensions.forEach((doctype) => { frappe.ui.form.on(doctype, { onload: function(frm) { erpnext.dimension_filters.forEach((dimension) => { @@ -27,41 +29,40 @@ erpnext.doctypes_with_dimensions.forEach((doctype) => { "is_group": 0 }); } - - if (Object.keys(erpnext.default_dimensions).length > 0) { - if (frappe.meta.has_field(doctype, dimension['fieldname'])) { - if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) { - frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); - } - } - - if (frm.doc.items && frm.doc.items.length && frm.doc.docstatus === 0 - && (!frm.doc.items[0][dimension['fieldname']])) { - frm.doc.items[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; - } - - if (frm.doc.accounts && frm.doc.accounts.length && frm.doc.docstatus === 0 - && (!frm.doc.items[0][dimension['fieldname']])) { - frm.doc.accounts[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; - } - } }); }); }, company: function(frm) { - if(frm.doc.company && (Object.keys(erpnext.default_dimensions).length > 0)) { - erpnext.dimension_filters.forEach((dimension) => { - if (frappe.meta.has_field(doctype, dimension['fieldname'])) { - frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); - } - }); + if(frm.doc.company && (Object.keys(default_dimensions || {}).length > 0) + && default_dimensions[frm.doc.company]) { + frm.trigger('update_dimension'); } }, + + update_dimension: function(frm) { + erpnext.dimension_filters.forEach((dimension) => { + if (frm.is_new()) { + if (frm.doc.company && Object.keys(default_dimensions || {}).length > 0 + && default_dimensions[frm.doc.company]) { + + if (frappe.meta.has_field(doctype, dimension['fieldname'])) { + frm.set_value(dimension['fieldname'], + default_dimensions[frm.doc.company][dimension['document_type']]); + } + + $.each(frm.doc.items || frm.doc.accounts || [], function(i, row) { + frappe.model.set_value(row.doctype, row.name, dimension['fieldname'], + default_dimensions[frm.doc.company][dimension['document_type']]) + }); + } + } + }); + } }); }); -erpnext.child_docs.forEach((doctype) => { +child_docs.forEach((doctype) => { frappe.ui.form.on(doctype, { items_add: function(frm, cdt, cdn) { erpnext.dimension_filters.forEach((dimension) => { @@ -77,14 +78,6 @@ erpnext.child_docs.forEach((doctype) => { }); }, - company: function(frm) { - if(frm.doc.company) { - erpnext.dimension_filters.forEach((dimension) => { - frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); - }); - } - }, - items_add: function(frm, cdt, cdn) { erpnext.dimension_filters.forEach((dimension) => { var row = frappe.get_doc(cdt, cdn); diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index a240e49b6ac..d75633e5a94 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -5,14 +5,13 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.show_dialog = show_dialog; // frm, item, warehouse_details, has_batch, oldest let d = this.item; - if (d && d.has_batch_no && (!d.batch_no || this.show_dialog)) { - this.has_batch = 1; - this.setup(); + this.has_batch = 0; this.has_serial_no = 0; + + if (d && d.has_batch_no && (!d.batch_no || this.show_dialog)) this.has_batch = 1; // !(this.show_dialog == false) ensures that show_dialog is implictly true, even when undefined - } else if(d && d.has_serial_no && !(this.show_dialog == false)) { - this.has_batch = 0; - this.setup(); - } + if(d && d.has_serial_no && !(this.show_dialog == false)) this.has_serial_no = 1; + + this.setup(); }, setup: function() { @@ -36,16 +35,16 @@ erpnext.SerialNoBatchSelector = Class.extend({ label: __('Item Code'), default: me.item_code }, - {fieldtype:'Column Break'}, { fieldname: 'warehouse', fieldtype:'Link', options: 'Warehouse', + reqd: me.has_batch && !me.has_serial_no ? 0 : 1, label: __(me.warehouse_details.type), - default: me.warehouse_details.name, + default: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '', onchange: function(e) { - if(me.has_batch) { + if(me.has_batch && !me.has_serial_no) { fields = fields.concat(me.get_batch_fields()); } else { fields = fields.concat(me.get_serial_no_fields()); @@ -74,15 +73,16 @@ erpnext.SerialNoBatchSelector = Class.extend({ { fieldname: 'qty', fieldtype:'Float', - read_only: me.has_batch, - label: __(me.has_batch ? 'Total Qty' : 'Qty'), + read_only: me.has_batch && !me.has_serial_no, + label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'), default: 0 }, { fieldname: 'auto_fetch_button', fieldtype:'Button', - hidden: me.has_batch, - label: __('Fetch based on FIFO'), + hidden: me.has_batch && !me.has_serial_no, + label: __('Auto Fetch'), + description: __('Fetch Serial Numbers based on FIFO'), click: () => { let qty = this.dialog.fields_dict.qty.get_value(); let numbers = frappe.call({ @@ -90,7 +90,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ args: { qty: qty, item_code: me.item_code, - warehouse: me.warehouse_details.name, + warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '', batch_no: me.item.batch_no || null } }); @@ -109,10 +109,12 @@ erpnext.SerialNoBatchSelector = Class.extend({ } ]; - if (this.has_batch) { + if (this.has_batch && !this.has_serial_no) { title = __("Select Batch Numbers"); fields = fields.concat(this.get_batch_fields()); } else { + // if only serial no OR + // if both batch_no & serial_no then only select serial_no and auto set batches nos title = __("Select Serial Numbers"); fields = fields.concat(this.get_serial_no_fields()); } @@ -122,25 +124,31 @@ erpnext.SerialNoBatchSelector = Class.extend({ fields: fields }); - if (this.item.serial_no) { - this.dialog.fields_dict.serial_no.set_value(this.item.serial_no); - } - this.dialog.set_primary_action(__('Insert'), function() { me.values = me.dialog.get_values(); if(me.validate()) { - me.set_items(); - me.dialog.hide(); + frappe.run_serially([ + () => me.update_batch_items(), + () => me.update_serial_no_item(), + () => me.update_batch_serial_no_items(), + () => { + refresh_field("items"); + if (me.callback) { + return me.callback(me.item); + } + }, + () => me.dialog.hide() + ]) } }); if(this.show_dialog) { let d = this.item; - if (d.has_serial_no && d.serial_no) { - this.dialog.set_value('serial_no', d.serial_no); + if (this.item.serial_no) { + this.dialog.fields_dict.serial_no.set_value(this.item.serial_no); } - - if (d.has_batch_no && d.batch_no) { + + if (this.has_batch && !this.has_serial_no && d.batch_no) { this.frm.doc.items.forEach(data => { if(data.item_code == d.item_code) { this.dialog.fields_dict.batches.df.data.push({ @@ -155,7 +163,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ } } - if (this.has_batch) { + if (this.has_batch && !this.has_serial_no) { this.update_total_qty(); } @@ -174,7 +182,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ frappe.throw(__("Please select a warehouse")); return false; } - if(this.has_batch) { + if(this.has_batch && !this.has_serial_no) { if(values.batches.length === 0 || !values.batches) { frappe.throw(__("Please select batches for batched item " + values.item_code)); @@ -193,34 +201,23 @@ erpnext.SerialNoBatchSelector = Class.extend({ } else { let serial_nos = values.serial_no || ''; if (!serial_nos || !serial_nos.replace(/\s/g, '').length) { - if (!this.show_dialog) { - frappe.throw(__("Please enter serial numbers for serialized item " - + values.item_code)); - return false; - } + frappe.throw(__("Please enter serial numbers for serialized item " + + values.item_code)); + return false; } return true; } }, - set_items: function() { - var me = this; - if(this.has_batch) { + update_batch_items() { + // clones an items if muliple batches are selected. + if(this.has_batch && !this.has_serial_no) { this.values.batches.map((batch, i) => { let batch_no = batch.batch_no; let row = ''; if (i !== 0 && !this.batch_exists(batch_no)) { - row = this.frm.add_child("items", { - 'item_code': this.item.item_code, - 'item_name': this.item.item_name, - 'price_list_rate': this.item.price_list_rate, - 'rate': this.item.rate, - 'qty': batch.selected_qty, - 'batch_no': batch_no, - 'actual_qty': this.item.actual_qty, - 'discount_percentage': this.item.discount_percentage - }); + row = this.frm.add_child("items", { ...this.item }); } else { row = this.frm.doc.items.find(i => i.batch_no === batch_no); } @@ -228,16 +225,59 @@ erpnext.SerialNoBatchSelector = Class.extend({ if (!row) { row = this.item; } - + // this ensures that qty & batch no is set this.map_row_values(row, batch, 'batch_no', 'selected_qty', this.values.warehouse); }); - } else { + } + }, + + update_serial_no_item() { + // just updates serial no for the item + if(this.has_serial_no && !this.has_batch) { this.map_row_values(this.item, this.values, 'serial_no', 'qty'); } + }, - refresh_field("items"); - this.callback && this.callback(this.item); + update_batch_serial_no_items() { + // if serial no selected is from different batches, adds new rows for each batch. + if(this.has_batch && this.has_serial_no) { + const selected_serial_nos = this.values.serial_no.split(/\n/g).filter(s => s); + + return frappe.db.get_list("Serial No", { + filters: { 'name': ["in", selected_serial_nos]}, + fields: ["batch_no", "name"] + }).then((data) => { + // data = [{batch_no: 'batch-1', name: "SR-001"}, + // {batch_no: 'batch-2', name: "SR-003"}, {batch_no: 'batch-2', name: "SR-004"}] + const batch_serial_map = data.reduce((acc, d) => { + if (!acc[d['batch_no']]) acc[d['batch_no']] = []; + acc[d['batch_no']].push(d['name']) + return acc + }, {}) + // batch_serial_map = { "batch-1": ['SR-001'], "batch-2": ["SR-003", "SR-004"]} + Object.keys(batch_serial_map).map((batch_no, i) => { + let row = ''; + const serial_no = batch_serial_map[batch_no]; + if (i == 0) { + row = this.item; + this.map_row_values(row, {qty: serial_no.length, batch_no: batch_no}, 'batch_no', + 'qty', this.values.warehouse); + } else if (!this.batch_exists(batch_no)) { + row = this.frm.add_child("items", { ...this.item }); + row.batch_no = batch_no; + } else { + row = this.frm.doc.items.find(i => i.batch_no === batch_no); + } + const values = { + 'qty': serial_no.length, + 'serial_no': serial_no.join('\n') + } + this.map_row_values(row, values, 'serial_no', + 'qty', this.values.warehouse); + }); + }) + } }, batch_exists: function(batch) { @@ -287,7 +327,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ return { filters: { item_code: me.item_code, - warehouse: me.warehouse || me.warehouse_details.name + warehouse: me.warehouse || typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '' }, query: 'erpnext.controllers.queries.get_batch_no' }; @@ -313,11 +353,15 @@ erpnext.SerialNoBatchSelector = Class.extend({ frappe.throw(__(`Batch ${val} already selected.`)); return; } + + let batch_number = me.item.batch_no || + this.grid_row.on_grid_fields_dict.batch_no.get_value(); + if (me.warehouse_details.name) { frappe.call({ method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', args: { - batch_no: me.item.batch_no, + batch_no: batch_number, warehouse: me.warehouse_details.name, item_code: me.item_code }, @@ -444,7 +488,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ { fieldname: 'serial_no', fieldtype: 'Small Text', - label: __(me.has_batch ? 'Selected Batch Numbers' : 'Selected Serial Numbers'), + label: __(me.has_batch && !me.has_serial_no ? 'Selected Batch Numbers' : 'Selected Serial Numbers'), onchange: function() { me.serial_list = this.get_value() .replace(/\n/g, ' ').match(/\S+/g) || []; diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js index 1e000b673e6..d8638ab05de 100644 --- a/erpnext/regional/report/datev/datev.js +++ b/erpnext/regional/report/datev/datev.js @@ -21,6 +21,12 @@ frappe.query_reports["DATEV"] = { "default": frappe.datetime.now_date(), "fieldtype": "Date", "reqd": 1 + }, + { + "fieldname": "voucher_type", + "label": __("Voucher Type"), + "fieldtype": "Select", + "options": "\nSales Invoice\nPurchase Invoice\nPayment Entry\nExpense Claim\nPayroll Entry\nBank Reconciliation\nAsset\nStock Entry" } ], onload: function(query_report) { diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 7ceaf50134c..a6579121cbf 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -62,6 +62,7 @@ def get_transactions(filters, as_dict=1): filters -- dict of filters to be passed to the sql query as_dict -- return as list of dicts [0,1] """ + filter_by_voucher = 'AND gl.voucher_type = %(voucher_type)s' if filters.get('voucher_type') else '' gl_entries = frappe.db.sql(""" SELECT @@ -80,8 +81,10 @@ def get_transactions(filters, as_dict=1): gl.posting_date as 'Belegdatum', gl.voucher_no as 'Belegfeld 1', gl.remarks as 'Buchungstext', - gl.against_voucher_type as 'Beleginfo - Art 1', - gl.against_voucher as 'Beleginfo - Inhalt 1' + gl.voucher_type as 'Beleginfo - Art 1', + gl.voucher_no as 'Beleginfo - Inhalt 1', + gl.against_voucher_type as 'Beleginfo - Art 2', + gl.against_voucher as 'Beleginfo - Inhalt 2' FROM `tabGL Entry` gl @@ -109,7 +112,8 @@ def get_transactions(filters, as_dict=1): WHERE gl.company = %(company)s AND DATE(gl.posting_date) >= %(from_date)s AND DATE(gl.posting_date) <= %(to_date)s - ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict, as_utf8=1) + {} + ORDER BY 'Belegdatum', gl.voucher_no""".format(filter_by_voucher), filters, as_dict=as_dict) return gl_entries @@ -160,7 +164,7 @@ def get_customers(filters): and ccl.company = par.company WHERE par.company = %(company)s - AND par.parenttype = 'Customer'""", filters, as_dict=1, as_utf8=1) + AND par.parenttype = 'Customer'""", filters, as_dict=1) def get_suppliers(filters): @@ -217,7 +221,7 @@ def get_suppliers(filters): and con.is_primary_contact = '1' WHERE par.company = %(company)s - AND par.parenttype = 'Supplier'""", filters, as_dict=1, as_utf8=1) + AND par.parenttype = 'Supplier'""", filters, as_dict=1) def get_account_names(filters): @@ -281,24 +285,24 @@ def get_datev_csv(data, filters, csv_class): def get_header(filters, csv_class): coa = frappe.get_value("Company", filters.get("company"), "chart_of_accounts") - coa_used = "SKR04" if "SKR04" in coa else ("SKR03" if "SKR03" in coa else "") + coa_used = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "") header = [ # DATEV format - # "DTVF" = created by DATEV software, - # "EXTF" = created by other software + # "DTVF" = created by DATEV software, + # "EXTF" = created by other software '"EXTF"', # version of the DATEV format - # 141 = 1.41, - # 510 = 5.10, - # 720 = 7.20 + # 141 = 1.41, + # 510 = 5.10, + # 720 = 7.20 '700', csv_class.DATA_CATEGORY, '"%s"' % csv_class.FORMAT_NAME, # Format version (regarding format name) csv_class.FORMAT_VERSION, # Generated on - datetime.datetime.now().strftime("%Y%m%d%H%M%S"), + datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '000', # Imported on -- stays empty '', # Origin. Any two symbols, will be replaced by "SV" on import. @@ -328,13 +332,21 @@ def get_header(filters, csv_class): # R = Diktatkürzel '', # S = Buchungstyp - # 1 = Transaction batch (Finanzbuchführung), - # 2 = Annual financial statement (Jahresabschluss) + # 1 = Transaction batch (Finanzbuchführung), + # 2 = Annual financial statement (Jahresabschluss) '1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # T = Rechnungslegungszweck - '', + # 0 oder leer = vom Rechnungslegungszweck unabhängig + # 50 = Handelsrecht + # 30 = Steuerrecht + # 64 = IFRS + # 40 = Kalkulatorik + # 11 = Reserviert + # 12 = Reserviert + '0', # U = Festschreibung - '', + # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1" + '0', # V = Default currency, for example, "EUR" '"%s"' % frappe.get_value("Company", filters.get("company"), "default_currency"), # reserviert diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/report/datev/datev_constants.py index a4cd5fc10ec..a059ed365a9 100644 --- a/erpnext/regional/report/datev/datev_constants.py +++ b/erpnext/regional/report/datev/datev_constants.py @@ -498,13 +498,27 @@ QUERY_REPORT_COLUMNS = [ }, { "label": "Beleginfo - Art 1", - "fieldname": "Beleginfo - Art 2", - "fieldtype": "Data", + "fieldname": "Beleginfo - Art 1", + "fieldtype": "Link", + "options": "DocType" }, { "label": "Beleginfo - Inhalt 1", + "fieldname": "Beleginfo - Inhalt 1", + "fieldtype": "Dynamic Link", + "options": "Beleginfo - Art 1" + }, + { + "label": "Beleginfo - Art 2", + "fieldname": "Beleginfo - Art 2", + "fieldtype": "Link", + "options": "DocType" + }, + { + "label": "Beleginfo - Inhalt 2", "fieldname": "Beleginfo - Inhalt 2", - "fieldtype": "Data", + "fieldtype": "Dynamic Link", + "options": "Beleginfo - Art 2" } ] diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 2c5ab7cb91b..fd1cc58c20a 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -54,8 +54,8 @@ class Gstr1Report(object): return self.columns, self.data def get_data(self): - if self.filters.get("type_of_business") == "B2C Small": - self.get_b2cs_data() + if self.filters.get("type_of_business") in ("B2C Small", "B2C Large"): + self.get_b2c_data() else: for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) @@ -69,7 +69,7 @@ class Gstr1Report(object): if taxable_value: self.data.append(row) - def get_b2cs_data(self): + def get_b2c_data(self): b2cs_output = {} for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): @@ -84,7 +84,10 @@ class Gstr1Report(object): "rate": "", "taxable_value": 0, "cess_amount": 0, - "type": "" + "type": "", + "invoice_number": invoice_details.get("invoice_number"), + "posting_date": invoice_details.get("posting_date"), + "invoice_value": invoice_details.get("base_grand_total"), }) row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) @@ -164,7 +167,7 @@ class Gstr1Report(object): frappe.throw(_("Please set B2C Limit in GST Settings.")) if self.filters.get("type_of_business") == "B2C Large": - conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2) + conditions += """ and ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) elif self.filters.get("type_of_business") == "B2C Small": @@ -581,6 +584,11 @@ def get_b2b_json(res, gstin): if not gst_in: continue for number, invoice in iteritems(res[gst_in]): + if not invoice[0]["place_of_supply"]: + frappe.throw(_("""{0} not entered in Invoice {1}. + Please update and try again""").format(frappe.bold("Place Of Supply"), + frappe.bold(invoice[0]['invoice_number']))) + inv_item = get_basic_invoice_detail(invoice[0]) inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]) inv_item["rchrg"] = invoice[0]["reverse_charge"] @@ -606,6 +614,9 @@ def get_b2cs_json(data, gstin): out = [] for d in data: + if not d.get("place_of_supply"): + frappe.throw(_("""{0} not entered in some invoices. + Please update and try again""").format(frappe.bold("Place Of Supply"))) pos = d.get('place_of_supply').split('-')[0] tax_details = {} @@ -642,6 +653,10 @@ def get_b2cs_json(data, gstin): def get_b2cl_json(res, gstin): out = [] for pos in res: + if not pos: + frappe.throw(_("""{0} not entered in some invoices. + Please update and try again""").format(frappe.bold("Place Of Supply"))) + b2cl_item, inv = {"pos": "%02d" % int(pos.split('-')[0]), "inv": []}, [] for row in res[pos]: diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 6571ecb404b..02667e8c233 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -268,9 +268,11 @@ def make_quotation(source_name, target_doc=None): target_doc.run_method("set_other_charges") target_doc.run_method("calculate_taxes_and_totals") - price_list = frappe.get_value("Customer", source_name, 'default_price_list') + price_list, currency = frappe.db.get_value("Customer", {'name': source_name}, ['default_price_list', 'default_currency']) if price_list: target_doc.selling_price_list = price_list + if currency: + target_doc.currency = currency return target_doc diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index fa765dfaad9..61aa608dd36 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -664,7 +664,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( reference_doctype: me.frm.doctype, reference_name: me.frm.docname, content: __('Reason for hold: ')+data.reason_for_hold, - comment_email: frappe.session.user + comment_email: frappe.session.user, + comment_by: frappe.session.user_fullname }, callback: function(r) { if(!r.exc) { diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 13d2b1519fb..ef2d19ac546 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -496,7 +496,7 @@ def close_or_unclose_sales_orders(names, status): def get_requested_item_qty(sales_order): return frappe._dict(frappe.db.sql(""" - select sales_order_item, sum(stock_qty) + select sales_order_item, sum(qty) from `tabMaterial Request Item` where docstatus = 1 and sales_order = %s @@ -507,16 +507,12 @@ def get_requested_item_qty(sales_order): def make_material_request(source_name, target_doc=None): requested_item_qty = get_requested_item_qty(source_name) - def postprocess(source, doc): - doc.material_request_type = "Purchase" - def update_item(source, target, source_parent): # qty is for packed items, because packed items don't have stock_qty field - qty = source.get("stock_qty") or source.get("qty") + qty = source.get("qty") target.project = source_parent.project target.qty = qty - requested_item_qty.get(source.name, 0) - target.conversion_factor = 1 - target.stock_qty = qty - requested_item_qty.get(source.name, 0) + target.stock_qty = flt(target.qty) * flt(target.conversion_factor) doc = get_mapped_doc("Sales Order", source_name, { "Sales Order": { @@ -537,14 +533,12 @@ def make_material_request(source_name, target_doc=None): "doctype": "Material Request Item", "field_map": { "name": "sales_order_item", - "parent": "sales_order", - "stock_uom": "uom", - "stock_qty": "qty" + "parent": "sales_order" }, "condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code) and doc.stock_qty > requested_item_qty.get(doc.name, 0), "postprocess": update_item } - }, target_doc, postprocess) + }, target_doc) return doc diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 1fc3663bed7..405004ece54 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -121,8 +121,8 @@ def get_columns(filters): }, { "label": _("Billed Amount"), - "fieldname": "rate", - "options": "billed_amount", + "fieldtype": "currency", + "fieldname": "billed_amount", "width": 120 }, { diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 8278745a804..af100692c6f 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -413,15 +413,20 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ */ set_batch_number: function(cdt, cdn) { const doc = frappe.get_doc(cdt, cdn); - if (doc && doc.has_batch_no) { + if (doc && doc.has_batch_no && doc.warehouse) { this._set_batch_number(doc); } }, _set_batch_number: function(doc) { + let args = {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)}; + if (doc.has_serial_no && doc.serial_no) { + args['serial_no'] = doc.serial_no + } + return frappe.call({ method: 'erpnext.stock.doctype.batch.batch.get_batch_no', - args: {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)}, + args: args, callback: function(r) { if(r.message) { frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message); diff --git a/erpnext/setup/desk_page/getting_started/getting_started.json b/erpnext/setup/desk_page/getting_started/getting_started.json index 00236ba5fe2..b045b5d083d 100644 --- a/erpnext/setup/desk_page/getting_started/getting_started.json +++ b/erpnext/setup/desk_page/getting_started/getting_started.json @@ -41,8 +41,7 @@ "charts": [ { "chart_name": "Bank Balance", - "label": "All Your Money", - "size": "Full" + "label": "Bank Balance" } ], "creation": "2020-01-23 13:46:38.833076", @@ -55,7 +54,7 @@ "idx": 0, "is_standard": 1, "label": "Getting Started", - "modified": "2020-03-12 16:30:37.821762", + "modified": "2020-03-23 11:20:49.161823", "modified_by": "Administrator", "module": "Setup", "name": "Getting Started", @@ -82,6 +81,16 @@ "is_query_report": 0, "link_to": "Sales Invoice", "type": "DocType" + }, + { + "is_query_report": 0, + "link_to": "dashboard", + "type": "Page" + }, + { + "is_query_report": 0, + "link_to": "leaderboard", + "type": "Page" } ] } \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 84810b6824f..020a93ff6ac 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -157,6 +157,7 @@ { "fieldname": "parent_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "in_list_view": 1, "label": "Parent Company", "options": "Company" @@ -277,6 +278,7 @@ "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"", "fieldname": "existing_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Existing Company ", "no_copy": 1, "options": "Company" @@ -728,7 +730,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2020-03-18 18:09:53.534211", + "modified": "2020-03-21 18:09:53.534211", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py index a4d4cbd8ef8..8d64efe41dd 100644 --- a/erpnext/stock/__init__.py +++ b/erpnext/stock/__init__.py @@ -13,12 +13,16 @@ install_docs = [ ] def get_warehouse_account_map(company=None): - if not frappe.flags.warehouse_account_map or frappe.flags.in_test: + company_warehouse_account_map = company and frappe.flags.setdefault('warehouse_account_map', {}).get(company) + warehouse_account_map = frappe.flags.warehouse_account_map + + if not warehouse_account_map or not company_warehouse_account_map or frappe.flags.in_test: warehouse_account = frappe._dict() filters = {} if company: filters['company'] = company + frappe.flags.setdefault('warehouse_account_map', {}).setdefault(company, {}) for d in frappe.get_all('Warehouse', fields = ["name", "account", "parent_warehouse", "company", "is_group"], @@ -30,10 +34,12 @@ def get_warehouse_account_map(company=None): if d.account: d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True) warehouse_account.setdefault(d.name, d) - - frappe.flags.warehouse_account_map = warehouse_account - - return frappe.flags.warehouse_account_map + if company: + frappe.flags.warehouse_account_map[company] = warehouse_account + else: + frappe.flags.warehouse_account_map = warehouse_account + + return frappe.flags.warehouse_account_map.get(company) or frappe.flags.warehouse_account_map def get_warehouse_account(warehouse, warehouse_account=None): account = warehouse.account diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 8ae978eaf05..9b7249e66b9 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -122,8 +122,11 @@ class Batch(Document): self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days) if has_expiry_date and not self.expiry_date: - frappe.msgprint(_('Expiry date is mandatory for selected item.')) - frappe.throw(_("Set item's shelf life in days, to set expiry based on manufacturing date plus shelf-life.")) + frappe.throw(msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.") \ + .format(frappe.bold("Shelf Life in Days"), + frappe.utils.get_link_to_form("Item", self.item), + frappe.bold("Batch Expiry Date")), + title=_("Expiry Date Mandatory")) def get_name_from_naming_series(self): """ diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index a34db451147..28e9533186c 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -238,7 +238,7 @@ class DeliveryTrip(Document): try: directions = maps_client.directions(**directions_data) except Exception as e: - frappe.throw(_(e)) + frappe.throw(_(str(e))) return directions[0] if directions else False diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 3503e7cc1c5..aa6b2fedd7c 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -343,7 +343,8 @@ { "fieldname": "shelf_life_in_days", "fieldtype": "Int", - "label": "Shelf Life In Days" + "label": "Shelf Life In Days", + "mandatory_depends_on": "eval:doc.has_batch_no && doc.has_expiry_date" }, { "default": "2099-12-31", @@ -1045,7 +1046,7 @@ "image_field": "image", "links": [], "max_attachments": 1, - "modified": "2020-01-02 19:13:59.295963", + "modified": "2020-03-24 16:14:36.950677", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index f805a674cac..e2e84c47473 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -741,14 +741,12 @@ class Item(WebsiteGenerator): defaults = frappe.defaults.get_defaults() or {} # To check default warehouse is belong to the default company - if defaults.get("default_warehouse") and frappe.db.exists("Warehouse", + if defaults.get("default_warehouse") and defaults.company and frappe.db.exists("Warehouse", {'name': defaults.default_warehouse, 'company': defaults.company}): - warehouse = defaults.default_warehouse - - self.append("item_defaults", { - "company": defaults.get("company"), - "default_warehouse": warehouse - }) + self.append("item_defaults", { + "company": defaults.get("company"), + "default_warehouse": defaults.default_warehouse + }) def update_variants(self): if self.flags.dont_update_variants or \ diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index d97b699a0f3..5ad0e13db9a 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -8,6 +8,7 @@ from frappe.utils import flt from frappe.model.meta import get_field_precision from frappe.model.document import Document from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.accounts.doctype.account.account import get_account_currency class LandedCostVoucher(Document): def get_items_from_purchase_receipts(self): @@ -43,6 +44,7 @@ class LandedCostVoucher(Document): else: self.validate_applicable_charges_for_item() self.validate_purchase_receipts() + self.validate_expense_accounts() self.set_total_taxes_and_charges() def check_mandatory(self): @@ -71,6 +73,14 @@ class LandedCostVoucher(Document): frappe.throw(_("Row {0}: Cost center is required for an item {1}") .format(item.idx, item.item_code)) + def validate_expense_accounts(self): + company_currency = erpnext.get_company_currency(self.company) + for account in self.taxes: + if get_account_currency(account.expense_account) != company_currency: + frappe.throw(msg=_(""" Row {0}: Expense account currency should be same as company's default currency. + Please select expense account with account currency as {1}""") + .format(account.idx, frappe.bold(company_currency)), title=_("Invalid Account Currency")) + def set_total_taxes_and_charges(self): self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")]) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 45428470167..285643d712c 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -501,7 +501,7 @@ def raise_work_orders(material_request): wo_order = frappe.new_doc("Work Order") wo_order.update({ "production_item": d.item_code, - "qty": d.qty - d.ordered_qty, + "qty": d.stock_qty - d.ordered_qty, "fg_warehouse": d.warehouse, "wip_warehouse": default_wip_warehouse, "description": d.description, diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 64d4c6c0829..772ac58af6f 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -523,12 +523,15 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note): return serial_nos @frappe.whitelist() -def auto_fetch_serial_number(qty, item_code, warehouse, batch_no=None): - serial_numbers = frappe.get_list("Serial No", filters={ +def auto_fetch_serial_number(qty, item_code, warehouse, batch_nos=None): + import json + filters = { "item_code": item_code, "warehouse": warehouse, - "batch_no": batch_no, "delivery_document_no": "", "sales_invoice": "" - }, limit=qty, order_by="creation") + } + if batch_nos: filters["batch_no"] = ["in", json.loads(batch_nos)] + + serial_numbers = frappe.get_list("Serial No", filters=filters, limit=qty, order_by="creation") return [item['name'] for item in serial_numbers] diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 2840a70dd0d..3bb941573b6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -60,7 +60,8 @@ frappe.ui.form.on('Stock Entry', { } } - if(item.s_warehouse) filters["warehouse"] = item.s_warehouse; + filters["warehouse"] = item.s_warehouse || item.t_warehouse; + return { query : "erpnext.controllers.queries.get_batch_no", filters: filters @@ -309,12 +310,12 @@ frappe.ui.form.on('Stock Entry', { method: "erpnext.stock.get_item_details.get_serial_no", args: {"args": args}, callback: function(r) { - if (!r.exe){ + if (!r.exe && r.message){ frappe.model.set_value(cdt, cdn, "serial_no", r.message); - } - if (callback) { - callback(); + if (callback) { + callback(); + } } } }); @@ -622,10 +623,15 @@ frappe.ui.form.on('Stock Entry Detail', { if(r.message) { var d = locals[cdt][cdn]; $.each(r.message, function(k, v) { - frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered + if (v) { + frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered + } }); refresh_field("items"); - erpnext.stock.select_batch_and_serial_no(frm, d); + + if (!d.serial_no) { + erpnext.stock.select_batch_and_serial_no(frm, d); + } } } }); @@ -964,7 +970,7 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => { } } - if(item && !item.has_serial_no && item.has_batch_no) return; + if(item && !item.has_serial_no && !item.has_batch_no) return; if (frm.doc.purpose === 'Material Receipt') return; frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8b072c66ee1..be4c78b1ebd 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -177,6 +177,10 @@ class StockEntry(StockController): stock_items = self.get_stock_items() serialized_items = self.get_serialized_items() for item in self.get("items"): + if item.qty and item.qty < 0: + frappe.throw(_("Row {0}: The item {1}, quantity must be positive number") + .format(item.idx, frappe.bold(item.item_code))) + if item.item_code not in stock_items: frappe.throw(_("{0} is not a stock Item").format(item.item_code)) @@ -234,7 +238,7 @@ class StockEntry(StockController): if self.purpose == "Manufacture" and self.work_order: production_item = frappe.get_value('Work Order', self.work_order, 'production_item') for item in self.items: - if item.item_code == production_item and item.qty != self.fg_completed_qty: + if item.item_code == production_item and item.t_warehouse and item.qty != self.fg_completed_qty: frappe.throw(_("Finished product quantity {0} and For Quantity {1} cannot be different") .format(item.qty, self.fg_completed_qty)) @@ -294,13 +298,8 @@ class StockEntry(StockController): if validate_for_manufacture: if d.bom_no: d.s_warehouse = None - if not d.t_warehouse: frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) - - elif self.pro_doc and (cstr(d.t_warehouse) != self.pro_doc.fg_warehouse and cstr(d.t_warehouse) != self.pro_doc.scrap_warehouse): - frappe.throw(_("Target warehouse in row {0} must be same as Work Order").format(d.idx)) - else: d.t_warehouse = None if not d.s_warehouse: diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index d86e68b7222..a848c80cf2c 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-03-29 18:22:12", "doctype": "DocType", @@ -479,8 +480,7 @@ "fieldname": "project", "fieldtype": "Link", "label": "Project", - "options": "Project", - "read_only": 1 + "options": "Project" }, { "fieldname": "po_detail", @@ -494,7 +494,8 @@ ], "idx": 1, "istable": 1, - "modified": "2019-08-20 14:01:02.319754", + "links": [], + "modified": "2020-03-19 12:34:09.836295", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index 3d5cfdc2743..9adfbf7cd03 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -29,7 +29,13 @@ frappe.query_reports["Stock Ledger"] = { "fieldname":"warehouse", "label": __("Warehouse"), "fieldtype": "Link", - "options": "Warehouse" + "options": "Warehouse", + "get_query": function() { + const company = frappe.query_report.get_filter_value('company'); + return { + filters: { 'company': company } + } + } }, { "fieldname":"item_code", diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json index 9b904ad7962..0f15e4737fd 100644 --- a/erpnext/support/web_form/issues/issues.json +++ b/erpnext/support/web_form/issues/issues.json @@ -18,7 +18,7 @@ "is_standard": 1, "login_required": 1, "max_attachment_size": 0, - "modified": "2019-12-10 13:48:19.894186", + "modified": "2020-03-06 05:24:05.749664", "modified_by": "Administrator", "module": "Support", "name": "issues", @@ -58,7 +58,7 @@ "options": "Open\nReplied\nHold\nClosed", "read_only": 1, "reqd": 0, - "show_in_filter": 0 + "show_in_filter": 1 }, { "allow_read_on_all_link_options": 0, diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index dfd3ed76bcb..16ecd5180b2 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -7,6 +7,8 @@ import frappe def create_test_contact_and_address(): frappe.db.sql('delete from tabContact') + frappe.db.sql('delete from `tabContact Email`') + frappe.db.sql('delete from `tabContact Phone`') frappe.db.sql('delete from tabAddress') frappe.db.sql('delete from `tabDynamic Link`') diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 20998108467..f88ffd44e3c 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import frappe.share from frappe import _ -from frappe.utils import cstr, now_datetime, cint, flt, get_time +from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_link_to_form from erpnext.controllers.status_updater import StatusUpdater from six import string_types @@ -123,8 +123,11 @@ class TransactionBase(StatusUpdater): ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate") if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01: - frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ") + frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ") .format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate)) + frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.") + .format(frappe.bold("Maintain Same Rate Throughout Sales Cycle"), + get_link_to_form("Selling Settings", "Selling Settings", frappe.bold("Selling Settings")))) def get_link_filters(self, for_doctype): if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):