Merge branch 'develop' into item_code_showing_as_mandatory_issue_develop

This commit is contained in:
Marica 2020-04-01 10:47:33 +05:30 committed by GitHub
commit 7ecacb6360
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 965 additions and 770 deletions

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '12.2.0' __version__ = '12.0.0-dev'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -102,7 +102,7 @@ class Account(NestedSet):
if not frappe.db.get_value("Account", if not frappe.db.get_value("Account",
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'): {'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) 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) descendants = get_descendants_of('Company', self.company)
if not descendants: return if not descendants: return
parent_acc_name_map = {} parent_acc_name_map = {}

View File

@ -2433,29 +2433,26 @@
"Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": { "Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": {
"account_number": "4849" "account_number": "4849"
}, },
"Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn) (Gruppe)": { "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": {
"is_group": 1, "account_number": "4850"
"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 (bei Buchgewinn)": { },
"account_number": "4851" "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": {
}, "account_number": "4852"
"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 Sachanlagen (Restbuchwert bei Buchvergewinn)": { },
"account_number": "4855" "Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": {
}, "account_number": "4856"
"Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": { },
"account_number": "4856" "Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": {
}, "account_number": "4857"
"Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": { },
"account_number": "4857" "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": {
}, "account_number": "4858"
"Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": {
"account_number": "4858"
}
}, },
"Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": { "Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": {
"account_number": "4910", "account_number": "4910",
@ -2578,20 +2575,17 @@
"Entnahme von Gegenst\u00e4nden ohne USt": { "Entnahme von Gegenst\u00e4nden ohne USt": {
"account_number": "4605" "account_number": "4605"
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt (Gruppe)": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": {
"is_group": 1, "account_number": "4630"
"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 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 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 ohne USt (Kfz-Nutzung)": {
"account_number": "4639"
}
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": {
"is_group": 1, "is_group": 1,
@ -2629,14 +2623,11 @@
"Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": { "Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": {
"account_number": "4689" "account_number": "4689"
}, },
"Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze) (Gruppe)": { "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": {
"is_group": 1, "account_number": "4690"
"Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": { },
"account_number": "4690" "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": {
}, "account_number": "4695"
"Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": {
"account_number": "4695"
}
}, },
"Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": { "Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": {
"is_group": 1, "is_group": 1,
@ -2646,41 +2637,35 @@
"Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": { "Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": {
"account_number": "7401" "account_number": "7401"
}, },
"Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam (Gruppe)": { "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": {
"is_group": 1, "account_number": "7450"
"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 aus der Anwendung von \u00dcbergangsvorschriften (Gruppe)": { "Ertr\u00e4ge durch Verschmelzung und Umwandlung": {
"is_group": 1, "account_number": "7451"
"Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": { },
"account_number": "7460" "Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": {
}, "account_number": "7452"
"Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": { },
"account_number": "7461" "Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": {
}, "account_number": "7453"
"Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": { },
"account_number": "7462" "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": {
}, "account_number": "7454"
"Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": { },
"account_number": "7463" "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": {
}, "account_number": "7460"
"Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": { },
"account_number": "7464" "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": { "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
"account_number": "4729" "account_number": "4729"
}
},
"Gew\u00e4hrte Skonti (Gruppe)": {
"is_group": 1,
"Gew. Skonti": {
"account_number": "4730"
}, },
"Gew\u00e4hrte Skonti (Gruppe)": { "Gew. Skonti 7 % USt": {
"is_group": 1, "account_number": "4731"
"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 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": { "Gew\u00e4hrte Boni 7 % USt": {
"account_number": "4750" "account_number": "4750"
}, },
@ -2864,103 +2852,79 @@
"account_number": "6398" "account_number": "6398"
} }
}, },
"Versicherungen (Gruppe)": { "Versicherungen": {
"is_group": 1, "account_number": "6400"
"Versicherungen": { },
"account_number": "6400" "Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": {
}, "account_number": "6405"
"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"
"Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": { },
"account_number": "6410" "Beitr\u00e4ge": {
}, "account_number": "6420"
"Beitr\u00e4ge": { },
"account_number": "6420" "Sonstige Abgaben": {
}, "account_number": "6430"
"Sonstige Abgaben": { },
"account_number": "6430" "Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
}, "account_number": "6436"
"Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { },
"account_number": "6436" "Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
}, "account_number": "6437"
"Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { },
"account_number": "6437" "Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": {
}, "account_number": "6440"
"Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": { },
"account_number": "6440" "Reparaturen und Instandhaltung von Bauten": {
}, "account_number": "6450"
"Reparaturen und Instandhaltung von Bauten": { },
"account_number": "6450" "Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": {
}, "account_number": "6460"
"Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": { },
"account_number": "6460" "Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": {
}, "account_number": "6470"
"Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": { },
"account_number": "6470" "Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": {
}, "account_number": "6475"
"Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": { },
"account_number": "6475" "Reparaturen und Instandhaltung von anderen Anlagen": {
}, "account_number": "6485"
"Reparaturen und Instandhaltung von anderen Anlagen": { },
"account_number": "6485" "Sonstige Reparaturen und Instandhaltungen": {
}, "account_number": "6490"
"Sonstige Reparaturen und Instandhaltungen": { },
"account_number": "6490" "Wartungskosten f. Hard- und Software": {
}, "account_number": "6495"
"Wartungskosten f. Hard- und Software": { },
"account_number": "6495" "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": {
}, "account_number": "6498"
"Mietleasing (bewegliche Wirtschaftsg\u00fcter)": {
"account_number": "6498"
}
}, },
"Fahrzeugkosten (Gruppe)": { "Fahrzeugkosten (Gruppe)": {
"is_group": 1, "is_group": 1,
"Fahrzeugkosten": { "Fahrzeugkosten": {
"account_number": "6500" "account_number": "6500"
}, },
"Kfz-Versicherungen (Gruppe)": { "Kfz-Versicherungen": {
"is_group": 1, "account_number": "6520"
"Kfz-Versicherungen": {
"account_number": "6520"
}
}, },
"Laufende Kfz-Betriebskosten (Gruppe)": { "Laufende Kfz-Betriebskosten": {
"is_group": 1, "account_number": "6530"
"Laufende Kfz-Betriebskosten": {
"account_number": "6530"
}
}, },
"Kfz-Reparaturen (Gruppe)": { "Kfz-Reparaturen": {
"is_group": 1, "account_number": "6540"
"Kfz-Reparaturen": {
"account_number": "6540"
}
}, },
"Garagenmiete (Gruppe)": { "Garagenmiete": {
"is_group": 1, "account_number": "6550"
"Garagenmiete": {
"account_number": "6550"
}
}, },
"Mietleasing Kfz (Gruppe)": { "Mietleasing Kfz": {
"is_group": 1, "account_number": "6560"
"Mietleasing Kfz": {
"account_number": "6560"
}
}, },
"Sonstige Kfz-Kosten (Gruppe)": { "Sonstige Kfz-Kosten": {
"is_group": 1, "account_number": "6570"
"Sonstige Kfz-Kosten": {
"account_number": "6570"
}
}, },
"Mautgeb\u00fchren (Gruppe)": { "Mautgeb\u00fchren": {
"is_group": 1, "account_number": "6580"
"Mautgeb\u00fchren": {
"account_number": "6580"
}
}, },
"Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": { "Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": {
"account_number": "6590" "account_number": "6590"
@ -3022,20 +2986,23 @@
"Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": { "Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": {
"account_number": "6645" "account_number": "6645"
}, },
"Reisekosten Arbeitnehmer": { "Reisekosten Arbeitnehmer (Gruppe)": {
"account_number": "6650" "is_group": 1,
}, "Reisekosten Arbeitnehmer": {
"Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": { "account_number": "6650"
"account_number": "6660" },
}, "Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": {
"Reisekosten Arbeitnehmer Fahrtkosten": { "account_number": "6660"
"account_number": "6663" },
}, "Reisekosten Arbeitnehmer Fahrtkosten": {
"Reisekosten Arbeitnehmer Verpflegungsmehraufwand": { "account_number": "6663"
"account_number": "6664" },
}, "Reisekosten Arbeitnehmer Verpflegungsmehraufwand": {
"Kilometergelderstattung Arbeitnehmer": { "account_number": "6664"
"account_number": "6668" },
"Kilometergelderstattung Arbeitnehmer": {
"account_number": "6668"
}
}, },
"Reisekosten Unternehmer (Gruppe)": { "Reisekosten Unternehmer (Gruppe)": {
"is_group": 1, "is_group": 1,

View File

@ -7,7 +7,20 @@ frappe.ui.form.on('Bank', {
}, },
refresh: function(frm) { refresh: function(frm) {
add_fields_to_mapping_table(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);
}
},
}); });

View File

@ -1,224 +1,137 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "allow_import": 1,
"allow_guest_to_view": 0, "allow_rename": 1,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:bank_name", "autoname": "field:bank_name",
"beta": 0,
"creation": "2018-04-07 16:59:59.496668", "creation": "2018-04-07 16:59:59.496668",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "", "document_type": "Setup",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "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": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_name", "fieldname": "bank_name",
"fieldtype": "Data", "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", "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, "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1 "unique": 1
}, },
{ {
"allow_bulk_edit": 0, "fieldname": "bank_details_section",
"allow_in_quick_entry": 0, "fieldtype": "Section Break",
"allow_on_submit": 0, "label": "Bank Details"
"bold": 0, },
{
"allow_in_quick_entry": 1,
"fieldname": "swift_number",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "SWIFT number",
"unique": 1
},
{
"fieldname": "column_break_1",
"fieldtype": "Column Break",
"search_index": 1
},
{
"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, "collapsible": 1,
"columns": 0,
"fieldname": "data_import_configuration_section", "fieldname": "data_import_configuration_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Data Import Configuration"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_transaction_mapping", "fieldname": "bank_transaction_mapping",
"fieldtype": "Table", "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", "label": "Bank Transaction Mapping",
"length": 0, "options": "Bank Transaction Mapping"
"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_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_4", "fieldname": "section_break_4",
"fieldtype": "Section Break", "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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "plaid_access_token", "fieldname": "plaid_access_token",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "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", "label": "Plaid Access Token",
"length": 0,
"no_copy": 1, "no_copy": 1,
"permlevel": 0, "read_only": 1
"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
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-03-25 21:22:33.496264",
"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", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank", "name": "Bank",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -5,6 +5,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address
class Bank(Document): 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)

View File

@ -24,8 +24,6 @@
"iban", "iban",
"column_break_12", "column_break_12",
"bank_account_no", "bank_account_no",
"branch_code",
"swift_number",
"address_and_contact", "address_and_contact",
"address_html", "address_html",
"website", "website",
@ -145,17 +143,6 @@
"label": "Bank Account No", "label": "Bank Account No",
"length": 30 "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", "fieldname": "address_and_contact",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -213,7 +200,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-01-29 20:42:26.458316", "modified": "2020-01-30 20:42:26.458316",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account", "name": "Bank Account",

View File

@ -4,8 +4,8 @@
cur_frm.add_fetch('bank_account','account','account'); 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','bank_account_no','bank_account_no');
cur_frm.add_fetch('bank_account','iban','iban'); cur_frm.add_fetch('bank_account','iban','iban');
cur_frm.add_fetch('bank_account','branch_code','branch_code'); cur_frm.add_fetch('bank','branch_code','branch_code');
cur_frm.add_fetch('bank_account','swift_number','swift_number'); cur_frm.add_fetch('bank','swift_number','swift_number');
frappe.ui.form.on('Bank Guarantee', { frappe.ui.form.on('Bank Guarantee', {
setup: function(frm) { setup: function(frm) {

View File

@ -114,13 +114,13 @@
"fieldname": "debit_in_account_currency", "fieldname": "debit_in_account_currency",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Debit Amount in Account Currency", "label": "Debit Amount in Account Currency",
"options": "currency" "options": "account_currency"
}, },
{ {
"fieldname": "credit_in_account_currency", "fieldname": "credit_in_account_currency",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Credit Amount in Account Currency", "label": "Credit Amount in Account Currency",
"options": "currency" "options": "account_currency"
}, },
{ {
"fieldname": "against", "fieldname": "against",
@ -250,7 +250,7 @@
"icon": "fa fa-list", "icon": "fa fa-list",
"idx": 1, "idx": 1,
"in_create": 1, "in_create": 1,
"modified": "2020-02-10 04:54:57.777905", "modified": "2020-03-28 16:22:33.766994",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "GL Entry", "name": "GL Entry",

View File

@ -33,7 +33,9 @@ frappe.ui.form.on('Payment Entry', {
frm.set_query("party_bank_account", function() { frm.set_query("party_bank_account", function() {
return { return {
filters: { filters: {
"is_company_account":0 "is_company_account":0,
party_type: frm.doc.party_type,
party: frm.doc.party
} }
} }
}); });

View File

@ -839,7 +839,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "bank_account.branch_code", "fetch_from": "bank.branch_code",
"fieldname": "branch_code", "fieldname": "branch_code",
"fieldtype": "Read Only", "fieldtype": "Read Only",
"hidden": 0, "hidden": 0,
@ -873,7 +873,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "bank_account.swift_number", "fetch_from": "bank.swift_number",
"fieldname": "swift_number", "fieldname": "swift_number",
"fieldtype": "Read Only", "fieldtype": "Read Only",
"hidden": 0, "hidden": 0,

View File

@ -317,13 +317,13 @@ def make_payment_request(**args):
"payment_request_type": args.get("payment_request_type"), "payment_request_type": args.get("payment_request_type"),
"currency": ref_doc.currency, "currency": ref_doc.currency,
"grand_total": grand_total, "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), "subject": _("Payment Request for {0}").format(args.dn),
"message": gateway_account.get("message") or get_dummy_message(ref_doc), "message": gateway_account.get("message") or get_dummy_message(ref_doc),
"reference_doctype": args.dt, "reference_doctype": args.dt,
"reference_name": args.dn, "reference_name": args.dn,
"party_type": args.get("party_type"), "party_type": args.get("party_type") or "Customer",
"party": args.get("party"), "party": args.get("party") or ref_doc.customer,
"bank_account": bank_account "bank_account": bank_account
}) })

View File

@ -437,13 +437,17 @@ class SalesInvoice(SellingController):
if (not for_validate) or (for_validate and not self.get(fieldname)): if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname)) self.set(fieldname, pos.get(fieldname))
customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list')
if pos.get("company_address"): if pos.get("company_address"):
self.company_address = pos.get("company_address") self.company_address = pos.get("company_address")
if not customer_price_list: customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
self.set('selling_price_list', pos.get('selling_price_list'))
customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list')
selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
if selling_price_list:
self.set('selling_price_list', selling_price_list)
if not for_validate: if not for_validate:
self.update_stock = cint(pos.get("update_stock")) self.update_stock = cint(pos.get("update_stock"))

View File

@ -11,7 +11,7 @@ from frappe.utils import (add_days, getdate, formatdate, date_diff,
add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day) add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day)
from frappe.contacts.doctype.address.address import (get_address_display, from frappe.contacts.doctype.address.address import (get_address_display,
get_default_address, get_company_address) get_default_address, get_company_address)
from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact from frappe.contacts.doctype.contact.contact import get_contact_details
from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext import get_company_currency from erpnext import get_company_currency
@ -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) existing_gle_currency = get_party_gle_currency(party_type, party, company)
if existing_gle_currency and party_account_currency != existing_gle_currency: 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}") frappe.throw(_("{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.")
.format(party_type, party, existing_gle_currency), InvalidAccountCurrency) .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
def validate_party_accounts(doc): def validate_party_accounts(doc):
companies = [] companies = []
@ -295,15 +295,13 @@ def validate_party_accounts(doc):
companies.append(account.company) companies.append(account.company)
party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True) 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"): if frappe.db.get_default("Company"):
company_default_currency = frappe.get_cached_value('Company', company_default_currency = frappe.get_cached_value('Company',
frappe.db.get_default("Company"), "default_currency") frappe.db.get_default("Company"), "default_currency")
else: else:
company_default_currency = frappe.db.get_value('Company', account.company, "default_currency") company_default_currency = frappe.db.get_value('Company', account.company, "default_currency")
if existing_gle_currency and party_account_currency != existing_gle_currency: validate_party_gle_currency(doc.doctype, doc.name, account.company, party_account_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))
if doc.get("default_currency") and party_account_currency and company_default_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: if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:
@ -615,3 +613,26 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None):
if data: if data:
return frappe._dict(data) return frappe._dict(data)
def get_default_contact(doctype, name):
"""
Returns default contact for the given doctype and name.
Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
"""
out = frappe.db.sql("""
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
FROM `tabDynamic Link` dl
INNER JOIN tabContact c ON c.name = dl.parent
WHERE
dl.link_doctype=%s AND
dl.link_name=%s AND
dl.parenttype = "Contact"
ORDER BY is_primary_contact DESC, is_billing_contact DESC
""", (doctype, name))
if out:
try:
return out[0][0]
except:
return None
else:
return None

View File

@ -218,15 +218,15 @@
<td></td> <td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td> <td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td> {%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %} {% if(!filters.show_future_payments) { %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td> <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td>
{% } %} {% } %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
{% if(filters.show_future_payments) { %} {% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
@ -234,8 +234,8 @@
{%= data[i]["po_no"] %}</td> {%= data[i]["po_no"] %}</td>
{% } %} {% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td> <td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td>
{% } %} {% } %}
{% } %} {% } %}
{% } else { %} {% } else { %}
@ -256,10 +256,10 @@
{% } else { %} {% } else { %}
<td><b>{%= __("Total") %}</b></td> <td><b>{%= __("Total") %}</b></td>
{% } %} {% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
{% } %} {% } %}
{% } %} {% } %}
</tr> </tr>

