mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-13 10:11:20 +00:00
Merge branch 'version-13-beta-pre-release' of https://github.com/frappe/erpnext into add_account_method_v13
This commit is contained in:
4
.github/helper/documentation.py
vendored
4
.github/helper/documentation.py
vendored
@@ -21,8 +21,8 @@ def docs_link_exists(body):
|
|||||||
if word.startswith('http') and uri_validator(word):
|
if word.startswith('http') and uri_validator(word):
|
||||||
parsed_url = urlparse(word)
|
parsed_url = urlparse(word)
|
||||||
if parsed_url.netloc == "github.com":
|
if parsed_url.netloc == "github.com":
|
||||||
_, org, repo, _type, ref = parsed_url.path.split('/')
|
parts = parsed_url.path.split('/')
|
||||||
if org == "frappe" and repo in docs_repos:
|
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '13.0.0-beta.7'
|
__version__ = '13.0.0-beta.13'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
@@ -132,16 +132,10 @@ def allow_regional(fn):
|
|||||||
|
|
||||||
return caller
|
return caller
|
||||||
|
|
||||||
def get_last_membership():
|
def get_last_membership(member):
|
||||||
'''Returns last membership if exists'''
|
'''Returns last membership if exists'''
|
||||||
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
|
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
|
||||||
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
|
dict(member=member, paid=1), order_by='to_date desc', limit=1)
|
||||||
|
|
||||||
return last_membership and last_membership[0]
|
if last_membership:
|
||||||
|
return last_membership[0]
|
||||||
def is_member():
|
|
||||||
'''Returns true if the user is still a member'''
|
|
||||||
last_membership = get_last_membership()
|
|
||||||
if last_membership and getdate(last_membership.to_date) > getdate():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|||||||
@@ -910,98 +910,8 @@
|
|||||||
},
|
},
|
||||||
"is_group": 1
|
"is_group": 1
|
||||||
},
|
},
|
||||||
"Passiva": {
|
"Passiva - Verbindlichkeiten": {
|
||||||
"root_type": "Liability",
|
"root_type": "Liability",
|
||||||
"A - Eigenkapital": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"I - Gezeichnetes Kapital": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gezeichnetes Kapital": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"account_number": "2900"
|
|
||||||
},
|
|
||||||
"Ausstehende Einlagen auf das gezeichnete Kapital": {
|
|
||||||
"account_number": "2910",
|
|
||||||
"is_group": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"II - Kapitalr\u00fccklage": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Kapitalr\u00fccklage": {
|
|
||||||
"account_number": "2920"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"III - Gewinnr\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"1 - gesetzliche R\u00fccklage": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gesetzliche R\u00fccklage": {
|
|
||||||
"account_number": "2930"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
|
||||||
"account_number": "2950"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"4 - andere Gewinnr\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
|
||||||
"is_group": 1,
|
|
||||||
"Gewinnr\u00fccklagen (BilMoG)": {
|
|
||||||
"account_number": "2963"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
|
||||||
"account_number": "2964"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
|
||||||
"account_number": "2965"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
|
||||||
"account_number": "2966"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2967"
|
|
||||||
},
|
|
||||||
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2968"
|
|
||||||
},
|
|
||||||
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2969"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"IV - Gewinnvortrag/Verlustvortrag": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gewinnvortrag vor Verwendung": {
|
|
||||||
"account_number": "2970"
|
|
||||||
},
|
|
||||||
"Verlustvortrag vor Verwendung": {
|
|
||||||
"account_number": "2978"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"Einlagen stiller Gesellschafter": {
|
|
||||||
"account_number": "9295"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"B - R\u00fcckstellungen": {
|
"B - R\u00fcckstellungen": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
||||||
@@ -1618,6 +1528,143 @@
|
|||||||
},
|
},
|
||||||
"is_group": 1
|
"is_group": 1
|
||||||
},
|
},
|
||||||
|
"Passiva - Eigenkapital": {
|
||||||
|
"root_type": "Equity",
|
||||||
|
"A - Eigenkapital": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"I - Gezeichnetes Kapital": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gezeichnetes Kapital": {
|
||||||
|
"account_number": "2900",
|
||||||
|
"account_type": "Equity"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
|
||||||
|
"account_number": "2901"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
|
||||||
|
"account_number": "2902"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
|
||||||
|
"account_number": "2903"
|
||||||
|
},
|
||||||
|
"R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||||
|
"account_number": "2906"
|
||||||
|
},
|
||||||
|
"Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||||
|
"account_number": "2907"
|
||||||
|
},
|
||||||
|
"Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
|
||||||
|
"account_number": "2908"
|
||||||
|
},
|
||||||
|
"Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
|
||||||
|
"account_number": "2910"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"II - Kapitalr\u00fccklage": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Kapitalr\u00fccklage": {
|
||||||
|
"account_number": "2920"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
|
||||||
|
"account_number": "2925"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
|
||||||
|
"account_number": "2926"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
|
||||||
|
"account_number": "2927"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
|
||||||
|
"account_number": "2928"
|
||||||
|
},
|
||||||
|
"Nachschusskapital (Gegenkonto 1299)": {
|
||||||
|
"account_number": "2929"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"III - Gewinnr\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"1 - gesetzliche R\u00fccklage": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gesetzliche R\u00fccklage": {
|
||||||
|
"account_number": "2930"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||||
|
"account_number": "2935"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||||
|
"account_number": "2950"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4 - andere Gewinnr\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Andere Gewinnr\u00fccklagen": {
|
||||||
|
"account_number": "2960"
|
||||||
|
},
|
||||||
|
"Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
|
||||||
|
"account_number": "2961"
|
||||||
|
},
|
||||||
|
"Eigenkapitalanteil von Wertaufholungen": {
|
||||||
|
"account_number": "2962"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
||||||
|
"is_group": 1,
|
||||||
|
"Gewinnr\u00fccklagen (BilMoG)": {
|
||||||
|
"account_number": "2963"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
||||||
|
"account_number": "2964"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
||||||
|
"account_number": "2965"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
||||||
|
"account_number": "2966"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2967"
|
||||||
|
},
|
||||||
|
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2968"
|
||||||
|
},
|
||||||
|
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2969"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is_group": 1
|
||||||
|
},
|
||||||
|
"IV - Gewinnvortrag/Verlustvortrag": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gewinnvortrag vor Verwendung": {
|
||||||
|
"account_number": "2970"
|
||||||
|
},
|
||||||
|
"Verlustvortrag vor Verwendung": {
|
||||||
|
"account_number": "2978"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1
|
||||||
|
},
|
||||||
|
"Einlagen stiller Gesellschafter": {
|
||||||
|
"account_number": "9295"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"1 - Umsatzerl\u00f6se": {
|
"1 - Umsatzerl\u00f6se": {
|
||||||
"root_type": "Income",
|
"root_type": "Income",
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
|
|||||||
@@ -254,7 +254,8 @@ def create_account(**kwargs):
|
|||||||
account_name = kwargs.get('account_name'),
|
account_name = kwargs.get('account_name'),
|
||||||
account_type = kwargs.get('account_type'),
|
account_type = kwargs.get('account_type'),
|
||||||
parent_account = kwargs.get('parent_account'),
|
parent_account = kwargs.get('parent_account'),
|
||||||
company = kwargs.get('company')
|
company = kwargs.get('company'),
|
||||||
|
account_currency = kwargs.get('account_currency')
|
||||||
))
|
))
|
||||||
|
|
||||||
account.save()
|
account.save()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Accounting Dimension', {
|
frappe.ui.form.on('Accounting Dimension', {
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frm.set_query('document_type', () => {
|
frm.set_query('document_type', () => {
|
||||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ class AccountingDimension(Document):
|
|||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
make_dimension_in_accounting_doctypes(doc=self)
|
make_dimension_in_accounting_doctypes(doc=self)
|
||||||
else:
|
else:
|
||||||
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self)
|
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue='long')
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
delete_accounting_dimension(doc=self)
|
delete_accounting_dimension(doc=self, queue='long')
|
||||||
else:
|
else:
|
||||||
frappe.enqueue(delete_accounting_dimension, doc=self)
|
frappe.enqueue(delete_accounting_dimension, doc=self)
|
||||||
|
|
||||||
@@ -48,6 +48,9 @@ class AccountingDimension(Document):
|
|||||||
if not self.fieldname:
|
if not self.fieldname:
|
||||||
self.fieldname = scrub(self.label)
|
self.fieldname = scrub(self.label)
|
||||||
|
|
||||||
|
def on_update(self):
|
||||||
|
frappe.flags.accounting_dimensions = None
|
||||||
|
|
||||||
def make_dimension_in_accounting_doctypes(doc):
|
def make_dimension_in_accounting_doctypes(doc):
|
||||||
doclist = get_doctypes_with_dimensions()
|
doclist = get_doctypes_with_dimensions()
|
||||||
doc_count = len(get_accounting_dimensions())
|
doc_count = len(get_accounting_dimensions())
|
||||||
@@ -165,9 +168,9 @@ def toggle_disabling(doc):
|
|||||||
frappe.clear_cache(doctype=doctype)
|
frappe.clear_cache(doctype=doctype)
|
||||||
|
|
||||||
def get_doctypes_with_dimensions():
|
def get_doctypes_with_dimensions():
|
||||||
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
|
doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset",
|
||||||
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
|
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
|
||||||
"Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
|
"Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
|
||||||
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
|
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
|
||||||
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
|
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
|
||||||
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
|
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
|
||||||
@@ -176,12 +179,14 @@ def get_doctypes_with_dimensions():
|
|||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
def get_accounting_dimensions(as_list=True):
|
def get_accounting_dimensions(as_list=True):
|
||||||
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"])
|
if frappe.flags.accounting_dimensions is None:
|
||||||
|
frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension",
|
||||||
|
fields=["label", "fieldname", "disabled", "document_type"])
|
||||||
|
|
||||||
if as_list:
|
if as_list:
|
||||||
return [d.fieldname for d in accounting_dimensions]
|
return [d.fieldname for d in frappe.flags.accounting_dimensions]
|
||||||
else:
|
else:
|
||||||
return accounting_dimensions
|
return frappe.flags.accounting_dimensions
|
||||||
|
|
||||||
def get_checks_for_pl_and_bs_accounts():
|
def get_checks_for_pl_and_bs_accounts():
|
||||||
dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||||
@@ -203,7 +208,7 @@ def get_dimension_with_children(doctype, dimension):
|
|||||||
return all_dimensions
|
return all_dimensions
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_dimension_filters():
|
def get_dimensions(with_cost_center_and_project=False):
|
||||||
dimension_filters = frappe.db.sql("""
|
dimension_filters = frappe.db.sql("""
|
||||||
SELECT label, fieldname, document_type
|
SELECT label, fieldname, document_type
|
||||||
FROM `tabAccounting Dimension`
|
FROM `tabAccounting Dimension`
|
||||||
@@ -214,6 +219,18 @@ def get_dimension_filters():
|
|||||||
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
||||||
WHERE c.parent = p.name""", as_dict=1)
|
WHERE c.parent = p.name""", as_dict=1)
|
||||||
|
|
||||||
|
if with_cost_center_and_project:
|
||||||
|
dimension_filters.extend([
|
||||||
|
{
|
||||||
|
'fieldname': 'cost_center',
|
||||||
|
'document_type': 'Cost Center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'project',
|
||||||
|
'document_type': 'Project'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
default_dimensions_map = {}
|
default_dimensions_map = {}
|
||||||
for dimension in default_dimensions:
|
for dimension in default_dimensions:
|
||||||
default_dimensions_map.setdefault(dimension.company, {})
|
default_dimensions_map.setdefault(dimension.company, {})
|
||||||
|
|||||||
@@ -11,37 +11,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import d
|
|||||||
|
|
||||||
class TestAccountingDimension(unittest.TestCase):
|
class TestAccountingDimension(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
frappe.set_user("Administrator")
|
create_dimension()
|
||||||
|
|
||||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
|
|
||||||
dimension = frappe.get_doc({
|
|
||||||
"doctype": "Accounting Dimension",
|
|
||||||
"document_type": "Department",
|
|
||||||
}).insert()
|
|
||||||
else:
|
|
||||||
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
|
||||||
dimension1.disabled = 0
|
|
||||||
dimension1.save()
|
|
||||||
|
|
||||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
|
|
||||||
dimension1 = frappe.get_doc({
|
|
||||||
"doctype": "Accounting Dimension",
|
|
||||||
"document_type": "Location",
|
|
||||||
})
|
|
||||||
|
|
||||||
dimension1.append("dimension_defaults", {
|
|
||||||
"company": "_Test Company",
|
|
||||||
"reference_document": "Location",
|
|
||||||
"default_dimension": "Block 1",
|
|
||||||
"mandatory_for_bs": 1
|
|
||||||
})
|
|
||||||
|
|
||||||
dimension1.insert()
|
|
||||||
dimension1.save()
|
|
||||||
else:
|
|
||||||
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
|
|
||||||
dimension1.disabled = 0
|
|
||||||
dimension1.save()
|
|
||||||
|
|
||||||
def test_dimension_against_sales_invoice(self):
|
def test_dimension_against_sales_invoice(self):
|
||||||
si = create_sales_invoice(do_not_save=1)
|
si = create_sales_invoice(do_not_save=1)
|
||||||
@@ -101,6 +71,38 @@ class TestAccountingDimension(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
disable_dimension()
|
disable_dimension()
|
||||||
|
|
||||||
|
def create_dimension():
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
|
||||||
|
frappe.get_doc({
|
||||||
|
"doctype": "Accounting Dimension",
|
||||||
|
"document_type": "Department",
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
dimension = frappe.get_doc("Accounting Dimension", "Department")
|
||||||
|
dimension.disabled = 0
|
||||||
|
dimension.save()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
|
||||||
|
dimension1 = frappe.get_doc({
|
||||||
|
"doctype": "Accounting Dimension",
|
||||||
|
"document_type": "Location",
|
||||||
|
})
|
||||||
|
|
||||||
|
dimension1.append("dimension_defaults", {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"reference_document": "Location",
|
||||||
|
"default_dimension": "Block 1",
|
||||||
|
"mandatory_for_bs": 1
|
||||||
|
})
|
||||||
|
|
||||||
|
dimension1.insert()
|
||||||
|
dimension1.save()
|
||||||
|
else:
|
||||||
|
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
|
||||||
|
dimension1.disabled = 0
|
||||||
|
dimension1.save()
|
||||||
|
|
||||||
def disable_dimension():
|
def disable_dimension():
|
||||||
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Accounting Dimension Filter', {
|
||||||
|
refresh: function(frm, cdt, cdn) {
|
||||||
|
if (frm.doc.accounting_dimension) {
|
||||||
|
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
|
||||||
|
}
|
||||||
|
|
||||||
|
let help_content =
|
||||||
|
`<table class="table table-bordered" style="background-color: #f9f9f9;">
|
||||||
|
<tr><td>
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-hand-right"></i>
|
||||||
|
{{__('Note: On checking Is Mandatory the accounting dimension will become mandatory against that specific account for all accounting transactions')}}
|
||||||
|
</p>
|
||||||
|
</td></tr>
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
frm.set_df_property('dimension_filter_help', 'options', help_content);
|
||||||
|
},
|
||||||
|
onload: function(frm) {
|
||||||
|
frm.set_query('applicable_on_account', 'accounts', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'company': frm.doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.db.get_list('Accounting Dimension',
|
||||||
|
{fields: ['document_type']}).then((res) => {
|
||||||
|
let options = ['Cost Center', 'Project'];
|
||||||
|
|
||||||
|
res.forEach((dimension) => {
|
||||||
|
options.push(dimension.document_type);
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_df_property('accounting_dimension', 'options', options);
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.trigger('setup_filters');
|
||||||
|
},
|
||||||
|
|
||||||
|
setup_filters: function(frm) {
|
||||||
|
let filters = {};
|
||||||
|
|
||||||
|
if (frm.doc.accounting_dimension) {
|
||||||
|
frappe.model.with_doctype(frm.doc.accounting_dimension, function() {
|
||||||
|
if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
|
||||||
|
filters['is_group'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) {
|
||||||
|
filters['company'] = frm.doc.company;
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.set_query('dimension_value', 'dimensions', function() {
|
||||||
|
return {
|
||||||
|
filters: filters
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
accounting_dimension: function(frm) {
|
||||||
|
frm.clear_table("dimensions");
|
||||||
|
let row = frm.add_child("dimensions");
|
||||||
|
row.accounting_dimension = frm.doc.accounting_dimension;
|
||||||
|
frm.refresh_field("dimensions");
|
||||||
|
frm.trigger('setup_filters');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Allowed Dimension', {
|
||||||
|
dimensions_add: function(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
row.accounting_dimension = frm.doc.accounting_dimension;
|
||||||
|
frm.refresh_field("dimensions");
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "format:{accounting_dimension}-{#####}",
|
||||||
|
"creation": "2020-11-08 18:28:11.906146",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"accounting_dimension",
|
||||||
|
"disabled",
|
||||||
|
"column_break_2",
|
||||||
|
"company",
|
||||||
|
"allow_or_restrict",
|
||||||
|
"section_break_4",
|
||||||
|
"accounts",
|
||||||
|
"column_break_6",
|
||||||
|
"dimensions",
|
||||||
|
"section_break_10",
|
||||||
|
"dimension_filter_help"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimension",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Accounting Dimension",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "allow_or_restrict",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Allow Or Restrict Dimension",
|
||||||
|
"options": "Allow\nRestrict",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounts",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Applicable On Account",
|
||||||
|
"options": "Applicable On Account",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.accounting_dimension",
|
||||||
|
"fieldname": "dimensions",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Applicable Dimension",
|
||||||
|
"options": "Allowed Dimension",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "disabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disabled",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_filter_help",
|
||||||
|
"fieldtype": "HTML",
|
||||||
|
"label": "Dimension Filter Help",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_10",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-02-03 12:04:58.678402",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Accounting Dimension Filter",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright, (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _, scrub
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class AccountingDimensionFilter(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_applicable_accounts()
|
||||||
|
|
||||||
|
def validate_applicable_accounts(self):
|
||||||
|
accounts = frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT a.applicable_on_account as account
|
||||||
|
FROM `tabApplicable On Account` a, `tabAccounting Dimension Filter` d
|
||||||
|
WHERE d.name = a.parent
|
||||||
|
and d.name != %s
|
||||||
|
and d.accounting_dimension = %s
|
||||||
|
""", (self.name, self.accounting_dimension), as_dict=1)
|
||||||
|
|
||||||
|
account_list = [d.account for d in accounts]
|
||||||
|
|
||||||
|
for account in self.get('accounts'):
|
||||||
|
if account.applicable_on_account in account_list:
|
||||||
|
frappe.throw(_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
|
||||||
|
account.idx, frappe.bold(account.applicable_on_account), frappe.bold(self.accounting_dimension)))
|
||||||
|
|
||||||
|
def get_dimension_filter_map():
|
||||||
|
filters = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||||
|
p.allow_or_restrict, a.is_mandatory
|
||||||
|
FROM
|
||||||
|
`tabApplicable On Account` a, `tabAllowed Dimension` d,
|
||||||
|
`tabAccounting Dimension Filter` p
|
||||||
|
WHERE
|
||||||
|
p.name = a.parent
|
||||||
|
AND p.disabled = 0
|
||||||
|
AND p.name = d.parent
|
||||||
|
""", as_dict=1)
|
||||||
|
|
||||||
|
dimension_filter_map = {}
|
||||||
|
|
||||||
|
for f in filters:
|
||||||
|
f.fieldname = scrub(f.accounting_dimension)
|
||||||
|
|
||||||
|
build_map(dimension_filter_map, f.fieldname, f.applicable_on_account, f.dimension_value,
|
||||||
|
f.allow_or_restrict, f.is_mandatory)
|
||||||
|
|
||||||
|
return dimension_filter_map
|
||||||
|
|
||||||
|
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
||||||
|
map_object.setdefault((dimension, account), {
|
||||||
|
'allowed_dimensions': [],
|
||||||
|
'is_mandatory': is_mandatory,
|
||||||
|
'allow_or_restrict': allow_or_restrict
|
||||||
|
})
|
||||||
|
map_object[(dimension, account)]['allowed_dimensions'].append(filter_value)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
|
||||||
|
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||||
|
|
||||||
|
class TestAccountingDimensionFilter(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
create_dimension()
|
||||||
|
create_accounting_dimension_filter()
|
||||||
|
self.invoice_list = []
|
||||||
|
|
||||||
|
def test_allowed_dimension_validation(self):
|
||||||
|
si = create_sales_invoice(do_not_save=1)
|
||||||
|
si.items[0].cost_center = 'Main - _TC'
|
||||||
|
si.department = 'Accounts - _TC'
|
||||||
|
si.location = 'Block 1'
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
self.assertRaises(InvalidAccountDimensionError, si.submit)
|
||||||
|
self.invoice_list.append(si)
|
||||||
|
|
||||||
|
def test_mandatory_dimension_validation(self):
|
||||||
|
si = create_sales_invoice(do_not_save=1)
|
||||||
|
si.department = ''
|
||||||
|
si.location = 'Block 1'
|
||||||
|
|
||||||
|
# Test with no department for Sales Account
|
||||||
|
si.items[0].department = ''
|
||||||
|
si.items[0].cost_center = '_Test Cost Center 2 - _TC'
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
self.assertRaises(MandatoryAccountDimensionError, si.submit)
|
||||||
|
self.invoice_list.append(si)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
disable_dimension_filter()
|
||||||
|
disable_dimension()
|
||||||
|
|
||||||
|
for si in self.invoice_list:
|
||||||
|
si.load_from_db()
|
||||||
|
if si.docstatus == 1:
|
||||||
|
si.cancel()
|
||||||
|
|
||||||
|
def create_accounting_dimension_filter():
|
||||||
|
if not frappe.db.get_value('Accounting Dimension Filter',
|
||||||
|
{'accounting_dimension': 'Cost Center'}):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Accounting Dimension Filter',
|
||||||
|
'accounting_dimension': 'Cost Center',
|
||||||
|
'allow_or_restrict': 'Allow',
|
||||||
|
'company': '_Test Company',
|
||||||
|
'accounts': [{
|
||||||
|
'applicable_on_account': 'Sales - _TC',
|
||||||
|
}],
|
||||||
|
'dimensions': [{
|
||||||
|
'accounting_dimension': 'Cost Center',
|
||||||
|
'dimension_value': '_Test Cost Center 2 - _TC'
|
||||||
|
}]
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
|
||||||
|
doc.disabled = 0
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
if not frappe.db.get_value('Accounting Dimension Filter',
|
||||||
|
{'accounting_dimension': 'Department'}):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Accounting Dimension Filter',
|
||||||
|
'accounting_dimension': 'Department',
|
||||||
|
'allow_or_restrict': 'Allow',
|
||||||
|
'company': '_Test Company',
|
||||||
|
'accounts': [{
|
||||||
|
'applicable_on_account': 'Sales - _TC',
|
||||||
|
'is_mandatory': 1
|
||||||
|
}],
|
||||||
|
'dimensions': [{
|
||||||
|
'accounting_dimension': 'Department',
|
||||||
|
'dimension_value': 'Accounts - _TC'
|
||||||
|
}]
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
|
||||||
|
doc.disabled = 0
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
def disable_dimension_filter():
|
||||||
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
|
||||||
|
doc.disabled = 1
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
|
||||||
|
doc.disabled = 1
|
||||||
|
doc.save()
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"book_asset_depreciation_entry_automatically",
|
"book_asset_depreciation_entry_automatically",
|
||||||
"add_taxes_from_item_tax_template",
|
"add_taxes_from_item_tax_template",
|
||||||
"automatically_fetch_payment_terms",
|
"automatically_fetch_payment_terms",
|
||||||
|
"delete_linked_ledger_entries",
|
||||||
"deferred_accounting_settings_section",
|
"deferred_accounting_settings_section",
|
||||||
"automatically_process_deferred_accounting_entry",
|
"automatically_process_deferred_accounting_entry",
|
||||||
"book_deferred_entries_based_on",
|
"book_deferred_entries_based_on",
|
||||||
@@ -219,6 +220,12 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Book Deferred Entries Based On",
|
"label": "Book Deferred Entries Based On",
|
||||||
"options": "Days\nMonths"
|
"options": "Days\nMonths"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "delete_linked_ledger_entries",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -226,7 +233,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-13 11:32:52.268826",
|
"modified": "2021-01-05 13:04:00.118892",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-11-08 18:22:36.001131",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"accounting_dimension",
|
||||||
|
"dimension_value"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimension",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Accounting Dimension",
|
||||||
|
"options": "DocType",
|
||||||
|
"read_only": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_value",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"options": "accounting_dimension",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-23 09:56:19.744200",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Allowed Dimension",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class AllowedDimension(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-11-08 18:20:00.944449",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"applicable_on_account",
|
||||||
|
"is_mandatory"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "applicable_on_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Accounts",
|
||||||
|
"options": "Account",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_mandatory",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Is Mandatory",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-22 19:55:13.324136",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Applicable On Account",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class ApplicableOnAccount(Document):
|
||||||
|
pass
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
frappe.provide('erpnext.integrations');
|
||||||
|
|
||||||
frappe.ui.form.on('Bank', {
|
frappe.ui.form.on('Bank', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
@@ -20,7 +21,12 @@ frappe.ui.form.on('Bank', {
|
|||||||
frm.set_df_property('address_and_contact', 'hidden', 0);
|
frm.set_df_property('address_and_contact', 'hidden', 0);
|
||||||
frappe.contacts.render_address_and_contact(frm);
|
frappe.contacts.render_address_and_contact(frm);
|
||||||
}
|
}
|
||||||
},
|
if (frm.doc.plaid_access_token) {
|
||||||
|
frm.add_custom_button(__('Refresh Plaid Link'), () => {
|
||||||
|
new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -41,3 +47,78 @@ let add_fields_to_mapping_table = function (frm) {
|
|||||||
|
|
||||||
frm.fields_dict.bank_transaction_mapping.grid.refresh();
|
frm.fields_dict.bank_transaction_mapping.grid.refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||||
|
constructor(access_token) {
|
||||||
|
this.access_token = access_token;
|
||||||
|
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
|
||||||
|
this.init_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init_config() {
|
||||||
|
this.plaid_env = await frappe.db.get_single_value('Plaid Settings', 'plaid_env');
|
||||||
|
this.token = await this.get_link_token_for_update();
|
||||||
|
this.init_plaid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_link_token_for_update() {
|
||||||
|
const token = frappe.xcall(
|
||||||
|
'erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update',
|
||||||
|
{ access_token: this.access_token }
|
||||||
|
)
|
||||||
|
if (!token) {
|
||||||
|
frappe.throw(__('Cannot retrieve link token for update. Check Error Log for more information'));
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_plaid() {
|
||||||
|
const me = this;
|
||||||
|
me.loadScript(me.plaidUrl)
|
||||||
|
.then(() => {
|
||||||
|
me.onScriptLoaded(me);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (me.linkHandler) {
|
||||||
|
me.linkHandler.open();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
me.onScriptError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadScript(src) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
if (document.querySelector("script[src='" + src + "']")) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const el = document.createElement('script');
|
||||||
|
el.type = 'text/javascript';
|
||||||
|
el.async = true;
|
||||||
|
el.src = src;
|
||||||
|
el.addEventListener('load', resolve);
|
||||||
|
el.addEventListener('error', reject);
|
||||||
|
el.addEventListener('abort', reject);
|
||||||
|
document.head.appendChild(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onScriptLoaded(me) {
|
||||||
|
me.linkHandler = Plaid.create({
|
||||||
|
env: me.plaid_env,
|
||||||
|
token: me.token,
|
||||||
|
onSuccess: me.plaid_success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onScriptError(error) {
|
||||||
|
frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
plaid_success(token, response) {
|
||||||
|
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,24 +1,9 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Budget', {
|
frappe.ui.form.on('Budget', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
frm.set_query("project", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
frm.set_query("account", "accounts", function() {
|
frm.set_query("account", "accounts", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -26,16 +11,18 @@ frappe.ui.form.on('Budget', {
|
|||||||
report_type: "Profit and Loss",
|
report_type: "Profit and Loss",
|
||||||
is_group: 0
|
is_group: 0
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
frm.set_query("monthly_distribution", function() {
|
frm.set_query("monthly_distribution", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
fiscal_year: frm.doc.fiscal_year
|
fiscal_year: frm.doc.fiscal_year
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
|||||||
@@ -122,8 +122,10 @@ class TestBudget(unittest.TestCase):
|
|||||||
|
|
||||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||||
|
|
||||||
|
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
|
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
|
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project=project, posting_date=nowdate())
|
||||||
|
|
||||||
self.assertRaises(BudgetError, jv.submit)
|
self.assertRaises(BudgetError, jv.submit)
|
||||||
|
|
||||||
@@ -147,8 +149,11 @@ class TestBudget(unittest.TestCase):
|
|||||||
|
|
||||||
budget = make_budget(budget_against="Project")
|
budget = make_budget(budget_against="Project")
|
||||||
|
|
||||||
|
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
|
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
|
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC",
|
||||||
|
project=project, posting_date=nowdate())
|
||||||
|
|
||||||
self.assertRaises(BudgetError, jv.submit)
|
self.assertRaises(BudgetError, jv.submit)
|
||||||
|
|
||||||
@@ -159,10 +164,10 @@ class TestBudget(unittest.TestCase):
|
|||||||
|
|
||||||
budget = make_budget(budget_against="Cost Center")
|
budget = make_budget(budget_against="Cost Center")
|
||||||
month = now_datetime().month
|
month = now_datetime().month
|
||||||
if month > 10:
|
if month > 9:
|
||||||
month = 10
|
month = 9
|
||||||
|
|
||||||
for i in range(month):
|
for i in range(month+1):
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
||||||
|
|
||||||
@@ -181,12 +186,14 @@ class TestBudget(unittest.TestCase):
|
|||||||
|
|
||||||
budget = make_budget(budget_against="Project")
|
budget = make_budget(budget_against="Project")
|
||||||
month = now_datetime().month
|
month = now_datetime().month
|
||||||
if month > 10:
|
if month > 9:
|
||||||
month = 10
|
month = 9
|
||||||
|
|
||||||
for i in range(month):
|
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
|
for i in range(month + 1):
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
|
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True,
|
||||||
|
project=project)
|
||||||
|
|
||||||
self.assertTrue(frappe.db.get_value("GL Entry",
|
self.assertTrue(frappe.db.get_value("GL Entry",
|
||||||
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
|
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
|
||||||
@@ -289,7 +296,7 @@ def make_budget(**args):
|
|||||||
budget = frappe.new_doc("Budget")
|
budget = frappe.new_doc("Budget")
|
||||||
|
|
||||||
if budget_against == "Project":
|
if budget_against == "Project":
|
||||||
budget.project = "_Test Project"
|
budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
else:
|
else:
|
||||||
budget.cost_center =cost_center or "_Test Cost Center - _TC"
|
budget.cost_center =cost_center or "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ from frappe.model.meta import get_field_precision
|
|||||||
from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
|
from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.exceptions import InvalidAccountCurrency
|
from erpnext.exceptions import InvalidAccountCurrency, InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
exclude_from_linked_with = True
|
exclude_from_linked_with = True
|
||||||
class GLEntry(Document):
|
class GLEntry(Document):
|
||||||
@@ -25,29 +27,30 @@ class GLEntry(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.flags.ignore_submit_comment = True
|
self.flags.ignore_submit_comment = True
|
||||||
self.check_mandatory()
|
|
||||||
self.validate_and_set_fiscal_year()
|
self.validate_and_set_fiscal_year()
|
||||||
self.pl_must_have_cost_center()
|
self.pl_must_have_cost_center()
|
||||||
self.validate_cost_center()
|
|
||||||
|
|
||||||
if not self.flags.from_repost:
|
if not self.flags.from_repost:
|
||||||
|
self.check_mandatory()
|
||||||
|
self.validate_cost_center()
|
||||||
self.check_pl_account()
|
self.check_pl_account()
|
||||||
self.validate_party()
|
self.validate_party()
|
||||||
self.validate_currency()
|
self.validate_currency()
|
||||||
|
|
||||||
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
|
def on_update(self):
|
||||||
if not from_repost:
|
adv_adj = self.flags.adv_adj
|
||||||
|
if not self.flags.from_repost:
|
||||||
self.validate_account_details(adv_adj)
|
self.validate_account_details(adv_adj)
|
||||||
self.validate_dimensions_for_pl_and_bs()
|
self.validate_dimensions_for_pl_and_bs()
|
||||||
|
self.validate_allowed_dimensions()
|
||||||
|
validate_balance_type(self.account, adv_adj)
|
||||||
|
validate_frozen_account(self.account, adv_adj)
|
||||||
|
|
||||||
validate_frozen_account(self.account, adv_adj)
|
# Update outstanding amt on against voucher
|
||||||
validate_balance_type(self.account, adv_adj)
|
if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees']
|
||||||
|
and self.against_voucher and self.flags.update_outstanding == 'Yes'):
|
||||||
# Update outstanding amt on against voucher
|
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
|
||||||
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
|
self.against_voucher)
|
||||||
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
|
|
||||||
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
|
|
||||||
self.against_voucher)
|
|
||||||
|
|
||||||
def check_mandatory(self):
|
def check_mandatory(self):
|
||||||
mandatory = ['account','voucher_type','voucher_no','company']
|
mandatory = ['account','voucher_type','voucher_no','company']
|
||||||
@@ -55,7 +58,7 @@ class GLEntry(Document):
|
|||||||
if not self.get(k):
|
if not self.get(k):
|
||||||
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
|
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
|
||||||
|
|
||||||
account_type = frappe.db.get_value("Account", self.account, "account_type")
|
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
||||||
if not (self.party_type and self.party):
|
if not (self.party_type and self.party):
|
||||||
if account_type == "Receivable":
|
if account_type == "Receivable":
|
||||||
frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}")
|
frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}")
|
||||||
@@ -70,17 +73,15 @@ class GLEntry(Document):
|
|||||||
.format(self.voucher_type, self.voucher_no, self.account))
|
.format(self.voucher_type, self.voucher_no, self.account))
|
||||||
|
|
||||||
def pl_must_have_cost_center(self):
|
def pl_must_have_cost_center(self):
|
||||||
if frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss":
|
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
|
||||||
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
|
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
|
||||||
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
|
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
|
||||||
.format(self.voucher_type, self.voucher_no, self.account))
|
.format(self.voucher_type, self.voucher_no, self.account))
|
||||||
|
|
||||||
def validate_dimensions_for_pl_and_bs(self):
|
def validate_dimensions_for_pl_and_bs(self):
|
||||||
|
|
||||||
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
||||||
|
|
||||||
for dimension in get_checks_for_pl_and_bs_accounts():
|
for dimension in get_checks_for_pl_and_bs_accounts():
|
||||||
|
|
||||||
if account_type == "Profit and Loss" \
|
if account_type == "Profit and Loss" \
|
||||||
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
|
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
|
||||||
if not self.get(dimension.fieldname):
|
if not self.get(dimension.fieldname):
|
||||||
@@ -93,6 +94,25 @@ class GLEntry(Document):
|
|||||||
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
|
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
|
||||||
.format(dimension.label, self.account))
|
.format(dimension.label, self.account))
|
||||||
|
|
||||||
|
def validate_allowed_dimensions(self):
|
||||||
|
dimension_filter_map = get_dimension_filter_map()
|
||||||
|
for key, value in iteritems(dimension_filter_map):
|
||||||
|
dimension = key[0]
|
||||||
|
account = key[1]
|
||||||
|
|
||||||
|
if self.account == account:
|
||||||
|
if value['is_mandatory'] and not self.get(dimension):
|
||||||
|
frappe.throw(_("{0} is mandatory for account {1}").format(
|
||||||
|
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), MandatoryAccountDimensionError)
|
||||||
|
|
||||||
|
if value['allow_or_restrict'] == 'Allow':
|
||||||
|
if self.get(dimension) and self.get(dimension) not in value['allowed_dimensions']:
|
||||||
|
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
|
||||||
|
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
|
||||||
|
else:
|
||||||
|
if self.get(dimension) and self.get(dimension) in value['allowed_dimensions']:
|
||||||
|
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
|
||||||
|
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
|
||||||
|
|
||||||
def check_pl_account(self):
|
def check_pl_account(self):
|
||||||
if self.is_opening=='Yes' and \
|
if self.is_opening=='Yes' and \
|
||||||
@@ -120,26 +140,18 @@ class GLEntry(Document):
|
|||||||
.format(self.voucher_type, self.voucher_no, self.account, self.company))
|
.format(self.voucher_type, self.voucher_no, self.account, self.company))
|
||||||
|
|
||||||
def validate_cost_center(self):
|
def validate_cost_center(self):
|
||||||
if not hasattr(self, "cost_center_company"):
|
if not self.cost_center: return
|
||||||
self.cost_center_company = {}
|
|
||||||
|
|
||||||
def _get_cost_center_company():
|
is_group, company = frappe.get_cached_value('Cost Center',
|
||||||
if not self.cost_center_company.get(self.cost_center):
|
self.cost_center, ['is_group', 'company'])
|
||||||
self.cost_center_company[self.cost_center] = frappe.db.get_value(
|
|
||||||
"Cost Center", self.cost_center, "company")
|
|
||||||
|
|
||||||
return self.cost_center_company[self.cost_center]
|
if company != self.company:
|
||||||
|
|
||||||
def _check_is_group():
|
|
||||||
return cint(frappe.get_cached_value('Cost Center', self.cost_center, 'is_group'))
|
|
||||||
|
|
||||||
if self.cost_center and _get_cost_center_company() != self.company:
|
|
||||||
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
|
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
|
||||||
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
|
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
|
||||||
|
|
||||||
if self.cost_center and _check_is_group():
|
if (self.voucher_type != 'Period Closing Voucher' and is_group):
|
||||||
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
|
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
|
||||||
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||||
|
|
||||||
def validate_party(self):
|
def validate_party(self):
|
||||||
validate_party_frozen_disabled(self.party_type, self.party)
|
validate_party_frozen_disabled(self.party_type, self.party)
|
||||||
@@ -149,7 +161,7 @@ class GLEntry(Document):
|
|||||||
account_currency = get_account_currency(self.account)
|
account_currency = get_account_currency(self.account)
|
||||||
|
|
||||||
if not self.account_currency:
|
if not self.account_currency:
|
||||||
self.account_currency = company_currency
|
self.account_currency = account_currency or company_currency
|
||||||
|
|
||||||
if account_currency != self.account_currency:
|
if account_currency != self.account_currency:
|
||||||
frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")
|
frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")
|
||||||
@@ -163,7 +175,6 @@ class GLEntry(Document):
|
|||||||
if not self.fiscal_year:
|
if not self.fiscal_year:
|
||||||
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
|
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
|
||||||
|
|
||||||
|
|
||||||
def validate_balance_type(account, adv_adj=False):
|
def validate_balance_type(account, adv_adj=False):
|
||||||
if not adv_adj and account:
|
if not adv_adj and account:
|
||||||
balance_must_be = frappe.db.get_value("Account", account, "balance_must_be")
|
balance_must_be = frappe.db.get_value("Account", account, "balance_must_be")
|
||||||
@@ -229,7 +240,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
|
|||||||
|
|
||||||
|
|
||||||
def validate_frozen_account(account, adv_adj=None):
|
def validate_frozen_account(account, adv_adj=None):
|
||||||
frozen_account = frappe.db.get_value("Account", account, "freeze_account")
|
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
|
||||||
if frozen_account == 'Yes' and not adv_adj:
|
if frozen_account == 'Yes' and not adv_adj:
|
||||||
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,
|
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,
|
||||||
'frozen_accounts_modifier')
|
'frozen_accounts_modifier')
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ def get_data():
|
|||||||
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
|
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'items': ['Item']
|
'label': _('Stock'),
|
||||||
|
'items': ['Item Groups', 'Item']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ frappe.ui.form.on("Journal Entry", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
voucher_type: function(frm){
|
voucher_type: function(frm){
|
||||||
@@ -197,6 +199,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
this.load_defaults();
|
this.load_defaults();
|
||||||
this.setup_queries();
|
this.setup_queries();
|
||||||
this.setup_balance_formatter();
|
this.setup_balance_formatter();
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
onload_post_render: function() {
|
onload_post_render: function() {
|
||||||
@@ -222,15 +225,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
return erpnext.journal_entry.account_query(me.frm);
|
return erpnext.journal_entry.account_query(me.frm);
|
||||||
});
|
});
|
||||||
|
|
||||||
me.frm.set_query("cost_center", "accounts", function(doc, cdt, cdn) {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: me.frm.doc.company,
|
|
||||||
is_group: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
|
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
|
|
||||||
@@ -406,6 +400,8 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cur_frm.cscript.update_totals(doc);
|
cur_frm.cscript.update_totals(doc);
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'accounts');
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -299,15 +299,20 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
|
|
||||||
def test_jv_with_project(self):
|
def test_jv_with_project(self):
|
||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
project = make_project({
|
|
||||||
'project_name': 'Journal Entry Project',
|
if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
|
||||||
'project_template_name': 'Test Project Template',
|
project = make_project({
|
||||||
'start_date': '2020-01-01'
|
'project_name': 'Journal Entry Project',
|
||||||
})
|
'project_template_name': 'Test Project Template',
|
||||||
|
'start_date': '2020-01-01'
|
||||||
|
})
|
||||||
|
project_name = project.name
|
||||||
|
else:
|
||||||
|
project_name = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
|
|
||||||
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
||||||
for d in jv.accounts:
|
for d in jv.accounts:
|
||||||
d.project = project.project_name
|
d.project = project_name
|
||||||
jv.voucher_type = "Bank Entry"
|
jv.voucher_type = "Bank Entry"
|
||||||
jv.multi_currency = 0
|
jv.multi_currency = 0
|
||||||
jv.cheque_no = "112233"
|
jv.cheque_no = "112233"
|
||||||
@@ -317,10 +322,10 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"_Test Cash - _TC": {
|
"_Test Cash - _TC": {
|
||||||
"project": project.project_name
|
"project": project_name
|
||||||
},
|
},
|
||||||
"_Test Bank - _TC": {
|
"_Test Bank - _TC": {
|
||||||
"project": project.project_name
|
"project": project_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Loyalty Program', {
|
frappe.ui.form.on('Loyalty Program', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
var help_content =
|
var help_content =
|
||||||
@@ -46,20 +48,17 @@ frappe.ui.form.on('Loyalty Program', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_value("company", frappe.defaults.get_user_default("Company"));
|
frm.set_value("company", frappe.defaults.get_user_default("Company"));
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
|
if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
|
||||||
frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
|
frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
|||||||
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
||||||
frm.page.set_indicator(__('In Progress'), 'orange');
|
frm.page.set_indicator(__('In Progress'), 'orange');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
@@ -100,6 +102,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
invoice_type: function(frm) {
|
invoice_type: function(frm) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Payment Entry', {
|
frappe.ui.form.on('Payment Entry', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
@@ -8,6 +9,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||||
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
|
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
@@ -88,24 +91,17 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", "deductions", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
"is_group": 0,
|
|
||||||
"company": frm.doc.company
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_query("reference_doctype", "references", function() {
|
frm.set_query("reference_doctype", "references", function() {
|
||||||
if (frm.doc.party_type=="Customer") {
|
if (frm.doc.party_type == "Customer") {
|
||||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||||
} else if (frm.doc.party_type=="Supplier") {
|
} else if (frm.doc.party_type == "Supplier") {
|
||||||
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
||||||
} else if (frm.doc.party_type=="Employee") {
|
} else if (frm.doc.party_type == "Employee") {
|
||||||
var doctypes = ["Expense Claim", "Journal Entry"];
|
var doctypes = ["Expense Claim", "Journal Entry"];
|
||||||
} else if (frm.doc.party_type=="Student") {
|
} else if (frm.doc.party_type == "Student") {
|
||||||
var doctypes = ["Fees"];
|
var doctypes = ["Fees"];
|
||||||
|
} else if (frm.doc.party_type == "Donor") {
|
||||||
|
var doctypes = ["Donation"];
|
||||||
} else {
|
} else {
|
||||||
var doctypes = ["Journal Entry"];
|
var doctypes = ["Journal Entry"];
|
||||||
}
|
}
|
||||||
@@ -134,7 +130,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
const child = locals[cdt][cdn];
|
const child = locals[cdt][cdn];
|
||||||
const filters = {"docstatus": 1, "company": doc.company};
|
const filters = {"docstatus": 1, "company": doc.company};
|
||||||
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
||||||
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
|
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation'];
|
||||||
|
|
||||||
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
||||||
filters[doc.party_type.toLowerCase()] = doc.party;
|
filters[doc.party_type.toLowerCase()] = doc.party;
|
||||||
@@ -167,6 +163,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
frm.events.set_dynamic_labels(frm);
|
frm.events.set_dynamic_labels(frm);
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
contact_person: function(frm) {
|
contact_person: function(frm) {
|
||||||
@@ -286,7 +283,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
let party_types = Object.keys(frappe.boot.party_account_types);
|
let party_types = Object.keys(frappe.boot.party_account_types);
|
||||||
if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){
|
if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){
|
||||||
frm.set_value("party_type", "");
|
frm.set_value("party_type", "");
|
||||||
frappe.throw(__("Party can only be one of "+ party_types.join(", ")));
|
frappe.throw(__("Party can only be one of {0}", [party_types.join(", ")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.set_query("party", function() {
|
frm.set_query("party", function() {
|
||||||
@@ -401,6 +398,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
set_account_currency_and_balance: function(frm, account, currency_field,
|
set_account_currency_and_balance: function(frm, account, currency_field,
|
||||||
balance_field, callback_function) {
|
balance_field, callback_function) {
|
||||||
|
|
||||||
|
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||||
if (frm.doc.posting_date && account) {
|
if (frm.doc.posting_date && account) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
|
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
|
||||||
@@ -427,6 +426,14 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
if(!frm.doc.paid_amount && frm.doc.received_amount)
|
if(!frm.doc.paid_amount && frm.doc.received_amount)
|
||||||
frm.events.received_amount(frm);
|
frm.events.received_amount(frm);
|
||||||
|
|
||||||
|
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
|
||||||
|
&& frm.doc.paid_amount != frm.doc.received_amount) {
|
||||||
|
if (company_currency != frm.doc.paid_from_account_currency &&
|
||||||
|
frm.doc.payment_type == "Pay") {
|
||||||
|
frm.doc.paid_amount = frm.doc.received_amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
@@ -700,7 +707,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
||||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
||||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
|
||||||
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
|
||||||
) {
|
) {
|
||||||
if(total_positive_outstanding > total_negative_outstanding)
|
if(total_positive_outstanding > total_negative_outstanding)
|
||||||
if (!frm.doc.paid_amount)
|
if (!frm.doc.paid_amount)
|
||||||
@@ -743,7 +751,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
||||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
||||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
|
||||||
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
|
||||||
) {
|
) {
|
||||||
if(total_positive_outstanding_including_order > paid_amount) {
|
if(total_positive_outstanding_including_order > paid_amount) {
|
||||||
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
|
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
|
||||||
@@ -900,6 +909,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
|
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.party_type == "Donor" && row.reference_doctype != "Donation") {
|
||||||
|
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
|
||||||
|
frappe.msgprint(__("Row #{0}: Reference Document Type must be Donation", [row.idx]));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row) {
|
if (row) {
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
|
self.update_donation()
|
||||||
self.update_payment_schedule()
|
self.update_payment_schedule()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
|
self.update_donation(cancel=1)
|
||||||
self.delink_advance_entry_references()
|
self.delink_advance_entry_references()
|
||||||
self.update_payment_schedule(cancel=1)
|
self.update_payment_schedule(cancel=1)
|
||||||
self.set_payment_req_status()
|
self.set_payment_req_status()
|
||||||
@@ -245,6 +247,8 @@ class PaymentEntry(AccountsController):
|
|||||||
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
|
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
|
||||||
elif self.party_type == "Shareholder":
|
elif self.party_type == "Shareholder":
|
||||||
valid_reference_doctypes = ("Journal Entry")
|
valid_reference_doctypes = ("Journal Entry")
|
||||||
|
elif self.party_type == "Donor":
|
||||||
|
valid_reference_doctypes = ("Donation")
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if not d.allocated_amount:
|
if not d.allocated_amount:
|
||||||
@@ -614,6 +618,13 @@ class PaymentEntry(AccountsController):
|
|||||||
doc = frappe.get_doc("Expense Claim", d.reference_name)
|
doc = frappe.get_doc("Expense Claim", d.reference_name)
|
||||||
update_reimbursed_amount(doc, self.name)
|
update_reimbursed_amount(doc, self.name)
|
||||||
|
|
||||||
|
def update_donation(self, cancel=0):
|
||||||
|
if self.payment_type == "Receive" and self.party_type == "Donor" and self.party:
|
||||||
|
for d in self.get("references"):
|
||||||
|
if d.reference_doctype=="Donation" and d.reference_name:
|
||||||
|
is_paid = 0 if cancel else 1
|
||||||
|
frappe.db.set_value("Donation", d.reference_name, "paid", is_paid)
|
||||||
|
|
||||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||||
self.reference_no = reference_doc.name
|
self.reference_no = reference_doc.name
|
||||||
self.reference_date = nowdate()
|
self.reference_date = nowdate()
|
||||||
@@ -913,6 +924,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
total_amount = ref_doc.get("grand_total")
|
total_amount = ref_doc.get("grand_total")
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
|
elif reference_doctype == "Donation":
|
||||||
|
total_amount = ref_doc.get("amount")
|
||||||
|
exchange_rate = 1
|
||||||
elif reference_doctype == "Dunning":
|
elif reference_doctype == "Dunning":
|
||||||
total_amount = ref_doc.get("dunning_amount")
|
total_amount = ref_doc.get("dunning_amount")
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
@@ -1162,8 +1176,10 @@ def set_party_type(dt):
|
|||||||
party_type = "Supplier"
|
party_type = "Supplier"
|
||||||
elif dt in ("Expense Claim", "Employee Advance"):
|
elif dt in ("Expense Claim", "Employee Advance"):
|
||||||
party_type = "Employee"
|
party_type = "Employee"
|
||||||
elif dt in ("Fees"):
|
elif dt == "Fees":
|
||||||
party_type = "Student"
|
party_type = "Student"
|
||||||
|
elif dt == "Donation":
|
||||||
|
party_type = "Donor"
|
||||||
return party_type
|
return party_type
|
||||||
|
|
||||||
def set_party_account(dt, dn, doc, party_type):
|
def set_party_account(dt, dn, doc, party_type):
|
||||||
@@ -1189,7 +1205,7 @@ def set_party_account_currency(dt, party_account, doc):
|
|||||||
return party_account_currency
|
return party_account_currency
|
||||||
|
|
||||||
def set_payment_type(dt, doc):
|
def set_payment_type(dt, doc):
|
||||||
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
if (dt in ("Sales Order", "Donation") or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
||||||
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
||||||
payment_type = "Receive"
|
payment_type = "Receive"
|
||||||
else:
|
else:
|
||||||
@@ -1222,6 +1238,9 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
|
|||||||
elif dt == "Dunning":
|
elif dt == "Dunning":
|
||||||
grand_total = doc.grand_total
|
grand_total = doc.grand_total
|
||||||
outstanding_amount = doc.grand_total
|
outstanding_amount = doc.grand_total
|
||||||
|
elif dt == "Donation":
|
||||||
|
grand_total = doc.amount
|
||||||
|
outstanding_amount = doc.amount
|
||||||
else:
|
else:
|
||||||
if party_account_currency == doc.company_currency:
|
if party_account_currency == doc.company_currency:
|
||||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import json
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
@@ -82,18 +83,37 @@ class PaymentRequest(Document):
|
|||||||
self.make_communication_entry()
|
self.make_communication_entry()
|
||||||
|
|
||||||
elif self.payment_channel == "Phone":
|
elif self.payment_channel == "Phone":
|
||||||
controller = get_payment_gateway_controller(self.payment_gateway)
|
self.request_phone_payment()
|
||||||
payment_record = dict(
|
|
||||||
reference_doctype="Payment Request",
|
def request_phone_payment(self):
|
||||||
reference_docname=self.name,
|
controller = get_payment_gateway_controller(self.payment_gateway)
|
||||||
payment_reference=self.reference_name,
|
request_amount = self.get_request_amount()
|
||||||
grand_total=self.grand_total,
|
|
||||||
sender=self.email_to,
|
payment_record = dict(
|
||||||
currency=self.currency,
|
reference_doctype="Payment Request",
|
||||||
payment_gateway=self.payment_gateway
|
reference_docname=self.name,
|
||||||
)
|
payment_reference=self.reference_name,
|
||||||
controller.validate_transaction_currency(self.currency)
|
request_amount=request_amount,
|
||||||
controller.request_for_payment(**payment_record)
|
sender=self.email_to,
|
||||||
|
currency=self.currency,
|
||||||
|
payment_gateway=self.payment_gateway
|
||||||
|
)
|
||||||
|
|
||||||
|
controller.validate_transaction_currency(self.currency)
|
||||||
|
controller.request_for_payment(**payment_record)
|
||||||
|
|
||||||
|
def get_request_amount(self):
|
||||||
|
data_of_completed_requests = frappe.get_all("Integration Request", filters={
|
||||||
|
'reference_doctype': self.doctype,
|
||||||
|
'reference_docname': self.name,
|
||||||
|
'status': 'Completed'
|
||||||
|
}, pluck="data")
|
||||||
|
|
||||||
|
if not data_of_completed_requests:
|
||||||
|
return self.grand_total
|
||||||
|
|
||||||
|
request_amounts = sum([json.loads(d).get('request_amount') for d in data_of_completed_requests])
|
||||||
|
return request_amounts
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.check_if_payment_entry_exists()
|
self.check_if_payment_entry_exists()
|
||||||
@@ -351,8 +371,8 @@ def make_payment_request(**args):
|
|||||||
if args.order_type == "Shopping Cart" or args.mute_email:
|
if args.order_type == "Shopping Cart" or args.mute_email:
|
||||||
pr.flags.mute_email = True
|
pr.flags.mute_email = True
|
||||||
|
|
||||||
|
pr.insert(ignore_permissions=True)
|
||||||
if args.submit_doc:
|
if args.submit_doc:
|
||||||
pr.insert(ignore_permissions=True)
|
|
||||||
pr.submit()
|
pr.submit()
|
||||||
|
|
||||||
if args.order_type == "Shopping Cart":
|
if args.order_type == "Shopping Cart":
|
||||||
@@ -412,8 +432,8 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
|
|||||||
|
|
||||||
def get_gateway_details(args):
|
def get_gateway_details(args):
|
||||||
"""return gateway and payment account of default payment gateway"""
|
"""return gateway and payment account of default payment gateway"""
|
||||||
if args.get("payment_gateway"):
|
if args.get("payment_gateway_account"):
|
||||||
return get_payment_gateway_account(args.get("payment_gateway"))
|
return get_payment_gateway_account(args.get("payment_gateway_account"))
|
||||||
|
|
||||||
if args.order_type == "Shopping Cart":
|
if args.order_type == "Shopping Cart":
|
||||||
payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account
|
payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ class TestPaymentRequest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_payment_request_linkings(self):
|
def test_payment_request_linkings(self):
|
||||||
so_inr = make_sales_order(currency="INR")
|
so_inr = make_sales_order(currency="INR")
|
||||||
pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com")
|
pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com",
|
||||||
|
payment_gateway_account="_Test Gateway - INR")
|
||||||
|
|
||||||
self.assertEqual(pr.reference_doctype, "Sales Order")
|
self.assertEqual(pr.reference_doctype, "Sales Order")
|
||||||
self.assertEqual(pr.reference_name, so_inr.name)
|
self.assertEqual(pr.reference_name, so_inr.name)
|
||||||
@@ -54,7 +55,8 @@ class TestPaymentRequest(unittest.TestCase):
|
|||||||
conversion_rate = get_exchange_rate("USD", "INR")
|
conversion_rate = get_exchange_rate("USD", "INR")
|
||||||
|
|
||||||
si_usd = create_sales_invoice(currency="USD", conversion_rate=conversion_rate)
|
si_usd = create_sales_invoice(currency="USD", conversion_rate=conversion_rate)
|
||||||
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com")
|
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
|
||||||
|
payment_gateway_account="_Test Gateway - USD")
|
||||||
|
|
||||||
self.assertEqual(pr.reference_doctype, "Sales Invoice")
|
self.assertEqual(pr.reference_doctype, "Sales Invoice")
|
||||||
self.assertEqual(pr.reference_name, si_usd.name)
|
self.assertEqual(pr.reference_name, si_usd.name)
|
||||||
@@ -68,7 +70,7 @@ class TestPaymentRequest(unittest.TestCase):
|
|||||||
|
|
||||||
so_inr = make_sales_order(currency="INR")
|
so_inr = make_sales_order(currency="INR")
|
||||||
pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com",
|
pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com",
|
||||||
mute_email=1, submit_doc=1, return_doc=1)
|
mute_email=1, payment_gateway_account="_Test Gateway - INR", submit_doc=1, return_doc=1)
|
||||||
pe = pr.set_as_paid()
|
pe = pr.set_as_paid()
|
||||||
|
|
||||||
so_inr = frappe.get_doc("Sales Order", so_inr.name)
|
so_inr = frappe.get_doc("Sales Order", so_inr.name)
|
||||||
@@ -79,7 +81,7 @@ class TestPaymentRequest(unittest.TestCase):
|
|||||||
currency="USD", conversion_rate=50)
|
currency="USD", conversion_rate=50)
|
||||||
|
|
||||||
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
|
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
|
||||||
mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1)
|
mute_email=1, payment_gateway_account="_Test Gateway - USD", submit_doc=1, return_doc=1)
|
||||||
|
|
||||||
pe = pr.set_as_paid()
|
pe = pr.set_as_paid()
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ class TestPaymentRequest(unittest.TestCase):
|
|||||||
currency="USD", conversion_rate=50)
|
currency="USD", conversion_rate=50)
|
||||||
|
|
||||||
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
|
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
|
||||||
mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1)
|
mute_email=1, payment_gateway_account="_Test Gateway - USD", submit_doc=1, return_doc=1)
|
||||||
|
|
||||||
pe = pr.create_payment_entry()
|
pe = pr.create_payment_entry()
|
||||||
pr.load_from_db()
|
pr.load_from_db()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from frappe import _
|
|||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
|
||||||
get_dimension_filters)
|
get_dimensions)
|
||||||
|
|
||||||
class PeriodClosingVoucher(AccountsController):
|
class PeriodClosingVoucher(AccountsController):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@@ -58,7 +58,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
for dimension in accounting_dimensions:
|
for dimension in accounting_dimensions:
|
||||||
dimension_fields.append('t1.{0}'.format(dimension))
|
dimension_fields.append('t1.{0}'.format(dimension))
|
||||||
|
|
||||||
dimension_filters, default_dimensions = get_dimension_filters()
|
dimension_filters, default_dimensions = get_dimensions()
|
||||||
|
|
||||||
pl_accounts = self.get_pl_balances(dimension_fields)
|
pl_accounts = self.get_pl_balances(dimension_fields)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('POS Closing Entry', {
|
frappe.ui.form.on('POS Closing Entry', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
|
frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
|
||||||
frm.set_query("pos_profile", function(doc) {
|
frm.set_query("pos_profile", function(doc) {
|
||||||
return {
|
return {
|
||||||
filters: { 'user': doc.user }
|
filters: { 'user': doc.user }
|
||||||
@@ -20,7 +21,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
return { filters: { 'status': 'Open', 'docstatus': 1 } };
|
return { filters: { 'status': 'Open', 'docstatus': 1 } };
|
||||||
});
|
});
|
||||||
|
|
||||||
if (frm.doc.docstatus === 0) frm.set_value("period_end_date", frappe.datetime.now_datetime());
|
if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime());
|
||||||
if (frm.doc.docstatus === 1) set_html_data(frm);
|
if (frm.doc.docstatus === 1) set_html_data(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"column_break_3",
|
"column_break_3",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"pos_opening_entry",
|
"pos_opening_entry",
|
||||||
|
"status",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"company",
|
"company",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
@@ -184,11 +185,27 @@
|
|||||||
"label": "POS Opening Entry",
|
"label": "POS Opening Entry",
|
||||||
"options": "POS Opening Entry",
|
"options": "POS Opening Entry",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"default": "Draft",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Draft\nSubmitted\nQueued\nCancelled",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2020-05-29 15:03:22.226113",
|
{
|
||||||
|
"link_doctype": "POS Invoice Merge Log",
|
||||||
|
"link_fieldname": "pos_closing_entry"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2021-01-12 12:21:05.388650",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Closing Entry",
|
"name": "POS Closing Entry",
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import json
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.utils import get_datetime, flt
|
||||||
from frappe.utils import getdate, get_datetime, flt
|
from erpnext.controllers.status_updater import StatusUpdater
|
||||||
from collections import defaultdict
|
|
||||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
||||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices, unconsolidate_pos_invoices
|
||||||
|
|
||||||
class POSClosingEntry(Document):
|
class POSClosingEntry(StatusUpdater):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||||
@@ -57,21 +56,30 @@ class POSClosingEntry(Document):
|
|||||||
if not invalid_rows:
|
if not invalid_rows:
|
||||||
return
|
return
|
||||||
|
|
||||||
error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
|
error_list = []
|
||||||
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
for row in invalid_rows:
|
||||||
|
for msg in row.get('msg'):
|
||||||
|
error_list.append(_("Row #{}: {}").format(row.get('idx'), msg))
|
||||||
|
|
||||||
def on_submit(self):
|
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||||
merge_pos_invoices(self.pos_transactions)
|
|
||||||
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
|
|
||||||
opening_entry.pos_closing_entry = self.name
|
|
||||||
opening_entry.set_status()
|
|
||||||
opening_entry.save()
|
|
||||||
|
|
||||||
def get_payment_reconciliation_details(self):
|
def get_payment_reconciliation_details(self):
|
||||||
currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
||||||
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
|
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
|
||||||
{"data": self, "currency": currency})
|
{"data": self, "currency": currency})
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
consolidate_pos_invoices(closing_entry=self)
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
unconsolidate_pos_invoices(closing_entry=self)
|
||||||
|
|
||||||
|
def update_opening_entry(self, for_cancel=False):
|
||||||
|
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
|
||||||
|
opening_entry.pos_closing_entry = self.name if not for_cancel else None
|
||||||
|
opening_entry.set_status()
|
||||||
|
opening_entry.save()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
// render
|
||||||
|
frappe.listview_settings['POS Closing Entry'] = {
|
||||||
|
get_indicator: function(doc) {
|
||||||
|
var status_color = {
|
||||||
|
"Draft": "red",
|
||||||
|
"Submitted": "blue",
|
||||||
|
"Queued": "orange",
|
||||||
|
"Cancelled": "red"
|
||||||
|
|
||||||
|
};
|
||||||
|
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -13,7 +13,6 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi
|
|||||||
class TestPOSClosingEntry(unittest.TestCase):
|
class TestPOSClosingEntry(unittest.TestCase):
|
||||||
def test_pos_closing_entry(self):
|
def test_pos_closing_entry(self):
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
|
||||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||||
|
|
||||||
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
||||||
@@ -45,6 +44,49 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
|
def test_cancelling_of_pos_closing_entry(self):
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||||
|
|
||||||
|
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
||||||
|
pos_inv1.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500
|
||||||
|
})
|
||||||
|
pos_inv1.submit()
|
||||||
|
|
||||||
|
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||||
|
pos_inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||||
|
})
|
||||||
|
pos_inv2.submit()
|
||||||
|
|
||||||
|
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||||
|
payment = pcv_doc.payment_reconciliation[0]
|
||||||
|
|
||||||
|
self.assertEqual(payment.mode_of_payment, 'Cash')
|
||||||
|
|
||||||
|
for d in pcv_doc.payment_reconciliation:
|
||||||
|
if d.mode_of_payment == 'Cash':
|
||||||
|
d.closing_amount = 6700
|
||||||
|
|
||||||
|
pcv_doc.submit()
|
||||||
|
|
||||||
|
pos_inv1.load_from_db()
|
||||||
|
self.assertRaises(frappe.ValidationError, pos_inv1.cancel)
|
||||||
|
|
||||||
|
si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice)
|
||||||
|
self.assertRaises(frappe.ValidationError, si_doc.cancel)
|
||||||
|
|
||||||
|
pcv_doc.load_from_db()
|
||||||
|
pcv_doc.cancel()
|
||||||
|
si_doc.load_from_db()
|
||||||
|
pos_inv1.load_from_db()
|
||||||
|
self.assertEqual(si_doc.docstatus, 2)
|
||||||
|
self.assertEqual(pos_inv1.status, 'Paid')
|
||||||
|
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
def init_user_and_profile(**args):
|
def init_user_and_profile(**args):
|
||||||
user = 'test@example.com'
|
user = 'test@example.com'
|
||||||
test_user = frappe.get_doc('User', user)
|
test_user = frappe.get_doc('User', user)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
{% include 'erpnext/selling/sales_common.js' %};
|
{% include 'erpnext/selling/sales_common.js' %};
|
||||||
|
frappe.provide("erpnext.accounts");
|
||||||
|
|
||||||
erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({
|
erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({
|
||||||
setup(doc) {
|
setup(doc) {
|
||||||
@@ -9,12 +10,19 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
|||||||
this._super(doc);
|
this._super(doc);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function() {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
onload(doc) {
|
onload(doc) {
|
||||||
this._super();
|
this._super();
|
||||||
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
|
||||||
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||||
this.frm.script_manager.trigger("is_pos");
|
this.frm.script_manager.trigger("is_pos");
|
||||||
this.frm.refresh_fields();
|
this.frm.refresh_fields();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh(doc) {
|
refresh(doc) {
|
||||||
@@ -187,18 +195,43 @@ frappe.ui.form.on('POS Invoice', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
request_for_payment: function (frm) {
|
request_for_payment: function (frm) {
|
||||||
|
if (!frm.doc.contact_mobile) {
|
||||||
|
frappe.throw(__('Please enter mobile number first.'));
|
||||||
|
}
|
||||||
|
frm.dirty();
|
||||||
frm.save().then(() => {
|
frm.save().then(() => {
|
||||||
frappe.dom.freeze();
|
frappe.dom.freeze(__('Waiting for payment...'));
|
||||||
frappe.call({
|
frappe
|
||||||
method: 'create_payment_request',
|
.call({
|
||||||
doc: frm.doc,
|
method: 'create_payment_request',
|
||||||
})
|
doc: frm.doc
|
||||||
|
})
|
||||||
.fail(() => {
|
.fail(() => {
|
||||||
frappe.dom.unfreeze();
|
frappe.dom.unfreeze();
|
||||||
frappe.msgprint('Payment request failed');
|
frappe.msgprint(__('Payment request failed'));
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(({ message }) => {
|
||||||
frappe.msgprint('Payment request sent successfully');
|
const payment_request_name = message.name;
|
||||||
|
setTimeout(() => {
|
||||||
|
frappe.db.get_value('Payment Request', payment_request_name, ['status', 'grand_total']).then(({ message }) => {
|
||||||
|
if (message.status != 'Paid') {
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
frappe.msgprint({
|
||||||
|
message: __('Payment Request took too long to respond. Please try requesting for payment again.'),
|
||||||
|
title: __('Request Timeout')
|
||||||
|
});
|
||||||
|
} else if (frappe.dom.freeze_count != 0) {
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
cur_frm.reload_doc();
|
||||||
|
cur_pos.payment.events.submit_invoice();
|
||||||
|
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __("Payment of {0} received successfully.", [format_currency(message.grand_total, frm.doc.currency, 0)]),
|
||||||
|
indicator: 'green'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 60000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.controllers.selling_controller import SellingController
|
|
||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date
|
||||||
|
from frappe.utils import cint, flt, getdate, nowdate, get_link_to_form
|
||||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
||||||
@@ -59,6 +58,22 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.check_phone_payments()
|
self.check_phone_payments()
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
||||||
|
|
||||||
|
def before_cancel(self):
|
||||||
|
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
|
||||||
|
pos_closing_entry = frappe.get_all(
|
||||||
|
"POS Invoice Reference",
|
||||||
|
ignore_permissions=True,
|
||||||
|
filters={ 'pos_invoice': self.name },
|
||||||
|
pluck="parent",
|
||||||
|
limit=1
|
||||||
|
)
|
||||||
|
frappe.throw(
|
||||||
|
_('You need to cancel POS Closing Entry {} to be able to cancel this document.').format(
|
||||||
|
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
|
||||||
|
),
|
||||||
|
title=_('Not Allowed')
|
||||||
|
)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
# run on cancel method of selling controller
|
# run on cancel method of selling controller
|
||||||
super(SalesInvoice, self).on_cancel()
|
super(SalesInvoice, self).on_cancel()
|
||||||
@@ -78,7 +93,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
mode_of_payment=pay.mode_of_payment, status="Paid"),
|
mode_of_payment=pay.mode_of_payment, status="Paid"),
|
||||||
fieldname="grand_total")
|
fieldname="grand_total")
|
||||||
|
|
||||||
if pay.amount != paid_amt:
|
if paid_amt and pay.amount != paid_amt:
|
||||||
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||||
|
|
||||||
def validate_stock_availablility(self):
|
def validate_stock_availablility(self):
|
||||||
@@ -266,6 +281,8 @@ class POSInvoice(SalesInvoice):
|
|||||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||||
if not self.pos_profile:
|
if not self.pos_profile:
|
||||||
pos_profile = get_pos_profile(self.company) or {}
|
pos_profile = get_pos_profile(self.company) or {}
|
||||||
|
if not pos_profile:
|
||||||
|
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
|
||||||
self.pos_profile = pos_profile.get('name')
|
self.pos_profile = pos_profile.get('name')
|
||||||
|
|
||||||
profile = {}
|
profile = {}
|
||||||
@@ -294,14 +311,21 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.set(fieldname, profile.get(fieldname))
|
self.set(fieldname, profile.get(fieldname))
|
||||||
|
|
||||||
if self.customer:
|
if self.customer:
|
||||||
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
customer_price_list, customer_group, customer_currency = frappe.db.get_value(
|
||||||
|
"Customer", self.customer, ['default_price_list', 'customer_group', 'default_currency']
|
||||||
|
)
|
||||||
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
||||||
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
|
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
|
||||||
|
if customer_currency != profile.get('currency'):
|
||||||
|
self.set('currency', customer_currency)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
selling_price_list = profile.get('selling_price_list')
|
selling_price_list = profile.get('selling_price_list')
|
||||||
|
|
||||||
if selling_price_list:
|
if selling_price_list:
|
||||||
self.set('selling_price_list', selling_price_list)
|
self.set('selling_price_list', selling_price_list)
|
||||||
|
if customer_currency != profile.get('currency'):
|
||||||
|
self.set('currency', customer_currency)
|
||||||
|
|
||||||
# set pos values in items
|
# set pos values in items
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
@@ -361,22 +385,48 @@ class POSInvoice(SalesInvoice):
|
|||||||
if not self.contact_mobile:
|
if not self.contact_mobile:
|
||||||
frappe.throw(_("Please enter the phone number first"))
|
frappe.throw(_("Please enter the phone number first"))
|
||||||
|
|
||||||
payment_gateway = frappe.db.get_value("Payment Gateway Account", {
|
pay_req = self.get_existing_payment_request(pay)
|
||||||
"payment_account": pay.account,
|
if not pay_req:
|
||||||
})
|
pay_req = self.get_new_payment_request(pay)
|
||||||
record = {
|
pay_req.submit()
|
||||||
"payment_gateway": payment_gateway,
|
else:
|
||||||
"dt": "POS Invoice",
|
pay_req.request_phone_payment()
|
||||||
"dn": self.name,
|
|
||||||
"payment_request_type": "Inward",
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": self.customer,
|
|
||||||
"mode_of_payment": pay.mode_of_payment,
|
|
||||||
"recipient_id": self.contact_mobile,
|
|
||||||
"submit_doc": True
|
|
||||||
}
|
|
||||||
|
|
||||||
return make_payment_request(**record)
|
return pay_req
|
||||||
|
|
||||||
|
def get_new_payment_request(self, mop):
|
||||||
|
payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
|
||||||
|
"payment_account": mop.account,
|
||||||
|
}, ["name"])
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"dt": "POS Invoice",
|
||||||
|
"dn": self.name,
|
||||||
|
"recipient_id": self.contact_mobile,
|
||||||
|
"mode_of_payment": mop.mode_of_payment,
|
||||||
|
"payment_gateway_account": payment_gateway_account,
|
||||||
|
"payment_request_type": "Inward",
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": self.customer,
|
||||||
|
"return_doc": True
|
||||||
|
}
|
||||||
|
return make_payment_request(**args)
|
||||||
|
|
||||||
|
def get_existing_payment_request(self, pay):
|
||||||
|
payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
|
||||||
|
"payment_account": pay.account,
|
||||||
|
}, ["name"])
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'doctype': 'Payment Request',
|
||||||
|
'reference_doctype': 'POS Invoice',
|
||||||
|
'reference_name': self.name,
|
||||||
|
'payment_gateway_account': payment_gateway_account,
|
||||||
|
'email_to': self.contact_mobile
|
||||||
|
}
|
||||||
|
pr = frappe.db.exists(args)
|
||||||
|
if pr:
|
||||||
|
return frappe.get_doc('Payment Request', pr[0][0])
|
||||||
|
|
||||||
def add_return_modes(doc, pos_profile):
|
def add_return_modes(doc, pos_profile):
|
||||||
def append_payment(payment_mode):
|
def append_payment(payment_mode):
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
def test_merging_into_sales_invoice_with_discount(self):
|
def test_merging_into_sales_invoice_with_discount(self):
|
||||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||||
|
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
@@ -306,7 +306,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
merge_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
@@ -315,7 +315,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
||||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||||
|
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
@@ -348,7 +348,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
merge_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
@@ -357,7 +357,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
def test_merging_with_validate_selling_price(self):
|
def test_merging_with_validate_selling_price(self):
|
||||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||||
|
|
||||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||||
@@ -393,7 +393,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
merge_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
pos_inv2.load_from_db()
|
pos_inv2.load_from_db()
|
||||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"customer",
|
"customer",
|
||||||
|
"column_break_3",
|
||||||
|
"pos_closing_entry",
|
||||||
"section_break_3",
|
"section_break_3",
|
||||||
"pos_invoices",
|
"pos_invoices",
|
||||||
"references_section",
|
"references_section",
|
||||||
@@ -76,11 +78,22 @@
|
|||||||
"label": "Consolidated Credit Note",
|
"label": "Consolidated Credit Note",
|
||||||
"options": "Sales Invoice",
|
"options": "Sales Invoice",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pos_closing_entry",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "POS Closing Entry",
|
||||||
|
"options": "POS Closing Entry"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:08:41.317100",
|
"modified": "2020-12-01 11:53:57.267579",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice Merge Log",
|
"name": "POS Invoice Merge Log",
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe.model.mapper import map_doc
|
|
||||||
from frappe.model import default_fields
|
from frappe.model import default_fields
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import flt, getdate, nowdate
|
||||||
|
from frappe.utils.background_jobs import enqueue
|
||||||
|
from frappe.model.mapper import map_doc, map_child_doc
|
||||||
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||||
|
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
@@ -61,7 +64,13 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||||
|
|
||||||
self.update_pos_invoices(sales_invoice, credit_note)
|
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
|
|
||||||
|
self.update_pos_invoices(pos_invoice_docs)
|
||||||
|
self.cancel_linked_invoices()
|
||||||
|
|
||||||
def process_merging_into_sales_invoice(self, data):
|
def process_merging_into_sales_invoice(self, data):
|
||||||
sales_invoice = self.get_new_sales_invoice()
|
sales_invoice = self.get_new_sales_invoice()
|
||||||
@@ -83,7 +92,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
credit_note.is_consolidated = 1
|
credit_note.is_consolidated = 1
|
||||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||||
credit_note.return_against = self.consolidated_invoice
|
# credit_note.return_against = self.consolidated_invoice
|
||||||
credit_note.save()
|
credit_note.save()
|
||||||
credit_note.submit()
|
credit_note.submit()
|
||||||
self.consolidated_credit_note = credit_note.name
|
self.consolidated_credit_note = credit_note.name
|
||||||
@@ -111,7 +120,9 @@ class POSInvoiceMergeLog(Document):
|
|||||||
i.qty = i.qty + item.qty
|
i.qty = i.qty + item.qty
|
||||||
if not found:
|
if not found:
|
||||||
item.rate = item.net_rate
|
item.rate = item.net_rate
|
||||||
items.append(item)
|
item.price_list_rate = 0
|
||||||
|
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
||||||
|
items.append(si_item)
|
||||||
|
|
||||||
for tax in doc.get('taxes'):
|
for tax in doc.get('taxes'):
|
||||||
found = False
|
found = False
|
||||||
@@ -147,6 +158,8 @@ class POSInvoiceMergeLog(Document):
|
|||||||
invoice.set('taxes', taxes)
|
invoice.set('taxes', taxes)
|
||||||
invoice.additional_discount_percentage = 0
|
invoice.additional_discount_percentage = 0
|
||||||
invoice.discount_amount = 0.0
|
invoice.discount_amount = 0.0
|
||||||
|
invoice.taxes_and_charges = None
|
||||||
|
invoice.ignore_pricing_rule = 1
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
@@ -159,17 +172,21 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
return sales_invoice
|
return sales_invoice
|
||||||
|
|
||||||
def update_pos_invoices(self, sales_invoice, credit_note):
|
def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
|
||||||
for d in self.pos_invoices:
|
for doc in invoice_docs:
|
||||||
doc = frappe.get_doc('POS Invoice', d.pos_invoice)
|
doc.load_from_db()
|
||||||
if not doc.is_return:
|
doc.update({ 'consolidated_invoice': None if self.docstatus==2 else (credit_note if doc.is_return else sales_invoice) })
|
||||||
doc.update({'consolidated_invoice': sales_invoice})
|
|
||||||
else:
|
|
||||||
doc.update({'consolidated_invoice': credit_note})
|
|
||||||
doc.set_status(update=True)
|
doc.set_status(update=True)
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
def get_all_invoices():
|
def cancel_linked_invoices(self):
|
||||||
|
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
|
||||||
|
if not si_name: continue
|
||||||
|
si = frappe.get_doc('Sales Invoice', si_name)
|
||||||
|
si.flags.ignore_validate = True
|
||||||
|
si.cancel()
|
||||||
|
|
||||||
|
def get_all_unconsolidated_invoices():
|
||||||
filters = {
|
filters = {
|
||||||
'consolidated_invoice': [ 'in', [ '', None ]],
|
'consolidated_invoice': [ 'in', [ '', None ]],
|
||||||
'status': ['not in', ['Consolidated']],
|
'status': ['not in', ['Consolidated']],
|
||||||
@@ -180,7 +197,7 @@ def get_all_invoices():
|
|||||||
|
|
||||||
return pos_invoices
|
return pos_invoices
|
||||||
|
|
||||||
def get_invoices_customer_map(pos_invoices):
|
def get_invoice_customer_map(pos_invoices):
|
||||||
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] }
|
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] }
|
||||||
pos_invoice_customer_map = {}
|
pos_invoice_customer_map = {}
|
||||||
for invoice in pos_invoices:
|
for invoice in pos_invoices:
|
||||||
@@ -190,20 +207,82 @@ def get_invoices_customer_map(pos_invoices):
|
|||||||
|
|
||||||
return pos_invoice_customer_map
|
return pos_invoice_customer_map
|
||||||
|
|
||||||
def merge_pos_invoices(pos_invoices=[]):
|
def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
|
||||||
if not pos_invoices:
|
invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices()
|
||||||
pos_invoices = get_all_invoices()
|
invoice_by_customer = get_invoice_customer_map(invoices)
|
||||||
|
|
||||||
pos_invoice_map = get_invoices_customer_map(pos_invoices)
|
if len(invoices) >= 5 and closing_entry:
|
||||||
create_merge_logs(pos_invoice_map)
|
enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
|
||||||
|
closing_entry.set_status(update=True, status='Queued')
|
||||||
|
else:
|
||||||
|
create_merge_logs(invoice_by_customer, closing_entry)
|
||||||
|
|
||||||
def create_merge_logs(pos_invoice_customer_map):
|
def unconsolidate_pos_invoices(closing_entry):
|
||||||
for customer, invoices in iteritems(pos_invoice_customer_map):
|
merge_logs = frappe.get_all(
|
||||||
|
'POS Invoice Merge Log',
|
||||||
|
filters={ 'pos_closing_entry': closing_entry.name },
|
||||||
|
pluck='name'
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(merge_logs) >= 5:
|
||||||
|
enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
|
||||||
|
closing_entry.set_status(update=True, status='Queued')
|
||||||
|
else:
|
||||||
|
cancel_merge_logs(merge_logs, closing_entry)
|
||||||
|
|
||||||
|
def create_merge_logs(invoice_by_customer, closing_entry={}):
|
||||||
|
for customer, invoices in iteritems(invoice_by_customer):
|
||||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||||
merge_log.posting_date = getdate(nowdate())
|
merge_log.posting_date = getdate(nowdate())
|
||||||
merge_log.customer = customer
|
merge_log.customer = customer
|
||||||
|
merge_log.pos_closing_entry = closing_entry.get('name', None)
|
||||||
|
|
||||||
merge_log.set('pos_invoices', invoices)
|
merge_log.set('pos_invoices', invoices)
|
||||||
merge_log.save(ignore_permissions=True)
|
merge_log.save(ignore_permissions=True)
|
||||||
merge_log.submit()
|
merge_log.submit()
|
||||||
|
|
||||||
|
if closing_entry:
|
||||||
|
closing_entry.set_status(update=True, status='Submitted')
|
||||||
|
closing_entry.update_opening_entry()
|
||||||
|
|
||||||
|
def cancel_merge_logs(merge_logs, closing_entry={}):
|
||||||
|
for log in merge_logs:
|
||||||
|
merge_log = frappe.get_doc('POS Invoice Merge Log', log)
|
||||||
|
merge_log.flags.ignore_permissions = True
|
||||||
|
merge_log.cancel()
|
||||||
|
|
||||||
|
if closing_entry:
|
||||||
|
closing_entry.set_status(update=True, status='Cancelled')
|
||||||
|
closing_entry.update_opening_entry(for_cancel=True)
|
||||||
|
|
||||||
|
def enqueue_job(job, invoice_by_customer, closing_entry):
|
||||||
|
check_scheduler_status()
|
||||||
|
|
||||||
|
job_name = closing_entry.get("name")
|
||||||
|
if not job_already_enqueued(job_name):
|
||||||
|
enqueue(
|
||||||
|
job,
|
||||||
|
queue="long",
|
||||||
|
timeout=10000,
|
||||||
|
event="processing_merge_logs",
|
||||||
|
job_name=job_name,
|
||||||
|
closing_entry=closing_entry,
|
||||||
|
invoice_by_customer=invoice_by_customer,
|
||||||
|
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||||
|
)
|
||||||
|
|
||||||
|
if job == create_merge_logs:
|
||||||
|
msg = _('POS Invoices will be consolidated in a background process')
|
||||||
|
else:
|
||||||
|
msg = _('POS Invoices will be unconsolidated in a background process')
|
||||||
|
|
||||||
|
frappe.msgprint(msg, alert=1)
|
||||||
|
|
||||||
|
def check_scheduler_status():
|
||||||
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
|
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
|
def job_already_enqueued(job_name):
|
||||||
|
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
||||||
|
if job_name in enqueued_jobs:
|
||||||
|
return True
|
||||||
@@ -7,7 +7,7 @@ import frappe
|
|||||||
import unittest
|
import unittest
|
||||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
|
|
||||||
class TestPOSInvoiceMergeLog(unittest.TestCase):
|
class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||||
@@ -34,7 +34,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
pos_inv3.submit()
|
pos_inv3.submit()
|
||||||
|
|
||||||
merge_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||||
@@ -79,7 +79,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
|||||||
pos_inv_cn.paid_amount = -300
|
pos_inv_cn.paid_amount = -300
|
||||||
pos_inv_cn.submit()
|
pos_inv_cn.submit()
|
||||||
|
|
||||||
merge_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, get_link_to_form
|
from frappe.utils import cint, get_link_to_form
|
||||||
from frappe.model.document import Document
|
|
||||||
from erpnext.controllers.status_updater import StatusUpdater
|
from erpnext.controllers.status_updater import StatusUpdater
|
||||||
|
|
||||||
class POSOpeningEntry(StatusUpdater):
|
class POSOpeningEntry(StatusUpdater):
|
||||||
@@ -25,10 +24,11 @@ class POSOpeningEntry(StatusUpdater):
|
|||||||
def validate_payment_method_account(self):
|
def validate_payment_method_account(self):
|
||||||
invalid_modes = []
|
invalid_modes = []
|
||||||
for d in self.balance_details:
|
for d in self.balance_details:
|
||||||
account = frappe.db.get_value("Mode of Payment Account",
|
if d.mode_of_payment:
|
||||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
account = frappe.db.get_value("Mode of Payment Account",
|
||||||
if not account:
|
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
||||||
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
if not account:
|
||||||
|
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||||
|
|
||||||
if invalid_modes:
|
if invalid_modes:
|
||||||
if invalid_modes == 1:
|
if invalid_modes == 1:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
frappe.listview_settings['POS Opening Entry'] = {
|
frappe.listview_settings['POS Opening Entry'] = {
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
var status_color = {
|
var status_color = {
|
||||||
"Draft": "grey",
|
"Draft": "red",
|
||||||
"Open": "orange",
|
"Open": "orange",
|
||||||
"Closed": "green",
|
"Closed": "green",
|
||||||
"Cancelled": "red"
|
"Cancelled": "red"
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
@@ -67,6 +69,7 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
|
|
||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
frm.trigger("toggle_display_account_head");
|
frm.trigger("toggle_display_account_head");
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle_display_account_head: function(frm) {
|
toggle_display_account_head: function(frm) {
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
"company",
|
"company",
|
||||||
"country",
|
"country",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"update_stock",
|
|
||||||
"ignore_pricing_rule",
|
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"campaign",
|
"campaign",
|
||||||
"company_address",
|
"company_address",
|
||||||
@@ -25,8 +23,14 @@
|
|||||||
"hide_images",
|
"hide_images",
|
||||||
"hide_unavailable_items",
|
"hide_unavailable_items",
|
||||||
"auto_add_item_to_cart",
|
"auto_add_item_to_cart",
|
||||||
"item_groups",
|
|
||||||
"column_break_16",
|
"column_break_16",
|
||||||
|
"update_stock",
|
||||||
|
"ignore_pricing_rule",
|
||||||
|
"allow_rate_change",
|
||||||
|
"allow_discount_change",
|
||||||
|
"section_break_23",
|
||||||
|
"item_groups",
|
||||||
|
"column_break_25",
|
||||||
"customer_groups",
|
"customer_groups",
|
||||||
"section_break_16",
|
"section_break_16",
|
||||||
"print_format",
|
"print_format",
|
||||||
@@ -309,6 +313,7 @@
|
|||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "update_stock",
|
"fieldname": "update_stock",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
"label": "Update Stock",
|
"label": "Update Stock",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -329,13 +334,34 @@
|
|||||||
"fieldname": "auto_add_item_to_cart",
|
"fieldname": "auto_add_item_to_cart",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Automatically Add Filtered Item To Cart"
|
"label": "Automatically Add Filtered Item To Cart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_rate_change",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow User to Edit Rate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_discount_change",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow User to Edit Discount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "section_break_23",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_25",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-20 13:59:28.877572",
|
"modified": "2021-01-06 14:42:41.713864",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Profile",
|
"name": "POS Profile",
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
self.assertEqual(details.get("discount_percentage"), 10)
|
self.assertEqual(details.get("discount_percentage"), 10)
|
||||||
|
|
||||||
prule = frappe.get_doc(test_record.copy())
|
prule = frappe.get_doc(test_record.copy())
|
||||||
|
prule.priority = 1
|
||||||
prule.applicable_for = "Customer"
|
prule.applicable_for = "Customer"
|
||||||
prule.title = "_Test Pricing Rule for Customer"
|
prule.title = "_Test Pricing Rule for Customer"
|
||||||
self.assertRaises(MandatoryError, prule.insert)
|
self.assertRaises(MandatoryError, prule.insert)
|
||||||
@@ -261,6 +262,7 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
"rate_or_discount": "Discount Percentage",
|
"rate_or_discount": "Discount Percentage",
|
||||||
"rate": 0,
|
"rate": 0,
|
||||||
"discount_percentage": 17.5,
|
"discount_percentage": 17.5,
|
||||||
|
"priority": 1,
|
||||||
"company": "_Test Company"
|
"company": "_Test Company"
|
||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
@@ -557,6 +559,7 @@ def make_pricing_rule(**args):
|
|||||||
"rate": args.rate or 0.0,
|
"rate": args.rate or 0.0,
|
||||||
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
||||||
"condition": args.condition or '',
|
"condition": args.condition or '',
|
||||||
|
"priority": 1,
|
||||||
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ def get_pricing_rules(args, doc=None):
|
|||||||
if not pricing_rules: return []
|
if not pricing_rules: return []
|
||||||
|
|
||||||
if apply_multiple_pricing_rules(pricing_rules):
|
if apply_multiple_pricing_rules(pricing_rules):
|
||||||
pricing_rules = sorted_by_priority(pricing_rules)
|
pricing_rules = sorted_by_priority(pricing_rules, args, doc)
|
||||||
for pricing_rule in pricing_rules:
|
for pricing_rule in pricing_rules:
|
||||||
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
if isinstance(pricing_rule, list):
|
||||||
if pricing_rule:
|
rules.extend(pricing_rule)
|
||||||
|
else:
|
||||||
rules.append(pricing_rule)
|
rules.append(pricing_rule)
|
||||||
else:
|
else:
|
||||||
pricing_rule = filter_pricing_rules(args, pricing_rules, doc)
|
pricing_rule = filter_pricing_rules(args, pricing_rules, doc)
|
||||||
@@ -53,17 +54,22 @@ def get_pricing_rules(args, doc=None):
|
|||||||
|
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
def sorted_by_priority(pricing_rules):
|
def sorted_by_priority(pricing_rules, args, doc=None):
|
||||||
# If more than one pricing rules, then sort by priority
|
# If more than one pricing rules, then sort by priority
|
||||||
pricing_rules_list = []
|
pricing_rules_list = []
|
||||||
pricing_rule_dict = {}
|
pricing_rule_dict = {}
|
||||||
for pricing_rule in pricing_rules:
|
|
||||||
if not pricing_rule.get("priority"): continue
|
|
||||||
|
|
||||||
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
|
for pricing_rule in pricing_rules:
|
||||||
|
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
||||||
|
if pricing_rule:
|
||||||
|
if not pricing_rule.get('priority'):
|
||||||
|
pricing_rule['priority'] = 1
|
||||||
|
|
||||||
|
if pricing_rule.get('apply_multiple_pricing_rules'):
|
||||||
|
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
|
||||||
|
|
||||||
for key in sorted(pricing_rule_dict):
|
for key in sorted(pricing_rule_dict):
|
||||||
pricing_rules_list.append(pricing_rule_dict.get(key))
|
pricing_rules_list.extend(pricing_rule_dict.get(key))
|
||||||
|
|
||||||
return pricing_rules_list or pricing_rules
|
return pricing_rules_list or pricing_rules
|
||||||
|
|
||||||
@@ -144,9 +150,7 @@ def apply_multiple_pricing_rules(pricing_rules):
|
|||||||
|
|
||||||
if not apply_multiple_rule: return False
|
if not apply_multiple_rule: return False
|
||||||
|
|
||||||
if (apply_multiple_rule
|
return True
|
||||||
and len(apply_multiple_rule) == len(pricing_rules)):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
||||||
field = frappe.scrub(parenttype)
|
field = frappe.scrub(parenttype)
|
||||||
@@ -264,18 +268,6 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
|||||||
if max_priority:
|
if max_priority:
|
||||||
pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
|
pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
|
||||||
|
|
||||||
# apply internal priority
|
|
||||||
all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory",
|
|
||||||
"supplier", "supplier_group", "campaign", "sales_partner", "variant_of"]
|
|
||||||
|
|
||||||
if len(pricing_rules) > 1:
|
|
||||||
for field_set in [["item_code", "variant_of", "item_group", "brand"],
|
|
||||||
["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]:
|
|
||||||
remaining_fields = list(set(all_fields) - set(field_set))
|
|
||||||
if if_all_rules_same(pricing_rules, remaining_fields):
|
|
||||||
pricing_rules = apply_internal_priority(pricing_rules, field_set, args)
|
|
||||||
break
|
|
||||||
|
|
||||||
if pricing_rules and not isinstance(pricing_rules, list):
|
if pricing_rules and not isinstance(pricing_rules, list):
|
||||||
pricing_rules = list(pricing_rules)
|
pricing_rules = list(pricing_rules)
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function() {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
onload: function() {
|
onload: function() {
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
@@ -41,6 +46,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
||||||
this.frm.trigger('supplier');
|
this.frm.trigger('supplier');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(doc) {
|
refresh: function(doc) {
|
||||||
@@ -268,8 +275,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
|
|
||||||
supplier: function() {
|
supplier: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
if(this.frm.updating_party_details)
|
|
||||||
|
// Do not update if inter company reference is there as the details will already be updated
|
||||||
|
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
|
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
|
||||||
{
|
{
|
||||||
posting_date: this.frm.doc.posting_date,
|
posting_date: this.frm.doc.posting_date,
|
||||||
@@ -498,7 +508,7 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
|||||||
frappe.ui.form.on("Purchase Invoice", {
|
frappe.ui.form.on("Purchase Invoice", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Purchase Invoice': 'Debit Note',
|
'Purchase Invoice': 'Return / Debit Note',
|
||||||
'Payment Entry': 'Payment'
|
'Payment Entry': 'Payment'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,15 +521,6 @@ frappe.ui.form.on("Purchase Invoice", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company,
|
|
||||||
is_group: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
|
|||||||
@@ -57,8 +57,9 @@
|
|||||||
"set_warehouse",
|
"set_warehouse",
|
||||||
"rejected_warehouse",
|
"rejected_warehouse",
|
||||||
"col_break_warehouse",
|
"col_break_warehouse",
|
||||||
"is_subcontracted",
|
"set_from_warehouse",
|
||||||
"supplier_warehouse",
|
"supplier_warehouse",
|
||||||
|
"is_subcontracted",
|
||||||
"items_section",
|
"items_section",
|
||||||
"update_stock",
|
"update_stock",
|
||||||
"scan_barcode",
|
"scan_barcode",
|
||||||
@@ -515,6 +516,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "update_stock",
|
"depends_on": "update_stock",
|
||||||
|
"description": "Sets 'Accepted Warehouse' in each row of the items table.",
|
||||||
"fieldname": "set_warehouse",
|
"fieldname": "set_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Set Accepted Warehouse",
|
"label": "Set Accepted Warehouse",
|
||||||
@@ -543,17 +545,6 @@
|
|||||||
"options": "No\nYes",
|
"options": "No\nYes",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
|
|
||||||
"fieldname": "supplier_warehouse",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Supplier Warehouse",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Warehouse",
|
|
||||||
"print_hide": 1,
|
|
||||||
"print_width": "50px",
|
|
||||||
"width": "50px"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "items_section",
|
"fieldname": "items_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@@ -1232,7 +1223,9 @@
|
|||||||
"fieldname": "inter_company_invoice_reference",
|
"fieldname": "inter_company_invoice_reference",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Inter Company Invoice Reference",
|
"label": "Inter Company Invoice Reference",
|
||||||
|
"no_copy": 1,
|
||||||
"options": "Sales Invoice",
|
"options": "Sales Invoice",
|
||||||
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1356,13 +1349,36 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Represents Company",
|
"label": "Represents Company",
|
||||||
"options": "Company"
|
"options": "Company"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
|
||||||
|
"description": "Sets 'From Warehouse' in each row of the items table.",
|
||||||
|
"fieldname": "set_from_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Set From Warehouse",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Warehouse",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_width": "50px",
|
||||||
|
"width": "50px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"",
|
||||||
|
"fieldname": "supplier_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Supplier Warehouse",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Warehouse",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_width": "50px",
|
||||||
|
"width": "50px"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-11 12:46:12.796378",
|
"modified": "2021-03-09 21:56:28.748582",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -480,7 +480,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||||
|
|
||||||
if grand_total and not self.is_internal_transfer():
|
if grand_total and not self.is_internal_transfer():
|
||||||
# Didnot use base_grand_total to book rounding loss gle
|
# Did not use base_grand_total to book rounding loss gle
|
||||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||||
self.precision("grand_total"))
|
self.precision("grand_total"))
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
@@ -511,8 +511,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
voucher_wise_stock_value = {}
|
voucher_wise_stock_value = {}
|
||||||
if self.update_stock:
|
if self.update_stock:
|
||||||
for d in frappe.get_all('Stock Ledger Entry',
|
for d in frappe.get_all('Stock Ledger Entry',
|
||||||
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
|
fields = ["voucher_detail_no", "stock_value_difference", "warehouse"], filters={'voucher_no': self.name}):
|
||||||
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
|
voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
|
||||||
|
|
||||||
valuation_tax_accounts = [d.account_head for d in self.get("taxes")
|
valuation_tax_accounts = [d.account_head for d in self.get("taxes")
|
||||||
if d.category in ('Valuation', 'Total and Valuation')
|
if d.category in ('Valuation', 'Total and Valuation')
|
||||||
@@ -563,16 +563,17 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
gl_entries.append(
|
if not self.is_internal_transfer():
|
||||||
self.get_gl_dict({
|
gl_entries.append(
|
||||||
"account": item.expense_account,
|
self.get_gl_dict({
|
||||||
"against": self.supplier,
|
"account": item.expense_account,
|
||||||
"debit": warehouse_debit_amount,
|
"against": self.supplier,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"debit": warehouse_debit_amount,
|
||||||
"cost_center": item.cost_center,
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"project": item.project or self.project
|
"cost_center": item.cost_center,
|
||||||
}, account_currency, item=item)
|
"project": item.project or self.project
|
||||||
)
|
}, account_currency, item=item)
|
||||||
|
)
|
||||||
|
|
||||||
# Amount added through landed-cost-voucher
|
# Amount added through landed-cost-voucher
|
||||||
if landed_cost_entries:
|
if landed_cost_entries:
|
||||||
@@ -582,7 +583,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"against": item.expense_account,
|
"against": item.expense_account,
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"credit": flt(amount),
|
"credit": flt(amount["base_amount"]),
|
||||||
|
"credit_in_account_currency": flt(amount["amount"]),
|
||||||
"project": item.project or self.project
|
"project": item.project or self.project
|
||||||
}, item=item))
|
}, item=item))
|
||||||
|
|
||||||
@@ -624,13 +626,14 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if expense_booked_in_pr:
|
if expense_booked_in_pr:
|
||||||
expense_account = service_received_but_not_billed_account
|
expense_account = service_received_but_not_billed_account
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
if not self.is_internal_transfer():
|
||||||
"account": expense_account,
|
gl_entries.append(self.get_gl_dict({
|
||||||
"against": self.supplier,
|
"account": expense_account,
|
||||||
"debit": amount,
|
"against": self.supplier,
|
||||||
"cost_center": item.cost_center,
|
"debit": amount,
|
||||||
"project": item.project or self.project
|
"cost_center": item.cost_center,
|
||||||
}, account_currency, item=item))
|
"project": item.project or self.project
|
||||||
|
}, account_currency, item=item))
|
||||||
|
|
||||||
# If asset is bought through this document and not linked to PR
|
# If asset is bought through this document and not linked to PR
|
||||||
if self.update_stock and item.landed_cost_voucher_amount:
|
if self.update_stock and item.landed_cost_voucher_amount:
|
||||||
@@ -795,10 +798,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
# Stock ledger value is not matching with the warehouse amount
|
# Stock ledger value is not matching with the warehouse amount
|
||||||
if (self.update_stock and voucher_wise_stock_value.get(item.name) and
|
if (self.update_stock and voucher_wise_stock_value.get(item.name) and
|
||||||
warehouse_debit_amount != flt(voucher_wise_stock_value.get(item.name), net_amt_precision)):
|
warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)):
|
||||||
|
|
||||||
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
|
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
|
||||||
stock_amount = flt(voucher_wise_stock_value.get(item.name), net_amt_precision)
|
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
|
||||||
stock_adjustment_amt = warehouse_debit_amount - stock_amount
|
stock_adjustment_amt = warehouse_debit_amount - stock_amount
|
||||||
|
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
|
|||||||
@@ -426,26 +426,31 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_total_purchase_cost_for_project(self):
|
def test_total_purchase_cost_for_project(self):
|
||||||
make_project({'project_name':'_Test Project'})
|
if not frappe.db.exists("Project", {"project_name": "_Test Project for Purchase"}):
|
||||||
|
project = make_project({'project_name':'_Test Project for Purchase'})
|
||||||
|
else:
|
||||||
|
project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
|
||||||
|
|
||||||
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
|
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
|
||||||
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
|
from `tabPurchase Invoice Item`
|
||||||
|
where project = '{0}'
|
||||||
|
and docstatus=1""".format(project.name))
|
||||||
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
|
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
|
||||||
|
|
||||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project="_Test Project")
|
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||||
existing_purchase_cost + 15000)
|
existing_purchase_cost + 15000)
|
||||||
|
|
||||||
pi1 = make_purchase_invoice(qty=10, project="_Test Project")
|
pi1 = make_purchase_invoice(qty=10, project=project.name)
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||||
existing_purchase_cost + 15500)
|
existing_purchase_cost + 15500)
|
||||||
|
|
||||||
pi1.cancel()
|
pi1.cancel()
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||||
existing_purchase_cost + 15000)
|
existing_purchase_cost + 15000)
|
||||||
|
|
||||||
pi.cancel()
|
pi.cancel()
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost)
|
||||||
|
|
||||||
def test_return_purchase_invoice_with_perpetual_inventory(self):
|
def test_return_purchase_invoice_with_perpetual_inventory(self):
|
||||||
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||||
@@ -860,17 +865,17 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
|
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
|
||||||
pi.items[0].project = item_project.project_name
|
pi.items[0].project = item_project.name
|
||||||
pi.project = project.project_name
|
pi.project = project.name
|
||||||
|
|
||||||
pi.submit()
|
pi.submit()
|
||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"Creditors - _TC": {
|
"Creditors - _TC": {
|
||||||
"project": project.project_name
|
"project": project.name
|
||||||
},
|
},
|
||||||
"_Test Account Cost for Goods Sold - _TC": {
|
"_Test Account Cost for Goods Sold - _TC": {
|
||||||
"project": item_project.project_name
|
"project": item_project.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2013-05-22 12:43:10",
|
"creation": "2013-05-22 12:43:10",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
"base_rate",
|
"base_rate",
|
||||||
"base_amount",
|
"base_amount",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
|
"stock_uom_rate",
|
||||||
"is_free_item",
|
"is_free_item",
|
||||||
"section_break_22",
|
"section_break_22",
|
||||||
"net_rate",
|
"net_rate",
|
||||||
@@ -87,6 +89,7 @@
|
|||||||
"po_detail",
|
"po_detail",
|
||||||
"purchase_receipt",
|
"purchase_receipt",
|
||||||
"pr_detail",
|
"pr_detail",
|
||||||
|
"sales_invoice_item",
|
||||||
"item_weight_details",
|
"item_weight_details",
|
||||||
"weight_per_unit",
|
"weight_per_unit",
|
||||||
"total_weight",
|
"total_weight",
|
||||||
@@ -553,8 +556,8 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Brand",
|
"label": "Brand",
|
||||||
"print_hide": 1,
|
"options": "Brand",
|
||||||
"options": "Brand"
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "item_code.item_group",
|
"fetch_from": "item_code.item_group",
|
||||||
@@ -562,9 +565,9 @@
|
|||||||
"fieldname": "item_group",
|
"fieldname": "item_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Item Group",
|
"label": "Item Group",
|
||||||
|
"options": "Item Group",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"options": "Item Group"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges",
|
"description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges",
|
||||||
@@ -759,10 +762,11 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
|
||||||
"fieldname": "from_warehouse",
|
"fieldname": "from_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"label": "Supplier Warehouse",
|
"label": "From Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -779,11 +783,28 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.uom != doc.stock_uom",
|
||||||
|
"fieldname": "stock_uom_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Rate of Stock UOM",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sales_invoice_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Sales Invoice Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2020-08-20 11:48:01.398356",
|
"links": [],
|
||||||
|
"modified": "2021-01-30 21:43:21.488258",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
|||||||
@@ -5,18 +5,22 @@
|
|||||||
cur_frm.pformat.print_heading = 'Invoice';
|
cur_frm.pformat.print_heading = 'Invoice';
|
||||||
|
|
||||||
{% include 'erpnext/selling/sales_common.js' %};
|
{% include 'erpnext/selling/sales_common.js' %};
|
||||||
|
|
||||||
|
|
||||||
frappe.provide("erpnext.accounts");
|
frappe.provide("erpnext.accounts");
|
||||||
|
|
||||||
|
|
||||||
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
|
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
|
||||||
setup: function(doc) {
|
setup: function(doc) {
|
||||||
this.setup_posting_date_time_check();
|
this.setup_posting_date_time_check();
|
||||||
this._super(doc);
|
this._super(doc);
|
||||||
},
|
},
|
||||||
|
company: function() {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||||
|
},
|
||||||
onload: function() {
|
onload: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice'];
|
||||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||||
// show debit_to in print format
|
// show debit_to in print format
|
||||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||||
@@ -33,6 +37,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
me.frm.refresh_fields();
|
me.frm.refresh_fields();
|
||||||
}
|
}
|
||||||
erpnext.queries.setup_warehouse_query(this.frm);
|
erpnext.queries.setup_warehouse_query(this.frm);
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(doc, dt, dn) {
|
refresh: function(doc, dt, dn) {
|
||||||
@@ -126,16 +131,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
|
|
||||||
this.set_default_print_format();
|
this.set_default_print_format();
|
||||||
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
|
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
|
||||||
frappe.model.with_doc("Customer", me.frm.doc.customer, function() {
|
let internal = me.frm.doc.is_internal_customer;
|
||||||
var customer = frappe.model.get_doc("Customer", me.frm.doc.customer);
|
if (internal) {
|
||||||
var internal = customer.is_internal_customer;
|
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Invoice" :
|
||||||
var disabled = customer.disabled;
|
"Inter Company Purchase Invoice";
|
||||||
if (internal == 1 && disabled == 0) {
|
|
||||||
me.frm.add_custom_button("Inter Company Invoice", function() {
|
me.frm.add_custom_button(button_label, function() {
|
||||||
me.make_inter_company_invoice();
|
me.make_inter_company_invoice();
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -571,15 +575,6 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company,
|
|
||||||
is_group: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_query("unrealized_profit_loss_account", function() {
|
frm.set_query("unrealized_profit_loss_account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -592,7 +587,7 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
|
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Delivery Note': 'Delivery',
|
'Delivery Note': 'Delivery',
|
||||||
'Sales Invoice': 'Sales Return',
|
'Sales Invoice': 'Return / Credit Note',
|
||||||
'Payment Request': 'Payment Request',
|
'Payment Request': 'Payment Request',
|
||||||
'Payment Entry': 'Payment'
|
'Payment Entry': 'Payment'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,6 +60,8 @@
|
|||||||
"ignore_pricing_rule",
|
"ignore_pricing_rule",
|
||||||
"sec_warehouse",
|
"sec_warehouse",
|
||||||
"set_warehouse",
|
"set_warehouse",
|
||||||
|
"column_break_55",
|
||||||
|
"set_target_warehouse",
|
||||||
"items_section",
|
"items_section",
|
||||||
"update_stock",
|
"update_stock",
|
||||||
"scan_barcode",
|
"scan_barcode",
|
||||||
@@ -1969,13 +1971,31 @@
|
|||||||
"label": "Represents Company",
|
"label": "Represents Company",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_55",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.is_internal_customer && doc.update_stock",
|
||||||
|
"fieldname": "set_target_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Set Target Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 181,
|
"idx": 181,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2020-12-11 12:48:31.769958",
|
{
|
||||||
|
"custom": 1,
|
||||||
|
"group": "Reference",
|
||||||
|
"link_doctype": "POS Invoice",
|
||||||
|
"link_fieldname": "consolidated_invoice"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2021-01-12 12:16:15.192520",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
|
from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
|
||||||
from frappe import _, msgprint, throw
|
from frappe import _, msgprint, throw
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date, get_party_details
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from erpnext.controllers.selling_controller import SellingController
|
from erpnext.controllers.selling_controller import SellingController
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
@@ -21,6 +21,8 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
|
|||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
||||||
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
|
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
|
||||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||||
|
from frappe.model.utils import get_fetch_values
|
||||||
|
from frappe.contacts.doctype.address.address import get_address_display
|
||||||
|
|
||||||
from erpnext.healthcare.utils import manage_invoice_submit_cancel
|
from erpnext.healthcare.utils import manage_invoice_submit_cancel
|
||||||
|
|
||||||
@@ -180,6 +182,9 @@ class SalesInvoice(SellingController):
|
|||||||
# this sequence because outstanding may get -ve
|
# this sequence because outstanding may get -ve
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
|
if self.update_stock == 1:
|
||||||
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
@@ -231,7 +236,25 @@ class SalesInvoice(SellingController):
|
|||||||
if len(self.payments) == 0 and self.is_pos:
|
if len(self.payments) == 0 and self.is_pos:
|
||||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||||
|
|
||||||
|
def check_if_consolidated_invoice(self):
|
||||||
|
# since POS Invoice extends Sales Invoice, we explicitly check if doctype is Sales Invoice
|
||||||
|
if self.doctype == "Sales Invoice" and self.is_consolidated:
|
||||||
|
invoice_or_credit_note = "consolidated_credit_note" if self.is_return else "consolidated_invoice"
|
||||||
|
pos_closing_entry = frappe.get_all(
|
||||||
|
"POS Invoice Merge Log",
|
||||||
|
filters={ invoice_or_credit_note: self.name },
|
||||||
|
pluck="pos_closing_entry"
|
||||||
|
)
|
||||||
|
if pos_closing_entry:
|
||||||
|
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format(
|
||||||
|
frappe.bold("Consolidated Sales Invoice"),
|
||||||
|
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
|
||||||
|
)
|
||||||
|
frappe.throw(msg, title=_("Not Allowed"))
|
||||||
|
|
||||||
def before_cancel(self):
|
def before_cancel(self):
|
||||||
|
self.check_if_consolidated_invoice()
|
||||||
|
|
||||||
super(SalesInvoice, self).before_cancel()
|
super(SalesInvoice, self).before_cancel()
|
||||||
self.update_time_sheet(None)
|
self.update_time_sheet(None)
|
||||||
|
|
||||||
@@ -433,7 +456,9 @@ class SalesInvoice(SellingController):
|
|||||||
if not for_validate and not self.customer:
|
if not for_validate and not self.customer:
|
||||||
self.customer = pos.customer
|
self.customer = pos.customer
|
||||||
|
|
||||||
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
if not for_validate:
|
||||||
|
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
||||||
|
|
||||||
if pos.get('account_for_change_amount'):
|
if pos.get('account_for_change_amount'):
|
||||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
self.account_for_change_amount = pos.get('account_for_change_amount')
|
||||||
|
|
||||||
@@ -549,7 +574,12 @@ class SalesInvoice(SellingController):
|
|||||||
self.against_income_account = ','.join(against_acc)
|
self.against_income_account = ','.join(against_acc)
|
||||||
|
|
||||||
def add_remarks(self):
|
def add_remarks(self):
|
||||||
if not self.remarks: self.remarks = 'No Remarks'
|
if not self.remarks:
|
||||||
|
if self.po_no and self.po_date:
|
||||||
|
self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
|
||||||
|
formatdate(self.po_date))
|
||||||
|
else:
|
||||||
|
self.remarks = _("No Remarks")
|
||||||
|
|
||||||
def validate_auto_set_posting_time(self):
|
def validate_auto_set_posting_time(self):
|
||||||
# Don't auto set the posting date and time if invoice is amended
|
# Don't auto set the posting date and time if invoice is amended
|
||||||
@@ -1529,7 +1559,7 @@ def validate_inter_company_transaction(doc, doctype):
|
|||||||
details = get_inter_company_details(doc, doctype)
|
details = get_inter_company_details(doc, doctype)
|
||||||
price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
|
price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
|
||||||
valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
|
valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
|
||||||
if not valid_price_list:
|
if not valid_price_list and not doc.is_internal_transfer():
|
||||||
frappe.throw(_("Selected Price List should have buying and selling fields checked."))
|
frappe.throw(_("Selected Price List should have buying and selling fields checked."))
|
||||||
|
|
||||||
party = details.get("party")
|
party = details.get("party")
|
||||||
@@ -1552,6 +1582,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||||
source_doc = frappe.get_doc(doctype, source_name)
|
source_doc = frappe.get_doc(doctype, source_name)
|
||||||
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
|
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
|
||||||
|
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
|
||||||
source_document_warehouse_field = 'target_warehouse'
|
source_document_warehouse_field = 'target_warehouse'
|
||||||
target_document_warehouse_field = 'from_warehouse'
|
target_document_warehouse_field = 'from_warehouse'
|
||||||
else:
|
else:
|
||||||
@@ -1565,6 +1596,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
|
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
|
set_purchase_references(target)
|
||||||
|
|
||||||
def update_details(source_doc, target_doc, source_parent):
|
def update_details(source_doc, target_doc, source_parent):
|
||||||
target_doc.inter_company_invoice_reference = source_doc.name
|
target_doc.inter_company_invoice_reference = source_doc.name
|
||||||
@@ -1572,19 +1604,38 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
|
currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
|
||||||
target_doc.company = details.get("company")
|
target_doc.company = details.get("company")
|
||||||
target_doc.supplier = details.get("party")
|
target_doc.supplier = details.get("party")
|
||||||
|
target_doc.is_internal_supplier = 1
|
||||||
|
target_doc.ignore_pricing_rule = 1
|
||||||
target_doc.buying_price_list = source_doc.selling_price_list
|
target_doc.buying_price_list = source_doc.selling_price_list
|
||||||
|
|
||||||
|
# Invert Addresses
|
||||||
|
update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
|
||||||
|
update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
|
||||||
|
|
||||||
if currency:
|
if currency:
|
||||||
target_doc.currency = currency
|
target_doc.currency = currency
|
||||||
|
|
||||||
|
update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
|
||||||
|
doctype=target_doc.doctype, party_address=target_doc.supplier_address,
|
||||||
|
company_address=target_doc.shipping_address)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
|
currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
|
||||||
target_doc.company = details.get("company")
|
target_doc.company = details.get("company")
|
||||||
target_doc.customer = details.get("party")
|
target_doc.customer = details.get("party")
|
||||||
target_doc.selling_price_list = source_doc.buying_price_list
|
target_doc.selling_price_list = source_doc.buying_price_list
|
||||||
|
|
||||||
|
update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
|
||||||
|
update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
|
||||||
|
update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
|
||||||
|
|
||||||
if currency:
|
if currency:
|
||||||
target_doc.currency = currency
|
target_doc.currency = currency
|
||||||
|
|
||||||
|
update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
|
||||||
|
doctype=target_doc.doctype, party_address=target_doc.customer_address,
|
||||||
|
company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
|
||||||
|
|
||||||
item_field_map = {
|
item_field_map = {
|
||||||
"doctype": target_doctype + " Item",
|
"doctype": target_doctype + " Item",
|
||||||
"field_no_map": [
|
"field_no_map": [
|
||||||
@@ -1592,25 +1643,33 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
"expense_account",
|
"expense_account",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"warehouse"
|
"warehouse"
|
||||||
]
|
],
|
||||||
|
"field_map": {
|
||||||
|
'rate': 'rate',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if source_doc.get('update_stock'):
|
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||||
item_field_map.update({
|
item_field_map["field_map"].update({
|
||||||
'field_map': {
|
"name": target_detail_field,
|
||||||
source_document_warehouse_field: target_document_warehouse_field,
|
|
||||||
'batch_no': 'batch_no',
|
|
||||||
'serial_no': 'serial_no'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if source_doc.get('update_stock'):
|
||||||
|
item_field_map["field_map"].update({
|
||||||
|
source_document_warehouse_field: target_document_warehouse_field,
|
||||||
|
'batch_no': 'batch_no',
|
||||||
|
'serial_no': 'serial_no'
|
||||||
|
})
|
||||||
|
|
||||||
doclist = get_mapped_doc(doctype, source_name, {
|
doclist = get_mapped_doc(doctype, source_name, {
|
||||||
doctype: {
|
doctype: {
|
||||||
"doctype": target_doctype,
|
"doctype": target_doctype,
|
||||||
"postprocess": update_details,
|
"postprocess": update_details,
|
||||||
|
"set_target_warehouse": "set_from_warehouse",
|
||||||
"field_no_map": [
|
"field_no_map": [
|
||||||
"taxes_and_charges"
|
"taxes_and_charges",
|
||||||
|
"set_warehouse",
|
||||||
|
"shipping_address"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
doctype +" Item": item_field_map
|
doctype +" Item": item_field_map
|
||||||
@@ -1619,6 +1678,110 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
|
|
||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
def set_purchase_references(doc):
|
||||||
|
# add internal PO or PR links if any
|
||||||
|
if doc.is_internal_transfer():
|
||||||
|
if doc.doctype == 'Purchase Receipt':
|
||||||
|
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
|
||||||
|
|
||||||
|
if so_item_map:
|
||||||
|
pd_item_map, parent_child_map, warehouse_map = \
|
||||||
|
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
|
||||||
|
|
||||||
|
update_pr_items(doc, so_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||||
|
|
||||||
|
elif doc.doctype == 'Purchase Invoice':
|
||||||
|
dn_item_map, so_item_map = get_sales_invoice_details(doc.inter_company_invoice_reference)
|
||||||
|
# First check for Purchase receipt
|
||||||
|
if list(dn_item_map.values()):
|
||||||
|
pd_item_map, parent_child_map, warehouse_map = \
|
||||||
|
get_pd_details('Purchase Receipt Item', dn_item_map, 'delivery_note_item')
|
||||||
|
|
||||||
|
update_pi_items(doc, 'pr_detail', 'purchase_receipt',
|
||||||
|
dn_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||||
|
|
||||||
|
if list(so_item_map.values()):
|
||||||
|
pd_item_map, parent_child_map, warehouse_map = \
|
||||||
|
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
|
||||||
|
|
||||||
|
update_pi_items(doc, 'po_detail', 'purchase_order',
|
||||||
|
so_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||||
|
|
||||||
|
def update_pi_items(doc, detail_field, parent_field, sales_item_map,
|
||||||
|
purchase_item_map, parent_child_map, warehouse_map):
|
||||||
|
for item in doc.get('items'):
|
||||||
|
item.set(detail_field, purchase_item_map.get(sales_item_map.get(item.sales_invoice_item)))
|
||||||
|
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
|
||||||
|
if doc.update_stock:
|
||||||
|
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
|
||||||
|
|
||||||
|
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
|
||||||
|
for item in doc.get('items'):
|
||||||
|
item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
|
||||||
|
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
|
||||||
|
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
|
||||||
|
|
||||||
|
def get_delivery_note_details(internal_reference):
|
||||||
|
so_item_map = {}
|
||||||
|
|
||||||
|
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
|
||||||
|
filters={'parent': internal_reference})
|
||||||
|
|
||||||
|
for d in si_item_details:
|
||||||
|
so_item_map.setdefault(d.name, d.so_detail)
|
||||||
|
|
||||||
|
return so_item_map
|
||||||
|
|
||||||
|
def get_sales_invoice_details(internal_reference):
|
||||||
|
dn_item_map = {}
|
||||||
|
so_item_map = {}
|
||||||
|
|
||||||
|
si_item_details = frappe.get_all('Sales Invoice Item', fields=['name', 'so_detail',
|
||||||
|
'dn_detail'], filters={'parent': internal_reference})
|
||||||
|
|
||||||
|
for d in si_item_details:
|
||||||
|
if d.dn_detail:
|
||||||
|
dn_item_map.setdefault(d.name, d.dn_detail)
|
||||||
|
if d.so_detail:
|
||||||
|
so_item_map.setdefault(d.name, d.so_detail)
|
||||||
|
|
||||||
|
return dn_item_map, so_item_map
|
||||||
|
|
||||||
|
def get_pd_details(doctype, sd_detail_map, sd_detail_field):
|
||||||
|
pd_item_map = {}
|
||||||
|
accepted_warehouse_map = {}
|
||||||
|
parent_child_map = {}
|
||||||
|
|
||||||
|
pd_item_details = frappe.get_all(doctype,
|
||||||
|
fields=[sd_detail_field, 'name', 'warehouse', 'parent'], filters={sd_detail_field: ('in', list(sd_detail_map.values()))})
|
||||||
|
|
||||||
|
for d in pd_item_details:
|
||||||
|
pd_item_map.setdefault(d.get(sd_detail_field), d.name)
|
||||||
|
parent_child_map.setdefault(d.get(sd_detail_field), d.parent)
|
||||||
|
accepted_warehouse_map.setdefault(d.get(sd_detail_field), d.warehouse)
|
||||||
|
|
||||||
|
return pd_item_map, parent_child_map, accepted_warehouse_map
|
||||||
|
|
||||||
|
def update_taxes(doc, party=None, party_type=None, company=None, doctype=None, party_address=None,
|
||||||
|
company_address=None, shipping_address_name=None, master_doctype=None):
|
||||||
|
# Update Party Details
|
||||||
|
party_details = get_party_details(party=party, party_type=party_type, company=company,
|
||||||
|
doctype=doctype, party_address=party_address, company_address=company_address,
|
||||||
|
shipping_address=shipping_address_name)
|
||||||
|
|
||||||
|
# Update taxes and charges if any
|
||||||
|
doc.taxes_and_charges = party_details.get('taxes_and_charges')
|
||||||
|
doc.set('taxes', party_details.get('taxes'))
|
||||||
|
|
||||||
|
def update_address(doc, address_field, address_display_field, address_name):
|
||||||
|
doc.set(address_field, address_name)
|
||||||
|
fetch_values = get_fetch_values(doc.doctype, address_field, address_name)
|
||||||
|
|
||||||
|
for key, value in fetch_values.items():
|
||||||
|
doc.set(key, value)
|
||||||
|
|
||||||
|
doc.set(address_display_field, get_address_display(doc.get(address_field)))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_loyalty_programs(customer):
|
def get_loyalty_programs(customer):
|
||||||
''' sets applicable loyalty program to the customer or returns a list of applicable programs '''
|
''' sets applicable loyalty program to the customer or returns a list of applicable programs '''
|
||||||
@@ -1694,6 +1857,7 @@ def get_mode_of_payment_info(mode_of_payment, company):
|
|||||||
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
|
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
|
||||||
(company, mode_of_payment), as_dict=1)
|
(company, mode_of_payment), as_dict=1)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def create_dunning(source_name, target_doc=None):
|
def create_dunning(source_name, target_doc=None):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
|
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from erpnext.regional.india.utils import get_ewb_data
|
|||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||||
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
|
|
||||||
class TestSalesInvoice(unittest.TestCase):
|
class TestSalesInvoice(unittest.TestCase):
|
||||||
def make(self):
|
def make(self):
|
||||||
@@ -1573,17 +1574,17 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
sales_invoice = create_sales_invoice(do_not_save=1)
|
sales_invoice = create_sales_invoice(do_not_save=1)
|
||||||
sales_invoice.items[0].project = item_project.project_name
|
sales_invoice.items[0].project = item_project.name
|
||||||
sales_invoice.project = project.project_name
|
sales_invoice.project = project.name
|
||||||
|
|
||||||
sales_invoice.submit()
|
sales_invoice.submit()
|
||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"Debtors - _TC": {
|
"Debtors - _TC": {
|
||||||
"project": project.project_name
|
"project": project.name
|
||||||
},
|
},
|
||||||
"Sales - _TC": {
|
"Sales - _TC": {
|
||||||
"project": item_project.project_name
|
"project": item_project.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1770,59 +1771,82 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(target_doc.company, "_Test Company 1")
|
self.assertEqual(target_doc.company, "_Test Company 1")
|
||||||
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
||||||
|
|
||||||
# def test_internal_transfer_gl_entry(self):
|
def test_internal_transfer_gl_entry(self):
|
||||||
# ## Create internal transfer account
|
## Create internal transfer account
|
||||||
# account = create_account(account_name="Unrealized Profit",
|
account = create_account(account_name="Unrealized Profit",
|
||||||
# parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
|
parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
|
||||||
|
|
||||||
# frappe.db.set_value('Company', '_Test Company with perpetual inventory',
|
frappe.db.set_value('Company', '_Test Company with perpetual inventory',
|
||||||
# 'unrealized_profit_loss_account', account)
|
'unrealized_profit_loss_account', account)
|
||||||
|
|
||||||
# customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
|
customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
|
||||||
# "_Test Company with perpetual inventory")
|
"_Test Company with perpetual inventory")
|
||||||
|
|
||||||
# create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
|
create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
|
||||||
# "_Test Company with perpetual inventory")
|
"_Test Company with perpetual inventory")
|
||||||
|
|
||||||
# si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
# company = "_Test Company with perpetual inventory",
|
company = "_Test Company with perpetual inventory",
|
||||||
# customer = customer,
|
customer = customer,
|
||||||
# debit_to = "Debtors - TCP1",
|
debit_to = "Debtors - TCP1",
|
||||||
# warehouse = "Stores - TCP1",
|
warehouse = "Stores - TCP1",
|
||||||
# income_account = "Sales - TCP1",
|
income_account = "Sales - TCP1",
|
||||||
# expense_account = "Cost of Goods Sold - TCP1",
|
expense_account = "Cost of Goods Sold - TCP1",
|
||||||
# cost_center = "Main - TCP1",
|
cost_center = "Main - TCP1",
|
||||||
# currency = "INR",
|
currency = "INR",
|
||||||
# do_not_save = 1
|
do_not_save = 1
|
||||||
# )
|
)
|
||||||
|
|
||||||
# si.selling_price_list = "_Test Price List Rest of the World"
|
si.selling_price_list = "_Test Price List Rest of the World"
|
||||||
# si.update_stock = 1
|
si.update_stock = 1
|
||||||
# si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
||||||
# add_taxes(si)
|
add_taxes(si)
|
||||||
# si.save()
|
si.save()
|
||||||
# si.submit()
|
|
||||||
|
|
||||||
# target_doc = make_inter_company_transaction("Sales Invoice", si.name)
|
rate = 0.0
|
||||||
# target_doc.company = '_Test Company with perpetual inventory'
|
for d in si.get('items'):
|
||||||
# target_doc.items[0].warehouse = 'Finished Goods - TCP1'
|
rate = get_incoming_rate({
|
||||||
# add_taxes(target_doc)
|
"item_code": d.item_code,
|
||||||
# target_doc.save()
|
"warehouse": d.warehouse,
|
||||||
# target_doc.submit()
|
"posting_date": si.posting_date,
|
||||||
|
"posting_time": si.posting_time,
|
||||||
|
"qty": -1 * flt(d.get('stock_qty')),
|
||||||
|
"serial_no": d.serial_no,
|
||||||
|
"company": si.company,
|
||||||
|
"voucher_type": 'Sales Invoice',
|
||||||
|
"voucher_no": si.name,
|
||||||
|
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||||
|
}, raise_error_if_no_rate=False)
|
||||||
|
|
||||||
# si_gl_entries = [
|
rate = flt(rate, 2)
|
||||||
# ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
|
|
||||||
# ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
|
si.submit()
|
||||||
|
|
||||||
# pi_gl_entries = [
|
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
|
||||||
# ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
|
target_doc.company = '_Test Company with perpetual inventory'
|
||||||
# ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
|
target_doc.items[0].warehouse = 'Finished Goods - TCP1'
|
||||||
# ]
|
add_taxes(target_doc)
|
||||||
|
target_doc.save()
|
||||||
|
target_doc.submit()
|
||||||
|
|
||||||
# check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
tax_amount = flt(rate * (12/100), 2)
|
||||||
|
si_gl_entries = [
|
||||||
|
["_Test Account Excise Duty - TCP1", 0.0, tax_amount, nowdate()],
|
||||||
|
["Unrealized Profit - TCP1", tax_amount, 0.0, nowdate()]
|
||||||
|
]
|
||||||
|
|
||||||
|
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
|
||||||
|
|
||||||
|
pi_gl_entries = [
|
||||||
|
["_Test Account Excise Duty - TCP1", tax_amount , 0.0, nowdate()],
|
||||||
|
["Unrealized Profit - TCP1", 0.0, tax_amount, nowdate()]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Sale and Purchase both should be at valuation rate
|
||||||
|
self.assertEqual(si.items[0].rate, rate)
|
||||||
|
self.assertEqual(target_doc.items[0].rate, rate)
|
||||||
|
|
||||||
|
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
||||||
|
|
||||||
def test_eway_bill_json(self):
|
def test_eway_bill_json(self):
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
@@ -1861,23 +1885,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
def test_einvoice_json(self):
|
def test_einvoice_json(self):
|
||||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||||
|
|
||||||
customer_gstin = '27AACCM7806M1Z3'
|
|
||||||
customer_gstin_dtls = {
|
|
||||||
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
|
|
||||||
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
|
|
||||||
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
|
||||||
}
|
|
||||||
company_gstin = '27AAECE4835E1ZR'
|
|
||||||
company_gstin_dtls = {
|
|
||||||
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
|
|
||||||
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
|
|
||||||
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
|
||||||
}
|
|
||||||
# set cache gstin details to avoid fetching details which will require connection to GSP servers
|
|
||||||
frappe.local.gstin_cache = {}
|
|
||||||
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
|
|
||||||
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
|
|
||||||
|
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
si.naming_series = 'INV-2020-.#####'
|
si.naming_series = 'INV-2020-.#####'
|
||||||
si.items = []
|
si.items = []
|
||||||
@@ -1885,8 +1892,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"item_code": "_Test Item",
|
"item_code": "_Test Item",
|
||||||
"uom": "Nos",
|
"uom": "Nos",
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
"qty": 2,
|
"qty": 2000,
|
||||||
"rate": 100,
|
"rate": 12,
|
||||||
"income_account": "Sales - _TC",
|
"income_account": "Sales - _TC",
|
||||||
"expense_account": "Cost of Goods Sold - _TC",
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
@@ -1895,31 +1902,52 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"item_code": "_Test Item 2",
|
"item_code": "_Test Item 2",
|
||||||
"uom": "Nos",
|
"uom": "Nos",
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
"qty": 4,
|
"qty": 420,
|
||||||
"rate": 150,
|
"rate": 15,
|
||||||
"income_account": "Sales - _TC",
|
"income_account": "Sales - _TC",
|
||||||
"expense_account": "Cost of Goods Sold - _TC",
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
})
|
})
|
||||||
|
si.discount_amount = 100
|
||||||
si.save()
|
si.save()
|
||||||
|
|
||||||
einvoice = make_einvoice(si)
|
einvoice = make_einvoice(si)
|
||||||
|
|
||||||
total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']])
|
total_item_ass_value = 0
|
||||||
total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']])
|
total_item_cgst_value = 0
|
||||||
total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']])
|
total_item_sgst_value = 0
|
||||||
total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']])
|
total_item_igst_value = 0
|
||||||
total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']])
|
total_item_value = 0
|
||||||
|
|
||||||
|
for item in einvoice['ItemList']:
|
||||||
|
total_item_ass_value += item['AssAmt']
|
||||||
|
total_item_cgst_value += item['CgstAmt']
|
||||||
|
total_item_sgst_value += item['SgstAmt']
|
||||||
|
total_item_igst_value += item['IgstAmt']
|
||||||
|
total_item_value += item['TotItemVal']
|
||||||
|
|
||||||
|
self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
|
||||||
|
self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
|
||||||
|
|
||||||
|
value_details = einvoice['ValDtls']
|
||||||
|
|
||||||
self.assertEqual(einvoice['Version'], '1.1')
|
self.assertEqual(einvoice['Version'], '1.1')
|
||||||
self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value)
|
self.assertEqual(value_details['AssVal'], total_item_ass_value)
|
||||||
self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value)
|
self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
|
||||||
self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value)
|
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
||||||
self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value)
|
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
||||||
self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value)
|
|
||||||
|
calculated_invoice_value = \
|
||||||
|
value_details['AssVal'] + value_details['CgstVal'] \
|
||||||
|
+ value_details['SgstVal'] + value_details['IgstVal'] \
|
||||||
|
+ value_details['OthChrg'] - value_details['Discount']
|
||||||
|
|
||||||
|
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
|
||||||
|
|
||||||
|
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
||||||
self.assertTrue(einvoice['EwbDtls'])
|
self.assertTrue(einvoice['EwbDtls'])
|
||||||
|
|
||||||
def make_sales_invoice_for_ewaybill():
|
def make_test_address_for_ewaybill():
|
||||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||||
address = frappe.get_doc({
|
address = frappe.get_doc({
|
||||||
"address_line1": "_Test Address Line 1",
|
"address_line1": "_Test Address Line 1",
|
||||||
@@ -1968,6 +1996,7 @@ def make_sales_invoice_for_ewaybill():
|
|||||||
|
|
||||||
address.save()
|
address.save()
|
||||||
|
|
||||||
|
def make_test_transporter_for_ewaybill():
|
||||||
if not frappe.db.exists('Supplier', '_Test Transporter'):
|
if not frappe.db.exists('Supplier', '_Test Transporter'):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Supplier",
|
"doctype": "Supplier",
|
||||||
@@ -1978,12 +2007,17 @@ def make_sales_invoice_for_ewaybill():
|
|||||||
"is_transporter": 1
|
"is_transporter": 1
|
||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
|
def make_sales_invoice_for_ewaybill():
|
||||||
|
make_test_address_for_ewaybill()
|
||||||
|
make_test_transporter_for_ewaybill()
|
||||||
|
|
||||||
gst_settings = frappe.get_doc("GST Settings")
|
gst_settings = frappe.get_doc("GST Settings")
|
||||||
|
|
||||||
gst_account = frappe.get_all(
|
gst_account = frappe.get_all(
|
||||||
"GST Account",
|
"GST Account",
|
||||||
fields=["cgst_account", "sgst_account", "igst_account"],
|
fields=["cgst_account", "sgst_account", "igst_account"],
|
||||||
filters = {"company": "_Test Company"})
|
filters = {"company": "_Test Company"}
|
||||||
|
)
|
||||||
|
|
||||||
if not gst_account:
|
if not gst_account:
|
||||||
gst_settings.append("gst_accounts", {
|
gst_settings.append("gst_accounts", {
|
||||||
@@ -1995,7 +2029,7 @@ def make_sales_invoice_for_ewaybill():
|
|||||||
|
|
||||||
gst_settings.save()
|
gst_settings.save()
|
||||||
|
|
||||||
si = create_sales_invoice(do_not_save =1, rate = '60000')
|
si = create_sales_invoice(do_not_save=1, rate='60000')
|
||||||
|
|
||||||
si.distance = 2000
|
si.distance = 2000
|
||||||
si.company_address = "_Test Address for Eway bill-Billing"
|
si.company_address = "_Test Address for Eway bill-Billing"
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"base_rate",
|
"base_rate",
|
||||||
"base_amount",
|
"base_amount",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
|
"stock_uom_rate",
|
||||||
"is_free_item",
|
"is_free_item",
|
||||||
"section_break_21",
|
"section_break_21",
|
||||||
"net_rate",
|
"net_rate",
|
||||||
@@ -565,11 +566,12 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: parent.is_internal_customer && parent.update_stock",
|
||||||
"fieldname": "target_warehouse",
|
"fieldname": "target_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"label": "Customer Warehouse (Optional)",
|
"label": "Target Warehouse",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
@@ -810,12 +812,20 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.uom != doc.stock_uom",
|
||||||
|
"fieldname": "stock_uom_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Rate of Stock UOM",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-23 19:59:04.879322",
|
"modified": "2021-01-30 21:42:37.796771",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ def valdiate_taxes_and_charges_template(doc):
|
|||||||
|
|
||||||
validate_disabled(doc)
|
validate_disabled(doc)
|
||||||
|
|
||||||
|
# Validate with existing taxes and charges template for unique tax category
|
||||||
|
validate_for_tax_category(doc)
|
||||||
|
|
||||||
for tax in doc.get("taxes"):
|
for tax in doc.get("taxes"):
|
||||||
validate_taxes_and_charges(tax)
|
validate_taxes_and_charges(tax)
|
||||||
validate_inclusive_tax(tax, doc)
|
validate_inclusive_tax(tax, doc)
|
||||||
@@ -41,3 +44,7 @@ def valdiate_taxes_and_charges_template(doc):
|
|||||||
def validate_disabled(doc):
|
def validate_disabled(doc):
|
||||||
if doc.is_default and doc.disabled:
|
if doc.is_default and doc.disabled:
|
||||||
frappe.throw(_("Disabled template must not be default template"))
|
frappe.throw(_("Disabled template must not be default template"))
|
||||||
|
|
||||||
|
def validate_for_tax_category(doc):
|
||||||
|
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
|
||||||
|
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Shipping Rule', {
|
frappe.provide('erpnext.accounts.dimensions');
|
||||||
refresh: function(frm) {
|
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
frappe.ui.form.on('Shipping Rule', {
|
||||||
|
onload: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function(frm) {
|
||||||
frm.set_query("account", function() {
|
frm.set_query("account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ class Subscription(Document):
|
|||||||
if not self.generate_invoice_at_period_start:
|
if not self.generate_invoice_at_period_start:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.is_new_subscription():
|
if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check invoice dates and make sure it doesn't have outstanding invoices
|
# Check invoice dates and make sure it doesn't have outstanding invoices
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ def validate_accounting_period(gl_map):
|
|||||||
frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
|
frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
|
||||||
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
|
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
|
||||||
|
|
||||||
def process_gl_map(gl_map, merge_entries=True):
|
def process_gl_map(gl_map, merge_entries=True, precision=None):
|
||||||
if merge_entries:
|
if merge_entries:
|
||||||
gl_map = merge_similar_entries(gl_map)
|
gl_map = merge_similar_entries(gl_map, precision)
|
||||||
for entry in gl_map:
|
for entry in gl_map:
|
||||||
# toggle debit, credit if negative entry
|
# toggle debit, credit if negative entry
|
||||||
if flt(entry.debit) < 0:
|
if flt(entry.debit) < 0:
|
||||||
@@ -69,7 +69,7 @@ def process_gl_map(gl_map, merge_entries=True):
|
|||||||
|
|
||||||
return gl_map
|
return gl_map
|
||||||
|
|
||||||
def merge_similar_entries(gl_map):
|
def merge_similar_entries(gl_map, precision=None):
|
||||||
merged_gl_map = []
|
merged_gl_map = []
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
for entry in gl_map:
|
for entry in gl_map:
|
||||||
@@ -88,7 +88,9 @@ def merge_similar_entries(gl_map):
|
|||||||
|
|
||||||
company = gl_map[0].company if gl_map else erpnext.get_default_company()
|
company = gl_map[0].company if gl_map else erpnext.get_default_company()
|
||||||
company_currency = erpnext.get_company_currency(company)
|
company_currency = erpnext.get_company_currency(company)
|
||||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
|
|
||||||
|
if not precision:
|
||||||
|
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
|
||||||
|
|
||||||
# filter zero debit and credit entries
|
# filter zero debit and credit entries
|
||||||
merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
|
merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
|
||||||
@@ -132,8 +134,8 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
|
|||||||
gle.update(args)
|
gle.update(args)
|
||||||
gle.flags.ignore_permissions = 1
|
gle.flags.ignore_permissions = 1
|
||||||
gle.flags.from_repost = from_repost
|
gle.flags.from_repost = from_repost
|
||||||
gle.insert()
|
gle.flags.adv_adj = adv_adj
|
||||||
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
|
gle.flags.update_outstanding = update_outstanding or 'Yes'
|
||||||
gle.submit()
|
gle.submit()
|
||||||
|
|
||||||
if not from_repost:
|
if not from_repost:
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
|
|||||||
party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
|
party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
|
||||||
party = party_details[party_type.lower()]
|
party = party_details[party_type.lower()]
|
||||||
|
|
||||||
if not ignore_permissions and not frappe.has_permission(party_type, "read", party):
|
if not ignore_permissions and not (frappe.has_permission(party_type, "read", party) or frappe.has_permission(party_type, "select", party)):
|
||||||
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
|
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
|
||||||
|
|
||||||
party = frappe.get_doc(party_type, party)
|
party = frappe.get_doc(party_type, party)
|
||||||
|
|||||||
@@ -152,7 +152,7 @@
|
|||||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
|
||||||
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
|
||||||
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
|
||||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
|
||||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -47,21 +47,22 @@ def get_data(filters):
|
|||||||
|
|
||||||
for d in gl_entries:
|
for d in gl_entries:
|
||||||
asset_data = assets_details.get(d.against_voucher)
|
asset_data = assets_details.get(d.against_voucher)
|
||||||
if not asset_data.get("accumulated_depreciation_amount"):
|
if asset_data:
|
||||||
asset_data.accumulated_depreciation_amount = d.debit
|
if not asset_data.get("accumulated_depreciation_amount"):
|
||||||
else:
|
asset_data.accumulated_depreciation_amount = d.debit
|
||||||
asset_data.accumulated_depreciation_amount += d.debit
|
else:
|
||||||
|
asset_data.accumulated_depreciation_amount += d.debit
|
||||||
|
|
||||||
row = frappe._dict(asset_data)
|
row = frappe._dict(asset_data)
|
||||||
row.update({
|
row.update({
|
||||||
"depreciation_amount": d.debit,
|
"depreciation_amount": d.debit,
|
||||||
"depreciation_date": d.posting_date,
|
"depreciation_date": d.posting_date,
|
||||||
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
|
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
|
||||||
flt(row.accumulated_depreciation_amount)),
|
flt(row.accumulated_depreciation_amount)),
|
||||||
"depreciation_entry": d.voucher_no
|
"depreciation_entry": d.voucher_no
|
||||||
})
|
})
|
||||||
|
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
|
|||||||
|
|
||||||
set_gl_entries_by_account(start_date,
|
set_gl_entries_by_account(start_date,
|
||||||
end_date, root.lft, root.rgt, filters,
|
end_date, root.lft, root.rgt, filters,
|
||||||
gl_entries_by_account, accounts_by_name, ignore_closing_entries=False)
|
gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False)
|
||||||
|
|
||||||
calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters)
|
calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters)
|
||||||
accumulate_values_into_parents(accounts, accounts_by_name, companies)
|
accumulate_values_into_parents(accounts, accounts_by_name, companies)
|
||||||
@@ -240,8 +240,7 @@ def get_company_currency(filters=None):
|
|||||||
def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
|
def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
|
||||||
for entries in gl_entries_by_account.values():
|
for entries in gl_entries_by_account.values():
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
key = entry.account_number or entry.account_name
|
d = accounts_by_name.get(entry.account_name)
|
||||||
d = accounts_by_name.get(key)
|
|
||||||
if d:
|
if d:
|
||||||
for company in companies:
|
for company in companies:
|
||||||
# check if posting date is within the period
|
# check if posting date is within the period
|
||||||
@@ -256,7 +255,8 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
|
|||||||
"""accumulate children's values in parent accounts"""
|
"""accumulate children's values in parent accounts"""
|
||||||
for d in reversed(accounts):
|
for d in reversed(accounts):
|
||||||
if d.parent_account:
|
if d.parent_account:
|
||||||
account = d.parent_account.split(' - ')[0].strip()
|
account = d.parent_account_name
|
||||||
|
|
||||||
if not accounts_by_name.get(account):
|
if not accounts_by_name.get(account):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -267,16 +267,34 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
|
|||||||
accounts_by_name[account]["opening_balance"] = \
|
accounts_by_name[account]["opening_balance"] = \
|
||||||
accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
|
accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
|
||||||
|
|
||||||
|
|
||||||
def get_account_heads(root_type, companies, filters):
|
def get_account_heads(root_type, companies, filters):
|
||||||
accounts = get_accounts(root_type, filters)
|
accounts = get_accounts(root_type, filters)
|
||||||
|
|
||||||
if not accounts:
|
if not accounts:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
accounts = update_parent_account_names(accounts)
|
||||||
|
|
||||||
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
|
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
|
||||||
|
|
||||||
return accounts, accounts_by_name
|
return accounts, accounts_by_name
|
||||||
|
|
||||||
|
def update_parent_account_names(accounts):
|
||||||
|
"""Update parent_account_name in accounts list.
|
||||||
|
|
||||||
|
parent_name is `name` of parent account which could have other prefix
|
||||||
|
of account_number and suffix of company abbr. This function adds key called
|
||||||
|
`parent_account_name` which does not have such prefix/suffix.
|
||||||
|
"""
|
||||||
|
name_to_account_map = { d.name : d.account_name for d in accounts }
|
||||||
|
|
||||||
|
for account in accounts:
|
||||||
|
if account.parent_account:
|
||||||
|
account["parent_account_name"] = name_to_account_map[account.parent_account]
|
||||||
|
|
||||||
|
return accounts
|
||||||
|
|
||||||
def get_companies(filters):
|
def get_companies(filters):
|
||||||
companies = {}
|
companies = {}
|
||||||
all_companies = get_subsidiary_companies(filters.get('company'))
|
all_companies = get_subsidiary_companies(filters.get('company'))
|
||||||
@@ -339,7 +357,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account,
|
def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account,
|
||||||
accounts_by_name, ignore_closing_entries=False):
|
accounts_by_name, accounts, ignore_closing_entries=False):
|
||||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||||
|
|
||||||
company_lft, company_rgt = frappe.get_cached_value('Company',
|
company_lft, company_rgt = frappe.get_cached_value('Company',
|
||||||
@@ -381,16 +399,32 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
|
|||||||
convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
|
convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
|
||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
key = entry.account_number or entry.account_name
|
account_name = entry.account_name
|
||||||
validate_entries(key, entry, accounts_by_name)
|
validate_entries(account_name, entry, accounts_by_name, accounts)
|
||||||
gl_entries_by_account.setdefault(key, []).append(entry)
|
gl_entries_by_account.setdefault(account_name, []).append(entry)
|
||||||
|
|
||||||
return gl_entries_by_account
|
return gl_entries_by_account
|
||||||
|
|
||||||
def validate_entries(key, entry, accounts_by_name):
|
def get_account_details(account):
|
||||||
|
return frappe.get_cached_value('Account', account, ['name', 'report_type', 'root_type', 'company',
|
||||||
|
'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1)
|
||||||
|
|
||||||
|
def validate_entries(key, entry, accounts_by_name, accounts):
|
||||||
if key not in accounts_by_name:
|
if key not in accounts_by_name:
|
||||||
field = "Account number" if entry.account_number else "Account name"
|
args = get_account_details(entry.account)
|
||||||
frappe.throw(_("{0} {1} is not present in the parent company").format(field, key))
|
|
||||||
|
if args.parent_account:
|
||||||
|
parent_args = get_account_details(args.parent_account)
|
||||||
|
|
||||||
|
args.update({
|
||||||
|
'lft': parent_args.lft + 1,
|
||||||
|
'rgt': parent_args.rgt - 1,
|
||||||
|
'root_type': parent_args.root_type,
|
||||||
|
'report_type': parent_args.report_type
|
||||||
|
})
|
||||||
|
|
||||||
|
accounts_by_name.setdefault(key, args)
|
||||||
|
accounts.append(args)
|
||||||
|
|
||||||
def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||||
additional_conditions = []
|
additional_conditions = []
|
||||||
@@ -436,8 +470,7 @@ def filter_accounts(accounts, depth=10):
|
|||||||
parent_children_map = {}
|
parent_children_map = {}
|
||||||
accounts_by_name = {}
|
accounts_by_name = {}
|
||||||
for d in accounts:
|
for d in accounts:
|
||||||
key = d.account_number or d.account_name
|
accounts_by_name[d.account_name] = d
|
||||||
accounts_by_name[key] = d
|
|
||||||
parent_children_map.setdefault(d.parent_account or None, []).append(d)
|
parent_children_map.setdefault(d.parent_account or None, []).append(d)
|
||||||
|
|
||||||
filtered_accounts = []
|
filtered_accounts = []
|
||||||
|
|||||||
@@ -49,12 +49,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
|||||||
elif d.po_detail:
|
elif d.po_detail:
|
||||||
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
|
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
|
||||||
|
|
||||||
expense_account = d.expense_account or aii_account_map.get(d.company)
|
expense_account = d.unrealized_profit_loss_account or d.expense_account \
|
||||||
|
or aii_account_map.get(d.company)
|
||||||
|
|
||||||
row = {
|
row = {
|
||||||
'item_code': d.item_code,
|
'item_code': d.item_code,
|
||||||
'item_name': item_record.item_name,
|
'item_name': item_record.item_name if item_record else d.item_name,
|
||||||
'item_group': item_record.item_group,
|
'item_group': item_record.item_group if item_record else d.item_group,
|
||||||
'description': d.description,
|
'description': d.description,
|
||||||
'invoice': d.parent,
|
'invoice': d.parent,
|
||||||
'posting_date': d.posting_date,
|
'posting_date': d.posting_date,
|
||||||
@@ -315,7 +316,9 @@ def get_items(filters, additional_query_columns):
|
|||||||
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
|
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
|
||||||
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
|
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
|
||||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||||
|
`tabPurchase Invoice`.unrealized_profit_loss_account,
|
||||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||||
|
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
|
||||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||||
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
|||||||
'company': d.company,
|
'company': d.company,
|
||||||
'sales_order': d.sales_order,
|
'sales_order': d.sales_order,
|
||||||
'delivery_note': d.delivery_note,
|
'delivery_note': d.delivery_note,
|
||||||
'income_account': d.income_account,
|
'income_account': d.unrealized_profit_loss_account or d.income_account,
|
||||||
'cost_center': d.cost_center,
|
'cost_center': d.cost_center,
|
||||||
'stock_qty': d.stock_qty,
|
'stock_qty': d.stock_qty,
|
||||||
'stock_uom': d.stock_uom
|
'stock_uom': d.stock_uom
|
||||||
@@ -379,6 +379,7 @@ def get_items(filters, additional_query_columns):
|
|||||||
select
|
select
|
||||||
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
|
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
|
||||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||||
|
`tabSales Invoice`.unrealized_profit_loss_account,
|
||||||
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||||
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
||||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||||
|
|||||||
@@ -59,23 +59,111 @@ def validate_filters(filters):
|
|||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
return [
|
return [
|
||||||
_("Payment Document") + ":: 100",
|
{
|
||||||
_("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140",
|
"fieldname": "payment_document",
|
||||||
_("Party Type") + "::100",
|
"label": _("Payment Document Type"),
|
||||||
_("Party") + ":Dynamic Link/Party Type:140",
|
"fieldtype": "Data",
|
||||||
_("Posting Date") + ":Date:100",
|
"width": 100
|
||||||
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"),
|
},
|
||||||
_("Invoice Posting Date") + ":Date:130",
|
{
|
||||||
_("Payment Due Date") + ":Date:130",
|
"fieldname": "payment_entry",
|
||||||
_("Debit") + ":Currency:120",
|
"label": _("Payment Document"),
|
||||||
_("Credit") + ":Currency:120",
|
"fieldtype": "Dynamic Link",
|
||||||
_("Remarks") + "::150",
|
"options": "payment_document",
|
||||||
_("Age") +":Int:40",
|
"width": 160
|
||||||
"0-30:Currency:100",
|
},
|
||||||
"30-60:Currency:100",
|
{
|
||||||
"60-90:Currency:100",
|
"fieldname": "party_type",
|
||||||
_("90-Above") + ":Currency:100",
|
"label": _("Party Type"),
|
||||||
_("Delay in payment (Days)") + "::150"
|
"fieldtype": "Data",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party",
|
||||||
|
"label": _("Party"),
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "party_type",
|
||||||
|
"width": 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"label": _("Posting Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice",
|
||||||
|
"label": _("Invoice"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice",
|
||||||
|
"width": 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_posting_date",
|
||||||
|
"label": _("Invoice Posting Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "due_date",
|
||||||
|
"label": _("Payment Due Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "debit",
|
||||||
|
"label": _("Debit"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "credit",
|
||||||
|
"label": _("Credit"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "remarks",
|
||||||
|
"label": _("Remarks"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "age",
|
||||||
|
"label": _("Age"),
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "range1",
|
||||||
|
"label": "0-30",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "range2",
|
||||||
|
"label": "30-60",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "range3",
|
||||||
|
"label": "60-90",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "range4",
|
||||||
|
"label": _("90 Above"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "delay_in_payment",
|
||||||
|
"label": _("Delay in payment (Days)"),
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 100
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
|
|||||||
@@ -14,13 +14,15 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
|||||||
if not filters: filters = {}
|
if not filters: filters = {}
|
||||||
|
|
||||||
invoice_list = get_invoices(filters, additional_query_columns)
|
invoice_list = get_invoices(filters, additional_query_columns)
|
||||||
columns, expense_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
|
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts \
|
||||||
|
= get_columns(invoice_list, additional_table_columns)
|
||||||
|
|
||||||
if not invoice_list:
|
if not invoice_list:
|
||||||
msgprint(_("No record found"))
|
msgprint(_("No record found"))
|
||||||
return columns, invoice_list
|
return columns, invoice_list
|
||||||
|
|
||||||
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
||||||
|
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
||||||
invoice_expense_map, expense_accounts)
|
invoice_expense_map, expense_accounts)
|
||||||
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||||
@@ -52,10 +54,17 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
|||||||
# map expense values
|
# map expense values
|
||||||
base_net_total = 0
|
base_net_total = 0
|
||||||
for expense_acc in expense_accounts:
|
for expense_acc in expense_accounts:
|
||||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
if inv.is_internal_supplier and inv.company == inv.represents_company:
|
||||||
|
expense_amount = 0
|
||||||
|
else:
|
||||||
|
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||||
base_net_total += expense_amount
|
base_net_total += expense_amount
|
||||||
row.append(expense_amount)
|
row.append(expense_amount)
|
||||||
|
|
||||||
|
# Add amount in unrealized account
|
||||||
|
for account in unrealized_profit_loss_accounts:
|
||||||
|
row.append(flt(internal_invoice_map.get((inv.name, account))))
|
||||||
|
|
||||||
# net total
|
# net total
|
||||||
row.append(base_net_total or inv.base_net_total)
|
row.append(base_net_total or inv.base_net_total)
|
||||||
|
|
||||||
@@ -96,7 +105,8 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
"width": 80
|
"width": 80
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
expense_accounts = tax_accounts = expense_columns = tax_columns = []
|
expense_accounts = tax_accounts = expense_columns = tax_columns = unrealized_profit_loss_accounts = \
|
||||||
|
unrealized_profit_loss_account_columns = []
|
||||||
|
|
||||||
if invoice_list:
|
if invoice_list:
|
||||||
expense_accounts = frappe.db.sql_list("""select distinct expense_account
|
expense_accounts = frappe.db.sql_list("""select distinct expense_account
|
||||||
@@ -112,17 +122,25 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
and parent in (%s) order by account_head""" %
|
and parent in (%s) order by account_head""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||||
|
|
||||||
|
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||||
|
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
|
||||||
|
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||||
|
order by unrealized_profit_loss_account""" %
|
||||||
|
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||||
|
|
||||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
||||||
|
unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
|
||||||
|
|
||||||
for account in tax_accounts:
|
for account in tax_accounts:
|
||||||
if account not in expense_accounts:
|
if account not in expense_accounts:
|
||||||
tax_columns.append(account + ":Currency/currency:120")
|
tax_columns.append(account + ":Currency/currency:120")
|
||||||
|
|
||||||
columns = columns + expense_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \
|
columns = columns + expense_columns + unrealized_profit_loss_account_columns + \
|
||||||
|
[_("Net Total") + ":Currency/currency:120"] + tax_columns + \
|
||||||
[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
|
[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
|
||||||
_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
|
_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
|
||||||
|
|
||||||
return columns, expense_accounts, tax_accounts
|
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
conditions = ""
|
conditions = ""
|
||||||
@@ -199,6 +217,19 @@ def get_invoice_expense_map(invoice_list):
|
|||||||
|
|
||||||
return invoice_expense_map
|
return invoice_expense_map
|
||||||
|
|
||||||
|
def get_internal_invoice_map(invoice_list):
|
||||||
|
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
||||||
|
base_net_total as amount from `tabPurchase Invoice` where name in (%s)
|
||||||
|
and is_internal_supplier = 1 and company = represents_company""" %
|
||||||
|
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
||||||
|
|
||||||
|
internal_invoice_map = {}
|
||||||
|
for d in unrealized_amount_details:
|
||||||
|
if d.unrealized_profit_loss_account:
|
||||||
|
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
|
||||||
|
|
||||||
|
return internal_invoice_map
|
||||||
|
|
||||||
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||||
tax_details = frappe.db.sql("""
|
tax_details = frappe.db.sql("""
|
||||||
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
|||||||
if not filters: filters = frappe._dict({})
|
if not filters: filters = frappe._dict({})
|
||||||
|
|
||||||
invoice_list = get_invoices(filters, additional_query_columns)
|
invoice_list = get_invoices(filters, additional_query_columns)
|
||||||
columns, income_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
|
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(invoice_list, additional_table_columns)
|
||||||
|
|
||||||
if not invoice_list:
|
if not invoice_list:
|
||||||
msgprint(_("No record found"))
|
msgprint(_("No record found"))
|
||||||
return columns, invoice_list
|
return columns, invoice_list
|
||||||
|
|
||||||
invoice_income_map = get_invoice_income_map(invoice_list)
|
invoice_income_map = get_invoice_income_map(invoice_list)
|
||||||
|
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||||
invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
||||||
invoice_income_map, income_accounts)
|
invoice_income_map, income_accounts)
|
||||||
#Cost Center & Warehouse Map
|
#Cost Center & Warehouse Map
|
||||||
@@ -70,12 +71,22 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
|||||||
# map income values
|
# map income values
|
||||||
base_net_total = 0
|
base_net_total = 0
|
||||||
for income_acc in income_accounts:
|
for income_acc in income_accounts:
|
||||||
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
|
if inv.is_internal_customer and inv.company == inv.represents_company:
|
||||||
|
income_amount = 0
|
||||||
|
else:
|
||||||
|
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
|
||||||
|
|
||||||
base_net_total += income_amount
|
base_net_total += income_amount
|
||||||
row.update({
|
row.update({
|
||||||
frappe.scrub(income_acc): income_amount
|
frappe.scrub(income_acc): income_amount
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Add amount in unrealized account
|
||||||
|
for account in unrealized_profit_loss_accounts:
|
||||||
|
row.update({
|
||||||
|
frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account)))
|
||||||
|
})
|
||||||
|
|
||||||
# net total
|
# net total
|
||||||
row.update({'net_total': base_net_total or inv.base_net_total})
|
row.update({'net_total': base_net_total or inv.base_net_total})
|
||||||
|
|
||||||
@@ -230,6 +241,8 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
tax_accounts = []
|
tax_accounts = []
|
||||||
income_columns = []
|
income_columns = []
|
||||||
tax_columns = []
|
tax_columns = []
|
||||||
|
unrealized_profit_loss_accounts = []
|
||||||
|
unrealized_profit_loss_account_columns = []
|
||||||
|
|
||||||
if invoice_list:
|
if invoice_list:
|
||||||
income_accounts = frappe.db.sql_list("""select distinct income_account
|
income_accounts = frappe.db.sql_list("""select distinct income_account
|
||||||
@@ -243,12 +256,18 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
and parent in (%s) order by account_head""" %
|
and parent in (%s) order by account_head""" %
|
||||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||||
|
|
||||||
|
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||||
|
from `tabSales Invoice` where docstatus = 1 and name in (%s)
|
||||||
|
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||||
|
order by unrealized_profit_loss_account""" %
|
||||||
|
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||||
|
|
||||||
for account in income_accounts:
|
for account in income_accounts:
|
||||||
income_columns.append({
|
income_columns.append({
|
||||||
"label": account,
|
"label": account,
|
||||||
"fieldname": frappe.scrub(account),
|
"fieldname": frappe.scrub(account),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": 'currency',
|
"options": "currency",
|
||||||
"width": 120
|
"width": 120
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -258,15 +277,24 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
"label": account,
|
"label": account,
|
||||||
"fieldname": frappe.scrub(account),
|
"fieldname": frappe.scrub(account),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": 'currency',
|
"options": "currency",
|
||||||
"width": 120
|
"width": 120
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for account in unrealized_profit_loss_accounts:
|
||||||
|
unrealized_profit_loss_account_columns.append({
|
||||||
|
"label": account,
|
||||||
|
"fieldname": frappe.scrub(account),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120
|
||||||
|
})
|
||||||
|
|
||||||
net_total_column = [{
|
net_total_column = [{
|
||||||
"label": _("Net Total"),
|
"label": _("Net Total"),
|
||||||
"fieldname": "net_total",
|
"fieldname": "net_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": 'currency',
|
"options": "currency",
|
||||||
"width": 120
|
"width": 120
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@@ -301,9 +329,10 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
columns = columns + income_columns + net_total_column + tax_columns + total_columns
|
columns = columns + income_columns + unrealized_profit_loss_account_columns + \
|
||||||
|
net_total_column + tax_columns + total_columns
|
||||||
|
|
||||||
return columns, income_accounts, tax_accounts
|
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
conditions = ""
|
conditions = ""
|
||||||
@@ -368,7 +397,8 @@ def get_invoices(filters, additional_query_columns):
|
|||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select name, posting_date, debit_to, project, customer,
|
select name, posting_date, debit_to, project, customer,
|
||||||
customer_name, owner, remarks, territory, tax_id, customer_group,
|
customer_name, owner, remarks, territory, tax_id, customer_group,
|
||||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount {0}
|
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
||||||
|
is_internal_customer, represents_company, company {0}
|
||||||
from `tabSales Invoice`
|
from `tabSales Invoice`
|
||||||
where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') %
|
where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') %
|
||||||
conditions, filters, as_dict=1)
|
conditions, filters, as_dict=1)
|
||||||
@@ -385,6 +415,19 @@ def get_invoice_income_map(invoice_list):
|
|||||||
|
|
||||||
return invoice_income_map
|
return invoice_income_map
|
||||||
|
|
||||||
|
def get_internal_invoice_map(invoice_list):
|
||||||
|
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
||||||
|
base_net_total as amount from `tabSales Invoice` where name in (%s)
|
||||||
|
and is_internal_customer = 1 and company = represents_company""" %
|
||||||
|
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
||||||
|
|
||||||
|
internal_invoice_map = {}
|
||||||
|
for d in unrealized_amount_details:
|
||||||
|
if d.unrealized_profit_loss_account:
|
||||||
|
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
|
||||||
|
|
||||||
|
return internal_invoice_map
|
||||||
|
|
||||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||||
tax_details = frappe.db.sql("""select parent, account_head,
|
tax_details = frappe.db.sql("""select parent, account_head,
|
||||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
sum(base_tax_amount_after_discount_amount) as tax_amount
|
||||||
|
|||||||
@@ -888,6 +888,11 @@ def get_coa(doctype, parent, is_root, chart=None):
|
|||||||
|
|
||||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
||||||
warehouse_account=None, company=None):
|
warehouse_account=None, company=None):
|
||||||
|
stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items, company)
|
||||||
|
repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company, warehouse_account)
|
||||||
|
|
||||||
|
|
||||||
|
def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, warehouse_account=None):
|
||||||
def _delete_gl_entries(voucher_type, voucher_no):
|
def _delete_gl_entries(voucher_type, voucher_no):
|
||||||
frappe.db.sql("""delete from `tabGL Entry`
|
frappe.db.sql("""delete from `tabGL Entry`
|
||||||
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
|
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
|
||||||
@@ -895,21 +900,21 @@ def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for
|
|||||||
if not warehouse_account:
|
if not warehouse_account:
|
||||||
warehouse_account = get_warehouse_account_map(company)
|
warehouse_account = get_warehouse_account_map(company)
|
||||||
|
|
||||||
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
|
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2
|
||||||
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
|
|
||||||
|
|
||||||
for voucher_type, voucher_no in future_stock_vouchers:
|
gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
|
||||||
|
for voucher_type, voucher_no in stock_vouchers:
|
||||||
existing_gle = gle.get((voucher_type, voucher_no), [])
|
existing_gle = gle.get((voucher_type, voucher_no), [])
|
||||||
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
|
voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no)
|
||||||
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
|
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
|
||||||
if expected_gle:
|
if expected_gle:
|
||||||
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
|
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
|
||||||
_delete_gl_entries(voucher_type, voucher_no)
|
_delete_gl_entries(voucher_type, voucher_no)
|
||||||
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
|
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
|
||||||
else:
|
else:
|
||||||
_delete_gl_entries(voucher_type, voucher_no)
|
_delete_gl_entries(voucher_type, voucher_no)
|
||||||
|
|
||||||
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
|
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None):
|
||||||
future_stock_vouchers = []
|
future_stock_vouchers = []
|
||||||
|
|
||||||
values = []
|
values = []
|
||||||
@@ -922,6 +927,10 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
|
|||||||
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
|
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
|
||||||
values += for_warehouses
|
values += for_warehouses
|
||||||
|
|
||||||
|
if company:
|
||||||
|
condition += " and company = %s"
|
||||||
|
values.append(company)
|
||||||
|
|
||||||
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
|
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
|
||||||
from `tabStock Ledger Entry` sle
|
from `tabStock Ledger Entry` sle
|
||||||
where
|
where
|
||||||
@@ -945,16 +954,17 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
|
|||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
def compare_existing_and_expected_gle(existing_gle, expected_gle):
|
def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
|
||||||
matched = True
|
matched = True
|
||||||
for entry in expected_gle:
|
for entry in expected_gle:
|
||||||
account_existed = False
|
account_existed = False
|
||||||
for e in existing_gle:
|
for e in existing_gle:
|
||||||
if entry.account == e.account:
|
if entry.account == e.account:
|
||||||
account_existed = True
|
account_existed = True
|
||||||
if entry.account == e.account and entry.against_account == e.against_account \
|
if (entry.account == e.account and entry.against_account == e.against_account
|
||||||
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \
|
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
|
||||||
and (entry.debit != e.debit or entry.credit != e.credit):
|
and ( flt(entry.debit, precision) != flt(e.debit, precision) or
|
||||||
|
flt(entry.credit, precision) != flt(e.credit, precision))):
|
||||||
matched = False
|
matched = False
|
||||||
break
|
break
|
||||||
if not account_existed:
|
if not account_existed:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class CropCycle(Document):
|
|||||||
|
|
||||||
def import_disease_tasks(self, disease, start_date):
|
def import_disease_tasks(self, disease, start_date):
|
||||||
disease_doc = frappe.get_doc('Disease', disease)
|
disease_doc = frappe.get_doc('Disease', disease)
|
||||||
self.create_task(disease_doc.treatment_task, self.name, start_date)
|
self.create_task(disease_doc.treatment_task, self.project, start_date)
|
||||||
|
|
||||||
def create_project(self, period, crop_tasks):
|
def create_project(self, period, crop_tasks):
|
||||||
project = frappe.get_doc({
|
project = frappe.get_doc({
|
||||||
|
|||||||
@@ -71,4 +71,4 @@ def check_task_creation():
|
|||||||
|
|
||||||
|
|
||||||
def check_project_creation():
|
def check_project_creation():
|
||||||
return True if frappe.db.exists('Project', 'Basil from seed 2017') else False
|
return True if frappe.db.exists('Project', {'project_name': 'Basil from seed 2017'}) else False
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext.asset");
|
frappe.provide("erpnext.asset");
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Asset', {
|
frappe.ui.form.on('Asset', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
@@ -32,13 +33,11 @@ frappe.ui.form.on('Asset', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", function() {
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
return {
|
},
|
||||||
"filters": {
|
|
||||||
"company": frm.doc.company,
|
company: function(frm) {
|
||||||
}
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Asset Value Adjustment', {
|
frappe.ui.form.on('Asset Value Adjustment', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.add_fetch('company', 'cost_center', 'cost_center');
|
frm.add_fetch('company', 'cost_center', 'cost_center');
|
||||||
@@ -13,11 +15,19 @@ frappe.ui.form.on('Asset Value Adjustment', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
if(frm.is_new() && frm.doc.asset) {
|
if(frm.is_new() && frm.doc.asset) {
|
||||||
frm.trigger("set_current_asset_value");
|
frm.trigger("set_current_asset_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
asset: function(frm) {
|
asset: function(frm) {
|
||||||
frm.trigger("set_current_asset_value");
|
frm.trigger("set_current_asset_value");
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,17 +13,14 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
|
|||||||
class AssetValueAdjustment(Document):
|
class AssetValueAdjustment(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_date()
|
self.validate_date()
|
||||||
self.set_difference_amount()
|
|
||||||
self.set_current_asset_value()
|
self.set_current_asset_value()
|
||||||
|
self.set_difference_amount()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.make_depreciation_entry()
|
self.make_depreciation_entry()
|
||||||
self.reschedule_depreciations(self.new_asset_value)
|
self.reschedule_depreciations(self.new_asset_value)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
if self.journal_entry:
|
|
||||||
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
|
|
||||||
|
|
||||||
self.reschedule_depreciations(self.current_asset_value)
|
self.reschedule_depreciations(self.current_asset_value)
|
||||||
|
|
||||||
def validate_date(self):
|
def validate_date(self):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext.buying");
|
frappe.provide("erpnext.buying");
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
{% include 'erpnext/public/js/controllers/buying.js' %};
|
{% include 'erpnext/public/js/controllers/buying.js' %};
|
||||||
|
|
||||||
frappe.ui.form.on("Purchase Order", {
|
frappe.ui.form.on("Purchase Order", {
|
||||||
@@ -30,6 +30,10 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
set_schedule_date(frm);
|
set_schedule_date(frm);
|
||||||
if (!frm.doc.transaction_date){
|
if (!frm.doc.transaction_date){
|
||||||
@@ -39,6 +43,8 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||||
return erpnext.queries.warehouse(frm.doc);
|
return erpnext.queries.warehouse(frm.doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,8 +64,8 @@ frappe.ui.form.on("Purchase Order Item", {
|
|||||||
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
|
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
|
||||||
setup: function() {
|
setup: function() {
|
||||||
this.frm.custom_make_buttons = {
|
this.frm.custom_make_buttons = {
|
||||||
'Purchase Receipt': 'Receipt',
|
'Purchase Receipt': 'Purchase Receipt',
|
||||||
'Purchase Invoice': 'Invoice',
|
'Purchase Invoice': 'Purchase Invoice',
|
||||||
'Stock Entry': 'Material to Supplier',
|
'Stock Entry': 'Material to Supplier',
|
||||||
'Payment Entry': 'Payment',
|
'Payment Entry': 'Payment',
|
||||||
}
|
}
|
||||||
@@ -158,16 +164,16 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
|||||||
|
|
||||||
if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
|
if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
|
||||||
let me = this;
|
let me = this;
|
||||||
frappe.model.with_doc("Supplier", me.frm.doc.supplier, () => {
|
let internal = me.frm.doc.is_internal_supplier;
|
||||||
let supplier = frappe.model.get_doc("Supplier", me.frm.doc.supplier);
|
if (internal) {
|
||||||
let internal = supplier.is_internal_supplier;
|
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Sales Order" :
|
||||||
let disabled = supplier.disabled;
|
"Inter Company Sales Order";
|
||||||
if (internal === 1 && disabled === 0) {
|
|
||||||
me.frm.add_custom_button("Inter Company Order", function() {
|
me.frm.add_custom_button(button_label, function() {
|
||||||
me.make_inter_company_order(me.frm);
|
me.make_inter_company_order(me.frm);
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +353,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
|||||||
make_purchase_receipt: function() {
|
make_purchase_receipt: function() {
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
|
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
|
||||||
frm: cur_frm
|
frm: cur_frm,
|
||||||
|
freeze_message: __("Creating Purchase Receipt ...")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -374,7 +381,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
|||||||
material_request_type: "Purchase",
|
material_request_type: "Purchase",
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
status: ["!=", "Stopped"],
|
status: ["!=", "Stopped"],
|
||||||
per_ordered: ["<", 99.99],
|
per_ordered: ["<", 100],
|
||||||
company: me.frm.doc.company
|
company: me.frm.doc.company
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -134,6 +134,8 @@
|
|||||||
"ref_sq",
|
"ref_sq",
|
||||||
"column_break_74",
|
"column_break_74",
|
||||||
"party_account_currency",
|
"party_account_currency",
|
||||||
|
"is_internal_supplier",
|
||||||
|
"represents_company",
|
||||||
"inter_company_order_reference"
|
"inter_company_order_reference"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -1101,13 +1103,28 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "items_col_break",
|
"fieldname": "items_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fetch_from": "supplier.is_internal_supplier",
|
||||||
|
"fieldname": "is_internal_supplier",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Internal Supplier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "supplier.represents_company",
|
||||||
|
"fieldname": "represents_company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Represents Company",
|
||||||
|
"options": "Company",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-03 16:46:44.229351",
|
"modified": "2021-01-20 22:07:23.487138",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
|||||||
@@ -123,8 +123,8 @@ class PurchaseOrder(BuyingController):
|
|||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted == "Yes":
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if not item.bom:
|
if not item.bom:
|
||||||
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}"\
|
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}")
|
||||||
.format(item.item_code, item.idx)))
|
.format(item.item_code, item.idx))
|
||||||
|
|
||||||
def get_schedule_dates(self):
|
def get_schedule_dates(self):
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"base_rate",
|
"base_rate",
|
||||||
"base_amount",
|
"base_amount",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
|
"stock_uom_rate",
|
||||||
"is_free_item",
|
"is_free_item",
|
||||||
"section_break_29",
|
"section_break_29",
|
||||||
"net_rate",
|
"net_rate",
|
||||||
@@ -726,13 +727,21 @@
|
|||||||
"fieldname": "more_info_section_break",
|
"fieldname": "more_info_section_break",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "More Information"
|
"label": "More Information"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.uom != doc.stock_uom",
|
||||||
|
"fieldname": "stock_uom_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Rate of Stock UOM",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-07 11:59:47.670951",
|
"modified": "2021-01-30 21:44:41.816974",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
|||||||
material_request_type: "Purchase",
|
material_request_type: "Purchase",
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
status: ["!=", "Stopped"],
|
status: ["!=", "Stopped"],
|
||||||
per_ordered: ["<", 99.99],
|
per_ordered: ["<", 100],
|
||||||
company: me.frm.doc.company
|
company: me.frm.doc.company
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -280,7 +280,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
|||||||
material_request_type: "Purchase",
|
material_request_type: "Purchase",
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
status: ["!=", "Stopped"],
|
status: ["!=", "Stopped"],
|
||||||
per_ordered: ["<", 99.99]
|
per_ordered: ["<", 100]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dialog.hide();
|
dialog.hide();
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ class Supplier(TransactionBase):
|
|||||||
self.validate_internal_supplier()
|
self.validate_internal_supplier()
|
||||||
|
|
||||||
def validate_internal_supplier(self):
|
def validate_internal_supplier(self):
|
||||||
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
|
internal_supplier = frappe.db.get_value("Supplier",
|
||||||
|
{"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
|
||||||
|
|
||||||
|
if internal_supplier:
|
||||||
frappe.throw(_("Internal Supplier for company {0} already exists").format(
|
frappe.throw(_("Internal Supplier for company {0} already exists").format(
|
||||||
frappe.bold(self.represents_company)))
|
frappe.bold(self.represents_company)))
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
|||||||
material_request_type: "Purchase",
|
material_request_type: "Purchase",
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
status: ["!=", "Stopped"],
|
status: ["!=", "Stopped"],
|
||||||
per_ordered: ["<", 99.99],
|
per_ordered: ["<", 100],
|
||||||
company: me.frm.doc.company
|
company: me.frm.doc.company
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,62 +75,70 @@ frappe.query_reports["Purchase Analytics"] = {
|
|||||||
return Object.assign(options, {
|
return Object.assign(options, {
|
||||||
checkboxColumn: true,
|
checkboxColumn: true,
|
||||||
events: {
|
events: {
|
||||||
onCheckRow: function(data) {
|
onCheckRow: function (data) {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const data_doctype = $(
|
||||||
|
data[2].html
|
||||||
|
)[0].attributes.getNamedItem("data-doctype").value;
|
||||||
|
const tree_type = frappe.query_report.filters[0].value;
|
||||||
|
if (data_doctype != tree_type) return;
|
||||||
|
|
||||||
row_name = data[2].content;
|
row_name = data[2].content;
|
||||||
length = data.length;
|
length = data.length;
|
||||||
|
|
||||||
var tree_type = frappe.query_report.filters[0].value;
|
if (tree_type == "Supplier") {
|
||||||
|
row_values = data
|
||||||
if(tree_type == "Supplier" || tree_type == "Item") {
|
.slice(4, length - 1)
|
||||||
row_values = data.slice(4,length-1).map(function (column) {
|
.map(function (column) {
|
||||||
return column.content;
|
return column.content;
|
||||||
})
|
});
|
||||||
}
|
} else if (tree_type == "Item") {
|
||||||
else {
|
row_values = data
|
||||||
row_values = data.slice(3,length-1).map(function (column) {
|
.slice(5, length - 1)
|
||||||
return column.content;
|
.map(function (column) {
|
||||||
})
|
return column.content;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
row_values = data
|
||||||
|
.slice(3, length - 1)
|
||||||
|
.map(function (column) {
|
||||||
|
return column.content;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
'name':row_name,
|
name: row_name,
|
||||||
'values':row_values
|
values: row_values,
|
||||||
}
|
};
|
||||||
|
|
||||||
let raw_data = frappe.query_report.chart.data;
|
let raw_data = frappe.query_report.chart.data;
|
||||||
let new_datasets = raw_data.datasets;
|
let new_datasets = raw_data.datasets;
|
||||||
|
|
||||||
var found = false;
|
let element_found = new_datasets.some((element, index, array)=>{
|
||||||
|
if(element.name == row_name){
|
||||||
for(var i=0; i < new_datasets.length;i++){
|
array.splice(index, 1)
|
||||||
if(new_datasets[i].name == row_name){
|
return true
|
||||||
found = true;
|
|
||||||
new_datasets.splice(i,1);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
if(!found){
|
if (!element_found) {
|
||||||
new_datasets.push(entry);
|
new_datasets.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_data = {
|
let new_data = {
|
||||||
labels: raw_data.labels,
|
labels: raw_data.labels,
|
||||||
datasets: new_datasets
|
datasets: new_datasets,
|
||||||
}
|
};
|
||||||
|
chart_options = {
|
||||||
setTimeout(() => {
|
data: new_data,
|
||||||
frappe.query_report.chart.update(new_data)
|
type: "line",
|
||||||
},500)
|
};
|
||||||
|
frappe.query_report.render_chart(chart_options);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
frappe.query_report.chart.draw(true);
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
frappe.query_report.raw_chart_data = new_data;
|
frappe.query_report.raw_chart_data = new_data;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ def update_last_purchase_rate(doc, is_submit):
|
|||||||
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
|
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
|
||||||
|
|
||||||
# update last purchsae rate
|
# update last purchsae rate
|
||||||
if last_purchase_rate:
|
frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate))
|
||||||
frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
|
|
||||||
(flt(last_purchase_rate), d.item_code))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_for_items(doc):
|
def validate_for_items(doc):
|
||||||
items = []
|
items = []
|
||||||
|
|||||||
77
erpnext/change_log/v13/v13_0_0-beta_11.md
Normal file
77
erpnext/change_log/v13/v13_0_0-beta_11.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
### Version 13.0.0 Beta 11 Release Notes
|
||||||
|
|
||||||
|
#### Features and Enhancements
|
||||||
|
|
||||||
|
- Provision to disable serial no and batch selector ([#24398](https://github.com/frappe/erpnext/pull/24398))
|
||||||
|
- Multi currency in landed cost voucher ([#24127](https://github.com/frappe/erpnext/pull/24127))
|
||||||
|
- Putaway ([#23969](https://github.com/frappe/erpnext/pull/23969))
|
||||||
|
- Item valuation for internal stock transfers ([#24200](https://github.com/frappe/erpnext/pull/24200))
|
||||||
|
- Batch wise item pricing ([#24470](https://github.com/frappe/erpnext/pull/24470))
|
||||||
|
- Project template with dependent tasks ([#24092](https://github.com/frappe/erpnext/pull/24092))
|
||||||
|
- Patient History Enhancements ([#24033](https://github.com/frappe/erpnext/pull/24033))
|
||||||
|
- Compute Year to Date for Salary Slip components ([#24362](https://github.com/frappe/erpnext/pull/24362))
|
||||||
|
- Configurable accounting dimension filters and validations ([#23912](https://github.com/frappe/erpnext/pull/23912))
|
||||||
|
- Issue Summary Script Report ([#23603](https://github.com/frappe/erpnext/pull/23603))
|
||||||
|
- Issue Analytics Script Report ([#23604](https://github.com/frappe/erpnext/pull/23604))
|
||||||
|
- Loan report and enhancements ([#24370](https://github.com/frappe/erpnext/pull/24370))
|
||||||
|
- Enhancements to erpnext membership ([#23865](https://github.com/frappe/erpnext/pull/23865))
|
||||||
|
- Allow Discharge despite Unbilled Healthcare Services ([#24281](https://github.com/frappe/erpnext/pull/24281))
|
||||||
|
- Patient appointment status changes ([#24201](https://github.com/frappe/erpnext/pull/24201))
|
||||||
|
- Allow selecting admission service unit in Patient Appointment for inpatients ([#24410](https://github.com/frappe/erpnext/pull/24410))
|
||||||
|
- Separate equity tree in CoA SKR04 ([#24095](https://github.com/frappe/erpnext/pull/24095))
|
||||||
|
- Do Not Bill Patient Encounters for Inpatients ([#24355](https://github.com/frappe/erpnext/pull/24355))
|
||||||
|
- Value Based and Numeric Quality Inspection ([#24181](https://github.com/frappe/erpnext/pull/24181))
|
||||||
|
- Deleting account & stock entries on deletion of transaction ([#24298](https://github.com/frappe/erpnext/pull/24298))
|
||||||
|
- Remove german sales invoice validation ([#24441](https://github.com/frappe/erpnext/pull/24441))
|
||||||
|
- Voice Call Settings doctype added ([#24126](https://github.com/frappe/erpnext/pull/24126))
|
||||||
|
- Shopping portal changes ([#24445](https://github.com/frappe/erpnext/pull/24445))
|
||||||
|
- Add "Sync Now" to Plaid Settings ([#23602](https://github.com/frappe/erpnext/pull/23602))
|
||||||
|
|
||||||
|
#### Fixes
|
||||||
|
|
||||||
|
- Multiple pricing rule with margin type as Percentage is not working ([#24204](https://github.com/frappe/erpnext/pull/24204))
|
||||||
|
- Allow statistical component in salary structure. ([#24424](https://github.com/frappe/erpnext/pull/24424))
|
||||||
|
- Set current asset value before calculating difference amount ([#24119](https://github.com/frappe/erpnext/pull/24119))
|
||||||
|
- To use Stock UoM in BOM Stock Report ([#24339](https://github.com/frappe/erpnext/pull/24339))
|
||||||
|
- Accounting entries of asset when submitting purchase receipt ([#24191](https://github.com/frappe/erpnext/pull/24191))
|
||||||
|
- Cancelling of asset value adjustement ([#24193](https://github.com/frappe/erpnext/pull/24193))
|
||||||
|
- Batch/Serial Selector for Scanned Batched Item ([#24338](https://github.com/frappe/erpnext/pull/24338))
|
||||||
|
- Link timesheets with corresponding projects ([#24346](https://github.com/frappe/erpnext/pull/24346))
|
||||||
|
- Material request wrong status issue ([#24019](https://github.com/frappe/erpnext/pull/24019))
|
||||||
|
- UX issues in e-invoicing ([#24358](https://github.com/frappe/erpnext/pull/24358))
|
||||||
|
- Company Wise Valuation Rate for RM in BOM ([#24324](https://github.com/frappe/erpnext/pull/24324))
|
||||||
|
- Stock ageing should not take cancelled stock entries. ([#24437](https://github.com/frappe/erpnext/pull/24437))
|
||||||
|
- Partial loan security unpledging ([#24252](https://github.com/frappe/erpnext/pull/24252))
|
||||||
|
- Asset depreciation ledger ([#24226](https://github.com/frappe/erpnext/pull/24226))
|
||||||
|
- Back Update from QC based on Batch No ([#24329](https://github.com/frappe/erpnext/pull/24329))
|
||||||
|
- Fix for not having fiscal year while creating new company ([#24130](https://github.com/frappe/erpnext/pull/24130))
|
||||||
|
- E-invoice print format not showing other charges ([#24474](https://github.com/frappe/erpnext/pull/24474))
|
||||||
|
- Tax template update on customer address change ([#24146](https://github.com/frappe/erpnext/pull/24146))
|
||||||
|
- Do not manufacture same serial no multiple times ([#24164](https://github.com/frappe/erpnext/pull/24164))
|
||||||
|
- Ignore group cost center validation for period closing voucher ([#24375](https://github.com/frappe/erpnext/pull/24375))
|
||||||
|
- Partial serial no return issue ([#24207](https://github.com/frappe/erpnext/pull/24207))
|
||||||
|
- GSTR-1 double entry issue ([#24376](https://github.com/frappe/erpnext/pull/24376))
|
||||||
|
- Not able to create dunning from sales invoice ([#24349](https://github.com/frappe/erpnext/pull/24349))
|
||||||
|
- Set company in leave allocation and leave ledger entry ([#24296](https://github.com/frappe/erpnext/pull/24296))
|
||||||
|
- Allow leave policy assignment to be canceled. ([#24265](https://github.com/frappe/erpnext/pull/24265))
|
||||||
|
- Removed all day event from shift assignment calendar ([#24397](https://github.com/frappe/erpnext/pull/24397))
|
||||||
|
- Tax calculation on salary slip for the first month ([#24272](https://github.com/frappe/erpnext/pull/24272))
|
||||||
|
- Validate tax template for tax category ([#24402](https://github.com/frappe/erpnext/pull/24402))
|
||||||
|
- Numeric/Non-numeric QI UX ([#24517](https://github.com/frappe/erpnext/pull/24517))
|
||||||
|
- Finished good produced qty validation ([#24220](https://github.com/frappe/erpnext/pull/24220))
|
||||||
|
- Incorrect serial no in the subcontracted purchase receipt ([#24354](https://github.com/frappe/erpnext/pull/24354))
|
||||||
|
- Don't validate warehouse values between Material Request and Stock Entry ([#24294](https://github.com/frappe/erpnext/pull/24294))
|
||||||
|
- Don't cancel job card if manufacturing entry has made ([#24063](https://github.com/frappe/erpnext/pull/24063))
|
||||||
|
- Subscription prepaid date validation ([#24356](https://github.com/frappe/erpnext/pull/24356))
|
||||||
|
- Allow addition and removal of employee in payroll Entry ([#24169](https://github.com/frappe/erpnext/pull/24169))
|
||||||
|
- Filter Therapy Types and Therapy Plan in Patient Appointment ([#24152](https://github.com/frappe/erpnext/pull/24152))
|
||||||
|
- Payment Period based on invoice date report fix/refactor ([#24378](https://github.com/frappe/erpnext/pull/24378))
|
||||||
|
- Drop ship partial order fixed ([#24072](https://github.com/frappe/erpnext/pull/24072))
|
||||||
|
- E-invoicing qrcode image generation ([#24395](https://github.com/frappe/erpnext/pull/24395))
|
||||||
|
- Payment entry multi-currency issue ([#24332](https://github.com/frappe/erpnext/pull/24332))
|
||||||
|
- Multiple pricing rule issue ([#24515](https://github.com/frappe/erpnext/pull/24515))
|
||||||
|
- Last purchase rate not updating when voucher cancelled if only one voucher is present ([#24322](https://github.com/frappe/erpnext/pull/24322))
|
||||||
|
- Do not cancel reference document on Quality Inspection cancellation ([#24197](https://github.com/frappe/erpnext/pull/24197))
|
||||||
|
- Refactored fetching & validating address from erpnext rather than gst portal ([#24297](https://github.com/frappe/erpnext/pull/24297))
|
||||||
|
- Opportunity Status fix ([#22944](https://github.com/frappe/erpnext/pull/22944))
|
||||||
|
- Extra transferred qty has not consumed against work order ([#24495](https://github.com/frappe/erpnext/pull/24495))
|
||||||
14
erpnext/change_log/v13/v13_0_0-beta_12.md
Normal file
14
erpnext/change_log/v13/v13_0_0-beta_12.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
### Version 13.0.0 Beta 12 Release Notes
|
||||||
|
#### Features
|
||||||
|
- Department wise Appointment Type charges ([#24572](https://github.com/frappe/erpnext/pull/24572))
|
||||||
|
- Capture Rate of stock UOM in purchase ([#24315](https://github.com/frappe/erpnext/pull/24315))
|
||||||
|
|
||||||
|
#### Fixes
|
||||||
|
|
||||||
|
- Fixed stock and account balance syncing ([#24644](https://github.com/frappe/erpnext/pull/24644))
|
||||||
|
- Fixed incorrect stock ledger qty in the stock ledger report and bin ([#24649](https://github.com/frappe/erpnext/pull/24649))
|
||||||
|
- Added patch to fix incorrect stock ledger and stock account value ([#24702](https://github.com/frappe/erpnext/pull/24702))
|
||||||
|
- Skip e-invoice generation for non-taxable invoices ([#24568](https://github.com/frappe/erpnext/pull/24568))
|
||||||
|
- Cannot cancel old invoices if eligible for e-invoicing ([#24608](https://github.com/frappe/erpnext/pull/24608))
|
||||||
|
- Mpesa fixes and enhancement ([#24306](https://github.com/frappe/erpnext/pull/24306))
|
||||||
|
- Fixed Consolidated Financial Statement report ([#24580](https://github.com/frappe/erpnext/pull/24580))
|
||||||
@@ -75,6 +75,9 @@ class AccountsController(TransactionBase):
|
|||||||
self.ensure_supplier_is_not_blocked()
|
self.ensure_supplier_is_not_blocked()
|
||||||
|
|
||||||
self.validate_date_with_fiscal_year()
|
self.validate_date_with_fiscal_year()
|
||||||
|
self.validate_inter_company_reference()
|
||||||
|
|
||||||
|
self.set_incoming_rate()
|
||||||
|
|
||||||
if self.meta.get_field("currency"):
|
if self.meta.get_field("currency"):
|
||||||
self.calculate_taxes_and_totals()
|
self.calculate_taxes_and_totals()
|
||||||
@@ -119,6 +122,12 @@ class AccountsController(TransactionBase):
|
|||||||
def before_cancel(self):
|
def before_cancel(self):
|
||||||
validate_einvoice_fields(self)
|
validate_einvoice_fields(self)
|
||||||
|
|
||||||
|
def on_trash(self):
|
||||||
|
# delete sl and gl entries on deletion of transaction
|
||||||
|
if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
|
||||||
|
frappe.db.sql("delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
|
||||||
|
frappe.db.sql("delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
|
||||||
|
|
||||||
def validate_deferred_start_and_end_date(self):
|
def validate_deferred_start_and_end_date(self):
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
|
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
|
||||||
@@ -206,6 +215,17 @@ class AccountsController(TransactionBase):
|
|||||||
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
|
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
|
||||||
self.meta.get_label(date_field), self)
|
self.meta.get_label(date_field), self)
|
||||||
|
|
||||||
|
def validate_inter_company_reference(self):
|
||||||
|
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.is_internal_transfer():
|
||||||
|
if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
|
||||||
|
or self.get('inter_company_order_reference')):
|
||||||
|
msg = _("Internal Sale or Delivery Reference missing. ")
|
||||||
|
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||||
|
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||||
|
|
||||||
def validate_due_date(self):
|
def validate_due_date(self):
|
||||||
if self.get('is_pos'): return
|
if self.get('is_pos'): return
|
||||||
|
|
||||||
@@ -282,6 +302,7 @@ class AccountsController(TransactionBase):
|
|||||||
args["doctype"] = self.doctype
|
args["doctype"] = self.doctype
|
||||||
args["name"] = self.name
|
args["name"] = self.name
|
||||||
args["child_docname"] = item.name
|
args["child_docname"] = item.name
|
||||||
|
args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0
|
||||||
|
|
||||||
if not args.get("transaction_date"):
|
if not args.get("transaction_date"):
|
||||||
args["transaction_date"] = args.get("posting_date")
|
args["transaction_date"] = args.get("posting_date")
|
||||||
@@ -448,8 +469,10 @@ class AccountsController(TransactionBase):
|
|||||||
account_currency = get_account_currency(gl_dict.account)
|
account_currency = get_account_currency(gl_dict.account)
|
||||||
|
|
||||||
if gl_dict.account and self.doctype not in ["Journal Entry",
|
if gl_dict.account and self.doctype not in ["Journal Entry",
|
||||||
"Period Closing Voucher", "Payment Entry"]:
|
"Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
|
||||||
self.validate_account_currency(gl_dict.account, account_currency)
|
self.validate_account_currency(gl_dict.account, account_currency)
|
||||||
|
|
||||||
|
if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]:
|
||||||
set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
|
set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
|
||||||
self.company_currency)
|
self.company_currency)
|
||||||
|
|
||||||
@@ -962,9 +985,9 @@ class AccountsController(TransactionBase):
|
|||||||
It will an internal transfer if its an internal customer and representation
|
It will an internal transfer if its an internal customer and representation
|
||||||
company is same as billing company
|
company is same as billing company
|
||||||
"""
|
"""
|
||||||
if self.doctype == 'Sales Invoice':
|
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Sales Order'):
|
||||||
internal_party_field = 'is_internal_customer'
|
internal_party_field = 'is_internal_customer'
|
||||||
else:
|
elif self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||||
internal_party_field = 'is_internal_supplier'
|
internal_party_field = 'is_internal_supplier'
|
||||||
|
|
||||||
if self.get(internal_party_field) and (self.represents_company == self.company):
|
if self.get(internal_party_field) and (self.represents_company == self.company):
|
||||||
@@ -1481,6 +1504,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
parent.flags.ignore_validate_update_after_submit = True
|
parent.flags.ignore_validate_update_after_submit = True
|
||||||
parent.set_qty_as_per_stock_uom()
|
parent.set_qty_as_per_stock_uom()
|
||||||
parent.calculate_taxes_and_totals()
|
parent.calculate_taxes_and_totals()
|
||||||
|
parent.set_total_in_words()
|
||||||
if parent_doctype == "Sales Order":
|
if parent_doctype == "Sales Order":
|
||||||
make_packing_list(parent)
|
make_packing_list(parent)
|
||||||
parent.set_gross_profit()
|
parent.set_gross_profit()
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ class BuyingController(StockController):
|
|||||||
self.validate_items()
|
self.validate_items()
|
||||||
self.set_qty_as_per_stock_uom()
|
self.set_qty_as_per_stock_uom()
|
||||||
self.validate_stock_or_nonstock_items()
|
self.validate_stock_or_nonstock_items()
|
||||||
self.update_tax_category_for_internal_transfer()
|
|
||||||
self.validate_warehouse()
|
self.validate_warehouse()
|
||||||
self.validate_from_warehouse()
|
self.validate_from_warehouse()
|
||||||
self.set_supplier_address()
|
self.set_supplier_address()
|
||||||
@@ -100,11 +99,6 @@ class BuyingController(StockController):
|
|||||||
msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
|
msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
|
||||||
self.update_tax_category(msg)
|
self.update_tax_category(msg)
|
||||||
|
|
||||||
def update_tax_category_for_internal_transfer(self):
|
|
||||||
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
|
|
||||||
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
|
|
||||||
self.update_tax_category(msg)
|
|
||||||
|
|
||||||
def update_tax_category(self, msg):
|
def update_tax_category(self, msg):
|
||||||
tax_for_valuation = [d for d in self.get("taxes")
|
tax_for_valuation = [d for d in self.get("taxes")
|
||||||
if d.category in ["Valuation", "Valuation and Total"]]
|
if d.category in ["Valuation", "Valuation and Total"]]
|
||||||
@@ -224,6 +218,48 @@ class BuyingController(StockController):
|
|||||||
else:
|
else:
|
||||||
item.valuation_rate = 0.0
|
item.valuation_rate = 0.0
|
||||||
|
|
||||||
|
def set_incoming_rate(self):
|
||||||
|
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
|
||||||
|
return
|
||||||
|
|
||||||
|
ref_doctype_map = {
|
||||||
|
"Purchase Order": "Sales Order Item",
|
||||||
|
"Purchase Receipt": "Delivery Note Item",
|
||||||
|
"Purchase Invoice": "Sales Invoice Item",
|
||||||
|
}
|
||||||
|
|
||||||
|
ref_doctype = ref_doctype_map.get(self.doctype)
|
||||||
|
items = self.get("items")
|
||||||
|
for d in items:
|
||||||
|
if not cint(self.get("is_return")):
|
||||||
|
# Get outgoing rate based on original item cost based on valuation method
|
||||||
|
|
||||||
|
if not d.get(frappe.scrub(ref_doctype)):
|
||||||
|
outgoing_rate = get_incoming_rate({
|
||||||
|
"item_code": d.item_code,
|
||||||
|
"warehouse": d.get('from_warehouse'),
|
||||||
|
"posting_date": self.get('posting_date') or self.get('transation_date'),
|
||||||
|
"posting_time": self.get('posting_time'),
|
||||||
|
"qty": -1 * flt(d.get('stock_qty')),
|
||||||
|
"serial_no": d.get('serial_no'),
|
||||||
|
"company": self.company,
|
||||||
|
"voucher_type": self.doctype,
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||||
|
}, raise_error_if_no_rate=False)
|
||||||
|
|
||||||
|
rate = flt(outgoing_rate * d.conversion_factor, d.precision('rate'))
|
||||||
|
else:
|
||||||
|
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), 'rate')
|
||||||
|
|
||||||
|
if self.is_internal_transfer():
|
||||||
|
if rate != d.rate:
|
||||||
|
d.rate = rate
|
||||||
|
d.discount_percentage = 0
|
||||||
|
d.discount_amount = 0
|
||||||
|
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
|
||||||
|
.format(d.idx), alert=1)
|
||||||
|
|
||||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||||
supplied_items_cost = 0.0
|
supplied_items_cost = 0.0
|
||||||
for d in self.get("supplied_items"):
|
for d in self.get("supplied_items"):
|
||||||
@@ -252,7 +288,7 @@ class BuyingController(StockController):
|
|||||||
|
|
||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted == "Yes":
|
||||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
||||||
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted Purchase Receipt"))
|
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype))
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item in self.sub_contracted_items and not item.bom:
|
if item in self.sub_contracted_items and not item.bom:
|
||||||
@@ -336,7 +372,7 @@ class BuyingController(StockController):
|
|||||||
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
||||||
|
|
||||||
consumed_qty = raw_material_data.get('qty', 0)
|
consumed_qty = raw_material_data.get('qty', 0)
|
||||||
consumed_serial_nos = raw_material_data.get('serial_nos', '')
|
consumed_serial_nos = raw_material_data.get('serial_no', '')
|
||||||
consumed_batch_nos = raw_material_data.get('batch_nos', '')
|
consumed_batch_nos = raw_material_data.get('batch_nos', '')
|
||||||
|
|
||||||
transferred_qty = raw_material.qty
|
transferred_qty = raw_material.qty
|
||||||
@@ -559,6 +595,8 @@ class BuyingController(StockController):
|
|||||||
from_warehouse_sle = self.get_sl_entries(d, {
|
from_warehouse_sle = self.get_sl_entries(d, {
|
||||||
"actual_qty": -1 * pr_qty,
|
"actual_qty": -1 * pr_qty,
|
||||||
"warehouse": d.from_warehouse,
|
"warehouse": d.from_warehouse,
|
||||||
|
"outgoing_rate": d.rate,
|
||||||
|
"recalculate_rate": 1,
|
||||||
"dependant_sle_voucher_detail_no": d.name
|
"dependant_sle_voucher_detail_no": d.name
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -493,6 +493,41 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
'company': filters.get("company", "")
|
'company': filters.get("company", "")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
|
||||||
|
dimension_filters = get_dimension_filter_map()
|
||||||
|
dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
|
||||||
|
query_filters = []
|
||||||
|
|
||||||
|
meta = frappe.get_meta(doctype)
|
||||||
|
if meta.is_tree:
|
||||||
|
query_filters.append(['is_group', '=', 0])
|
||||||
|
|
||||||
|
if meta.has_field('company'):
|
||||||
|
query_filters.append(['company', '=', filters.get('company')])
|
||||||
|
|
||||||
|
if txt:
|
||||||
|
query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt])
|
||||||
|
|
||||||
|
if dimension_filters:
|
||||||
|
if dimension_filters['allow_or_restrict'] == 'Allow':
|
||||||
|
query_selector = 'in'
|
||||||
|
else:
|
||||||
|
query_selector = 'not in'
|
||||||
|
|
||||||
|
if len(dimension_filters['allowed_dimensions']) == 1:
|
||||||
|
dimensions = tuple(dimension_filters['allowed_dimensions'] * 2)
|
||||||
|
else:
|
||||||
|
dimensions = tuple(dimension_filters['allowed_dimensions'])
|
||||||
|
|
||||||
|
query_filters.append(['name', query_selector, dimensions])
|
||||||
|
|
||||||
|
output = frappe.get_all(doctype, filters=query_filters)
|
||||||
|
result = [d.name for d in output]
|
||||||
|
|
||||||
|
return [(d,) for d in set(result)]
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
@@ -620,6 +655,34 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
return frappe.db.sql(query, filters)
|
return frappe.db.sql(query, filters)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
query = """
|
||||||
|
select name
|
||||||
|
from `tabHealthcare Service Unit`
|
||||||
|
where
|
||||||
|
is_group = 0
|
||||||
|
and company = {company}
|
||||||
|
and name like {txt}""".format(
|
||||||
|
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||||
|
|
||||||
|
if filters and filters.get('inpatient_record'):
|
||||||
|
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
|
||||||
|
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
|
||||||
|
|
||||||
|
# if the patient is admitted, then appointments should be allowed against the admission service unit,
|
||||||
|
# inspite of it being an Inpatient Occupancy service unit
|
||||||
|
if service_unit:
|
||||||
|
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
|
||||||
|
else:
|
||||||
|
query += " and allow_appointments = 1"
|
||||||
|
else:
|
||||||
|
query += " and allow_appointments = 1"
|
||||||
|
|
||||||
|
return frappe.db.sql(query, filters)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
|
|
||||||
if doc.get("is_return"):
|
if doc.get("is_return"):
|
||||||
if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice':
|
if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice':
|
||||||
|
doc.consolidated_invoice = ""
|
||||||
doc.set('payments', [])
|
doc.set('payments', [])
|
||||||
for data in source.payments:
|
for data in source.payments:
|
||||||
paid_amount = 0.00
|
paid_amount = 0.00
|
||||||
@@ -328,6 +329,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
target_doc.po_detail = source_doc.po_detail
|
target_doc.po_detail = source_doc.po_detail
|
||||||
target_doc.pr_detail = source_doc.pr_detail
|
target_doc.pr_detail = source_doc.pr_detail
|
||||||
target_doc.purchase_invoice_item = source_doc.name
|
target_doc.purchase_invoice_item = source_doc.name
|
||||||
|
target_doc.price_list_rate = 0
|
||||||
|
|
||||||
elif doctype == "Delivery Note":
|
elif doctype == "Delivery Note":
|
||||||
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
||||||
@@ -353,6 +355,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
target_doc.dn_detail = source_doc.dn_detail
|
target_doc.dn_detail = source_doc.dn_detail
|
||||||
target_doc.expense_account = source_doc.expense_account
|
target_doc.expense_account = source_doc.expense_account
|
||||||
target_doc.sales_invoice_item = source_doc.name
|
target_doc.sales_invoice_item = source_doc.name
|
||||||
|
target_doc.price_list_rate = 0
|
||||||
if default_warehouse_for_sales_return:
|
if default_warehouse_for_sales_return:
|
||||||
target_doc.warehouse = default_warehouse_for_sales_return
|
target_doc.warehouse = default_warehouse_for_sales_return
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form
|
from frappe.utils import cint, flt, cstr, get_link_to_form, nowtime
|
||||||
from frappe import _, throw
|
from frappe import _, throw
|
||||||
from erpnext.stock.get_item_details import get_bin_details
|
from erpnext.stock.get_item_details import get_bin_details
|
||||||
from erpnext.stock.utils import get_incoming_rate
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
@@ -49,7 +49,6 @@ class SellingController(StockController):
|
|||||||
self.set_customer_address()
|
self.set_customer_address()
|
||||||
self.validate_for_duplicate_items()
|
self.validate_for_duplicate_items()
|
||||||
self.validate_target_warehouse()
|
self.validate_target_warehouse()
|
||||||
self.set_incoming_rate()
|
|
||||||
|
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
|
|
||||||
@@ -233,7 +232,7 @@ class SellingController(StockController):
|
|||||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||||
'dn_detail': d.get("dn_detail"),
|
'dn_detail': d.get("dn_detail"),
|
||||||
'incoming_rate': p.incoming_rate
|
'incoming_rate': p.get("incoming_rate")
|
||||||
}))
|
}))
|
||||||
else:
|
else:
|
||||||
il.append(frappe._dict({
|
il.append(frappe._dict({
|
||||||
@@ -252,7 +251,7 @@ class SellingController(StockController):
|
|||||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||||
'dn_detail': d.get("dn_detail"),
|
'dn_detail': d.get("dn_detail"),
|
||||||
'incoming_rate': d.incoming_rate
|
'incoming_rate': d.get("incoming_rate")
|
||||||
}))
|
}))
|
||||||
return il
|
return il
|
||||||
|
|
||||||
@@ -312,7 +311,7 @@ class SellingController(StockController):
|
|||||||
sales_order.update_reserved_qty(so_item_rows)
|
sales_order.update_reserved_qty(so_item_rows)
|
||||||
|
|
||||||
def set_incoming_rate(self):
|
def set_incoming_rate(self):
|
||||||
if self.doctype not in ("Delivery Note", "Sales Invoice"):
|
if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
|
||||||
return
|
return
|
||||||
|
|
||||||
items = self.get("items") + (self.get("packed_items") or [])
|
items = self.get("items") + (self.get("packed_items") or [])
|
||||||
@@ -322,15 +321,26 @@ class SellingController(StockController):
|
|||||||
d.incoming_rate = get_incoming_rate({
|
d.incoming_rate = get_incoming_rate({
|
||||||
"item_code": d.item_code,
|
"item_code": d.item_code,
|
||||||
"warehouse": d.warehouse,
|
"warehouse": d.warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.get('posting_date') or self.get('transaction_date'),
|
||||||
"posting_time": self.posting_time,
|
"posting_time": self.get('posting_time') or nowtime(),
|
||||||
"qty": -1*flt(d.qty),
|
"qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
|
||||||
"serial_no": d.serial_no,
|
"serial_no": d.get('serial_no'),
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||||
}, raise_error_if_no_rate=False)
|
}, raise_error_if_no_rate=False)
|
||||||
|
|
||||||
|
# For internal transfers use incoming rate as the valuation rate
|
||||||
|
if self.is_internal_transfer():
|
||||||
|
rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate'))
|
||||||
|
if d.rate != rate:
|
||||||
|
d.rate = rate
|
||||||
|
d.discount_percentage = 0
|
||||||
|
d.discount_amount = 0
|
||||||
|
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
|
||||||
|
.format(d.idx), alert=1)
|
||||||
|
|
||||||
elif self.get("return_against"):
|
elif self.get("return_against"):
|
||||||
# Get incoming rate of return entry from reference document
|
# Get incoming rate of return entry from reference document
|
||||||
# based on original item cost as per valuation method
|
# based on original item cost as per valuation method
|
||||||
@@ -446,9 +456,13 @@ class SellingController(StockController):
|
|||||||
check_list, chk_dupl_itm = [], []
|
check_list, chk_dupl_itm = [], []
|
||||||
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
|
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
|
||||||
return
|
return
|
||||||
|
if self.doctype == "Sales Invoice" and self.is_consolidated:
|
||||||
|
return
|
||||||
|
if self.doctype == "POS Invoice":
|
||||||
|
return
|
||||||
|
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if self.doctype in ["POS Invoice","Sales Invoice"]:
|
if self.doctype == "Sales Invoice":
|
||||||
stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
|
stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
|
||||||
non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
|
non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
|
||||||
elif self.doctype == "Delivery Note":
|
elif self.doctype == "Delivery Note":
|
||||||
@@ -459,13 +473,19 @@ class SellingController(StockController):
|
|||||||
non_stock_items = [d.item_code, d.description]
|
non_stock_items = [d.item_code, d.description]
|
||||||
|
|
||||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
||||||
|
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
|
||||||
|
duplicate_items_msg += "<br><br>"
|
||||||
|
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
|
||||||
|
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
|
||||||
|
get_link_to_form("Selling Settings", "Selling Settings")
|
||||||
|
)
|
||||||
if stock_items in check_list:
|
if stock_items in check_list:
|
||||||
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
|
frappe.throw(duplicate_items_msg)
|
||||||
else:
|
else:
|
||||||
check_list.append(stock_items)
|
check_list.append(stock_items)
|
||||||
else:
|
else:
|
||||||
if non_stock_items in chk_dupl_itm:
|
if non_stock_items in chk_dupl_itm:
|
||||||
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
|
frappe.throw(duplicate_items_msg)
|
||||||
else:
|
else:
|
||||||
chk_dupl_itm.append(non_stock_items)
|
chk_dupl_itm.append(non_stock_items)
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,12 @@ status_map = {
|
|||||||
["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"],
|
["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"],
|
||||||
["Closed", "eval:self.docstatus == 1 and self.pos_closing_entry"],
|
["Closed", "eval:self.docstatus == 1 and self.pos_closing_entry"],
|
||||||
["Cancelled", "eval:self.docstatus == 2"],
|
["Cancelled", "eval:self.docstatus == 2"],
|
||||||
|
],
|
||||||
|
"POS Closing Entry": [
|
||||||
|
["Draft", None],
|
||||||
|
["Submitted", "eval:self.docstatus == 1"],
|
||||||
|
["Queued", "eval:self.status == 'Queued'"],
|
||||||
|
["Cancelled", "eval:self.docstatus == 2"],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import frappe, erpnext
|
|||||||
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
|
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
|
||||||
from frappe import _
|
from frappe import _
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
|
from collections import defaultdict
|
||||||
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
|
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
|
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
@@ -23,6 +24,9 @@ class StockController(AccountsController):
|
|||||||
self.validate_inspection()
|
self.validate_inspection()
|
||||||
self.validate_serialized_batch()
|
self.validate_serialized_batch()
|
||||||
self.validate_customer_provided_item()
|
self.validate_customer_provided_item()
|
||||||
|
self.set_rate_of_stock_uom()
|
||||||
|
self.validate_internal_transfer()
|
||||||
|
self.validate_putaway_capacity()
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
@@ -70,8 +74,9 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
gl_list = []
|
gl_list = []
|
||||||
warehouse_with_no_account = []
|
warehouse_with_no_account = []
|
||||||
precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
|
precision = self.get_debit_field_precision()
|
||||||
for item_row in voucher_details:
|
for item_row in voucher_details:
|
||||||
|
|
||||||
sle_list = sle_map.get(item_row.name)
|
sle_list = sle_map.get(item_row.name)
|
||||||
if sle_list:
|
if sle_list:
|
||||||
for sle in sle_list:
|
for sle in sle_list:
|
||||||
@@ -126,7 +131,13 @@ class StockController(AccountsController):
|
|||||||
if frappe.db.get_value("Warehouse", wh, "company"):
|
if frappe.db.get_value("Warehouse", wh, "company"):
|
||||||
frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
|
frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
|
||||||
|
|
||||||
return process_gl_map(gl_list)
|
return process_gl_map(gl_list, precision=precision)
|
||||||
|
|
||||||
|
def get_debit_field_precision(self):
|
||||||
|
if not frappe.flags.debit_field_precision:
|
||||||
|
frappe.flags.debit_field_precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
|
||||||
|
|
||||||
|
return frappe.flags.debit_field_precision
|
||||||
|
|
||||||
def update_stock_ledger_entries(self, sle):
|
def update_stock_ledger_entries(self, sle):
|
||||||
sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
||||||
@@ -216,7 +227,7 @@ class StockController(AccountsController):
|
|||||||
""", (self.doctype, self.name), as_dict=True)
|
""", (self.doctype, self.name), as_dict=True)
|
||||||
|
|
||||||
for sle in stock_ledger_entries:
|
for sle in stock_ledger_entries:
|
||||||
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||||
return stock_ledger
|
return stock_ledger
|
||||||
|
|
||||||
def make_batches(self, warehouse_field):
|
def make_batches(self, warehouse_field):
|
||||||
@@ -239,7 +250,7 @@ class StockController(AccountsController):
|
|||||||
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
|
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
is_expense_account = frappe.db.get_value("Account",
|
is_expense_account = frappe.get_cached_value("Account",
|
||||||
item.get("expense_account"), "report_type")=="Profit and Loss"
|
item.get("expense_account"), "report_type")=="Profit and Loss"
|
||||||
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account:
|
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account:
|
||||||
frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account")
|
frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account")
|
||||||
@@ -391,6 +402,89 @@ class StockController(AccountsController):
|
|||||||
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
||||||
d.allow_zero_valuation_rate = 1
|
d.allow_zero_valuation_rate = 1
|
||||||
|
|
||||||
|
def set_rate_of_stock_uom(self):
|
||||||
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
|
||||||
|
for d in self.get("items"):
|
||||||
|
d.stock_uom_rate = d.rate / d.conversion_factor
|
||||||
|
|
||||||
|
def validate_internal_transfer(self):
|
||||||
|
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
|
||||||
|
and self.is_internal_transfer():
|
||||||
|
self.validate_in_transit_warehouses()
|
||||||
|
self.validate_multi_currency()
|
||||||
|
self.validate_packed_items()
|
||||||
|
|
||||||
|
def validate_in_transit_warehouses(self):
|
||||||
|
if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note':
|
||||||
|
for item in self.get('items'):
|
||||||
|
if not item.target_warehouse:
|
||||||
|
frappe.throw(_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx))
|
||||||
|
|
||||||
|
if (self.doctype == 'Purchase Invoice' and self.get('update_stock')) or self.doctype == 'Purchase Receipt':
|
||||||
|
for item in self.get('items'):
|
||||||
|
if not item.from_warehouse:
|
||||||
|
frappe.throw(_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx))
|
||||||
|
|
||||||
|
def validate_multi_currency(self):
|
||||||
|
if self.currency != self.company_currency:
|
||||||
|
frappe.throw(_("Internal transfers can only be done in company's default currency"))
|
||||||
|
|
||||||
|
def validate_packed_items(self):
|
||||||
|
if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'):
|
||||||
|
frappe.throw(_("Packed Items cannot be transferred internally"))
|
||||||
|
|
||||||
|
def validate_putaway_capacity(self):
|
||||||
|
# if over receipt is attempted while 'apply putaway rule' is disabled
|
||||||
|
# and if rule was applied on the transaction, validate it.
|
||||||
|
from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
|
||||||
|
valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
|
||||||
|
"Stock Reconciliation")
|
||||||
|
|
||||||
|
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
|
||||||
|
valid_doctype = False
|
||||||
|
|
||||||
|
if valid_doctype:
|
||||||
|
rule_map = defaultdict(dict)
|
||||||
|
for item in self.get("items"):
|
||||||
|
warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
|
||||||
|
rule = frappe.db.get_value("Putaway Rule",
|
||||||
|
{
|
||||||
|
"item_code": item.get("item_code"),
|
||||||
|
"warehouse": item.get(warehouse_field)
|
||||||
|
},
|
||||||
|
["name", "disable"], as_dict=True)
|
||||||
|
if rule:
|
||||||
|
if rule.get("disabled"): continue # dont validate for disabled rule
|
||||||
|
|
||||||
|
if self.doctype == "Stock Reconciliation":
|
||||||
|
stock_qty = flt(item.qty)
|
||||||
|
else:
|
||||||
|
stock_qty = flt(item.transfer_qty) if self.doctype == "Stock Entry" else flt(item.stock_qty)
|
||||||
|
|
||||||
|
rule_name = rule.get("name")
|
||||||
|
if not rule_map[rule_name]:
|
||||||
|
rule_map[rule_name]["warehouse"] = item.get(warehouse_field)
|
||||||
|
rule_map[rule_name]["item"] = item.get("item_code")
|
||||||
|
rule_map[rule_name]["qty_put"] = 0
|
||||||
|
rule_map[rule_name]["capacity"] = get_available_putaway_capacity(rule_name)
|
||||||
|
rule_map[rule_name]["qty_put"] += flt(stock_qty)
|
||||||
|
|
||||||
|
for rule, values in rule_map.items():
|
||||||
|
if flt(values["qty_put"]) > flt(values["capacity"]):
|
||||||
|
message = self.prepare_over_receipt_message(rule, values)
|
||||||
|
frappe.throw(msg=message, title=_("Over Receipt"))
|
||||||
|
|
||||||
|
def prepare_over_receipt_message(self, rule, values):
|
||||||
|
message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
|
||||||
|
.format(
|
||||||
|
frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
|
||||||
|
frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
|
||||||
|
)
|
||||||
|
message += "<br><br>"
|
||||||
|
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
|
||||||
|
message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||||
|
return message
|
||||||
|
|
||||||
def repost_future_sle_and_gle(self):
|
def repost_future_sle_and_gle(self):
|
||||||
args = frappe._dict({
|
args = frappe._dict({
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
@@ -399,7 +493,6 @@ class StockController(AccountsController):
|
|||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"company": self.company
|
"company": self.company
|
||||||
})
|
})
|
||||||
|
|
||||||
if check_if_future_sle_exists(args):
|
if check_if_future_sle_exists(args):
|
||||||
create_repost_item_valuation_entry(args)
|
create_repost_item_valuation_entry(args)
|
||||||
elif not is_reposting_pending():
|
elif not is_reposting_pending():
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from erpnext.controllers.accounts_controller import validate_conversion_rate, \
|
|||||||
validate_taxes_and_charges, validate_inclusive_tax
|
validate_taxes_and_charges, validate_inclusive_tax
|
||||||
from erpnext.stock.get_item_details import _get_item_tax_template
|
from erpnext.stock.get_item_details import _get_item_tax_template
|
||||||
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
|
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
|
||||||
|
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
|
||||||
|
|
||||||
class calculate_taxes_and_totals(object):
|
class calculate_taxes_and_totals(object):
|
||||||
def __init__(self, doc):
|
def __init__(self, doc):
|
||||||
@@ -106,7 +107,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
elif item.discount_amount and item.pricing_rules:
|
elif item.discount_amount and item.pricing_rules:
|
||||||
item.rate = item.price_list_rate - item.discount_amount
|
item.rate = item.price_list_rate - item.discount_amount
|
||||||
|
|
||||||
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']:
|
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']:
|
||||||
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
|
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
|
||||||
if flt(item.rate_with_margin) > 0:
|
if flt(item.rate_with_margin) > 0:
|
||||||
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
|
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
|
||||||
@@ -615,7 +616,6 @@ class calculate_taxes_and_totals(object):
|
|||||||
self.doc.precision("base_write_off_amount"))
|
self.doc.precision("base_write_off_amount"))
|
||||||
|
|
||||||
def calculate_margin(self, item):
|
def calculate_margin(self, item):
|
||||||
|
|
||||||
rate_with_margin = 0.0
|
rate_with_margin = 0.0
|
||||||
base_rate_with_margin = 0.0
|
base_rate_with_margin = 0.0
|
||||||
if item.price_list_rate:
|
if item.price_list_rate:
|
||||||
@@ -624,8 +624,8 @@ class calculate_taxes_and_totals(object):
|
|||||||
for d in get_applied_pricing_rules(item.pricing_rules):
|
for d in get_applied_pricing_rules(item.pricing_rules):
|
||||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||||
|
|
||||||
if (pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == self.doc.currency)\
|
if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
|
||||||
or (pricing_rule.margin_type == 'Percentage'):
|
pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
|
||||||
item.margin_type = pricing_rule.margin_type
|
item.margin_type = pricing_rule.margin_type
|
||||||
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||||
has_margin = True
|
has_margin = True
|
||||||
@@ -758,3 +758,35 @@ def get_rounded_tax_amount(itemised_tax, precision):
|
|||||||
for taxes in itemised_tax.values():
|
for taxes in itemised_tax.values():
|
||||||
for tax_account in taxes:
|
for tax_account in taxes:
|
||||||
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
|
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
|
||||||
|
|
||||||
|
class init_landed_taxes_and_totals(object):
|
||||||
|
def __init__(self, doc):
|
||||||
|
self.doc = doc
|
||||||
|
self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
|
||||||
|
self.set_account_currency()
|
||||||
|
self.set_exchange_rate()
|
||||||
|
self.set_amounts_in_company_currency()
|
||||||
|
|
||||||
|
def set_account_currency(self):
|
||||||
|
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||||
|
for d in self.doc.get(self.tax_field):
|
||||||
|
if not d.account_currency:
|
||||||
|
account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
|
||||||
|
d.account_currency = account_currency or company_currency
|
||||||
|
|
||||||
|
def set_exchange_rate(self):
|
||||||
|
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||||
|
for d in self.doc.get(self.tax_field):
|
||||||
|
if d.account_currency == company_currency:
|
||||||
|
d.exchange_rate = 1
|
||||||
|
elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date:
|
||||||
|
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
|
||||||
|
account_currency=d.account_currency, company=self.doc.company)
|
||||||
|
|
||||||
|
if not d.exchange_rate:
|
||||||
|
frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
|
||||||
|
|
||||||
|
def set_amounts_in_company_currency(self):
|
||||||
|
for d in self.doc.get(self.tax_field):
|
||||||
|
d.amount = flt(d.amount, d.precision("amount"))
|
||||||
|
d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
|
||||||
@@ -6,6 +6,7 @@ import unittest
|
|||||||
|
|
||||||
from erpnext.stock.doctype.item.test_item import set_item_variant_settings
|
from erpnext.stock.doctype.item.test_item import set_item_variant_settings
|
||||||
from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
|
from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
|
||||||
|
from erpnext.stock.doctype.quality_inspection.test_quality_inspection import create_quality_inspection_parameter
|
||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ def make_quality_inspection_template():
|
|||||||
|
|
||||||
qc = frappe.new_doc("Quality Inspection Template")
|
qc = frappe.new_doc("Quality Inspection Template")
|
||||||
qc.quality_inspection_template_name = qc_template
|
qc.quality_inspection_template_name = qc_template
|
||||||
|
|
||||||
|
create_quality_inspection_parameter("Moisture")
|
||||||
qc.append('item_quality_inspection_parameter', {
|
qc.append('item_quality_inspection_parameter', {
|
||||||
"specification": "Moisture",
|
"specification": "Moisture",
|
||||||
"value": "< 5%",
|
"value": "< 5%",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class Appointment(Document):
|
|||||||
add_assignemnt({
|
add_assignemnt({
|
||||||
'doctype': self.doctype,
|
'doctype': self.doctype,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'assign_to': existing_assignee
|
'assign_to': [existing_assignee]
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
if self._assign:
|
if self._assign:
|
||||||
@@ -139,7 +139,7 @@ class Appointment(Document):
|
|||||||
add_assignemnt({
|
add_assignemnt({
|
||||||
'doctype': self.doctype,
|
'doctype': self.doctype,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'assign_to': agent
|
'assign_to': [agent]
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user