diff --git a/README.md b/README.md index 64f8d67d8e8..ed57a17279c 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,26 @@ -Includes: Accounting, Inventory, Manufacturing, CRM, Sales, Purchase, Project Management, HRMS. Requires MariaDB. +ERPNext as a monolith includes the following areas for managing businesses: -ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript. +1. [Accounting](https://erpnext.com/docs/user/manual/en/accounts) +1. [Inventory](https://erpnext.com/docs/user/manual/en/stock) +1. [CRM](https://erpnext.com/docs/user/manual/en/CRM) +1. [Sales](https://erpnext.com/docs/user/manual/en/selling) +1. [Purchase](https://erpnext.com/docs/user/manual/en/buying) +1. [HRMS](https://erpnext.com/docs/user/manual/en/human-resources) +1. [Project Management](https://erpnext.com/docs/user/manual/en/projects) +1. [Support](https://erpnext.com/docs/user/manual/en/support) +1. [Asset Management](https://erpnext.com/docs/user/manual/en/asset) +1. [Quality Management](https://erpnext.com/docs/user/manual/en/quality-management) +1. [Manufacturing](https://erpnext.com/docs/user/manual/en/manufacturing) +1. [Website Management](https://erpnext.com/docs/user/manual/en/website) +1. [Customize ERPNext](https://erpnext.com/docs/user/manual/en/customize-erpnext) +1. [And More](https://erpnext.com/docs/user/manual/en/) + +ERPNext requires MariaDB. + +ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a full-stack web app framework built with Python & JavaScript. - [User Guide](https://erpnext.com/docs/user) - [Discussion Forum](https://discuss.erpnext.com/) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 180460c091c..f48d6dfc953 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -41,8 +41,8 @@ class AccountingPeriod(Document): def get_doctypes_for_closing(self): docs_for_closing = [] - doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", "Bank Reconciliation", - "Asset", "Purchase Order", "Sales Order", "Leave Application", "Leave Allocation", "Stock Entry"] + doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \ + "Bank Reconciliation", "Asset", "Stock Entry"] closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes] for closed_doctype in closed_doctypes: docs_for_closing.append(closed_doctype) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 078e05816db..041e419752b 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -29,7 +29,6 @@ class GLEntry(Document): self.validate_and_set_fiscal_year() self.pl_must_have_cost_center() self.validate_cost_center() - self.validate_dimensions_for_pl_and_bs() if not self.flags.from_repost: self.check_pl_account() @@ -39,6 +38,7 @@ class GLEntry(Document): def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): if not from_repost: self.validate_account_details(adv_adj) + self.validate_dimensions_for_pl_and_bs() check_freezing_date(self.posting_date, adv_adj) validate_frozen_account(self.account, adv_adj) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index d6236cdb04f..3604b60b751 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -190,7 +190,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ if(jvd.reference_type==="Employee Advance") { return { filters: { - 'status': ['=', 'Unpaid'], 'docstatus': 1 } }; diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index e25942ca34d..88973373ed7 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -968,7 +968,7 @@ def get_exchange_rate(posting_date, account=None, account_currency=None, company # The date used to retreive the exchange rate here is the date passed # in as an argument to this function. - elif (not exchange_rate or exchange_rate==1) and account_currency and posting_date: + elif (not exchange_rate or flt(exchange_rate)==1) and account_currency and posting_date: exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date) else: exchange_rate = 1 diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index ab811d81b25..9552e60a857 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -90,7 +90,6 @@ "fieldtype": "Column Break" }, { - "default": "Customer", "fieldname": "party_type", "fieldtype": "Link", "in_list_view": 1, @@ -201,7 +200,7 @@ "fieldname": "reference_type", "fieldtype": "Select", "label": "Reference Type", - "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting" + "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees" }, { "fieldname": "reference_name", @@ -272,7 +271,7 @@ ], "idx": 1, "istable": 1, - "modified": "2019-10-02 12:23:21.693443", + "modified": "2020-01-13 12:41:33.968025", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", @@ -281,4 +280,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index adf47ed2764..2192b7bf989 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -652,14 +652,16 @@ frappe.ui.form.on('Payment Entry', { (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ) { if(total_positive_outstanding > total_negative_outstanding) - frm.set_value("paid_amount", - total_positive_outstanding - total_negative_outstanding); + if (!frm.doc.paid_amount) + frm.set_value("paid_amount", + total_positive_outstanding - total_negative_outstanding); } else if ( total_negative_outstanding && total_positive_outstanding < total_negative_outstanding ) { - frm.set_value("received_amount", - total_negative_outstanding - total_positive_outstanding); + if (!frm.doc.received_amount) + frm.set_value("received_amount", + total_negative_outstanding - total_positive_outstanding); } } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9530fc9556b..214d6088667 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -911,7 +911,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= else: party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company) - party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account) + if dt not in ("Sales Invoice", "Purchase Invoice"): + party_account_currency = get_account_currency(party_account) + else: + party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account) # payment type if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees") and doc.outstanding_amount > 0)) \ diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index d85344e8b7a..2c04a27b0cd 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -23,6 +23,8 @@ class PaymentReconciliation(Document): if self.party_type in ["Customer", "Supplier"]: dr_or_cr_notes = self.get_dr_or_cr_notes() + else: + dr_or_cr_notes = [] self.add_payment_entries(payment_entries + journal_entries + dr_or_cr_notes) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index e2510f675f3..e1e43140c01 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -2,6 +2,16 @@ cur_frm.add_fetch("payment_gateway_account", "payment_account", "payment_account cur_frm.add_fetch("payment_gateway_account", "payment_gateway", "payment_gateway") cur_frm.add_fetch("payment_gateway_account", "message", "message") +frappe.ui.form.on("Payment Request", { + setup: function(frm) { + frm.set_query("party_type", function() { + return { + query: "erpnext.setup.doctype.party_type.party_type.get_party_type", + }; + }); + } +}) + frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){ if (frm.doc.reference_doctype) { frappe.call({ diff --git a/erpnext/accounts/doctype/pos_field/__init__.py b/erpnext/accounts/doctype/pos_field/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/pos_field/pos_field.json b/erpnext/accounts/doctype/pos_field/pos_field.json new file mode 100644 index 00000000000..13edabd985d --- /dev/null +++ b/erpnext/accounts/doctype/pos_field/pos_field.json @@ -0,0 +1,77 @@ +{ + "creation": "2019-08-22 14:35:39.043242", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "fieldname", + "label", + "fieldtype", + "column_break_7", + "options", + "default_value", + "reqd", + "read_only" + ], + "fields": [ + { + "fieldname": "fieldname", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Fieldname" + }, + { + "fieldname": "fieldtype", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Fieldtype", + "read_only": 1 + }, + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "read_only": 1 + }, + { + "fieldname": "options", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Options", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "reqd", + "fieldtype": "Check", + "label": "Mandatory" + }, + { + "default": "0", + "fieldname": "read_only", + "fieldtype": "Check", + "label": "Read Only" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_value", + "fieldtype": "Data", + "label": "Default Value" + } + ], + "istable": 1, + "modified": "2019-08-23 13:59:34.025523", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Field", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_field/pos_field.py b/erpnext/accounts/doctype/pos_field/pos_field.py new file mode 100644 index 00000000000..b4720b309bd --- /dev/null +++ b/erpnext/accounts/doctype/pos_field/pos_field.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class POSField(Document): + pass diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js index 1a146185139..f5b681bd41d 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.js +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js @@ -2,7 +2,46 @@ // For license information, please see license.txt frappe.ui.form.on('POS Settings', { - refresh: function() { + onload: function(frm) { + frm.trigger("get_invoice_fields"); + }, + use_pos_in_offline_mode: function(frm) { + frm.trigger("get_invoice_fields"); + }, + + get_invoice_fields: function(frm) { + if (!frm.doc.use_pos_in_offline_mode) { + frappe.model.with_doctype("Sales Invoice", () => { + var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) { + if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || + d.fieldtype === 'Table') { + return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; + } else { + return null; + } + }); + + frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields); + }); + } else { + frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""]; + } + } +}); + +frappe.ui.form.on("POS Field", { + fieldname: function(frm, doctype, name) { + var doc = frappe.get_doc(doctype, name); + var df = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) { + return doc.fieldname == d.fieldname ? d : null; + })[0]; + + doc.label = df.label; + doc.reqd = df.reqd; + doc.options = df.options; + doc.fieldtype = df.fieldtype; + doc.default_value = df.default; + frm.refresh_field("fields"); } }); diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json index 8f5b631c89a..1d55880415f 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.json +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json @@ -1,133 +1,68 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-08-28 16:46:41.732676", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2017-08-28 16:46:41.732676", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "use_pos_in_offline_mode", + "section_break_2", + "fields" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "use_pos_in_offline_mode", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Use POS in Offline Mode", - "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, - "unique": 0 + "default": "0", + "fieldname": "use_pos_in_offline_mode", + "fieldtype": "Check", + "label": "Use POS in Offline Mode" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:!doc.use_pos_in_offline_mode", + "fieldname": "fields", + "fieldtype": "Table", + "label": "POS Field", + "options": "POS Field" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2017-09-11 13:57:28.787023", - "modified_by": "Administrator", - "module": "Accounts", - "name": "POS Settings", - "name_case": "", - "owner": "Administrator", + ], + "issingle": 1, + "links": [], + "modified": "2019-12-26 11:50:47.122997", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Settings", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Sales User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "email": 1, + "print": 1, + "read": 1, + "role": "Sales User", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 971d308368a..29d83783d07 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -389,8 +389,7 @@ "fieldname": "rate_or_discount", "fieldtype": "Select", "label": "Rate or Discount", - "options": "\nRate\nDiscount Percentage\nDiscount Amount", - "reqd": 1 + "options": "\nRate\nDiscount Percentage\nDiscount Amount" }, { "default": "Grand Total", @@ -439,19 +438,20 @@ }, { "default": "0", - "depends_on": "eval:!doc.mixed_conditions", + "depends_on": "eval:!doc.mixed_conditions && doc.apply_on != 'Transaction'", "fieldname": "same_item", "fieldtype": "Check", "label": "Same Item" }, { - "depends_on": "eval:!doc.same_item || doc.mixed_conditions", + "depends_on": "eval:(!doc.same_item || doc.apply_on == 'Transaction') || doc.mixed_conditions", "fieldname": "free_item", "fieldtype": "Link", "label": "Free Item", "options": "Item" }, { + "default": "0", "fieldname": "free_qty", "fieldtype": "Float", "label": "Qty" @@ -554,7 +554,7 @@ ], "icon": "fa fa-gift", "idx": 1, - "modified": "2019-10-15 12:39:40.399792", + "modified": "2019-12-18 17:29:22.957077", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index e871d98af6a..3c14819e6fe 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -47,6 +47,9 @@ class PricingRule(Document): if tocheck and not self.get(tocheck): throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError) + if self.price_or_product_discount == 'Price' and not self.rate_or_discount: + throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError) + def validate_applicable_for_selling_or_buying(self): if not self.selling and not self.buying: throw(_("Atleast one of the Selling or Buying must be selected")) @@ -182,7 +185,7 @@ def get_serial_no_for_item(args): def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, - get_applied_pricing_rules, get_pricing_rule_items) + get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) if isinstance(doc, string_types): doc = json.loads(doc) @@ -241,9 +244,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if pricing_rule.coupon_code_based==1 and args.coupon_code==None: return item_details - if (not pricing_rule.validate_applied_rule and - pricing_rule.price_or_product_discount == "Price"): - apply_price_discount_pricing_rule(pricing_rule, item_details, args) + if not pricing_rule.validate_applied_rule: + if pricing_rule.price_or_product_discount == "Price": + apply_price_discount_rule(pricing_rule, item_details, args) + else: + get_product_discount_rule(pricing_rule, item_details, doc) item_details.has_pricing_rule = 1 @@ -293,7 +298,7 @@ def get_pricing_rule_details(args, pricing_rule): 'child_docname': args.get('child_docname') }) -def apply_price_discount_pricing_rule(pricing_rule, item_details, args): +def apply_price_discount_rule(pricing_rule, item_details, args): item_details.pricing_rule_for = pricing_rule.rate_or_discount if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 637e503e658..fe68fdb6c0f 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import frappe, copy, json from frappe import throw, _ from six import string_types -from frappe.utils import flt, cint, get_datetime +from frappe.utils import flt, cint, get_datetime, get_link_to_form, today from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.get_item_details import get_conversion_factor @@ -284,7 +284,7 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None): status = True # if user has created item price against the transaction UOM - if rule.get("uom") == args.get("uom"): + if args and rule.get("uom") == args.get("uom"): conversion_factor = 1.0 if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor) @@ -408,7 +408,8 @@ def apply_pricing_rule_on_transaction(doc): conditions = get_other_conditions(conditions, values, doc) pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule` - where {conditions} """.format(conditions = conditions), values, as_dict=1) + where {conditions} and `tabPricing Rule`.disable = 0 + """.format(conditions = conditions), values, as_dict=1) if pricing_rules: pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, @@ -420,39 +421,65 @@ def apply_pricing_rule_on_transaction(doc): doc.set('apply_discount_on', d.apply_discount_on) for field in ['additional_discount_percentage', 'discount_amount']: - if not d.get(field): continue - pr_field = ('discount_percentage' if field == 'additional_discount_percentage' else field) + if not d.get(pr_field): continue + if d.validate_applied_rule and doc.get(field) < d.get(pr_field): frappe.msgprint(_("User has not applied rule on the invoice {0}") .format(doc.name)) else: doc.set(field, d.get(pr_field)) + + doc.calculate_taxes_and_totals() elif d.price_or_product_discount == 'Product': - apply_pricing_rule_for_free_items(doc, d) + item_details = frappe._dict({'parenttype': doc.doctype}) + get_product_discount_rule(d, item_details, doc) + apply_pricing_rule_for_free_items(doc, item_details.free_item_data) + doc.set_missing_values() def get_applied_pricing_rules(item_row): return (item_row.get("pricing_rules").split(',') if item_row.get("pricing_rules") else []) -def apply_pricing_rule_for_free_items(doc, pricing_rule): - if pricing_rule.get('free_item'): +def get_product_discount_rule(pricing_rule, item_details, doc=None): + free_item = (pricing_rule.free_item + if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code) + + if not free_item: + frappe.throw(_("Free item not set in the pricing rule {0}") + .format(get_link_to_form("Pricing Rule", pricing_rule.name))) + + item_details.free_item_data = { + 'item_code': free_item, + 'qty': pricing_rule.free_qty or 1, + 'rate': pricing_rule.free_item_rate or 0, + 'price_list_rate': pricing_rule.free_item_rate or 0, + 'is_free_item': 1 + } + + item_data = frappe.get_cached_value('Item', free_item, ['item_name', + 'description', 'stock_uom'], as_dict=1) + + item_details.free_item_data.update(item_data) + item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom + item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, + item_details.free_item_data['uom']).get("conversion_factor", 1) + + if item_details.get("parenttype") == 'Purchase Order': + item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today() + + if item_details.get("parenttype") == 'Sales Order': + item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() + +def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): + if pricing_rule_args.get('item_code'): items = [d.item_code for d in doc.items - if d.item_code == (d.item_code - if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item] + if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item] if not items: - doc.append('items', { - 'item_code': pricing_rule.get('free_item'), - 'qty': pricing_rule.get('free_qty'), - 'uom': pricing_rule.get('free_item_uom'), - 'rate': pricing_rule.get('free_item_rate') or 0, - 'is_free_item': 1 - }) - - doc.set_missing_values() + doc.append('items', pricing_rule_args) def get_pricing_rule_items(pr_doc): apply_on_data = [] @@ -468,7 +495,7 @@ def get_pricing_rule_items(pr_doc): if pr_doc.apply_rule_on_other: apply_on = frappe.scrub(pr_doc.apply_rule_on_other) - apply_on_data.append(pr_doc.get(apply_on)) + apply_on_data.append(pr_doc.get("other_" + apply_on)) return list(set(apply_on_data)) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index d7e64cf36fd..643de7d300a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -382,21 +382,11 @@ cur_frm.fields_dict['items'].grid.get_field("item_code").get_query = function(do cur_frm.fields_dict['credit_to'].get_query = function(doc) { // filter on Account - if (doc.supplier) { - return { - filters: { - 'account_type': 'Payable', - 'is_group': 0, - 'company': doc.company - } - } - } else { - return { - filters: { - 'report_type': 'Balance Sheet', - 'is_group': 0, - 'company': doc.company - } + return { + filters: { + 'account_type': 'Payable', + 'is_group': 0, + 'company': doc.company } } } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 6fe18115c04..7725994c6b0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -417,6 +418,7 @@ "fieldname": "contact_email", "fieldtype": "Small Text", "label": "Contact Email", + "options": "Email", "print_hide": 1, "read_only": 1 }, @@ -705,7 +707,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Text", + "fieldtype": "Long Text", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -1287,7 +1289,8 @@ "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, - "modified": "2019-09-17 22:31:42.666601", + "links": [], + "modified": "2019-12-30 19:13:49.610538", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 7f4ae3c1fc4..db6ac55a119 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -556,22 +556,11 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) { } cur_frm.set_query("debit_to", function(doc) { - // filter on Account - if (doc.customer) { - return { - filters: { - 'account_type': 'Receivable', - 'is_group': 0, - 'company': doc.company - } - } - } else { - return { - filters: { - 'report_type': 'Balance Sheet', - 'is_group': 0, - 'company': doc.company - } + return { + filters: { + 'account_type': 'Receivable', + 'is_group': 0, + 'company': doc.company } } }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 96aceac8cd9..33ee7a2974e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:05", @@ -774,7 +775,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Text", + "fieldtype": "Long Text", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -1567,7 +1568,8 @@ "icon": "fa fa-file-text", "idx": 181, "is_submittable": 1, - "modified": "2019-10-05 21:39:49.235990", + "links": [], + "modified": "2019-12-30 19:15:59.580414", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0f4d4451be9..703df796c06 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -90,6 +90,7 @@ class SalesInvoice(SellingController): self.validate_account_for_change_amount() self.validate_fixed_asset() self.set_income_account_for_fixed_assets() + self.validate_item_cost_centers() validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference) if cint(self.is_pos): @@ -147,6 +148,12 @@ class SalesInvoice(SellingController): elif asset.status in ("Scrapped", "Cancelled", "Sold"): frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status)) + def validate_item_cost_centers(self): + for item in self.items: + cost_center_company = frappe.get_cached_value("Cost Center", item.cost_center, "company") + if cost_center_company != self.company: + frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company))) + def before_save(self): set_account_for_mode_of_payment(self) @@ -348,7 +355,8 @@ class SalesInvoice(SellingController): "print_format": print_format, "allow_edit_rate": pos.get("allow_user_to_edit_rate"), "allow_edit_discount": pos.get("allow_user_to_edit_discount"), - "campaign": pos.get("campaign") + "campaign": pos.get("campaign"), + "allow_print_before_pay": pos.get("allow_print_before_pay") } def update_time_sheet(self, sales_invoice): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 530bd893c0e..a2a47b3a19c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest, copy, time -from frappe.utils import nowdate, flt, getdate, cint +from frappe.utils import nowdate, flt, getdate, cint, add_days from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice @@ -1847,6 +1847,26 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) + def test_item_tax_validity(self): + item = frappe.get_doc("Item", "_Test Item 2") + + if item.taxes: + item.taxes = [] + item.save() + + item.append("taxes", { + "item_tax_template": "_Test Item Tax Template 1", + "valid_from": add_days(nowdate(), 1) + }) + + item.save() + + sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1) + sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1" + self.assertRaises(frappe.ValidationError, sales_invoice.save) + + item.taxes = [] + item.save() def create_sales_invoice(**args): si = frappe.new_doc("Sales Invoice") diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.json b/erpnext/accounts/doctype/share_transfer/share_transfer.json index f17bf04cafd..59a305317d8 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.json +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.json @@ -188,7 +188,7 @@ } ], "is_submittable": 1, - "modified": "2019-11-07 13:31:17.999744", + "modified": "2019-12-20 14:48:01.990600", "modified_by": "Administrator", "module": "Accounts", "name": "Share Transfer", @@ -196,6 +196,7 @@ "permissions": [ { "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -221,6 +222,7 @@ "write": 1 }, { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -230,6 +232,7 @@ "report": 1, "role": "Accounts Manager", "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index f13ca4c49e8..54827503759 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -338,6 +338,16 @@ class Subscription(Document): # Check invoice dates and make sure it doesn't have outstanding invoices return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice() + + def is_current_invoice_paid(self): + if self.is_new_subscription(): + return False + + last_invoice = frappe.get_doc('Sales Invoice', self.invoices[-1].invoice) + if getdate(last_invoice.posting_date) == getdate(self.current_invoice_start) and last_invoice.status == 'Paid': + return True + + return False def process_for_active(self): """ @@ -348,7 +358,7 @@ class Subscription(Document): 2. Change the `Subscription` status to 'Past Due Date' 3. Change the `Subscription` status to 'Cancelled' """ - if self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice(): + if not self.is_current_invoice_paid() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): self.generate_invoice() if self.current_invoice_is_past_due(): self.status = 'Past Due Date' diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index feb598a2e51..bb1b7e392dc 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -90,8 +90,12 @@ def merge_similar_entries(gl_map): else: merged_gl_map.append(entry) + company = gl_map[0].company if gl_map else erpnext.get_default_company() + company_currency = erpnext.get_company_currency(company) + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency) + # filter zero debit and credit entries - merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map) + merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map) merged_gl_map = list(merged_gl_map) return merged_gl_map diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index bd4b4d7e0b1..69f9907a8d8 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -18,6 +18,10 @@ def reconcile(bank_transaction, payment_doctype, payment_name): account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name)) + if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount: + frappe.throw(_("The unallocated amount of Payment Entry {0} \ + is greater than the Bank Transaction's unallocated amount").format(payment_name)) + if transaction.unallocated_amount == 0: frappe.throw(_("This bank transaction is already fully reconciled")) @@ -373,4 +377,4 @@ def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): 'start': start, 'page_len': page_len } - ) \ No newline at end of file + ) diff --git a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html index 69e42c3bdbc..6fe69990513 100644 --- a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html +++ b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html @@ -49,7 +49,7 @@ {% endfor %}
\n\t{{ doc.company }}
\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
\n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
\n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t{% endif %}\n
\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{{ _(\"Customer\") }}:
\n\t\t{{ doc.customer_name }}
\n\t\t{{ customer_address }}\n\t{% endif %}\n
| {{ _(\"Item\") }} | \n\t\t\t{{ _(\"Qty\") }} | \n\t\t\t{{ _(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t {{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t {{ _(\"Serial No\") }}: {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t | \n\t\t\t{{ item.qty }} @ {{ item.rate }} | \n\t\t\t{{ item.get_formatted(\"amount\") }} | \n\t\t
| \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t | \n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t | \n\t\t\t{% endif %}\n\t\t
| \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t | \n\t\t
{{ doc.terms or \"\" }}
\n{{ _(\"Thank you, please visit again.\") }}
", - "idx": 0, - "line_breaks": 0, - "modified": "2019-01-24 17:09:27.190929", - "modified_by": "Administrator", - "module": "Accounts", - "name": "GST POS Invoice", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Server", - "show_section_headings": 0, + "align_labels_right": 0, + "creation": "2017-08-08 12:33:04.773099", + "custom_format": 1, + "disabled": 0, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n\t{{ doc.company }}
\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
\n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
\n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t{% endif %}\n
\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{{ _(\"Customer\") }}:
\n\t\t{{ doc.customer_name }}
\n\t\t{{ customer_address }}\n\t{% endif %}\n
| {{ _(\"Item\") }} | \n\t\t\t{{ _(\"Qty\") }} | \n\t\t\t{{ _(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t {{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t {{ _(\"Serial No\") }}: {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t | \n\t\t\t{{ item.qty }} @ {{ item.rate }} | \n\t\t\t{{ item.get_formatted(\"amount\") }} | \n\t\t
| \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t | \n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t | \n\t\t\t{% endif %}\n\t\t
| \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t | \n\t\t
{{ doc.terms or \"\" }}
\n{{ _(\"Thank you, please visit again.\") }}
", + "idx": 0, + "line_breaks": 0, + "modified": "2019-12-09 17:39:23.356573", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GST POS Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/pos_invoice/pos_invoice.json b/erpnext/accounts/print_format/pos_invoice/pos_invoice.json index c3450d6a73c..be699228c52 100644 --- a/erpnext/accounts/print_format/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/print_format/pos_invoice/pos_invoice.json @@ -1,21 +1,22 @@ { - "align_labels_right": 0, - "creation": "2011-12-21 11:08:55", - "custom_format": 1, - "disabled": 0, - "doc_type": "Sales Invoice", - "docstatus": 0, - "doctype": "Print Format", - "html": "\n\n\n\t{{ doc.company }}
\n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \" + (doc.select_print_heading or _(\"Invoice\")) }}
\n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t{% endif %}\n
\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n
| {{ _(\"Item\") }} | \n\t\t\t{{ _(\"Qty\") }} | \n\t\t\t{{ _(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t {{ item.item_name }}{%- endif -%}\n\t\t\t | \n\t\t\t{{ item.qty }} @ {{ item.get_formatted(\"rate\") }} | \n\t\t\t{{ item.get_formatted(\"amount\") }} | \n\t\t
| \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t | \n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t | \n\t\t\t{% endif %}\n\t\t
| \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Total Qty\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"pos_total_qty\") }}\n\t\t\t | \n\t\t
{{ doc.terms or \"\" }}
\n{{ _(\"Thank you, please visit again.\") }}
", - "idx": 1, - "line_breaks": 0, - "modified": "2018-03-20 14:24:12.394354", - "modified_by": "Administrator", - "module": "Accounts", - "name": "POS Invoice", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Server", - "show_section_headings": 0, + "align_labels_right": 0, + "creation": "2011-12-21 11:08:55", + "custom_format": 1, + "disabled": 0, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n\n\t{{ doc.company }}
\n\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n
\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n
| {{ _(\"Item\") }} | \n\t\t\t{{ _(\"Qty\") }} | \n\t\t\t{{ _(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t {{ item.item_name }}{%- endif -%}\n\t\t\t | \n\t\t\t{{ item.qty }} @ {{ item.get_formatted(\"rate\") }} | \n\t\t\t{{ item.get_formatted(\"amount\") }} | \n\t\t
| \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t | \n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t | \n\t\t\t{% endif %}\n\t\t
| \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t | \n\t\t\t
{{ doc.terms or \"\" }}
\n{{ _(\"Thank you, please visit again.\") }}
", + "idx": 1, + "line_breaks": 0, + "modified": "2019-12-09 17:40:53.183574", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 2c53f6e9971..f82146a1df8 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -171,7 +171,7 @@ class ReceivablePayableReport(object): row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision) row.invoice_grand_total = row.invoiced - if abs(row.outstanding) > 0.1/10 ** self.currency_precision: + if abs(row.outstanding) > 1.0/10 ** self.currency_precision: # non-zero oustanding, we must consider this row if self.is_invoice(row) and self.filters.based_on_payment_terms: @@ -285,7 +285,7 @@ class ReceivablePayableReport(object): def set_party_details(self, row): # customer / supplier name - party_details = self.get_party_details(row.party) + party_details = self.get_party_details(row.party) or {} row.update(party_details) if self.filters.get(scrub(self.filters.party_type)): row.currency = row.account_currency diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 0c99f1424cf..78546609adb 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -4,126 +4,141 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import formatdate, getdate, flt, add_days +from frappe.utils import formatdate, flt, add_days + def execute(filters=None): filters.day_before_from_date = add_days(filters.from_date, -1) columns, data = get_columns(filters), get_data(filters) return columns, data - + + def get_data(filters): data = [] - + asset_categories = get_asset_categories(filters) assets = get_assets(filters) - asset_costs = get_asset_costs(assets, filters) - asset_depreciations = get_accumulated_depreciations(assets, filters) - + for asset_category in asset_categories: row = frappe._dict() - row.asset_category = asset_category - row.update(asset_costs.get(asset_category)) + # row.asset_category = asset_category + row.update(asset_category) + + row.cost_as_on_to_date = (flt(row.cost_as_on_from_date) + flt(row.cost_of_new_purchase) - + flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset)) + + row.update(next(asset for asset in assets if asset["asset_category"] == asset_category.get("asset_category", ""))) + row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + + flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated)) + + row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - + flt(row.accumulated_depreciation_as_on_from_date)) + + row.net_asset_value_as_on_to_date = (flt(row.cost_as_on_to_date) - + flt(row.accumulated_depreciation_as_on_to_date)) - row.cost_as_on_to_date = (flt(row.cost_as_on_from_date) + flt(row.cost_of_new_purchase) - - flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset)) - - row.update(asset_depreciations.get(asset_category)) - row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + - flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated)) - - row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - - flt(row.accumulated_depreciation_as_on_from_date)) - - row.net_asset_value_as_on_to_date = (flt(row.cost_as_on_to_date) - - flt(row.accumulated_depreciation_as_on_to_date)) - data.append(row) - + return data - + + def get_asset_categories(filters): - return frappe.db.sql_list(""" - select distinct asset_category from `tabAsset` - where docstatus=1 and company=%s and purchase_date <= %s - """, (filters.company, filters.to_date)) - + return frappe.db.sql(""" + SELECT asset_category, + ifnull(sum(case when purchase_date < %(from_date)s then + case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then + gross_purchase_amount + else + 0 + end + else + 0 + end), 0) as cost_as_on_from_date, + ifnull(sum(case when purchase_date >= %(from_date)s then + gross_purchase_amount + else + 0 + end), 0) as cost_of_new_purchase, + ifnull(sum(case when ifnull(disposal_date, 0) != 0 + and disposal_date >= %(from_date)s + and disposal_date <= %(to_date)s then + case when status = "Sold" then + gross_purchase_amount + else + 0 + end + else + 0 + end), 0) as cost_of_sold_asset, + ifnull(sum(case when ifnull(disposal_date, 0) != 0 + and disposal_date >= %(from_date)s + and disposal_date <= %(to_date)s then + case when status = "Scrapped" then + gross_purchase_amount + else + 0 + end + else + 0 + end), 0) as cost_of_scrapped_asset + from `tabAsset` + where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s + group by asset_category + """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) + + def get_assets(filters): return frappe.db.sql(""" - select name, asset_category, purchase_date, gross_purchase_amount, disposal_date, status - from `tabAsset` - where docstatus=1 and company=%s and purchase_date <= %s""", - (filters.company, filters.to_date), as_dict=1) - -def get_asset_costs(assets, filters): - asset_costs = frappe._dict() - for d in assets: - asset_costs.setdefault(d.asset_category, frappe._dict({ - "cost_as_on_from_date": 0, - "cost_of_new_purchase": 0, - "cost_of_sold_asset": 0, - "cost_of_scrapped_asset": 0 - })) - - costs = asset_costs[d.asset_category] - - if getdate(d.purchase_date) < getdate(filters.from_date): - if not d.disposal_date or getdate(d.disposal_date) >= getdate(filters.from_date): - costs.cost_as_on_from_date += flt(d.gross_purchase_amount) - else: - costs.cost_of_new_purchase += flt(d.gross_purchase_amount) - - if d.disposal_date and getdate(d.disposal_date) >= getdate(filters.from_date) \ - and getdate(d.disposal_date) <= getdate(filters.to_date): - if d.status == "Sold": - costs.cost_of_sold_asset += flt(d.gross_purchase_amount) - elif d.status == "Scrapped": - costs.cost_of_scrapped_asset += flt(d.gross_purchase_amount) - - return asset_costs - -def get_accumulated_depreciations(assets, filters): - asset_depreciations = frappe._dict() - for d in assets: - asset = frappe.get_doc("Asset", d.name) - - if d.asset_category in asset_depreciations: - asset_depreciations[d.asset_category]['accumulated_depreciation_as_on_from_date'] += asset.opening_accumulated_depreciation - else: - asset_depreciations.setdefault(d.asset_category, frappe._dict({ - "accumulated_depreciation_as_on_from_date": asset.opening_accumulated_depreciation, - "depreciation_amount_during_the_period": 0, - "depreciation_eliminated_during_the_period": 0 - })) + SELECT results.asset_category, + sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date, + sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, + sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period + from (SELECT a.asset_category, + ifnull(sum(a.opening_accumulated_depreciation + + case when ds.schedule_date < %(from_date)s and + (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then + ds.depreciation_amount + else + 0 + end), 0) as accumulated_depreciation_as_on_from_date, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s and ds.schedule_date <= a.disposal_date then + ds.depreciation_amount + else + 0 + end), 0) as depreciation_eliminated_during_the_period, - depr = asset_depreciations[d.asset_category] + ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s + and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then + ds.depreciation_amount + else + 0 + end), 0) as depreciation_amount_during_the_period + from `tabAsset` a, `tabDepreciation Schedule` ds + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent + group by a.asset_category + union + SELECT a.asset_category, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then + 0 + else + a.opening_accumulated_depreciation + end), 0) as accumulated_depreciation_as_on_from_date, + ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then + a.opening_accumulated_depreciation + else + 0 + end), 0) as depreciation_eliminated_during_the_period, + 0 as depreciation_amount_during_the_period + from `tabAsset` a + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s + and not exists(select * from `tabDepreciation Schedule` ds where a.name = ds.parent) + group by a.asset_category) as results + group by results.asset_category + """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) - if not asset.schedules: # if no schedule, - if asset.disposal_date: - # and disposal is NOT within the period, then opening accumulated depreciation not included - if getdate(asset.disposal_date) < getdate(filters.from_date) or getdate(asset.disposal_date) > getdate(filters.to_date): - asset_depreciations[d.asset_category]['accumulated_depreciation_as_on_from_date'] = 0 - # if no schedule, and disposal is within period, accumulated dep is the amount eliminated - if getdate(asset.disposal_date) >= getdate(filters.from_date) and getdate(asset.disposal_date) <= getdate(filters.to_date): - depr.depreciation_eliminated_during_the_period += asset.opening_accumulated_depreciation - - for schedule in asset.get("schedules"): - if getdate(schedule.schedule_date) < getdate(filters.from_date): - if not asset.disposal_date or getdate(asset.disposal_date) >= getdate(filters.from_date): - depr.accumulated_depreciation_as_on_from_date += flt(schedule.depreciation_amount) - elif getdate(schedule.schedule_date) <= getdate(filters.to_date): - if not asset.disposal_date: - depr.depreciation_amount_during_the_period += flt(schedule.depreciation_amount) - else: - if getdate(schedule.schedule_date) <= getdate(asset.disposal_date): - depr.depreciation_amount_during_the_period += flt(schedule.depreciation_amount) - - if asset.disposal_date and getdate(asset.disposal_date) >= getdate(filters.from_date) and getdate(asset.disposal_date) <= getdate(filters.to_date): - if getdate(schedule.schedule_date) <= getdate(asset.disposal_date): - depr.depreciation_eliminated_during_the_period += flt(schedule.depreciation_amount) - - return asset_depreciations - def get_columns(filters): return [ { diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index 24511871fdb..3ec4d306c35 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -46,13 +46,24 @@ frappe.query_reports["Budget Variance Report"] = { fieldtype: "Select", options: ["Cost Center", "Project"], default: "Cost Center", - reqd: 1 + reqd: 1, + on_change: function() { + frappe.query_report.set_filter_value("budget_against_filter", []); + frappe.query_report.refresh(); + } }, { - fieldname: "cost_center", - label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center" + fieldname:"budget_against_filter", + label: __('Dimension Filter'), + fieldtype: "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let budget_against = frappe.query_report.get_filter_value('budget_against'); + if (!budget_against) return; + + return frappe.db.get_link_options(budget_against, txt); + } }, { fieldname:"show_cumulative", diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 8d65ac87148..39e218bfad2 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -12,22 +12,22 @@ from six import iteritems from pprint import pprint def execute(filters=None): if not filters: filters = {} - validate_filters(filters) + columns = get_columns(filters) - if filters.get("cost_center"): - cost_centers = [filters.get("cost_center")] + if filters.get("budget_against_filter"): + dimensions = filters.get("budget_against_filter") else: - cost_centers = get_cost_centers(filters) + dimensions = get_cost_centers(filters) period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"]) - cam_map = get_cost_center_account_month_map(filters) + cam_map = get_dimension_account_month_map(filters) data = [] - for cost_center in cost_centers: - cost_center_items = cam_map.get(cost_center) - if cost_center_items: - for account, monthwise_data in iteritems(cost_center_items): - row = [cost_center, account] + for dimension in dimensions: + dimension_items = cam_map.get(dimension) + if dimension_items: + for account, monthwise_data in iteritems(dimension_items): + row = [dimension, account] totals = [0, 0, 0] for year in get_fiscal_years(filters): last_total = 0 @@ -55,10 +55,6 @@ def execute(filters=None): return columns, data -def validate_filters(filters): - if filters.get("budget_against") != "Cost Center" and filters.get("cost_center"): - frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center")) - def get_columns(filters): columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"] @@ -98,11 +94,12 @@ def get_cost_centers(filters): else: return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec -#Get cost center & target details -def get_cost_center_target_details(filters): +#Get dimension & target details +def get_dimension_target_details(filters): cond = "" - if filters.get("cost_center"): - cond += " and b.cost_center=%s" % frappe.db.escape(filters.get("cost_center")) + if filters.get("budget_against_filter"): + cond += " and b.{budget_against} in (%s)".format(budget_against = \ + frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter'))) return frappe.db.sql(""" select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year @@ -110,8 +107,8 @@ def get_cost_center_target_details(filters): where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year """.format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond), - (filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True) - + tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')), + as_dict=True) #Get target distribution details of accounts of cost center @@ -153,14 +150,14 @@ def get_actual_details(name, filters): return cc_actual_details -def get_cost_center_account_month_map(filters): +def get_dimension_account_month_map(filters): import datetime - cost_center_target_details = get_cost_center_target_details(filters) + dimension_target_details = get_dimension_target_details(filters) tdd = get_target_distribution_details(filters) cam_map = {} - for ccd in cost_center_target_details: + for ccd in dimension_target_details: actual_details = get_actual_details(ccd.budget_against, filters) for month_id in range(1, 13): diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html index 4081723bf0f..50947ecf5ef 100644 --- a/erpnext/accounts/report/financial_statements.html +++ b/erpnext/accounts/report/financial_statements.html @@ -1,5 +1,6 @@ {% var report_columns = report.get_columns_for_print(); + report_columns = report_columns.filter(col => !col.hidden); if (report_columns.length > 8) { frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application.")); @@ -15,34 +16,35 @@ height: 37px; } -{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) %} -{% if(letterhead) { %} -| - {% for(var i=2, l=report_columns.length; i |
|---|
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
++ Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %} +
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 3c8de6026a6..40d5682726d 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -264,8 +264,8 @@ def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False def add_total_row(out, root_type, balance_must_be, period_list, company_currency): total_row = { - "account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'", - "account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'", + "account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)), + "account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)), "currency": company_currency } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 17da3b915f8..40469aecc1d 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -33,7 +33,7 @@ - {% for(var i=0, l=data.length-1; iNotes:
\n\nbase for using base salary of the EmployeeBS = Basic SalaryEmployment Type = employment_typeBranch = branchPayment Days = payment_daysLeave without pay = leave_without_paybase\nCondition: base < 10000\nFormula: base * .2BS \nCondition: BS > 2000\nFormula: BS * .1employment_type \nCondition: employment_type==\"Intern\"\nAmount: 1000Notes:
\n\nbase for using base salary of the EmployeeBS = Basic SalaryEmployment Type = employment_typeBranch = branchPayment Days = payment_daysLeave without pay = leave_without_paybase\nCondition: base < 10000\nFormula: base * .2BS \nCondition: BS > 2000\nFormula: BS * .1employment_type \nCondition: employment_type==\"Intern\"\nAmount: 1000| + |
+
+ {{_("Training Event:")}} {{ doc.event_name }}
+
+ |
+ + |
{{ doc.introduction }}
- -| + |
+
+ {{ doc.introduction }}
+
+
|
+ + |
{{ doc.introduction }}
\n\n| \n | \n \n {{_(\"Training Event:\")}} {{ doc.event_name }}\n \n | \n \n |
| \n | \n \n \n
| \n \n |
{{ message }}
+| + |
+
+ {{_("Training Event:")}} {{ doc.event_name }}
+
+ |
+ + |
| + |
+
+ {{ doc.introduction }}
+
+
|
+ + |
- {%= i+1 %}. {%= addr_list[i].address_type!="Other" ? __(addr_list[i].address_type) : addr_list[i].address_title %} - {% if(addr_list[i].is_primary_address) { %} - ({%= __("Primary") %}){% } %} - {% if(addr_list[i].is_shipping_address) { %} - ({%= __("Shipping") %}){% } %} +
+ {%= i+1 %}. {%= addr_list[i].address_title %}{% if(addr_list[i].address_type!="Other") { %} + ({%= __(addr_list[i].address_type) %}){% } %} + {% if(addr_list[i].is_primary_address) { %} + ({%= __("Primary") %}){% } %} + {% if(addr_list[i].is_shipping_address) { %} + ({%= __("Shipping") %}){% } %} - - {%= __("Edit") %} -
-{%= addr_list[i].display %}
-{%= addr_list[i].display %}
+{%= __("No address added yet.") %}
{% } %} - - + \ No newline at end of file diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index d5a78d4f1f0..3f444f83879 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -458,7 +458,8 @@ erpnext.utils.update_child_items = function(opts) { fieldname:"item_code", options: 'Item', in_list_view: 1, - read_only: 1, + read_only: 0, + disabled: 0, label: __('Item Code') }, { fieldtype:'Float', @@ -501,6 +502,7 @@ erpnext.utils.update_child_items = function(opts) { frm.doc[opts.child_docname].forEach(d => { dialog.fields_dict.trans_items.df.data.push({ "docname": d.name, + "name": d.name, "item_code": d.item_code, "qty": d.qty, "rate": d.rate, diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 41a59d0af5d..e64d5458b30 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -139,7 +139,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.dialog.set_value('serial_no', d.serial_no); } - if (d.batch_no) { + if (d.has_batch_no && d.batch_no) { this.frm.doc.items.forEach(data => { if(data.item_code == d.item_code) { this.dialog.fields_dict.batches.df.data.push({ @@ -389,12 +389,14 @@ erpnext.SerialNoBatchSelector = Class.extend({ let serial_no_filters = { item_code: me.item_code, + batch_no: this.doc.batch_no || null, delivery_document_no: "" } if (me.warehouse_details.name) { serial_no_filters['warehouse'] = me.warehouse_details.name; } + return [ {fieldtype: 'Section Break', label: __('Serial Numbers')}, { diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py index fa2cb1299a5..86cd4d1545d 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py @@ -25,5 +25,9 @@ def update_item_document(items, taxes): item_to_be_updated.taxes = [] for tax in taxes: tax = frappe._dict(tax) - item_to_be_updated.append("taxes", {'item_tax_template': tax.item_tax_template, 'tax_category': tax.tax_category}) + item_to_be_updated.append("taxes", { + 'item_tax_template': tax.item_tax_template, + 'tax_category': tax.tax_category, + 'valid_from': tax.valid_from + }) item_to_be_updated.save() \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/__init__.py b/erpnext/regional/doctype/import_supplier_invoice/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js new file mode 100644 index 00000000000..c2d6edfc773 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -0,0 +1,46 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.ui.form.on('Import Supplier Invoice', { + onload: function(frm) { + frappe.realtime.on("import_invoice_update", function (data) { + frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message); + if (data.count == data.total) { + window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title); + } + }); + }, + setup: function(frm) { + frm.set_query("tax_account", function(doc) { + return { + filters: { + account_type: 'Tax', + company: doc.company + } + }; + }); + + frm.set_query("default_buying_price_list", function(doc) { + return { + filters: { + currency: frappe.get_doc(":Company", doc.company).default_currency + } + }; + }); + }, + + refresh: function(frm) { + frm.trigger("toggle_read_only_fields"); + }, + + toggle_read_only_fields: function(frm) { + if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) { + cur_frm.set_read_only(); + cur_frm.refresh_fields(); + frm.set_df_property("import_invoices", "hidden", 1); + } else { + frm.set_df_property("import_invoices", "hidden", 0); + } + } + +}); \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json new file mode 100644 index 00000000000..59e955c23f4 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json @@ -0,0 +1,105 @@ +{ + "actions": [], + "creation": "2019-10-15 12:33:21.845329", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "invoice_series", + "company", + "item_code", + "column_break_5", + "supplier_group", + "tax_account", + "default_buying_price_list", + "upload_xml_invoices_section", + "zip_file", + "import_invoices", + "status" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fieldname": "supplier_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier Group", + "options": "Supplier Group", + "reqd": 1 + }, + { + "fieldname": "tax_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Tax Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "zip_file", + "fieldtype": "Attach", + "label": "Zip File" + }, + { + "description": "Click on Import Invoices button once the zip file has been attached to the document. Any errors related to processing will be shown in the Error Log.", + "fieldname": "import_invoices", + "fieldtype": "Button", + "label": "Import Invoices", + "options": "process_file_data" + }, + { + "fieldname": "status", + "fieldtype": "Data", + "label": "Status", + "read_only": 1 + }, + { + "fieldname": "invoice_series", + "fieldtype": "Select", + "label": "Invoice Series", + "options": "ACC-PINV-.YYYY.-", + "reqd": 1 + }, + { + "fieldname": "default_buying_price_list", + "fieldtype": "Link", + "label": "Default Buying Price List", + "options": "Price List", + "reqd": 1 + }, + { + "fieldname": "upload_xml_invoices_section", + "fieldtype": "Section Break", + "label": "Upload XML Invoices" + } + ], + "links": [], + "modified": "2019-12-10 16:37:26.793398", + "modified_by": "Administrator", + "module": "Regional", + "name": "Import Supplier Invoice", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py new file mode 100644 index 00000000000..72fe17fb379 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -0,0 +1,402 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +from decimal import Decimal +import json +import re +import traceback +import zipfile +import frappe, erpnext +from frappe import _ +from frappe.model.document import Document +from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.utils.data import format_datetime +from bs4 import BeautifulSoup as bs +from frappe.utils import cint, flt, today, nowdate, add_days, get_files_path, get_datetime_str +import dateutil +from frappe.utils.file_manager import save_file + +class ImportSupplierInvoice(Document): + def validate(self): + if not frappe.db.get_value("Stock Settings", fieldname="stock_uom"): + frappe.throw(_("Please set default UOM in Stock Settings")) + + def autoname(self): + if not self.name: + self.name = "Import Invoice on " + format_datetime(self.creation) + + def import_xml_data(self): + import_file = frappe.get_doc("File", {"file_url": self.zip_file}) + self.publish("File Import", _("Processing XML Files"), 1, 3) + + self.file_count = 0 + self.purchase_invoices_count = 0 + self.default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") + + with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: + for file_name in zf.namelist(): + content = get_file_content(file_name, zf) + file_content = bs(content, "xml") + self.prepare_data_for_import(file_content, file_name, content) + + if self.purchase_invoices_count == self.file_count: + self.status = "File Import Completed" + self.publish("File Import", _("XML Files Processed"), 2, 3) + else: + self.status = "Partially Completed - Check Error Log" + self.publish("File Import", _("XML Files Processed"), 2, 3) + + self.save() + self.publish("File Import", _("XML Files Processed"), 3, 3) + + def prepare_data_for_import(self, file_content, file_name, encoded_content): + for line in file_content.find_all("DatiGeneraliDocumento"): + invoices_args = { + "company": self.company, + "naming_series": self.invoice_series, + "document_type": line.TipoDocumento.text, + "bill_date": get_datetime_str(line.Data.text), + "invoice_no": line.Numero.text, + "total_discount": 0, + "items": [], + "buying_price_list": self.default_buying_price_list + } + + if not invoices_args.get("invoice_no", ''): return + + supp_dict = get_supplier_details(file_content) + invoices_args["destination_code"] = get_destination_code_from_file(file_content) + self.prepare_items_for_invoice(file_content, invoices_args) + invoices_args["taxes"] = get_taxes_from_file(file_content, self.tax_account) + invoices_args["terms"] = get_payment_terms_from_file(file_content) + + supplier_name = create_supplier(self.supplier_group, supp_dict) + address = create_address(supplier_name, supp_dict) + pi_name = create_purchase_invoice(supplier_name, file_name, invoices_args, self.name) + + self.file_count += 1 + if pi_name: + self.purchase_invoices_count += 1 + file_save = save_file(file_name, encoded_content, "Purchase Invoice", + pi_name, folder=None, decode=False, is_private=0, df=None) + + def prepare_items_for_invoice(self, file_content, invoices_args): + qty = 1 + rate, tax_rate = [0 ,0] + uom = self.default_uom + + #read file for item information + for line in file_content.find_all("DettaglioLinee"): + if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): + rate = flt(line.PrezzoUnitario.text) or 0 + line_total = flt(line.PrezzoTotale.text) or 0 + + if rate and flt(line_total) / rate != 1.0 and line.find("Quantita"): + qty = flt(line.Quantita.text) or 0 + if line.find("UnitaMisura"): + uom = create_uom(line.UnitaMisura.text) + + if (rate < 0 and line_total < 0): + qty *= -1 + invoices_args["return_invoice"] = 1 + + if line.find("AliquotaIVA"): + tax_rate = flt(line.AliquotaIVA.text) + + line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text) + item_name = line_str[0:140] + + invoices_args['items'].append({ + "item_code": self.item_code, + "item_name": item_name, + "description": line_str, + "qty": qty, + "uom": uom, + "rate": abs(rate), + "conversion_factor": 1.0, + "tax_rate": tax_rate + }) + + for disc_line in line.find_all("ScontoMaggiorazione"): + if disc_line.find("Percentuale"): + invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty)) + + def process_file_data(self): + self.status = "Processing File Data" + self.save() + frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600) + + def publish(self, title, message, count, total): + frappe.publish_realtime("import_invoice_update", {"title": title, "message": message, "count": count, "total": total}) + +def get_file_content(file_name, zip_file_object): + content = '' + encoded_content = zip_file_object.read(file_name) + + try: + content = encoded_content.decode("utf-8-sig") + except UnicodeDecodeError: + try: + content = encoded_content.decode("utf-16") + except UnicodeDecodeError as e: + frappe.log_error(message=e, title="UTF-16 encoding error for File Name: " + file_name) + + return content + +def get_supplier_details(file_content): + supplier_info = {} + for line in file_content.find_all("CedentePrestatore"): + supplier_info['tax_id'] = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text + if line.find("CodiceFiscale"): + supplier_info['fiscal_code'] = line.DatiAnagrafici.CodiceFiscale.text + + if line.find("RegimeFiscale"): + supplier_info['fiscal_regime'] = line.DatiAnagrafici.RegimeFiscale.text + + if line.find("Denominazione"): + supplier_info['supplier'] = line.DatiAnagrafici.Anagrafica.Denominazione.text + + if line.find("Nome"): + supplier_info['supplier'] = (line.DatiAnagrafici.Anagrafica.Nome.text + + " " + line.DatiAnagrafici.Anagrafica.Cognome.text) + + supplier_info['address_line1'] = line.Sede.Indirizzo.text + supplier_info['city'] = line.Sede.Comune.text + if line.find("Provincia"): + supplier_info['province'] = line.Sede.Provincia.text + + supplier_info['pin_code'] = line.Sede.CAP.text + supplier_info['country'] = get_country(line.Sede.Nazione.text) + + return supplier_info + +def get_taxes_from_file(file_content, tax_account): + taxes = [] + #read file for taxes information + for line in file_content.find_all("DatiRiepilogo"): + if line.find("AliquotaIVA"): + if line.find("EsigibilitaIVA"): + descr = line.EsigibilitaIVA.text + else: + descr = "None" + taxes.append({ + "charge_type": "Actual", + "account_head": tax_account, + "tax_rate": flt(line.AliquotaIVA.text) or 0, + "description": descr, + "tax_amount": flt(line.Imposta.text) if len(line.find("Imposta"))!=0 else 0 + }) + + return taxes + +def get_payment_terms_from_file(file_content): + terms = [] + #Get mode of payment dict from setup + mop_options = frappe.get_meta('Mode of Payment').fields[4].options + mop_str = re.sub('\n', ',', mop_options) + mop_dict = dict(item.split("-") for item in mop_str.split(",")) + #read file for payment information + for line in file_content.find_all("DettaglioPagamento"): + mop_code = line.ModalitaPagamento.text + '-' + mop_dict.get(line.ModalitaPagamento.text) + if line.find("DataScadenzaPagamento"): + due_date = dateutil.parser.parse(line.DataScadenzaPagamento.text).strftime("%Y-%m-%d") + else: + due_date = today() + terms.append({ + "mode_of_payment_code": mop_code, + "bank_account_iban": line.IBAN.text if line.find("IBAN") else "", + "due_date": due_date, + "payment_amount": line.ImportoPagamento.text + }) + + return terms + +def get_destination_code_from_file(file_content): + destination_code = '' + for line in file_content.find_all("DatiTrasmissione"): + destination_code = line.CodiceDestinatario.text + + return destination_code + +def create_supplier(supplier_group, args): + args = frappe._dict(args) + + existing_supplier_name = frappe.db.get_value("Supplier", + filters={"tax_id": args.tax_id}, fieldname="name") + if existing_supplier_name: + pass + else: + existing_supplier_name = frappe.db.get_value("Supplier", + filters={"name": args.supplier}, fieldname="name") + + if existing_supplier_name: + filters = [ + ["Dynamic Link", "link_doctype", "=", "Supplier"], + ["Dynamic Link", "link_name", "=", args.existing_supplier_name], + ["Dynamic Link", "parenttype", "=", "Contact"] + ] + + if not frappe.get_list("Contact", filters): + new_contact = frappe.new_doc("Contact") + new_contact.first_name = args.supplier + new_contact.append('links', { + "link_doctype": "Supplier", + "link_name": existing_supplier_name + }) + new_contact.insert(ignore_mandatory=True) + + return existing_supplier_name + else: + + new_supplier = frappe.new_doc("Supplier") + new_supplier.supplier_name = args.supplier + new_supplier.supplier_group = supplier_group + new_supplier.tax_id = args.tax_id + new_supplier.fiscal_code = args.fiscal_code + new_supplier.fiscal_regime = args.fiscal_regime + new_supplier.save() + + new_contact = frappe.new_doc("Contact") + new_contact.first_name = args.supplier + new_contact.append('links', { + "link_doctype": "Supplier", + "link_name": new_supplier.name + }) + + new_contact.insert(ignore_mandatory=True) + + return new_supplier.name + +def create_address(supplier_name, args): + args = frappe._dict(args) + + filters = [ + ["Dynamic Link", "link_doctype", "=", "Supplier"], + ["Dynamic Link", "link_name", "=", supplier_name], + ["Dynamic Link", "parenttype", "=", "Address"] + ] + + existing_address = frappe.get_list("Address", filters) + + if args.address_line1: + new_address_doc = frappe.new_doc("Address") + new_address_doc.address_line1 = args.address_line1 + + if args.city: + new_address_doc.city = args.city + else: + new_address_doc.city = "Not Provided" + + for field in ["province", "pincode", "country"]: + if args.get(field): + new_address_doc.set(field, args.get(field)) + + for address in existing_address: + address_doc = frappe.get_doc("Address", address["name"]) + if (address_doc.address_line1 == new_address_doc.address_line1 and + address_doc.pincode == new_address_doc.pincode): + return address + + new_address_doc.append("links", { + "link_doctype": "Supplier", + "link_name": supplier_name + }) + new_address_doc.address_type = "Billing" + new_address_doc.insert(ignore_mandatory=True) + return new_address_doc.name + else: + return None + +def create_purchase_invoice(supplier_name, file_name, args, name): + args = frappe._dict(args) + pi = frappe.get_doc({ + "doctype": "Purchase Invoice", + "company": args.company, + "currency": erpnext.get_company_currency(args.company), + "naming_series": args.naming_series, + "supplier": supplier_name, + "is_return": args.is_return, + "posting_date": today(), + "bill_no": args.bill_no, + "buying_price_list": args.buying_price_list, + "bill_date": args.bill_date, + "destination_code": args.destination_code, + "document_type": args.document_type, + "disable_rounded_total": 1, + "items": args["items"], + "taxes": args["taxes"] + }) + + try: + pi.set_missing_values() + pi.insert(ignore_mandatory=True) + + #if discount exists in file, apply any discount on grand total + if args.total_discount > 0: + pi.apply_discount_on = "Grand Total" + pi.discount_amount = args.total_discount + pi.save() + #adjust payment amount to match with grand total calculated + calc_total = 0 + adj = 0 + for term in args.terms: + calc_total += flt(term["payment_amount"]) + if flt(calc_total - flt(pi.grand_total)) != 0: + adj = calc_total - flt(pi.grand_total) + pi.payment_schedule = [] + for term in args.terms: + pi.append('payment_schedule',{"mode_of_payment_code": term["mode_of_payment_code"], + "bank_account_iban": term["bank_account_iban"], + "due_date": term["due_date"], + "payment_amount": flt(term["payment_amount"]) - adj }) + adj = 0 + pi.imported_grand_total = calc_total + pi.save() + return pi.name + except Exception as e: + frappe.db.set_value("Import Supplier Invoice", name, "status", "Error") + frappe.log_error(message=e, + title="Create Purchase Invoice: " + args.get("bill_no") + "File Name: " + file_name) + return None + +def get_country(code): + existing_country_name = frappe.db.get_value("Country", + filters={"code": code}, fieldname="name") + if existing_country_name: + return existing_country_name + else: + frappe.throw(_("Country Code in File does not match with country code set up in the system")) + +def create_uom(uom): + existing_uom = frappe.db.get_value("UOM", + filters={"uom_name": uom}, fieldname="uom_name") + if existing_uom: + return existing_uom + else: + new_uom = frappe.new_doc("UOM") + new_uom.uom_name = uom + new_uom.save() + return new_uom.uom_name + +def get_full_path(file_name): + """Returns file path from given file name""" + file_path = file_name + + if "/" not in file_path: + file_path = "/files/" + file_path + + if file_path.startswith("/private/files/"): + file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1) + + elif file_path.startswith("/files/"): + file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/")) + + elif file_path.startswith("http"): + pass + + elif not self.file_url: + frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) + + return file_path \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py new file mode 100644 index 00000000000..d1caf77fc2c --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestImportSupplierInvoice(unittest.TestCase): + pass diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 1526d6f62f0..2d0ad66b0a0 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -155,6 +155,31 @@ def make_custom_fields(update=True): fetch_from="country.code"), dict(fieldname='state_code', label='State Code', fieldtype='Data', insert_after='state', print_hide=1) + ], + 'Purchase Invoice': [ + dict(fieldname='document_type', label='Document Type', + fieldtype='Data', insert_after='company', print_hide=1, read_only=1 + ), + dict(fieldname='destination_code', label='Destination Code', + fieldtype='Data', insert_after='company', print_hide=1, read_only=1 + ), + dict(fieldname='imported_grand_total', label='Imported Grand Total', + fieldtype='Data', insert_after='update_auto_repeat_reference', print_hide=1, read_only=1 + ) + ], + 'Purchase Taxes and Charges': [ + dict(fieldname='tax_rate', label='Tax Rate', + fieldtype='Data', insert_after='parenttype', print_hide=1, read_only=0 + ) + ], + 'Supplier': [ + dict(fieldname='fiscal_code', label='Fiscal Code', + fieldtype='Data', insert_after='tax_id', print_hide=1, read_only=1 + ), + dict(fieldname='fiscal_regime', label='Fiscal Regime', + fieldtype='Select', insert_after='fiscal_code', print_hide=1, read_only=1, + options= "\nRF01\nRF02\nRF04\nRF05\nRF06\nRF07\nRF08\nRF09\nRF10\nRF11\nRF12\nRF13\nRF14\nRF15\nRF16\nRF17\nRF18\nRF19" + ) ] } diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index bc8d00d8b8b..2af72f8e276 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -13,6 +13,8 @@ from erpnext.regional.italy import state_codes def update_itemised_tax_data(doc): if not doc.taxes: return + if doc.doctype == "Purchase Invoice": return + itemised_tax = get_itemised_tax(doc.taxes) for row in doc.items: diff --git a/erpnext/regional/print_format/purchase_einvoice/__init__.py b/erpnext/regional/print_format/purchase_einvoice/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/print_format/purchase_einvoice/purchase_einvoice.json b/erpnext/regional/print_format/purchase_einvoice/purchase_einvoice.json new file mode 100644 index 00000000000..88f31dd1308 --- /dev/null +++ b/erpnext/regional/print_format/purchase_einvoice/purchase_einvoice.json @@ -0,0 +1,23 @@ +{ + "align_labels_right": 0, + "creation": "2019-10-16 00:47:08.877767", + "custom_format": 0, + "disabled": 1, + "doc_type": "Purchase Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"| \\nCedente/prestatore (fornitore)\\n | \\n\\nCessionario/committente (cliente)\\n | \\n\\n\\n
|---|---|
| \\n Identificstivo fiscale ai fini IVA: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"tax_id\\\")}} \\nCodice fiscale: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"fiscal_code\\\")}} \\nDenominazione: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"supplier_name\\\")}} \\nRegime fiscale: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"fiscal_regime\\\")}} \\nIndrizo: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"address_line1\\\")}} \\nCommune: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"city\\\")}} Provincia: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"state_code\\\")}} \\nCap: {{(frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"pincode\\\")) or \\\" \\\"}} Nazione: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"country\\\")}} \\n | \\n\\n Identificstivo fiscale ai fini IVA: {{frappe.db.get_value(\\\"Company\\\", doc.company, \\\"tax_id\\\")}} \\nCodice fiscale: {{frappe.db.get_value(\\\"Company\\\", doc.company, \\\"fiscal_code\\\")}} \\nDenominazione: {{doc.company}} \\nIndrizo: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"address_line1\\\")}} \\nCommune: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"city\\\")}} Provincia: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"state_code\\\")}} \\nCap: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"pincode\\\")}} Nazione: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"country\\\")}} \\n | \\n\\n
| \\nTipologla\\n | \\n\\nArt. 73\\n | \\n\\nNumero documento\\n | \\n\\nData documento\\n | \\n\\nCodice destinatario\\n | \\n\\n\\n
|---|---|---|---|---|
| \\n{{doc.document_type or \\\" \\\"}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{doc.bill_no or \\\" \\\"}}\\n | \\n\\n{{doc.get_formatted(\\\"bill_date\\\") or \\\" \\\"}}\\n | \\n\\n{{doc.destination_code or \\\" \\\"}}\\n | \\n
| \\nDescrizione\\n | \\n\\nQuantita\\n | \\n\\nPrezzo unitario\\n | \\n\\nUM\\n | \\n\\n%IVA\\n | \\n\\nPrezzo totale\\n | \\n\\n\\n\\n{%- for row in doc.items -%}\\n
|---|---|---|---|---|---|
| \\n{{row.description or \\\" \\\"}}\\n | \\n\\n{{row.get_formatted(\\\"qty\\\", doc)}}\\n | \\n\\n{{row.get_formatted(\\\"rate\\\", doc)}}\\n | \\n\\n{{row.get_formatted(\\\"uom\\\", doc)}}\\n | \\n\\n{{row.get_formatted(\\\"tax_rate\\\", doc)}}\\n | \\n\\n{{row.get_formatted(\\\"amount\\\", doc)}}\\n | \\n{%- endfor -%}\\n\\n
| \\nesigibilita immediata / riferimenti normativi\\n | \\n\\n%IVA\\n | \\n\\nSpese accessorie\\n | \\n\\nArr.\\n | \\n\\nTotale imponibile\\n | \\n\\nTotale Imposta\\n | \\n\\n\\n\\n{%- for row in doc.taxes -%}\\n
|---|---|---|---|---|---|
| \\n{% if 'None' in row.description %}\\n {{ \\\" \\\" }}\\n{% else %}\\n{{row.description}}\\n{% endif %}\\n | \\n\\n{{row.get_formatted(\\\"tax_rate\\\", doc)}}\\n | \\n\\n{{\\\"0,00\\\"}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{doc.get_formatted(\\\"base_net_total\\\")}}\\n | \\n\\n{{row.get_formatted(\\\"tax_amount\\\", doc)}}\\n | \\n{%- endfor -%}\\n\\n
| \\nImporto bolio\\n | \\n\\nSconto/Magglorazione\\n | \\n\\nArr.\\n | \\n\\nTotale documento\\n | \\n\\n\\n\\n
|---|---|---|---|
| \\n{{\\\" \\\"}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{doc.get_formatted(\\\"base_grand_total\\\")}}\\n | \\n\\n
| \\nModalita pagamento\\n | \\n\\nIBAN\\n | \\n\\nInstituto\\n | \\n\\nData scadenza\\n | \\n\\nImporto\\n | \\n\\n\\n\\n{%- for row in doc.payment_schedule -%}\\n
|---|---|---|---|---|
| \\n{{row.get_formatted(\\\"mode_of_payment_code\\\",doc)}}\\n | \\n\\n{{row.get_formatted(\\\"bank_account_iban\\\",doc)}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{row.get_formatted(\\\"due_date\\\",doc)}}\\n | \\n\\n{{row.get_formatted(\\\"payment_amount\\\",doc)}}\\n | \\n{%- endfor -%}\\n\\n