View File

@ -131,7 +131,7 @@ def get_gl_entries(filters):
gl_entries = frappe.db.sql( gl_entries = frappe.db.sql(
""" """
select select
posting_date, account, party_type, party, name as gl_entry, posting_date, account, party_type, party,
voucher_type, voucher_no, cost_center, project, voucher_type, voucher_no, cost_center, project,
against_voucher_type, against_voucher, account_currency, against_voucher_type, against_voucher, account_currency,
remarks, against, is_opening {select_fields} remarks, against, is_opening {select_fields}
@ -362,6 +362,12 @@ def get_columns(filters):
currency = get_company_currency(company) currency = get_company_currency(company)
columns = [ columns = [
{
"fieldname": "gl_entry",
"fieldtype": "Link",
"options": "GL Entry",
"hidden": 1
},
{ {
"label": _("Posting Date"), "label": _("Posting Date"),
"fieldname": "posting_date", "fieldname": "posting_date",

View File

@ -499,7 +499,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
reference_doctype: me.frm.doctype, reference_doctype: me.frm.doctype,
reference_name: me.frm.docname, reference_name: me.frm.docname,
content: __('Reason for hold: ')+data.reason_for_hold, 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) { callback: function(r) {
if(!r.exc) { if(!r.exc) {

View File

@ -4,15 +4,17 @@
// attach required files // attach required files
{% include 'erpnext/public/js/controllers/buying.js' %}; {% 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({ 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() { refresh: function() {
var me = this; var me = this;
this._super(); this._super();

View File

@ -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": []
}

View File

@ -672,19 +672,32 @@ class BuyingController(StockController):
# If asset has to be auto created # If asset has to be auto created
# Check for asset naming series # Check for asset naming series
if item_data.get('asset_naming_series'): if item_data.get('asset_naming_series'):
created_assets = []
for qty in range(cint(d.qty)): for qty in range(cint(d.qty)):
self.make_asset(d) asset = self.make_asset(d)
is_plural = 's' if cint(d.qty) != 1 else '' created_assets.append(asset)
messages.append(_('{0} Asset{2} Created for <b>{1}</b>').format(cint(d.qty), d.item_code, is_plural))
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: else:
frappe.throw(_("Row {1}: Asset Naming Series is mandatory for the auto creation for item {0}") frappe.throw(_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}")
.format(d.item_code, d.idx)) .format(d.idx, frappe.bold(d.item_code)))
else: else:
messages.append(_("Assets not created for <b>{0}</b>. You will have to create asset manually.") messages.append(_("Assets not created for {0}. You will have to create asset manually.")
.format(d.item_code)) .format(frappe.bold(d.item_code)))
for message in messages: for message in messages:
frappe.msgprint(message, title="Success") frappe.msgprint(message, title="Success", indicator="green")
def make_asset(self, row): def make_asset(self, row):
if not row.asset_location: if not row.asset_location:
@ -716,6 +729,8 @@ class BuyingController(StockController):
asset.set_missing_values() asset.set_missing_values()
asset.insert() asset.insert()
return asset.name
def update_fixed_asset(self, field, delete_asset = False): def update_fixed_asset(self, field, delete_asset = False):
for d in self.get("items"): for d in self.get("items"):
if d.is_fixed_asset: if d.is_fixed_asset:
@ -745,7 +760,7 @@ class BuyingController(StockController):
asset.supplier = None asset.supplier = None
if asset.docstatus == 1 and delete_asset: if asset.docstatus == 1 and delete_asset:
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\ 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_validate_update_after_submit = True
asset.flags.ignore_mandatory = True asset.flags.ignore_mandatory = True

View File

@ -3,6 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import erpnext
from frappe.desk.reportview import get_match_cond, get_filters_cond from frappe.desk.reportview import get_match_cond, get_filters_cond
from frappe.utils import nowdate, getdate from frappe.utils import nowdate, getdate
from collections import defaultdict 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): 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 tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount
where tabAccount.docstatus!=2 where tabAccount.docstatus!=2
and account_type in (%s) and account_type in (%s)
and is_group = 0 and is_group = 0
and company = %s and company = %s
and account_currency = %s
and `%s` LIKE %s and `%s` LIKE %s
order by idx desc, name order by idx desc, name
limit %s, %s""" % limit %s, %s""" %
(", ".join(['%s']*len(filters.get("account_type"))), "%s", searchfield, "%s", "%s", "%s"), (", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"),
tuple(filters.get("account_type") + [filters.get("company"), "%%%s%%" % txt, tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt,
start, page_len])) start, page_len]))
if not tax_accounts: if not tax_accounts:
tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount
where tabAccount.docstatus!=2 and is_group = 0 where tabAccount.docstatus!=2 and is_group = 0
and company = %s and `%s` LIKE %s limit %s, %s""" and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec
% ("%s", searchfield, "%s", "%s", "%s"), % ("%s", "%s", searchfield, "%s", "%s", "%s"),
(filters.get("company"), "%%%s%%" % txt, start, page_len)) (filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len))
return tax_accounts return tax_accounts

View File

@ -171,7 +171,6 @@
"options": "Customer" "options": "Customer"
}, },
{ {
"depends_on": "eval: doc.source==\"Campaign\"",
"fieldname": "campaign_name", "fieldname": "campaign_name",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Campaign Name", "label": "Campaign Name",

View File

@ -135,10 +135,17 @@ class Lead(SellingController):
# do not create an address if no fields are available, # do not create an address if no fields are available,
# skipping country since the system auto-sets it from system defaults # skipping country since the system auto-sets it from system defaults
if not any([self.get(field) for field in address_fields if field != "country"]): address = frappe.new_doc("Address")
mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ]
if not all([self.get(field) for field in mandatory_fields]):
frappe.msgprint(_('Missing mandatory fields in address. \
{0} to create address' ).format("<a href='desk#Form/Address/New Address 1' \
> Click here </a>"),
alert=True, indicator='yellow')
return return
address = frappe.new_doc("Address")
address.update({addr_field: self.get(addr_field) for addr_field in address_fields}) address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
address.update({info_field: self.get(info_field) for info_field in info_fields}) address.update({info_field: self.get(info_field) for info_field in info_fields})
address.insert() address.insert()

View File

@ -6,18 +6,13 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.utils.make_random import get_random 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 from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset
def work(): def work():
frappe.set_user(frappe.db.get_global('demo_accounts_user')) 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 # Enable booking asset depreciation entry automatically
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
@ -33,19 +28,6 @@ def work():
# Sell a random asset # Sell a random asset
sell_an_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
def sell_an_asset(): def sell_an_asset():
asset = get_random_asset() asset = get_random_asset()
@ -56,6 +38,7 @@ def sell_an_asset():
si.save() si.save()
si.submit() si.submit()
def get_random_asset(): def get_random_asset():
return frappe.db.sql(""" select name, item_code, value_after_depreciation, gross_purchase_amount return frappe.db.sql(""" select name, item_code, value_after_depreciation, gross_purchase_amount
from `tabAsset` from `tabAsset`

View File

@ -74,7 +74,7 @@
} }
], ],
"image_field": "hero_image", "image_field": "hero_image",
"modified": "2019-06-12 12:34:23.748157", "modified": "2020-03-29 12:50:27.677589",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Course", "name": "Course",
@ -103,6 +103,30 @@
"role": "Instructor", "role": "Instructor",
"share": 1, "share": 1,
"write": 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", "restrict_to_domain": "Education",

View File

@ -0,0 +1,60 @@
{
"custom_fields": [
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2019-12-02 11:00:03.432994",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Contact",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "is_billing_contact",
"fieldtype": "Check",
"hidden": 0,
"idx": 27,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"insert_after": "is_primary_contact",
"label": "Is Billing Contact",
"length": 0,
"modified": "2019-12-02 11:00:03.432994",
"modified_by": "Administrator",
"name": "Contact-is_billing_contact",
"no_copy": 0,
"options": null,
"owner": "Administrator",
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0,
"width": null
}
],
"custom_perms": [],
"doctype": "Contact",
"property_setters": [],
"sync_on_migrate": 1
}

View File

@ -273,11 +273,11 @@ class TestLoan(unittest.TestCase):
penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year)) 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"], lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name')
filters={"loan": loan.name}, order_by="posting_date") lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name')
self.assertTrue(lia[0].get('is_paid')) self.assertTrue(lia1)
self.assertFalse(lia[1].get('is_paid')) self.assertTrue(lia2)
def test_security_shortfall(self): def test_security_shortfall(self):
pledges = [] pledges = []

View File

@ -43,8 +43,7 @@ frappe.ui.form.on("BOM", {
frm.set_query("item_code", "items", function() { frm.set_query("item_code", "items", function() {
return { return {
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query"
filters: [["Item", "name", "!=", cur_frm.doc.item]]
}; };
}); });
@ -135,6 +134,7 @@ frappe.ui.form.on("BOM", {
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order", method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
args: { args: {
bom_no: frm.doc.name,
item: frm.doc.item, item: frm.doc.item,
qty: data.qty || 0.0, qty: data.qty || 0.0,
project: frm.doc.project project: frm.doc.project

View File

@ -114,10 +114,6 @@ class BOM(WebsiteGenerator):
child = self.append('operations', d) child = self.append('operations', d)
child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2) 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): def set_bom_material_details(self):
for item in self.get("items"): for item in self.get("items"):
self.validate_bom_currecny(item) self.validate_bom_currecny(item)
@ -147,7 +143,6 @@ class BOM(WebsiteGenerator):
args = json.loads(args) args = json.loads(args)
item = self.get_item_det(args['item_code']) 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['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 args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or

View File

@ -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.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons"); frm.trigger("prepare_timer_buttons");
} }
@ -59,10 +59,14 @@ frappe.ui.form.on('Job Card', {
let completed_time = frappe.datetime.now_datetime(); let completed_time = frappe.datetime.now_datetime();
frm.trigger("hide_timer"); frm.trigger("hide_timer");
frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), if (frm.doc.for_quantity) {
fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => { frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
frm.events.complete_job(frm, completed_time, data.qty); fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => {
}, __("Enter Value"), __("Complete")); frm.events.complete_job(frm, completed_time, data.qty);
}, __("Enter Value"), __("Complete"));
} else {
frm.events.complete_job(frm, completed_time, 0);
}
}).addClass("btn-primary"); }).addClass("btn-primary");
} }
}, },

View File

@ -99,8 +99,7 @@
"fieldname": "for_quantity", "fieldname": "for_quantity",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Qty To Manufacture", "label": "Qty To Manufacture"
"reqd": 1
}, },
{ {
"fieldname": "wip_warehouse", "fieldname": "wip_warehouse",
@ -122,6 +121,7 @@
"options": "Employee" "options": "Employee"
}, },
{ {
"allow_bulk_edit": 1,
"fieldname": "time_logs", "fieldname": "time_logs",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Time Logs", "label": "Time Logs",
@ -290,7 +290,7 @@
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-12-03 13:08:57.926201", "modified": "2020-03-27 13:36:35.417502",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Job Card", "name": "Job Card",

View File

@ -191,12 +191,9 @@ class JobCard(Document):
if not self.time_logs: if not self.time_logs:
frappe.throw(_("Time logs are required for job card {0}").format(self.name)) frappe.throw(_("Time logs are required for job card {0}").format(self.name))
if self.total_completed_qty <= 0.0: if self.for_quantity and self.total_completed_qty != self.for_quantity:
frappe.throw(_("Total completed qty must be greater than zero")) 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.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): def update_work_order(self):
if not self.work_order: if not self.work_order:
@ -205,27 +202,34 @@ class JobCard(Document):
for_quantity, time_in_mins = 0, 0 for_quantity, time_in_mins = 0, 0
from_time_list, to_time_list = [], [] 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 data = frappe.get_all('Job Card',
time_in_mins += doc.total_time_in_mins fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
for time_log in doc.time_logs: filters = {"docstatus": 1, "work_order": self.work_order,
if time_log.from_time: "workstation": self.workstation, "operation": self.operation})
from_time_list.append(time_log.from_time)
if time_log.to_time: if data and len(data) > 0:
to_time_list.append(time_log.to_time) for_quantity = data[0].completed_qty
time_in_mins = data[0].time_in_mins
if for_quantity: 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) wo = frappe.get_doc('Work Order', self.work_order)
for data in wo.operations: 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.completed_qty = for_quantity
data.actual_operation_time = time_in_mins data.actual_operation_time = time_in_mins
data.actual_start_time = min(from_time_list) if from_time_list else None data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = max(to_time_list) if to_time_list 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.flags.ignore_validate_update_after_submit = True
wo.update_operation_status() wo.update_operation_status()

View File

@ -144,7 +144,7 @@ class ProductionPlan(Document):
item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code)) 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, 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 from `tabMaterial Request Item` mr_item
where parent in (%s) and docstatus = 1 and qty > ordered_qty 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 and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code

View File

@ -314,7 +314,7 @@ class WorkOrder(Document):
stock_entry = frappe.db.sql("""select name from `tabStock Entry` stock_entry = frappe.db.sql("""select name from `tabStock Entry`
where work_order = %s and docstatus = 1""", self.name) where work_order = %s and docstatus = 1""", self.name)
if stock_entry: 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): def update_planned_qty(self):
update_bin_qty(self.production_item, self.fg_warehouse, { 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) d.db_set('transferred_qty', flt(transferred_qty), update_modified = False)
def update_consumed_qty_for_required_items(self): 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: for item in self.required_items:
consumed_qty = frappe.db.sql('''select sum(qty) consumed_qty = frappe.db.sql('''
from `tabStock Entry` entry, `tabStock Entry Detail` detail SELECT
where SUM(qty)
FROM
`tabStock Entry` entry,
`tabStock Entry Detail` detail
WHERE
entry.work_order = %(name)s entry.work_order = %(name)s
and (entry.purpose = "Material Consumption for Manufacture" AND (entry.purpose = "Material Consumption for Manufacture"
or entry.purpose = "Manufacture") OR entry.purpose = "Manufacture")
and entry.docstatus = 1 AND entry.docstatus = 1
and detail.parent = entry.name AND detail.parent = entry.name
and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', { AND detail.s_warehouse IS NOT null
'name': self.name, AND (detail.item_code = %(item)s
'item': d.item_code OR detail.original_item = %(item)s)
})[0][0] ''', {
'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): def make_bom(self):
data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse 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 return res
@frappe.whitelist() @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"): if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError) 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 = frappe.new_doc("Work Order")
wo_doc.production_item = item wo_doc.production_item = item
wo_doc.update(item_details) wo_doc.update(item_details)
wo_doc.bom_no = bom_no
if flt(qty) > 0: if flt(qty) > 0:
wo_doc.qty = flt(qty) wo_doc.qty = flt(qty)

View File

@ -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_published_in_hub_tracked_item
erpnext.patches.v12_0.set_job_offer_applicant_email erpnext.patches.v12_0.set_job_offer_applicant_email
erpnext.patches.v12_0.create_irs_1099_field_united_states 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 erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22

View File

@ -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')

View File

@ -18,7 +18,7 @@ frappe.ui.form.on("Project", {
}; };
}, },
onload: function (frm) { 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) { so.get_route_options_for_new_doc = function (field) {
if (frm.is_new()) return; if (frm.is_new()) return;
return { return {

View File

@ -4,7 +4,7 @@
erpnext.TransactionController = erpnext.taxes_and_totals.extend({ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
setup: function() { setup: function() {
this._super(); 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) { frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn); var item = frappe.get_doc(cdt, cdn);
var has_margin_field = frappe.meta.has_field(cdt, 'margin_type'); 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"; 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() { onload: function() {
var me = this; var me = this;
@ -352,12 +362,17 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
['serial_no', 'batch_no', 'barcode'].forEach(field => { ['serial_no', 'batch_no', 'barcode'].forEach(field => {
if (data[field] && frappe.meta.has_field(row_to_modify.doctype, 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, 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(''); scan_barcode_field.set_value('');
refresh_field("items");
}); });
} }
return false; return false;
@ -519,6 +534,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
() => me.toggle_conversion_factor(item), () => 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) { if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog) {
var d = locals[cdt][cdn]; 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) => { erpnext.show_serial_batch_selector(me.frm, d, (item) => {
me.frm.script_manager.trigger('qty', item.doctype, item.name); 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), () => me.conversion_factor(doc, cdt, cdn, true),

View File

@ -5,14 +5,13 @@ erpnext.SerialNoBatchSelector = Class.extend({
this.show_dialog = show_dialog; this.show_dialog = show_dialog;
// frm, item, warehouse_details, has_batch, oldest // frm, item, warehouse_details, has_batch, oldest
let d = this.item; let d = this.item;
if (d && d.has_batch_no && (!d.batch_no || this.show_dialog)) { this.has_batch = 0; this.has_serial_no = 0;
this.has_batch = 1;
this.setup(); 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 // !(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)) { if(d && d.has_serial_no && !(this.show_dialog == false)) this.has_serial_no = 1;
this.has_batch = 0;
this.setup(); this.setup();
}
}, },
setup: function() { setup: function() {
@ -36,16 +35,16 @@ erpnext.SerialNoBatchSelector = Class.extend({
label: __('Item Code'), label: __('Item Code'),
default: me.item_code default: me.item_code
}, },
{fieldtype:'Column Break'},
{ {
fieldname: 'warehouse', fieldname: 'warehouse',
fieldtype:'Link', fieldtype:'Link',
options: 'Warehouse', options: 'Warehouse',
reqd: me.has_batch && !me.has_serial_no ? 0 : 1,
label: __(me.warehouse_details.type), label: __(me.warehouse_details.type),
default: me.warehouse_details.name, default: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
onchange: function(e) { onchange: function(e) {
if(me.has_batch) { if(me.has_batch && !me.has_serial_no) {
fields = fields.concat(me.get_batch_fields()); fields = fields.concat(me.get_batch_fields());
} else { } else {
fields = fields.concat(me.get_serial_no_fields()); fields = fields.concat(me.get_serial_no_fields());
@ -74,15 +73,16 @@ erpnext.SerialNoBatchSelector = Class.extend({
{ {
fieldname: 'qty', fieldname: 'qty',
fieldtype:'Float', fieldtype:'Float',
read_only: me.has_batch, read_only: me.has_batch && !me.has_serial_no,
label: __(me.has_batch ? 'Total Qty' : 'Qty'), label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'),
default: 0 default: 0
}, },
{ {
fieldname: 'auto_fetch_button', fieldname: 'auto_fetch_button',
fieldtype:'Button', fieldtype:'Button',
hidden: me.has_batch, hidden: me.has_batch && !me.has_serial_no,
label: __('Fetch based on FIFO'), label: __('Auto Fetch'),
description: __('Fetch Serial Numbers based on FIFO'),
click: () => { click: () => {
let qty = this.dialog.fields_dict.qty.get_value(); let qty = this.dialog.fields_dict.qty.get_value();
let numbers = frappe.call({ let numbers = frappe.call({
@ -90,7 +90,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
args: { args: {
qty: qty, qty: qty,
item_code: me.item_code, 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 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"); title = __("Select Batch Numbers");
fields = fields.concat(this.get_batch_fields()); fields = fields.concat(this.get_batch_fields());
} else { } 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"); title = __("Select Serial Numbers");
fields = fields.concat(this.get_serial_no_fields()); fields = fields.concat(this.get_serial_no_fields());
} }
@ -122,25 +124,31 @@ erpnext.SerialNoBatchSelector = Class.extend({
fields: fields 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() { this.dialog.set_primary_action(__('Insert'), function() {
me.values = me.dialog.get_values(); me.values = me.dialog.get_values();
if(me.validate()) { if(me.validate()) {
me.set_items(); frappe.run_serially([
me.dialog.hide(); () => 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) { if(this.show_dialog) {
let d = this.item; let d = this.item;
if (d.has_serial_no && d.serial_no) { if (this.item.serial_no) {
this.dialog.set_value('serial_no', d.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 => { this.frm.doc.items.forEach(data => {
if(data.item_code == d.item_code) { if(data.item_code == d.item_code) {
this.dialog.fields_dict.batches.df.data.push({ 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(); this.update_total_qty();
} }
@ -174,7 +182,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
frappe.throw(__("Please select a warehouse")); frappe.throw(__("Please select a warehouse"));
return false; return false;
} }
if(this.has_batch) { if(this.has_batch && !this.has_serial_no) {
if(values.batches.length === 0 || !values.batches) { if(values.batches.length === 0 || !values.batches) {
frappe.throw(__("Please select batches for batched item " frappe.throw(__("Please select batches for batched item "
+ values.item_code)); + values.item_code));
@ -193,34 +201,23 @@ erpnext.SerialNoBatchSelector = Class.extend({
} else { } else {
let serial_nos = values.serial_no || ''; let serial_nos = values.serial_no || '';
if (!serial_nos || !serial_nos.replace(/\s/g, '').length) { if (!serial_nos || !serial_nos.replace(/\s/g, '').length) {
if (!this.show_dialog) { frappe.throw(__("Please enter serial numbers for serialized item "
frappe.throw(__("Please enter serial numbers for serialized item " + values.item_code));
+ values.item_code)); return false;
return false;
}
} }
return true; return true;
} }
}, },
set_items: function() { update_batch_items() {
var me = this; // clones an items if muliple batches are selected.
if(this.has_batch) { if(this.has_batch && !this.has_serial_no) {
this.values.batches.map((batch, i) => { this.values.batches.map((batch, i) => {
let batch_no = batch.batch_no; let batch_no = batch.batch_no;
let row = ''; let row = '';
if (i !== 0 && !this.batch_exists(batch_no)) { if (i !== 0 && !this.batch_exists(batch_no)) {
row = this.frm.add_child("items", { row = this.frm.add_child("items", { ...this.item });
'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
});
} else { } else {
row = this.frm.doc.items.find(i => i.batch_no === batch_no); row = this.frm.doc.items.find(i => i.batch_no === batch_no);
} }
@ -228,16 +225,59 @@ erpnext.SerialNoBatchSelector = Class.extend({
if (!row) { if (!row) {
row = this.item; row = this.item;
} }
// this ensures that qty & batch no is set
this.map_row_values(row, batch, 'batch_no', this.map_row_values(row, batch, 'batch_no',
'selected_qty', this.values.warehouse); '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'); this.map_row_values(this.item, this.values, 'serial_no', 'qty');
} }
},
refresh_field("items"); update_batch_serial_no_items() {
this.callback && this.callback(this.item); // 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) { batch_exists: function(batch) {
@ -287,7 +327,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
return { return {
filters: { filters: {
item_code: me.item_code, 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' query: 'erpnext.controllers.queries.get_batch_no'
}; };
@ -313,11 +353,15 @@ erpnext.SerialNoBatchSelector = Class.extend({
frappe.throw(__(`Batch ${val} already selected.`)); frappe.throw(__(`Batch ${val} already selected.`));
return; return;
} }
let batch_number = me.item.batch_no ||
this.grid_row.on_grid_fields_dict.batch_no.get_value();
if (me.warehouse_details.name) { if (me.warehouse_details.name) {
frappe.call({ frappe.call({
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
args: { args: {
batch_no: me.item.batch_no, batch_no: batch_number,
warehouse: me.warehouse_details.name, warehouse: me.warehouse_details.name,
item_code: me.item_code item_code: me.item_code
}, },
@ -444,7 +488,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
{ {
fieldname: 'serial_no', fieldname: 'serial_no',
fieldtype: 'Small Text', 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() { onchange: function() {
me.serial_list = this.get_value() me.serial_list = this.get_value()
.replace(/\n/g, ' ').match(/\S+/g) || []; .replace(/\n/g, ' ').match(/\S+/g) || [];

View File

@ -21,6 +21,12 @@ frappe.query_reports["DATEV"] = {
"default": frappe.datetime.now_date(), "default": frappe.datetime.now_date(),
"fieldtype": "Date", "fieldtype": "Date",
"reqd": 1 "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) { onload: function(query_report) {

View File

@ -62,6 +62,7 @@ def get_transactions(filters, as_dict=1):
filters -- dict of filters to be passed to the sql query filters -- dict of filters to be passed to the sql query
as_dict -- return as list of dicts [0,1] 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(""" gl_entries = frappe.db.sql("""
SELECT SELECT
@ -80,8 +81,10 @@ def get_transactions(filters, as_dict=1):
gl.posting_date as 'Belegdatum', gl.posting_date as 'Belegdatum',
gl.voucher_no as 'Belegfeld 1', gl.voucher_no as 'Belegfeld 1',
gl.remarks as 'Buchungstext', gl.remarks as 'Buchungstext',
gl.against_voucher_type as 'Beleginfo - Art 1', gl.voucher_type as 'Beleginfo - Art 1',
gl.against_voucher as 'Beleginfo - Inhalt 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 FROM `tabGL Entry` gl
@ -109,7 +112,8 @@ def get_transactions(filters, as_dict=1):
WHERE gl.company = %(company)s WHERE gl.company = %(company)s
AND DATE(gl.posting_date) >= %(from_date)s AND DATE(gl.posting_date) >= %(from_date)s
AND DATE(gl.posting_date) <= %(to_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 return gl_entries
@ -160,7 +164,7 @@ def get_customers(filters):
and ccl.company = par.company and ccl.company = par.company
WHERE par.company = %(company)s 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): def get_suppliers(filters):
@ -217,7 +221,7 @@ def get_suppliers(filters):
and con.is_primary_contact = '1' and con.is_primary_contact = '1'
WHERE par.company = %(company)s 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): def get_account_names(filters):
@ -281,24 +285,24 @@ def get_datev_csv(data, filters, csv_class):
def get_header(filters, csv_class): def get_header(filters, csv_class):
coa = frappe.get_value("Company", filters.get("company"), "chart_of_accounts") 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 = [ header = [
# DATEV format # DATEV format
# "DTVF" = created by DATEV software, # "DTVF" = created by DATEV software,
# "EXTF" = created by other software # "EXTF" = created by other software
'"EXTF"', '"EXTF"',
# version of the DATEV format # version of the DATEV format
# 141 = 1.41, # 141 = 1.41,
# 510 = 5.10, # 510 = 5.10,
# 720 = 7.20 # 720 = 7.20
'700', '700',
csv_class.DATA_CATEGORY, csv_class.DATA_CATEGORY,
'"%s"' % csv_class.FORMAT_NAME, '"%s"' % csv_class.FORMAT_NAME,
# Format version (regarding format name) # Format version (regarding format name)
csv_class.FORMAT_VERSION, csv_class.FORMAT_VERSION,
# Generated on # 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 # Imported on -- stays empty
'', '',
# Origin. Any two symbols, will be replaced by "SV" on import. # Origin. Any two symbols, will be replaced by "SV" on import.
@ -328,13 +332,21 @@ def get_header(filters, csv_class):
# R = Diktatkürzel # R = Diktatkürzel
'', '',
# S = Buchungstyp # S = Buchungstyp
# 1 = Transaction batch (Finanzbuchführung), # 1 = Transaction batch (Finanzbuchführung),
# 2 = Annual financial statement (Jahresabschluss) # 2 = Annual financial statement (Jahresabschluss)
'1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', '1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# T = Rechnungslegungszweck # T = Rechnungslegungszweck
'', # 0 oder leer = vom Rechnungslegungszweck unabhängig
# 50 = Handelsrecht
# 30 = Steuerrecht
# 64 = IFRS
# 40 = Kalkulatorik
# 11 = Reserviert
# 12 = Reserviert
'0',
# U = Festschreibung # U = Festschreibung
'', # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
'0',
# V = Default currency, for example, "EUR" # V = Default currency, for example, "EUR"
'"%s"' % frappe.get_value("Company", filters.get("company"), "default_currency"), '"%s"' % frappe.get_value("Company", filters.get("company"), "default_currency"),
# reserviert # reserviert

View File

@ -498,13 +498,27 @@ QUERY_REPORT_COLUMNS = [
}, },
{ {
"label": "Beleginfo - Art 1", "label": "Beleginfo - Art 1",
"fieldname": "Beleginfo - Art 2", "fieldname": "Beleginfo - Art 1",
"fieldtype": "Data", "fieldtype": "Link",
"options": "DocType"
}, },
{ {
"label": "Beleginfo - Inhalt 1", "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", "fieldname": "Beleginfo - Inhalt 2",
"fieldtype": "Data", "fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 2"
} }
] ]

View File

@ -54,8 +54,8 @@ class Gstr1Report(object):
return self.columns, self.data return self.columns, self.data
def get_data(self): def get_data(self):
if self.filters.get("type_of_business") == "B2C Small": if self.filters.get("type_of_business") in ("B2C Small", "B2C Large"):
self.get_b2cs_data() self.get_b2c_data()
else: else:
for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
invoice_details = self.invoices.get(inv) invoice_details = self.invoices.get(inv)
@ -69,7 +69,7 @@ class Gstr1Report(object):
if taxable_value: if taxable_value:
self.data.append(row) self.data.append(row)
def get_b2cs_data(self): def get_b2c_data(self):
b2cs_output = {} b2cs_output = {}
for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
@ -84,7 +84,10 @@ class Gstr1Report(object):
"rate": "", "rate": "",
"taxable_value": 0, "taxable_value": 0,
"cess_amount": 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)) 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.")) frappe.throw(_("Please set B2C Limit in GST Settings."))
if self.filters.get("type_of_business") == "B2C Large": 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)) 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": elif self.filters.get("type_of_business") == "B2C Small":
@ -581,6 +584,11 @@ def get_b2b_json(res, gstin):
if not gst_in: continue if not gst_in: continue
for number, invoice in iteritems(res[gst_in]): 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 = get_basic_invoice_detail(invoice[0])
inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]) inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0])
inv_item["rchrg"] = invoice[0]["reverse_charge"] inv_item["rchrg"] = invoice[0]["reverse_charge"]
@ -606,6 +614,9 @@ def get_b2cs_json(data, gstin):
out = [] out = []
for d in data: 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] pos = d.get('place_of_supply').split('-')[0]
tax_details = {} tax_details = {}
@ -642,6 +653,10 @@ def get_b2cs_json(data, gstin):
def get_b2cl_json(res, gstin): def get_b2cl_json(res, gstin):
out = [] out = []
for pos in res: 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": []}, [] b2cl_item, inv = {"pos": "%02d" % int(pos.split('-')[0]), "inv": []}, []
for row in res[pos]: for row in res[pos]:

View File

@ -268,9 +268,11 @@ def make_quotation(source_name, target_doc=None):
target_doc.run_method("set_other_charges") target_doc.run_method("set_other_charges")
target_doc.run_method("calculate_taxes_and_totals") 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: if price_list:
target_doc.selling_price_list = price_list target_doc.selling_price_list = price_list
if currency:
target_doc.currency = currency
return target_doc return target_doc

View File

@ -664,7 +664,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
reference_doctype: me.frm.doctype, reference_doctype: me.frm.doctype,
reference_name: me.frm.docname, reference_name: me.frm.docname,
content: __('Reason for hold: ')+data.reason_for_hold, 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) { callback: function(r) {
if(!r.exc) { if(!r.exc) {

View File

@ -496,7 +496,7 @@ def close_or_unclose_sales_orders(names, status):
def get_requested_item_qty(sales_order): def get_requested_item_qty(sales_order):
return frappe._dict(frappe.db.sql(""" return frappe._dict(frappe.db.sql("""
select sales_order_item, sum(stock_qty) select sales_order_item, sum(qty)
from `tabMaterial Request Item` from `tabMaterial Request Item`
where docstatus = 1 where docstatus = 1
and sales_order = %s and sales_order = %s
@ -507,16 +507,12 @@ def get_requested_item_qty(sales_order):
def make_material_request(source_name, target_doc=None): def make_material_request(source_name, target_doc=None):
requested_item_qty = get_requested_item_qty(source_name) 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): def update_item(source, target, source_parent):
# qty is for packed items, because packed items don't have stock_qty field # 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.project = source_parent.project
target.qty = qty - requested_item_qty.get(source.name, 0) target.qty = qty - requested_item_qty.get(source.name, 0)
target.conversion_factor = 1 target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
target.stock_qty = qty - requested_item_qty.get(source.name, 0)
doc = get_mapped_doc("Sales Order", source_name, { doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
@ -537,14 +533,12 @@ def make_material_request(source_name, target_doc=None):
"doctype": "Material Request Item", "doctype": "Material Request Item",
"field_map": { "field_map": {
"name": "sales_order_item", "name": "sales_order_item",
"parent": "sales_order", "parent": "sales_order"
"stock_uom": "uom",
"stock_qty": "qty"
}, },
"condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code) and doc.stock_qty > requested_item_qty.get(doc.name, 0), "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 "postprocess": update_item
} }
}, target_doc, postprocess) }, target_doc)
return doc return doc

View File

@ -64,30 +64,40 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
for d in item_prices_data: for d in item_prices_data:
item_prices[d.item_code] = d item_prices[d.item_code] = d
# prepare filter for bin query
bin_filters = {'item_code': ['in', items]}
if warehouse:
bin_filters['warehouse'] = warehouse
if display_items_in_stock: if display_items_in_stock:
filters = {'actual_qty': [">", 0], 'item_code': ['in', items]} bin_filters['actual_qty'] = [">", 0]
if warehouse: # query item bin
filters['warehouse'] = warehouse bin_data = frappe.get_all(
'Bin', fields=['item_code', 'sum(actual_qty) as actual_qty'],
filters=bin_filters, group_by='item_code'
)
bin_data = frappe._dict( # convert list of dict into dict as {item_code: actual_qty}
frappe.get_all("Bin", fields = ["item_code", "sum(actual_qty) as actual_qty"], bin_dict = {}
filters = filters, group_by = "item_code") for b in bin_data:
) bin_dict[b.get('item_code')] = b.get('actual_qty')
for item in items_data: for item in items_data:
row = {} item_code = item.item_code
item_price = item_prices.get(item_code) or {}
item_stock_qty = bin_dict.get(item_code)
row.update(item) if display_items_in_stock and not item_stock_qty:
item_price = item_prices.get(item.item_code) or {} pass
row.update({ else:
'price_list_rate': item_price.get('price_list_rate'), row = {}
'currency': item_price.get('currency'), row.update(item)
'actual_qty': bin_data.get('actual_qty') row.update({
}) 'price_list_rate': item_price.get('price_list_rate'),
'currency': item_price.get('currency'),
result.append(row) 'actual_qty': item_stock_qty,
})
result.append(row)
res = { res = {
'items': result 'items': result

View File

@ -121,8 +121,8 @@ def get_columns(filters):
}, },
{ {
"label": _("Billed Amount"), "label": _("Billed Amount"),
"fieldname": "rate", "fieldtype": "currency",
"options": "billed_amount", "fieldname": "billed_amount",
"width": 120 "width": 120
}, },
{ {

View File

@ -413,15 +413,20 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
*/ */
set_batch_number: function(cdt, cdn) { set_batch_number: function(cdt, cdn) {
const doc = frappe.get_doc(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); this._set_batch_number(doc);
} }
}, },
_set_batch_number: function(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({ return frappe.call({
method: 'erpnext.stock.doctype.batch.batch.get_batch_no', 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) { callback: function(r) {
if(r.message) { if(r.message) {
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message); frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);

View File

@ -41,8 +41,7 @@
"charts": [ "charts": [
{ {
"chart_name": "Bank Balance", "chart_name": "Bank Balance",
"label": "All Your Money", "label": "Bank Balance"
"size": "Full"
} }
], ],
"creation": "2020-01-23 13:46:38.833076", "creation": "2020-01-23 13:46:38.833076",
@ -55,7 +54,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Getting Started", "label": "Getting Started",
"modified": "2020-03-12 16:30:37.821762", "modified": "2020-03-23 11:20:49.161823",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Getting Started", "name": "Getting Started",
@ -82,6 +81,16 @@
"is_query_report": 0, "is_query_report": 0,
"link_to": "Sales Invoice", "link_to": "Sales Invoice",
"type": "DocType" "type": "DocType"
},
{
"is_query_report": 0,
"link_to": "dashboard",
"type": "Page"
},
{
"is_query_report": 0,
"link_to": "leaderboard",
"type": "Page"
} }
] ]
} }

View File

@ -157,6 +157,7 @@
{ {
"fieldname": "parent_company", "fieldname": "parent_company",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Parent Company", "label": "Parent Company",
"options": "Company" "options": "Company"
@ -277,6 +278,7 @@
"depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"", "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"",
"fieldname": "existing_company", "fieldname": "existing_company",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Existing Company ", "label": "Existing Company ",
"no_copy": 1, "no_copy": 1,
"options": "Company" "options": "Company"
@ -728,7 +730,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-03-18 18:09:53.534211", "modified": "2020-03-21 18:09:53.534211",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",

View File

@ -13,12 +13,16 @@ install_docs = [
] ]
def get_warehouse_account_map(company=None): 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() warehouse_account = frappe._dict()
filters = {} filters = {}
if company: if company:
filters['company'] = company filters['company'] = company
frappe.flags.setdefault('warehouse_account_map', {}).setdefault(company, {})
for d in frappe.get_all('Warehouse', for d in frappe.get_all('Warehouse',
fields = ["name", "account", "parent_warehouse", "company", "is_group"], fields = ["name", "account", "parent_warehouse", "company", "is_group"],
@ -30,10 +34,12 @@ def get_warehouse_account_map(company=None):
if d.account: if d.account:
d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True) d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True)
warehouse_account.setdefault(d.name, d) warehouse_account.setdefault(d.name, d)
if company:
frappe.flags.warehouse_account_map[company] = warehouse_account
else:
frappe.flags.warehouse_account_map = warehouse_account
frappe.flags.warehouse_account_map = warehouse_account return frappe.flags.warehouse_account_map.get(company) or frappe.flags.warehouse_account_map
return frappe.flags.warehouse_account_map
def get_warehouse_account(warehouse, warehouse_account=None): def get_warehouse_account(warehouse, warehouse_account=None):
account = warehouse.account account = warehouse.account

View File

@ -122,8 +122,11 @@ class Batch(Document):
self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days) self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
if has_expiry_date and not self.expiry_date: if has_expiry_date and not self.expiry_date:
frappe.msgprint(_('Expiry date is mandatory for selected item.')) frappe.throw(msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.") \
frappe.throw(_("Set item's shelf life in days, to set expiry based on manufacturing date plus shelf-life.")) .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): def get_name_from_naming_series(self):
""" """

View File

@ -343,7 +343,8 @@
{ {
"fieldname": "shelf_life_in_days", "fieldname": "shelf_life_in_days",
"fieldtype": "Int", "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", "default": "2099-12-31",
@ -1045,7 +1046,7 @@
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"max_attachments": 1, "max_attachments": 1,
"modified": "2020-01-02 19:13:59.295963", "modified": "2020-03-24 16:14:36.950677",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@ -741,14 +741,12 @@ class Item(WebsiteGenerator):
defaults = frappe.defaults.get_defaults() or {} defaults = frappe.defaults.get_defaults() or {}
# To check default warehouse is belong to the default company # 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}): {'name': defaults.default_warehouse, 'company': defaults.company}):
warehouse = defaults.default_warehouse self.append("item_defaults", {
"company": defaults.get("company"),
self.append("item_defaults", { "default_warehouse": defaults.default_warehouse
"company": defaults.get("company"), })
"default_warehouse": warehouse
})
def update_variants(self): def update_variants(self):
if self.flags.dont_update_variants or \ if self.flags.dont_update_variants or \

View File

@ -8,6 +8,7 @@ from frappe.utils import flt
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos 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): class LandedCostVoucher(Document):
def get_items_from_purchase_receipts(self): def get_items_from_purchase_receipts(self):
@ -43,6 +44,7 @@ class LandedCostVoucher(Document):
else: else:
self.validate_applicable_charges_for_item() self.validate_applicable_charges_for_item()
self.validate_purchase_receipts() self.validate_purchase_receipts()
self.validate_expense_accounts()
self.set_total_taxes_and_charges() self.set_total_taxes_and_charges()
def check_mandatory(self): def check_mandatory(self):
@ -71,6 +73,14 @@ class LandedCostVoucher(Document):
frappe.throw(_("Row {0}: Cost center is required for an item {1}") frappe.throw(_("Row {0}: Cost center is required for an item {1}")
.format(item.idx, item.item_code)) .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): def set_total_taxes_and_charges(self):
self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")]) self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")])

View File

@ -501,7 +501,7 @@ def raise_work_orders(material_request):
wo_order = frappe.new_doc("Work Order") wo_order = frappe.new_doc("Work Order")
wo_order.update({ wo_order.update({
"production_item": d.item_code, "production_item": d.item_code,
"qty": d.qty - d.ordered_qty, "qty": d.stock_qty - d.ordered_qty,
"fg_warehouse": d.warehouse, "fg_warehouse": d.warehouse,
"wip_warehouse": default_wip_warehouse, "wip_warehouse": default_wip_warehouse,
"description": d.description, "description": d.description,

View File

@ -523,12 +523,15 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note):
return serial_nos return serial_nos
@frappe.whitelist() @frappe.whitelist()
def auto_fetch_serial_number(qty, item_code, warehouse, batch_no=None): def auto_fetch_serial_number(qty, item_code, warehouse, batch_nos=None):
serial_numbers = frappe.get_list("Serial No", filters={ import json
filters = {
"item_code": item_code, "item_code": item_code,
"warehouse": warehouse, "warehouse": warehouse,
"batch_no": batch_no,
"delivery_document_no": "", "delivery_document_no": "",
"sales_invoice": "" "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] return [item['name'] for item in serial_numbers]

View File

@ -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 { return {
query : "erpnext.controllers.queries.get_batch_no", query : "erpnext.controllers.queries.get_batch_no",
filters: filters filters: filters
@ -309,12 +310,12 @@ frappe.ui.form.on('Stock Entry', {
method: "erpnext.stock.get_item_details.get_serial_no", method: "erpnext.stock.get_item_details.get_serial_no",
args: {"args": args}, args: {"args": args},
callback: function(r) { callback: function(r) {
if (!r.exe){ if (!r.exe && r.message){
frappe.model.set_value(cdt, cdn, "serial_no", r.message); frappe.model.set_value(cdt, cdn, "serial_no", r.message);
}
if (callback) { if (callback) {
callback(); callback();
}
} }
} }
}); });
@ -622,10 +623,15 @@ frappe.ui.form.on('Stock Entry Detail', {
if(r.message) { if(r.message) {
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
$.each(r.message, function(k, v) { $.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"); 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; if (frm.doc.purpose === 'Material Receipt') return;
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {

View File

@ -238,7 +238,7 @@ class StockEntry(StockController):
if self.purpose == "Manufacture" and self.work_order: if self.purpose == "Manufacture" and self.work_order:
production_item = frappe.get_value('Work Order', self.work_order, 'production_item') production_item = frappe.get_value('Work Order', self.work_order, 'production_item')
for item in self.items: 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 <b>{0}</b> and For Quantity <b>{1}</b> cannot be different") frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different")
.format(item.qty, self.fg_completed_qty)) .format(item.qty, self.fg_completed_qty))
@ -298,13 +298,8 @@ class StockEntry(StockController):
if validate_for_manufacture: if validate_for_manufacture:
if d.bom_no: if d.bom_no:
d.s_warehouse = None d.s_warehouse = None
if not d.t_warehouse: if not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) 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: else:
d.t_warehouse = None d.t_warehouse = None
if not d.s_warehouse: if not d.s_warehouse:

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-03-29 18:22:12", "creation": "2013-03-29 18:22:12",
"doctype": "DocType", "doctype": "DocType",
@ -479,8 +480,7 @@
"fieldname": "project", "fieldname": "project",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Project", "label": "Project",
"options": "Project", "options": "Project"
"read_only": 1
}, },
{ {
"fieldname": "po_detail", "fieldname": "po_detail",
@ -494,7 +494,8 @@
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-08-20 14:01:02.319754", "links": [],
"modified": "2020-03-19 12:34:09.836295",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Detail", "name": "Stock Entry Detail",

View File

@ -29,7 +29,13 @@ frappe.query_reports["Stock Ledger"] = {
"fieldname":"warehouse", "fieldname":"warehouse",
"label": __("Warehouse"), "label": __("Warehouse"),
"fieldtype": "Link", "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", "fieldname":"item_code",

View File

@ -18,7 +18,7 @@
"is_standard": 1, "is_standard": 1,
"login_required": 1, "login_required": 1,
"max_attachment_size": 0, "max_attachment_size": 0,
"modified": "2019-12-10 13:48:19.894186", "modified": "2020-03-06 05:24:05.749664",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Support", "module": "Support",
"name": "issues", "name": "issues",
@ -58,7 +58,7 @@
"options": "Open\nReplied\nHold\nClosed", "options": "Open\nReplied\nHold\nClosed",
"read_only": 1, "read_only": 1,
"reqd": 0, "reqd": 0,
"show_in_filter": 0 "show_in_filter": 1
}, },
{ {
"allow_read_on_all_link_options": 0, "allow_read_on_all_link_options": 0,

View File

@ -7,6 +7,8 @@ import frappe
def create_test_contact_and_address(): def create_test_contact_and_address():
frappe.db.sql('delete from tabContact') 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 tabAddress')
frappe.db.sql('delete from `tabDynamic Link`') frappe.db.sql('delete from `tabDynamic Link`')

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import frappe.share import frappe.share
from frappe import _ 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 erpnext.controllers.status_updater import StatusUpdater
from six import string_types 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") 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: 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)) .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): def get_link_filters(self, for_doctype):
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype): if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):