Merge branch 'develop' of github.com:frappe/erpnext into employee-leave-balance-summary
This commit is contained in:
commit
a14ee7f1fe
@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '12.1.0'
|
__version__ = '12.1.1'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
@ -12,11 +12,14 @@ from frappe.utils.nestedset import get_descendants_of
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@cache_source
|
@cache_source
|
||||||
def get(chart_name=None, from_date = None, to_date = None):
|
def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_date = None):
|
||||||
|
if chart_name:
|
||||||
chart = frappe.get_doc('Dashboard Chart', chart_name)
|
chart = frappe.get_doc('Dashboard Chart', chart_name)
|
||||||
|
else:
|
||||||
|
chart = frappe._dict(frappe.parse_json(chart))
|
||||||
timespan = chart.timespan
|
timespan = chart.timespan
|
||||||
timegrain = chart.time_interval
|
timegrain = chart.time_interval
|
||||||
filters = json.loads(chart.filters_json)
|
filters = frappe.parse_json(chart.filters_json)
|
||||||
|
|
||||||
account = filters.get("account")
|
account = filters.get("account")
|
||||||
company = filters.get("company")
|
company = filters.get("company")
|
||||||
|
@ -407,6 +407,10 @@
|
|||||||
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
|
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
|
||||||
"account_number": "9960"
|
"account_number": "9960"
|
||||||
},
|
},
|
||||||
|
"Debitoren": {
|
||||||
|
"is_group": 1,
|
||||||
|
"account_number": "10000"
|
||||||
|
},
|
||||||
"Forderungen aus Lieferungen und Leistungen": {
|
"Forderungen aus Lieferungen und Leistungen": {
|
||||||
"account_number": "1200",
|
"account_number": "1200",
|
||||||
"account_type": "Receivable"
|
"account_type": "Receivable"
|
||||||
@ -1077,7 +1081,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"C - Verb.": {
|
"C - Verbindlichkeiten": {
|
||||||
"account_type": "Payable",
|
"account_type": "Payable",
|
||||||
"1 - Anleihen": {
|
"1 - Anleihen": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
@ -1194,6 +1198,14 @@
|
|||||||
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
|
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
|
||||||
"account_number": "9964"
|
"account_number": "9964"
|
||||||
},
|
},
|
||||||
|
"Kreditoren": {
|
||||||
|
"account_number": "70000",
|
||||||
|
"is_group": 1,
|
||||||
|
"Wareneingangs-Verrechnungskonto" : {
|
||||||
|
"account_number": "70001",
|
||||||
|
"account_type": "Stock Received But Not Billed"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Verb. aus Lieferungen und Leistungen": {
|
"Verb. aus Lieferungen und Leistungen": {
|
||||||
"account_number": "3300",
|
"account_number": "3300",
|
||||||
"account_type": "Payable"
|
"account_type": "Payable"
|
||||||
@ -1682,90 +1694,6 @@
|
|||||||
"account_type": "Income Account"
|
"account_type": "Income Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Erl\u00f6sschm\u00e4lerungen (Gruppe)": {
|
|
||||||
"is_group": 1,
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen": {
|
|
||||||
"account_number": "4700"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen aus steuerfreien Ums\u00e4tzen \u00a7 4 Nr. 1a UStG": {
|
|
||||||
"account_number": "4705"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen 7 % USt": {
|
|
||||||
"account_number": "4710"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen 19 % USt": {
|
|
||||||
"account_number": "4720"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen 16 % USt": {
|
|
||||||
"account_number": "4723"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen aus steuerfreien innergem. Lieferungen": {
|
|
||||||
"account_number": "4724"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 7 % USt": {
|
|
||||||
"account_number": "4725"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 19 % USt": {
|
|
||||||
"account_number": "4726"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen aus im anderen EU-Land steuerpfl. Lieferungen": {
|
|
||||||
"account_number": "4727"
|
|
||||||
},
|
|
||||||
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
|
|
||||||
"account_number": "4729"
|
|
||||||
},
|
|
||||||
"Gew\u00e4hrte Skonti (Gruppe)": {
|
|
||||||
"is_group": 1,
|
|
||||||
"Gew. Skonti": {
|
|
||||||
"account_number": "4730"
|
|
||||||
},
|
|
||||||
"Gew. Skonti 7 % USt": {
|
|
||||||
"account_number": "4731"
|
|
||||||
},
|
|
||||||
"Gew. Skonti 19 % USt": {
|
|
||||||
"account_number": "4736"
|
|
||||||
},
|
|
||||||
"Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": {
|
|
||||||
"account_number": "4738"
|
|
||||||
},
|
|
||||||
"Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": {
|
|
||||||
"account_number": "4741"
|
|
||||||
},
|
|
||||||
"Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": {
|
|
||||||
"account_number": "4742"
|
|
||||||
},
|
|
||||||
"Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": {
|
|
||||||
"account_number": "4743"
|
|
||||||
},
|
|
||||||
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": {
|
|
||||||
"account_number": "4745"
|
|
||||||
},
|
|
||||||
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": {
|
|
||||||
"account_number": "4746"
|
|
||||||
},
|
|
||||||
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": {
|
|
||||||
"account_number": "4748"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Gew\u00e4hrte Boni 7 % USt": {
|
|
||||||
"account_number": "4750"
|
|
||||||
},
|
|
||||||
"Gew\u00e4hrte Boni 19 % USt": {
|
|
||||||
"account_number": "4760"
|
|
||||||
},
|
|
||||||
"Gew\u00e4hrte Boni": {
|
|
||||||
"account_number": "4769"
|
|
||||||
},
|
|
||||||
"Gew\u00e4hrte Rabatte": {
|
|
||||||
"account_number": "4770"
|
|
||||||
},
|
|
||||||
"Gew\u00e4hrte Rabatte 7 % USt": {
|
|
||||||
"account_number": "4780"
|
|
||||||
},
|
|
||||||
"Gew\u00e4hrte Rabatte 19 % USt": {
|
|
||||||
"account_number": "4790"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Grundst\u00fccksertr\u00e4ge (Gruppe)": {
|
"Grundst\u00fccksertr\u00e4ge (Gruppe)": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Grundst\u00fccksertr\u00e4ge": {
|
"Grundst\u00fccksertr\u00e4ge": {
|
||||||
@ -2049,48 +1977,6 @@
|
|||||||
"Erh. Skonti aus Erwerb Waren als letzter Abnehmer innerh. Dreiecksgesch. 19% Vorst. u. 19% Ust.": {
|
"Erh. Skonti aus Erwerb Waren als letzter Abnehmer innerh. Dreiecksgesch. 19% Vorst. u. 19% Ust.": {
|
||||||
"account_number": "5793"
|
"account_number": "5793"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Erhaltene Boni (Gruppe)": {
|
|
||||||
"is_group": 1,
|
|
||||||
"Erhaltene Boni 7 % Vorsteuer": {
|
|
||||||
"account_number": "5750"
|
|
||||||
},
|
|
||||||
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
|
|
||||||
"account_number": "5753"
|
|
||||||
},
|
|
||||||
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
|
|
||||||
"account_number": "5754"
|
|
||||||
},
|
|
||||||
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
|
|
||||||
"account_number": "5755"
|
|
||||||
},
|
|
||||||
"Erhaltene Boni 19 % Vorsteuer": {
|
|
||||||
"account_number": "5760"
|
|
||||||
},
|
|
||||||
"Erhaltene Boni": {
|
|
||||||
"account_number": "5769"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Erhaltene Rabatte (Gruppe)": {
|
|
||||||
"is_group": 1,
|
|
||||||
"Erhaltene Rabatte": {
|
|
||||||
"account_number": "5770"
|
|
||||||
},
|
|
||||||
"Erhaltene Rabatte 7 % Vorsteuer": {
|
|
||||||
"account_number": "5780"
|
|
||||||
},
|
|
||||||
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
|
|
||||||
"account_number": "5783"
|
|
||||||
},
|
|
||||||
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
|
|
||||||
"account_number": "5784"
|
|
||||||
},
|
|
||||||
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
|
|
||||||
"account_number": "5785"
|
|
||||||
},
|
|
||||||
"Erhaltene Rabatte 19 % Vorsteuer": {
|
|
||||||
"account_number": "5790"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Bezugsnebenkosten (Gruppe)": {
|
"Bezugsnebenkosten (Gruppe)": {
|
||||||
@ -2410,6 +2296,48 @@
|
|||||||
"6 - sonstige betriebliche Ertr\u00e4ge": {
|
"6 - sonstige betriebliche Ertr\u00e4ge": {
|
||||||
"root_type": "Income",
|
"root_type": "Income",
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
|
"Erhaltene Boni (Gruppe)": {
|
||||||
|
"is_group": 1,
|
||||||
|
"Erhaltene Boni 7 % Vorsteuer": {
|
||||||
|
"account_number": "5750"
|
||||||
|
},
|
||||||
|
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
|
||||||
|
"account_number": "5753"
|
||||||
|
},
|
||||||
|
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
|
||||||
|
"account_number": "5754"
|
||||||
|
},
|
||||||
|
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
|
||||||
|
"account_number": "5755"
|
||||||
|
},
|
||||||
|
"Erhaltene Boni 19 % Vorsteuer": {
|
||||||
|
"account_number": "5760"
|
||||||
|
},
|
||||||
|
"Erhaltene Boni": {
|
||||||
|
"account_number": "5769"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Erhaltene Rabatte (Gruppe)": {
|
||||||
|
"is_group": 1,
|
||||||
|
"Erhaltene Rabatte": {
|
||||||
|
"account_number": "5770"
|
||||||
|
},
|
||||||
|
"Erhaltene Rabatte 7 % Vorsteuer": {
|
||||||
|
"account_number": "5780"
|
||||||
|
},
|
||||||
|
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
|
||||||
|
"account_number": "5783"
|
||||||
|
},
|
||||||
|
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
|
||||||
|
"account_number": "5784"
|
||||||
|
},
|
||||||
|
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
|
||||||
|
"account_number": "5785"
|
||||||
|
},
|
||||||
|
"Erhaltene Rabatte 19 % Vorsteuer": {
|
||||||
|
"account_number": "5790"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Andere aktivierte Eigenleistungen": {
|
"Andere aktivierte Eigenleistungen": {
|
||||||
"account_number": "4820"
|
"account_number": "4820"
|
||||||
},
|
},
|
||||||
@ -2733,6 +2661,90 @@
|
|||||||
"7 - sonstige betriebliche Aufwendungen": {
|
"7 - sonstige betriebliche Aufwendungen": {
|
||||||
"root_type": "Expense",
|
"root_type": "Expense",
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen (Gruppe)": {
|
||||||
|
"is_group": 1,
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen": {
|
||||||
|
"account_number": "4700"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen aus steuerfreien Ums\u00e4tzen \u00a7 4 Nr. 1a UStG": {
|
||||||
|
"account_number": "4705"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen 7 % USt": {
|
||||||
|
"account_number": "4710"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen 19 % USt": {
|
||||||
|
"account_number": "4720"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen 16 % USt": {
|
||||||
|
"account_number": "4723"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen aus steuerfreien innergem. Lieferungen": {
|
||||||
|
"account_number": "4724"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 7 % USt": {
|
||||||
|
"account_number": "4725"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 19 % USt": {
|
||||||
|
"account_number": "4726"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen aus im anderen EU-Land steuerpfl. Lieferungen": {
|
||||||
|
"account_number": "4727"
|
||||||
|
},
|
||||||
|
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
|
||||||
|
"account_number": "4729"
|
||||||
|
},
|
||||||
|
"Gew\u00e4hrte Skonti (Gruppe)": {
|
||||||
|
"is_group": 1,
|
||||||
|
"Gew. Skonti": {
|
||||||
|
"account_number": "4730"
|
||||||
|
},
|
||||||
|
"Gew. Skonti 7 % USt": {
|
||||||
|
"account_number": "4731"
|
||||||
|
},
|
||||||
|
"Gew. Skonti 19 % USt": {
|
||||||
|
"account_number": "4736"
|
||||||
|
},
|
||||||
|
"Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": {
|
||||||
|
"account_number": "4738"
|
||||||
|
},
|
||||||
|
"Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": {
|
||||||
|
"account_number": "4741"
|
||||||
|
},
|
||||||
|
"Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": {
|
||||||
|
"account_number": "4742"
|
||||||
|
},
|
||||||
|
"Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": {
|
||||||
|
"account_number": "4743"
|
||||||
|
},
|
||||||
|
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": {
|
||||||
|
"account_number": "4745"
|
||||||
|
},
|
||||||
|
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": {
|
||||||
|
"account_number": "4746"
|
||||||
|
},
|
||||||
|
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": {
|
||||||
|
"account_number": "4748"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Gew\u00e4hrte Boni 7 % USt": {
|
||||||
|
"account_number": "4750"
|
||||||
|
},
|
||||||
|
"Gew\u00e4hrte Boni 19 % USt": {
|
||||||
|
"account_number": "4760"
|
||||||
|
},
|
||||||
|
"Gew\u00e4hrte Boni": {
|
||||||
|
"account_number": "4769"
|
||||||
|
},
|
||||||
|
"Gew\u00e4hrte Rabatte": {
|
||||||
|
"account_number": "4770"
|
||||||
|
},
|
||||||
|
"Gew\u00e4hrte Rabatte 7 % USt": {
|
||||||
|
"account_number": "4780"
|
||||||
|
},
|
||||||
|
"Gew\u00e4hrte Rabatte 19 % USt": {
|
||||||
|
"account_number": "4790"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Sonstige betriebliche Aufwendungen": {
|
"Sonstige betriebliche Aufwendungen": {
|
||||||
"account_number": "6300"
|
"account_number": "6300"
|
||||||
},
|
},
|
||||||
@ -3609,18 +3621,6 @@
|
|||||||
"Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen f. sonstige Steuern": {
|
"Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen f. sonstige Steuern": {
|
||||||
"account_number": "7694"
|
"account_number": "7694"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Debitoren": {
|
|
||||||
"root_type": "Asset",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"Kreditoren": {
|
|
||||||
"root_type": "Liability",
|
|
||||||
"is_group": 1,
|
|
||||||
"Wareneingangs-Verrechnungskonto" : {
|
|
||||||
"account_number": "70001",
|
|
||||||
"account_type": "Stock Received But Not Billed"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,8 +304,10 @@ class SalesInvoice(SellingController):
|
|||||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||||
|
|
||||||
validate_against_credit_limit = False
|
validate_against_credit_limit = False
|
||||||
bypass_credit_limit_check_at_sales_order = cint(frappe.get_cached_value("Customer", self.customer,
|
bypass_credit_limit_check_at_sales_order = frappe.db.get_value("Customer Credit Limit",
|
||||||
"bypass_credit_limit_check_at_sales_order"))
|
filters={'parent': self.customer, 'parenttype': 'Customer', 'company': self.company},
|
||||||
|
fieldname=["bypass_credit_limit_check"])
|
||||||
|
|
||||||
if bypass_credit_limit_check_at_sales_order:
|
if bypass_credit_limit_check_at_sales_order:
|
||||||
validate_against_credit_limit = True
|
validate_against_credit_limit = True
|
||||||
|
|
||||||
|
@ -292,8 +292,11 @@ def validate_party_accounts(doc):
|
|||||||
|
|
||||||
party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True)
|
party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True)
|
||||||
existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company)
|
existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company)
|
||||||
|
if frappe.db.get_default("Company"):
|
||||||
company_default_currency = frappe.get_cached_value('Company',
|
company_default_currency = frappe.get_cached_value('Company',
|
||||||
frappe.db.get_default("Company"), "default_currency")
|
frappe.db.get_default("Company"), "default_currency")
|
||||||
|
else:
|
||||||
|
company_default_currency = frappe.db.get_value('Company', account.company, "default_currency")
|
||||||
|
|
||||||
if existing_gle_currency and party_account_currency != existing_gle_currency:
|
if existing_gle_currency and party_account_currency != existing_gle_currency:
|
||||||
frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company))
|
frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company))
|
||||||
@ -365,7 +368,7 @@ def validate_due_date(posting_date, due_date, party_type, party, company=None, b
|
|||||||
.format(formatdate(default_due_date)))
|
.format(formatdate(default_due_date)))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_address_tax_category(tax_category, billing_address=None, shipping_address=None):
|
def get_address_tax_category(tax_category=None, billing_address=None, shipping_address=None):
|
||||||
addr_tax_category_from = frappe.db.get_single_value("Accounts Settings", "determine_address_tax_category_from")
|
addr_tax_category_from = frappe.db.get_single_value("Accounts Settings", "determine_address_tax_category_from")
|
||||||
if addr_tax_category_from == "Shipping Address":
|
if addr_tax_category_from == "Shipping Address":
|
||||||
if shipping_address:
|
if shipping_address:
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
<style>
|
<style>
|
||||||
.print-format {
|
.print-format {
|
||||||
padding: 4mm;
|
padding: 4mm;
|
||||||
font-size: 8.0pt !important;
|
font-size: 8.0pt !important;
|
||||||
}
|
}
|
||||||
.print-format td {
|
.print-format td {
|
||||||
vertical-align:middle !important;
|
vertical-align:middle !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
|
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{% if (filters.customer_name) { %}
|
{% if (filters.customer_name) { %}
|
||||||
{%= filters.customer_name %}
|
{%= filters.customer_name %}
|
||||||
{% } else { %}
|
{% } else { %}
|
||||||
{%= filters.customer || filters.supplier %}
|
{%= filters.customer || filters.supplier %}
|
||||||
{% } %}
|
{% } %}
|
||||||
</h4>
|
</h4>
|
||||||
<h6 class="text-center">
|
<h6 class="text-center">
|
||||||
{% if (filters.tax_id) { %}
|
{% if (filters.tax_id) { %}
|
||||||
{%= __("Tax Id: ")%} {%= filters.tax_id %}
|
{%= __("Tax Id: ")%} {%= filters.tax_id %}
|
||||||
{% } %}
|
{% } %}
|
||||||
</h6>
|
</h6>
|
||||||
<h5 class="text-center">
|
<h5 class="text-center">
|
||||||
{%= __(filters.ageing_based_on) %}
|
{%= __(filters.ageing_based_on) %}
|
||||||
{%= __("Until") %}
|
{%= __("Until") %}
|
||||||
{%= frappe.datetime.str_to_user(filters.report_date) %}
|
{%= frappe.datetime.str_to_user(filters.report_date) %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
{% if(filters.payment_terms) { %}
|
{% if(filters.payment_terms) { %}
|
||||||
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
|
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
|
||||||
@ -38,20 +38,19 @@
|
|||||||
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
|
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
|
||||||
{% } %}
|
{% } %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if(filters.show_future_payments) { %}
|
{% if(filters.show_future_payments) { %}
|
||||||
{% var balance_row = data.slice(-1).pop();
|
{% var balance_row = data.slice(-1).pop();
|
||||||
var range1 = report.columns[11].label;
|
var range1 = report.columns[11].label;
|
||||||
var range2 = report.columns[12].label;
|
var range2 = report.columns[12].label;
|
||||||
var range3 = report.columns[13].label;
|
var range3 = report.columns[13].label;
|
||||||
var range4 = report.columns[14].label;
|
var range4 = report.columns[14].label;
|
||||||
var range5 = report.columns[15].label;
|
var range5 = report.columns[15].label;
|
||||||
var range6 = report.columns[16].label;
|
|
||||||
%}
|
%}
|
||||||
{% if(balance_row) { %}
|
{% if(balance_row) { %}
|
||||||
<table class="table table-bordered table-condensed">
|
<table class="table table-bordered table-condensed">
|
||||||
<caption class="text-right">(Amount in {%= data[0][__("currency")] || "" %})</caption>
|
<caption class="text-right">(Amount in {%= data[0]["currency"] || "" %})</caption>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col style="width: 30mm;">
|
<col style="width: 30mm;">
|
||||||
<col style="width: 18mm;">
|
<col style="width: 18mm;">
|
||||||
@ -61,7 +60,6 @@
|
|||||||
<col style="width: 18mm;">
|
<col style="width: 18mm;">
|
||||||
<col style="width: 18mm;">
|
<col style="width: 18mm;">
|
||||||
<col style="width: 18mm;">
|
<col style="width: 18mm;">
|
||||||
<col style="width: 18mm;">
|
|
||||||
</colgroup>
|
</colgroup>
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
@ -72,32 +70,29 @@
|
|||||||
<th>{%= __(range3) %}</th>
|
<th>{%= __(range3) %}</th>
|
||||||
<th>{%= __(range4) %}</th>
|
<th>{%= __(range4) %}</th>
|
||||||
<th>{%= __(range5) %}</th>
|
<th>{%= __(range5) %}</th>
|
||||||
<th>{%= __(range6) %}</th>
|
|
||||||
<th>{%= __("Total") %}</th>
|
<th>{%= __("Total") %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{%= __("Total Outstanding") %}</td>
|
<td>{%= __("Total Outstanding") %}</td>
|
||||||
<td class="text-right">{%= format_number(balance_row[range1], null, 2) %}</td>
|
<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
|
||||||
<td class="text-right">{%= format_currency(balance_row[range2]) %}</td>
|
<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
|
||||||
<td class="text-right">{%= format_currency(balance_row[range3]) %}</td>
|
<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
|
||||||
<td class="text-right">{%= format_currency(balance_row[range4]) %}</td>
|
<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
|
||||||
<td class="text-right">{%= format_currency(balance_row[range5]) %}</td>
|
<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
|
||||||
<td class="text-right">{%= format_currency(balance_row[range6]) %}</td>
|
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{%= format_currency(flt(balance_row[("outstanding_amount")]), data[data.length-1]["currency"]) %}
|
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<td>{%= __("PDC/LC") %}</td>
|
<td>{%= __("Future Payments") %}</td>
|
||||||
<td></td>
|
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{%= format_currency(flt(balance_row[("pdc/lc_amount")]), data[data.length-1]["currency"]) %}
|
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
|
||||||
</td>
|
</td>
|
||||||
<tr class="cvs-footer">
|
<tr class="cvs-footer">
|
||||||
<th class="text-left">{%= __("Cheques Required") %}</th>
|
<th class="text-left">{%= __("Cheques Required") %}</th>
|
||||||
@ -106,16 +101,15 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
|
||||||
<th class="text-right">
|
<th class="text-right">
|
||||||
{%= format_currency(flt(balance_row[("outstanding_amount")]-balance_row[("pdc/lc_amount")]), data[data.length-1]["currency"]) %}</th>
|
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } %}
|
{% } %}
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
|
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
|
||||||
@ -141,8 +135,8 @@
|
|||||||
{% if(report.report_name === "Accounts Receivable") { %}
|
{% if(report.report_name === "Accounts Receivable") { %}
|
||||||
<th style="width: 12%">{%= __("Customer LPO No.") %}</th>
|
<th style="width: 12%">{%= __("Customer LPO No.") %}</th>
|
||||||
{% } %}
|
{% } %}
|
||||||
<th style="width: 10%">{%= __("PDC/LC Ref") %}</th>
|
<th style="width: 10%">{%= __("Future Payment Ref") %}</th>
|
||||||
<th style="width: 10%">{%= __("PDC/LC Amount") %}</th>
|
<th style="width: 10%">{%= __("Future Payment Amount") %}</th>
|
||||||
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
|
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } else { %}
|
{% } else { %}
|
||||||
@ -158,9 +152,9 @@
|
|||||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||||
<tr>
|
<tr>
|
||||||
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
|
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
|
||||||
{% if(data[i][__("Customer")] || data[i][__("Supplier")]) { %}
|
{% if(data[i]["party"]) { %}
|
||||||
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
|
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
|
||||||
<td style="text-align: right">{%= data[i][__("Age (Days)")] %}</td>
|
<td style="text-align: right">{%= data[i]["age"] %}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if(!filters.show_future_payments) { %}
|
{% if(!filters.show_future_payments) { %}
|
||||||
{%= data[i]["voucher_type"] %}
|
{%= data[i]["voucher_type"] %}
|
||||||
@ -176,42 +170,42 @@
|
|||||||
{% if(!filters.show_future_payments) { %}
|
{% if(!filters.show_future_payments) { %}
|
||||||
<td>
|
<td>
|
||||||
{% if(!(filters.customer || filters.supplier)) { %}
|
{% if(!(filters.customer || filters.supplier)) { %}
|
||||||
{%= data[i][__("Customer")] || data[i][__("Supplier")] %}
|
{%= data[i]["party"] %}
|
||||||
{% if(data[i][__("Customer Name")] && data[i][__("Customer Name")] != data[i][__("Customer")]) { %}
|
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
|
||||||
<br> {%= data[i][__("Customer Name")] %}
|
<br> {%= data[i]["customer_name"] %}
|
||||||
{% } else if(data[i][__("Supplier Name")] != data[i][__("Supplier")]) { %}
|
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
|
||||||
<br> {%= data[i][__("Supplier Name")] %}
|
<br> {%= data[i]["supplier_name"] %}
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } %}
|
{% } %}
|
||||||
<div>
|
<div>
|
||||||
{% if data[i][__("Remarks")] %}
|
{% if data[i]["remarks"] %}
|
||||||
{%= __("Remarks") %}:
|
{%= __("Remarks") %}:
|
||||||
{%= data[i][__("Remarks")] %}
|
{%= data[i]["remarks"] %}
|
||||||
{% } %}
|
{% } %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
|
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"]) %}</td>
|
{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
|
||||||
|
|
||||||
{% if(!filters.show_future_payments) { %}
|
{% if(!filters.show_future_payments) { %}
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td>
|
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %}</td>
|
{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td>
|
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||||
|
|
||||||
{% if(filters.show_future_payments) { %}
|
{% if(filters.show_future_payments) { %}
|
||||||
{% if(report.report_name === "Accounts Receivable") { %}
|
{% if(report.report_name === "Accounts Receivable") { %}
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= data[i]["po_no"] %}</td>
|
{%= data[i]["po_no"] %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
<td style="text-align: right">{%= data[i][("pdc/lc_ref")] %}</td>
|
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } else { %}
|
{% } else { %}
|
||||||
<td></td>
|
<td></td>
|
||||||
@ -224,52 +218,52 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
|
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"] ) %}</td>
|
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
|
||||||
|
|
||||||
{% if(!filters.show_future_payments) { %}
|
{% if(!filters.show_future_payments) { %}
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td>
|
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %} </td>
|
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
|
||||||
{% } %}
|
{% } %}
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td>
|
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||||
|
|
||||||
{% if(filters.show_future_payments) { %}
|
{% if(filters.show_future_payments) { %}
|
||||||
{% if(report.report_name === "Accounts Receivable") { %}
|
{% if(report.report_name === "Accounts Receivable") { %}
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= data[i][__("Customer LPO")] %}</td>
|
{%= data[i]["po_no"] %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
<td style="text-align: right">{%= data[i][("pdc/lc_ref")] %}</td>
|
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } else { %}
|
{% } else { %}
|
||||||
{% if(data[i][__("Customer")] || data[i][__("Supplier")]|| " ") { %}
|
{% if(data[i]["party"]|| " ") { %}
|
||||||
{% if((data[i][__("Customer")] || data[i][__("Supplier")]) != __("'Total'")) { %}
|
{% if((data[i]["party"]) != __("'Total'")) { %}
|
||||||
<td>
|
<td>
|
||||||
{% if(!(filters.customer || filters.supplier)) { %}
|
{% if(!(filters.customer || filters.supplier)) { %}
|
||||||
{%= data[i][__("Customer")] || data[i][__("Supplier")] %}
|
{%= data[i]["party"] %}
|
||||||
{% if(data[i][__("Customer Name")] && data[i][__("Customer Name")] != data[i][__("Customer")]) { %}
|
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
|
||||||
<br> {%= data[i][__("Customer Name")] %}
|
<br> {%= data[i]["customer_name"] %}
|
||||||
{% } else if(data[i][__("Supplier Name")] != data[i][__("Supplier")]) { %}
|
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
|
||||||
<br> {%= data[i][__("Supplier Name")] %}
|
<br> {%= data[i]["supplier_name"] %}
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } %}
|
{% } %}
|
||||||
<br>{%= __("Remarks") %}:
|
<br>{%= __("Remarks") %}:
|
||||||
{%= data[i][__("Remarks")] %}
|
{%= data[i]["remarks"] %}
|
||||||
</td>
|
</td>
|
||||||
{% } else { %}
|
{% } else { %}
|
||||||
<td><b>{%= __("Total") %}</b></td>
|
<td><b>{%= __("Total") %}</b></td>
|
||||||
{% } %}
|
{% } %}
|
||||||
<td style="text-align: right">{%= format_currency(data[i][("total_invoiced_amt")], data[i]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i][("total_paid_amt")], data[i]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= report.report_name === "Accounts Receivable Summary" ? format_currency(data[i][__("credit_note_amt")], data[i]["currency"]) : format_currency(data[i][__("debit_note_amt")], data[i]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i][("total_outstanding_amt")], data[i]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } %}
|
{% } %}
|
||||||
</tr>
|
</tr>
|
||||||
{% } %}
|
{% } %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
|
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
|
||||||
|
@ -16,9 +16,9 @@ frappe.listview_settings['Purchase Order'] = {
|
|||||||
return [__("To Receive"), "orange",
|
return [__("To Receive"), "orange",
|
||||||
"per_received,<,100|per_billed,=,100|status,!=,Closed"];
|
"per_received,<,100|per_billed,=,100|status,!=,Closed"];
|
||||||
}
|
}
|
||||||
} else if (flt(doc.per_received, 2) == 100 && flt(doc.per_billed, 2) < 100 && doc.status !== "Closed") {
|
} else if (flt(doc.per_received, 2) >= 100 && flt(doc.per_billed, 2) < 100 && doc.status !== "Closed") {
|
||||||
return [__("To Bill"), "orange", "per_received,=,100|per_billed,<,100|status,!=,Closed"];
|
return [__("To Bill"), "orange", "per_received,=,100|per_billed,<,100|status,!=,Closed"];
|
||||||
} else if (flt(doc.per_received, 2) == 100 && flt(doc.per_billed, 2) == 100 && doc.status !== "Closed") {
|
} else if (flt(doc.per_received, 2) >= 100 && flt(doc.per_billed, 2) == 100 && doc.status !== "Closed") {
|
||||||
return [__("Completed"), "green", "per_received,=,100|per_billed,=,100|status,!=,Closed"];
|
return [__("Completed"), "green", "per_received,=,100|per_billed,=,100|status,!=,Closed"];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -57,9 +57,9 @@ status_map = {
|
|||||||
"Purchase Order": [
|
"Purchase Order": [
|
||||||
["Draft", None],
|
["Draft", None],
|
||||||
["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],
|
["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||||
["To Bill", "eval:self.per_received == 100 and self.per_billed < 100 and self.docstatus == 1"],
|
["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||||
["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||||
["Completed", "eval:self.per_received == 100 and self.per_billed == 100 and self.docstatus == 1"],
|
["Completed", "eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||||
["Delivered", "eval:self.status=='Delivered'"],
|
["Delivered", "eval:self.status=='Delivered'"],
|
||||||
["Cancelled", "eval:self.docstatus==2"],
|
["Cancelled", "eval:self.docstatus==2"],
|
||||||
["On Hold", "eval:self.status=='On Hold'"],
|
["On Hold", "eval:self.status=='On Hold'"],
|
||||||
|
@ -39,7 +39,6 @@ def validate_filters(filters):
|
|||||||
frappe.throw(_("'Based On' and 'Group By' can not be same"))
|
frappe.throw(_("'Based On' and 'Group By' can not be same"))
|
||||||
|
|
||||||
def get_data(filters, conditions):
|
def get_data(filters, conditions):
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
inc, cond= '',''
|
inc, cond= '',''
|
||||||
query_details = conditions["based_on_select"] + conditions["period_wise_select"]
|
query_details = conditions["based_on_select"] + conditions["period_wise_select"]
|
||||||
@ -47,10 +46,11 @@ def get_data(filters, conditions):
|
|||||||
posting_date = 't1.transaction_date'
|
posting_date = 't1.transaction_date'
|
||||||
if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']:
|
if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']:
|
||||||
posting_date = 't1.posting_date'
|
posting_date = 't1.posting_date'
|
||||||
|
if filters.period_based_on:
|
||||||
|
posting_date = 't1.'+filters.period_based_on
|
||||||
|
|
||||||
if conditions["based_on_select"] in ["t1.project,", "t2.project,"]:
|
if conditions["based_on_select"] in ["t1.project,", "t2.project,"]:
|
||||||
cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL'
|
cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL'
|
||||||
|
|
||||||
if conditions.get('trans') in ['Sales Order', 'Purchase Order']:
|
if conditions.get('trans') in ['Sales Order', 'Purchase Order']:
|
||||||
cond += " and t1.status != 'Closed'"
|
cond += " and t1.status != 'Closed'"
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ class Student(Document):
|
|||||||
'send_welcome_email': 1,
|
'send_welcome_email': 1,
|
||||||
'user_type': 'Website User'
|
'user_type': 'Website User'
|
||||||
})
|
})
|
||||||
|
student_user.flags.ignore_permissions = True
|
||||||
student_user.add_roles("Student")
|
student_user.add_roles("Student")
|
||||||
student_user.save()
|
student_user.save()
|
||||||
update_password_link = student_user.reset_password()
|
update_password_link = student_user.reset_password()
|
||||||
|
@ -69,33 +69,10 @@ def validate_service_item(item, msg):
|
|||||||
|
|
||||||
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
|
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
|
||||||
fields = ["name", "first_name", "mobile_phone"]
|
fields = ["name", "first_name", "mobile_phone"]
|
||||||
match_conditions = build_match_conditions("Healthcare Practitioner")
|
|
||||||
match_conditions = "and {}".format(match_conditions) if match_conditions else ""
|
|
||||||
|
|
||||||
if filters:
|
filters = {
|
||||||
filter_conditions = get_filters_cond(doctype, filters, [])
|
'name': ("like", "%%%s%%" % txt)
|
||||||
match_conditions += "{}".format(filter_conditions)
|
}
|
||||||
|
|
||||||
return frappe.db.sql("""select %s from `tabHealthcare Practitioner` where docstatus < 2
|
return frappe.get_all("Healthcare Practitioner", fields = fields,
|
||||||
and (%s like %s or first_name like %s)
|
filters = filters, start=start, page_length=page_len, order_by="name, first_name", as_list=1)
|
||||||
and active = 1
|
|
||||||
{match_conditions}
|
|
||||||
order by
|
|
||||||
case when name like %s then 0 else 1 end,
|
|
||||||
case when first_name like %s then 0 else 1 end,
|
|
||||||
name, first_name limit %s, %s""".format(
|
|
||||||
match_conditions=match_conditions) %
|
|
||||||
(
|
|
||||||
", ".join(fields),
|
|
||||||
frappe.db.escape(searchfield),
|
|
||||||
"%s", "%s", "%s", "%s", "%s", "%s"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"%%%s%%" % frappe.db.escape(txt),
|
|
||||||
"%%%s%%" % frappe.db.escape(txt),
|
|
||||||
"%%%s%%" % frappe.db.escape(txt),
|
|
||||||
"%%%s%%" % frappe.db.escape(txt),
|
|
||||||
start,
|
|
||||||
page_len
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
@ -6,7 +6,7 @@ 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 frappe.utils import cint, cstr, getdate
|
from frappe.utils import cint, cstr, getdate, flt
|
||||||
import dateutil
|
import dateutil
|
||||||
from frappe.model.naming import set_name_by_naming_series
|
from frappe.model.naming import set_name_by_naming_series
|
||||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account,send_registration_sms
|
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account,send_registration_sms
|
||||||
@ -64,7 +64,7 @@ class Patient(Document):
|
|||||||
def invoice_patient_registration(self):
|
def invoice_patient_registration(self):
|
||||||
frappe.db.set_value("Patient", self.name, "disabled", 0)
|
frappe.db.set_value("Patient", self.name, "disabled", 0)
|
||||||
send_registration_sms(self)
|
send_registration_sms(self)
|
||||||
if(frappe.get_value("Healthcare Settings", None, "registration_fee")>0):
|
if(flt(frappe.get_value("Healthcare Settings", None, "registration_fee"))>0):
|
||||||
company = frappe.defaults.get_user_default('company')
|
company = frappe.defaults.get_user_default('company')
|
||||||
if not company:
|
if not company:
|
||||||
company = frappe.db.get_value("Global Defaults", None, "default_company")
|
company = frappe.db.get_value("Global Defaults", None, "default_company")
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"basic_information",
|
"basic_information",
|
||||||
"employee",
|
"employee",
|
||||||
@ -54,6 +55,7 @@
|
|||||||
"column_break_44",
|
"column_break_44",
|
||||||
"holiday_list",
|
"holiday_list",
|
||||||
"default_shift",
|
"default_shift",
|
||||||
|
"leave_approver",
|
||||||
"salary_information",
|
"salary_information",
|
||||||
"salary_mode",
|
"salary_mode",
|
||||||
"bank_name",
|
"bank_name",
|
||||||
@ -767,12 +769,18 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Default Shift",
|
"label": "Default Shift",
|
||||||
"options": "Shift Type"
|
"options": "Shift Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "leave_approver",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Leave Approver",
|
||||||
|
"options": "User"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 24,
|
"idx": 24,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"modified": "2019-06-01 16:05:55.132180",
|
"modified": "2019-09-06 15:54:36.735147",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee",
|
"name": "Employee",
|
||||||
|
@ -142,8 +142,10 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
|||||||
|
|
||||||
elif check_in_out_type == 'Strictly based on Log Type in Employee Checkin':
|
elif check_in_out_type == 'Strictly based on Log Type in Employee Checkin':
|
||||||
if working_hours_calc_type == 'First Check-in and Last Check-out':
|
if working_hours_calc_type == 'First Check-in and Last Check-out':
|
||||||
first_in_log = logs[find_index_in_dict(logs, 'log_type', 'IN')]
|
first_in_log_index = find_index_in_dict(logs, 'log_type', 'IN')
|
||||||
last_out_log = logs[len(logs)-1-find_index_in_dict(reversed(logs), 'log_type', 'OUT')]
|
first_in_log = logs[first_in_log_index] if first_in_log_index or first_in_log_index == 0 else None
|
||||||
|
last_out_log_index = find_index_in_dict(reversed(logs), 'log_type', 'OUT')
|
||||||
|
last_out_log = logs[len(logs)-1-last_out_log_index] if last_out_log_index or last_out_log_index == 0 else None
|
||||||
if first_in_log and last_out_log:
|
if first_in_log and last_out_log:
|
||||||
in_time, out_time = first_in_log.time, last_out_log.time
|
in_time, out_time = first_in_log.time, last_out_log.time
|
||||||
total_hours = time_diff_in_hours(in_time, out_time)
|
total_hours = time_diff_in_hours(in_time, out_time)
|
||||||
|
@ -745,10 +745,12 @@ def get_approved_leaves_for_period(employee, leave_type, from_date, to_date):
|
|||||||
return leave_days
|
return leave_days
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_leave_approver(employee, department=None):
|
def get_leave_approver(employee):
|
||||||
if not department:
|
leave_approver, department = frappe.db.get_value("Employee",
|
||||||
department = frappe.db.get_value('Employee', employee, 'department')
|
employee, ["leave_approver", "department"])
|
||||||
|
|
||||||
if department:
|
if not leave_approver and department:
|
||||||
return frappe.db.get_value('Department Approver', {'parent': department,
|
leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
|
||||||
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
|
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
|
||||||
|
|
||||||
|
return leave_approver
|
@ -1,648 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe
|
|
||||||
import json
|
|
||||||
from six import text_type
|
|
||||||
from frappe import _
|
|
||||||
from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
|
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
|
||||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
|
|
||||||
from erpnext.projects.doctype.timesheet.timesheet import OverlapError
|
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
|
|
||||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
|
||||||
from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
|
|
||||||
from frappe.utils.csvutils import getlink
|
|
||||||
from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
|
|
||||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
|
||||||
|
|
||||||
class OverProductionError(frappe.ValidationError): pass
|
|
||||||
class StockOverProductionError(frappe.ValidationError): pass
|
|
||||||
class OperationTooLongError(frappe.ValidationError): pass
|
|
||||||
class ItemHasVariantError(frappe.ValidationError): pass
|
|
||||||
|
|
||||||
form_grid_templates = {
|
|
||||||
"operations": "templates/form_grid/production_order_grid.html"
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProductionOrder(Document):
|
|
||||||
def validate(self):
|
|
||||||
self.validate_production_item()
|
|
||||||
if self.bom_no:
|
|
||||||
validate_bom_no(self.production_item, self.bom_no)
|
|
||||||
|
|
||||||
self.validate_sales_order()
|
|
||||||
self.set_default_warehouse()
|
|
||||||
self.validate_warehouse_belongs_to_company()
|
|
||||||
self.calculate_operating_cost()
|
|
||||||
self.validate_qty()
|
|
||||||
self.validate_operation_time()
|
|
||||||
self.status = self.get_status()
|
|
||||||
|
|
||||||
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
|
|
||||||
|
|
||||||
self.set_required_items(reset_only_qty = len(self.get("required_items")))
|
|
||||||
|
|
||||||
def validate_sales_order(self):
|
|
||||||
if self.sales_order:
|
|
||||||
so = frappe.db.sql("""
|
|
||||||
select so.name, so_item.delivery_date, so.project
|
|
||||||
from `tabSales Order` so
|
|
||||||
inner join `tabSales Order Item` so_item on so_item.parent = so.name
|
|
||||||
left join `tabProduct Bundle Item` pk_item on so_item.item_code = pk_item.parent
|
|
||||||
where so.name=%s and so.docstatus = 1 and (
|
|
||||||
so_item.item_code=%s or
|
|
||||||
pk_item.item_code=%s )
|
|
||||||
""", (self.sales_order, self.production_item, self.production_item), as_dict=1)
|
|
||||||
|
|
||||||
if not so:
|
|
||||||
so = frappe.db.sql("""
|
|
||||||
select
|
|
||||||
so.name, so_item.delivery_date, so.project
|
|
||||||
from
|
|
||||||
`tabSales Order` so, `tabSales Order Item` so_item, `tabPacked Item` packed_item
|
|
||||||
where so.name=%s
|
|
||||||
and so.name=so_item.parent
|
|
||||||
and so.name=packed_item.parent
|
|
||||||
and so_item.item_code = packed_item.parent_item
|
|
||||||
and so.docstatus = 1 and packed_item.item_code=%s
|
|
||||||
""", (self.sales_order, self.production_item), as_dict=1)
|
|
||||||
|
|
||||||
if len(so):
|
|
||||||
if not self.expected_delivery_date:
|
|
||||||
self.expected_delivery_date = so[0].delivery_date
|
|
||||||
|
|
||||||
if so[0].project:
|
|
||||||
self.project = so[0].project
|
|
||||||
|
|
||||||
if not self.material_request:
|
|
||||||
self.validate_production_order_against_so()
|
|
||||||
else:
|
|
||||||
frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order))
|
|
||||||
|
|
||||||
def set_default_warehouse(self):
|
|
||||||
if not self.wip_warehouse:
|
|
||||||
self.wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
|
|
||||||
if not self.fg_warehouse:
|
|
||||||
self.fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse")
|
|
||||||
|
|
||||||
def validate_warehouse_belongs_to_company(self):
|
|
||||||
warehouses = [self.fg_warehouse, self.wip_warehouse]
|
|
||||||
for d in self.get("required_items"):
|
|
||||||
if d.source_warehouse not in warehouses:
|
|
||||||
warehouses.append(d.source_warehouse)
|
|
||||||
|
|
||||||
for wh in warehouses:
|
|
||||||
validate_warehouse_company(wh, self.company)
|
|
||||||
|
|
||||||
def calculate_operating_cost(self):
|
|
||||||
self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0
|
|
||||||
for d in self.get("operations"):
|
|
||||||
d.planned_operating_cost = flt(d.hour_rate) * (flt(d.time_in_mins) / 60.0)
|
|
||||||
d.actual_operating_cost = flt(d.hour_rate) * (flt(d.actual_operation_time) / 60.0)
|
|
||||||
|
|
||||||
self.planned_operating_cost += flt(d.planned_operating_cost)
|
|
||||||
self.actual_operating_cost += flt(d.actual_operating_cost)
|
|
||||||
|
|
||||||
variable_cost = self.actual_operating_cost if self.actual_operating_cost \
|
|
||||||
else self.planned_operating_cost
|
|
||||||
self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost)
|
|
||||||
|
|
||||||
def validate_production_order_against_so(self):
|
|
||||||
# already ordered qty
|
|
||||||
ordered_qty_against_so = frappe.db.sql("""select sum(qty) from `tabProduction Order`
|
|
||||||
where production_item = %s and sales_order = %s and docstatus < 2 and name != %s""",
|
|
||||||
(self.production_item, self.sales_order, self.name))[0][0]
|
|
||||||
|
|
||||||
total_qty = flt(ordered_qty_against_so) + flt(self.qty)
|
|
||||||
|
|
||||||
# get qty from Sales Order Item table
|
|
||||||
so_item_qty = frappe.db.sql("""select sum(stock_qty) from `tabSales Order Item`
|
|
||||||
where parent = %s and item_code = %s""",
|
|
||||||
(self.sales_order, self.production_item))[0][0]
|
|
||||||
# get qty from Packing Item table
|
|
||||||
dnpi_qty = frappe.db.sql("""select sum(qty) from `tabPacked Item`
|
|
||||||
where parent = %s and parenttype = 'Sales Order' and item_code = %s""",
|
|
||||||
(self.sales_order, self.production_item))[0][0]
|
|
||||||
# total qty in SO
|
|
||||||
so_qty = flt(so_item_qty) + flt(dnpi_qty)
|
|
||||||
|
|
||||||
allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
|
|
||||||
"over_production_allowance_percentage"))
|
|
||||||
|
|
||||||
if total_qty > so_qty + (allowance_percentage/100 * so_qty):
|
|
||||||
frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}")
|
|
||||||
.format(self.production_item, so_qty), OverProductionError)
|
|
||||||
|
|
||||||
def update_status(self, status=None):
|
|
||||||
'''Update status of production order if unknown'''
|
|
||||||
if status != "Stopped":
|
|
||||||
status = self.get_status(status)
|
|
||||||
|
|
||||||
if status != self.status:
|
|
||||||
self.db_set("status", status)
|
|
||||||
|
|
||||||
self.update_required_items()
|
|
||||||
|
|
||||||
return status
|
|
||||||
|
|
||||||
def get_status(self, status=None):
|
|
||||||
'''Return the status based on stock entries against this production order'''
|
|
||||||
if not status:
|
|
||||||
status = self.status
|
|
||||||
|
|
||||||
if self.docstatus==0:
|
|
||||||
status = 'Draft'
|
|
||||||
elif self.docstatus==1:
|
|
||||||
if status != 'Stopped':
|
|
||||||
stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty)
|
|
||||||
from `tabStock Entry` where production_order=%s and docstatus=1
|
|
||||||
group by purpose""", self.name))
|
|
||||||
|
|
||||||
status = "Not Started"
|
|
||||||
if stock_entries:
|
|
||||||
status = "In Process"
|
|
||||||
produced_qty = stock_entries.get("Manufacture")
|
|
||||||
if flt(produced_qty) == flt(self.qty):
|
|
||||||
status = "Completed"
|
|
||||||
else:
|
|
||||||
status = 'Cancelled'
|
|
||||||
|
|
||||||
return status
|
|
||||||
|
|
||||||
def update_production_order_qty(self):
|
|
||||||
"""Update **Manufactured Qty** and **Material Transferred for Qty** in Production Order
|
|
||||||
based on Stock Entry"""
|
|
||||||
|
|
||||||
for purpose, fieldname in (("Manufacture", "produced_qty"),
|
|
||||||
("Material Transfer for Manufacture", "material_transferred_for_manufacturing")):
|
|
||||||
qty = flt(frappe.db.sql("""select sum(fg_completed_qty)
|
|
||||||
from `tabStock Entry` where production_order=%s and docstatus=1
|
|
||||||
and purpose=%s""", (self.name, purpose))[0][0])
|
|
||||||
|
|
||||||
if qty > self.qty:
|
|
||||||
frappe.throw(_("{0} ({1}) cannot be greater than planned quanitity ({2}) in Production Order {3}").format(\
|
|
||||||
self.meta.get_label(fieldname), qty, self.qty, self.name), StockOverProductionError)
|
|
||||||
|
|
||||||
self.db_set(fieldname, qty)
|
|
||||||
|
|
||||||
def before_submit(self):
|
|
||||||
self.make_time_logs()
|
|
||||||
|
|
||||||
def on_submit(self):
|
|
||||||
if not self.wip_warehouse:
|
|
||||||
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
|
|
||||||
if not self.fg_warehouse:
|
|
||||||
frappe.throw(_("For Warehouse is required before Submit"))
|
|
||||||
|
|
||||||
self.update_reserved_qty_for_production()
|
|
||||||
self.update_completed_qty_in_material_request()
|
|
||||||
self.update_planned_qty()
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
self.validate_cancel()
|
|
||||||
|
|
||||||
frappe.db.set(self,'status', 'Cancelled')
|
|
||||||
self.delete_timesheet()
|
|
||||||
self.update_completed_qty_in_material_request()
|
|
||||||
self.update_planned_qty()
|
|
||||||
self.update_reserved_qty_for_production()
|
|
||||||
|
|
||||||
def validate_cancel(self):
|
|
||||||
if self.status == "Stopped":
|
|
||||||
frappe.throw(_("Stopped Production Order cannot be cancelled, Unstop it first to cancel"))
|
|
||||||
|
|
||||||
# Check whether any stock entry exists against this Production Order
|
|
||||||
stock_entry = frappe.db.sql("""select name from `tabStock Entry`
|
|
||||||
where production_order = %s and docstatus = 1""", self.name)
|
|
||||||
if stock_entry:
|
|
||||||
frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(stock_entry[0][0]))
|
|
||||||
|
|
||||||
def update_planned_qty(self):
|
|
||||||
update_bin_qty(self.production_item, self.fg_warehouse, {
|
|
||||||
"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)
|
|
||||||
})
|
|
||||||
|
|
||||||
if self.material_request:
|
|
||||||
mr_obj = frappe.get_doc("Material Request", self.material_request)
|
|
||||||
mr_obj.update_requested_qty([self.material_request_item])
|
|
||||||
|
|
||||||
def update_completed_qty_in_material_request(self):
|
|
||||||
if self.material_request:
|
|
||||||
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
|
|
||||||
|
|
||||||
def set_production_order_operations(self):
|
|
||||||
"""Fetch operations from BOM and set in 'Production Order'"""
|
|
||||||
self.set('operations', [])
|
|
||||||
|
|
||||||
if not self.bom_no \
|
|
||||||
or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")):
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.use_multi_level_bom:
|
|
||||||
bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree()
|
|
||||||
else:
|
|
||||||
bom_list = [self.bom_no]
|
|
||||||
|
|
||||||
operations = frappe.db.sql("""
|
|
||||||
select
|
|
||||||
operation, description, workstation, idx,
|
|
||||||
base_hour_rate as hour_rate, time_in_mins,
|
|
||||||
"Pending" as status, parent as bom
|
|
||||||
from
|
|
||||||
`tabBOM Operation`
|
|
||||||
where
|
|
||||||
parent in (%s) order by idx
|
|
||||||
""" % ", ".join(["%s"]*len(bom_list)), tuple(bom_list), as_dict=1)
|
|
||||||
|
|
||||||
self.set('operations', operations)
|
|
||||||
self.calculate_time()
|
|
||||||
|
|
||||||
def calculate_time(self):
|
|
||||||
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
|
||||||
|
|
||||||
for d in self.get("operations"):
|
|
||||||
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * flt(self.qty)
|
|
||||||
|
|
||||||
self.calculate_operating_cost()
|
|
||||||
|
|
||||||
def get_holidays(self, workstation):
|
|
||||||
holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list")
|
|
||||||
|
|
||||||
holidays = {}
|
|
||||||
|
|
||||||
if holiday_list not in holidays:
|
|
||||||
holiday_list_days = [getdate(d[0]) for d in frappe.get_all("Holiday", fields=["holiday_date"],
|
|
||||||
filters={"parent": holiday_list}, order_by="holiday_date", limit_page_length=0, as_list=1)]
|
|
||||||
|
|
||||||
holidays[holiday_list] = holiday_list_days
|
|
||||||
|
|
||||||
return holidays[holiday_list]
|
|
||||||
|
|
||||||
def make_time_logs(self, open_new=False):
|
|
||||||
"""Capacity Planning. Plan time logs based on earliest availablity of workstation after
|
|
||||||
Planned Start Date. Time logs will be created and remain in Draft mode and must be submitted
|
|
||||||
before manufacturing entry can be made."""
|
|
||||||
|
|
||||||
if not self.operations:
|
|
||||||
return
|
|
||||||
|
|
||||||
timesheets = []
|
|
||||||
plan_days = frappe.db.get_single_value("Manufacturing Settings", "capacity_planning_for_days") or 30
|
|
||||||
|
|
||||||
timesheet = make_timesheet(self.name, self.company)
|
|
||||||
timesheet.set('time_logs', [])
|
|
||||||
|
|
||||||
for i, d in enumerate(self.operations):
|
|
||||||
|
|
||||||
if d.status != 'Completed':
|
|
||||||
self.set_start_end_time_for_workstation(d, i)
|
|
||||||
|
|
||||||
args = self.get_operations_data(d)
|
|
||||||
|
|
||||||
add_timesheet_detail(timesheet, args)
|
|
||||||
original_start_time = d.planned_start_time
|
|
||||||
|
|
||||||
# validate operating hours if workstation [not mandatory] is specified
|
|
||||||
try:
|
|
||||||
timesheet.validate_time_logs()
|
|
||||||
except OverlapError:
|
|
||||||
if frappe.message_log: frappe.message_log.pop()
|
|
||||||
timesheet.schedule_for_production_order(d.idx)
|
|
||||||
except WorkstationHolidayError:
|
|
||||||
if frappe.message_log: frappe.message_log.pop()
|
|
||||||
timesheet.schedule_for_production_order(d.idx)
|
|
||||||
|
|
||||||
from_time, to_time = self.get_start_end_time(timesheet, d.name)
|
|
||||||
|
|
||||||
if date_diff(from_time, original_start_time) > cint(plan_days):
|
|
||||||
frappe.throw(_("Unable to find Time Slot in the next {0} days for Operation {1}").format(plan_days, d.operation))
|
|
||||||
break
|
|
||||||
|
|
||||||
d.planned_start_time = from_time
|
|
||||||
d.planned_end_time = to_time
|
|
||||||
d.db_update()
|
|
||||||
|
|
||||||
if timesheet and open_new:
|
|
||||||
return timesheet
|
|
||||||
|
|
||||||
if timesheet and timesheet.get("time_logs"):
|
|
||||||
timesheet.save()
|
|
||||||
timesheets.append(getlink("Timesheet", timesheet.name))
|
|
||||||
|
|
||||||
self.planned_end_date = self.operations[-1].planned_end_time
|
|
||||||
if timesheets:
|
|
||||||
frappe.local.message_log = []
|
|
||||||
frappe.msgprint(_("Timesheet created:") + "\n" + "\n".join(timesheets))
|
|
||||||
|
|
||||||
def get_operations_data(self, data):
|
|
||||||
return {
|
|
||||||
'from_time': get_datetime(data.planned_start_time),
|
|
||||||
'hours': data.time_in_mins / 60.0,
|
|
||||||
'to_time': get_datetime(data.planned_end_time),
|
|
||||||
'project': self.project,
|
|
||||||
'operation': data.operation,
|
|
||||||
'operation_id': data.name,
|
|
||||||
'workstation': data.workstation,
|
|
||||||
'completed_qty': flt(self.qty) - flt(data.completed_qty)
|
|
||||||
}
|
|
||||||
|
|
||||||
def set_start_end_time_for_workstation(self, data, index):
|
|
||||||
"""Set start and end time for given operation. If first operation, set start as
|
|
||||||
`planned_start_date`, else add time diff to end time of earlier operation."""
|
|
||||||
|
|
||||||
if index == 0:
|
|
||||||
data.planned_start_time = self.planned_start_date
|
|
||||||
else:
|
|
||||||
data.planned_start_time = get_datetime(self.operations[index-1].planned_end_time)\
|
|
||||||
+ get_mins_between_operations()
|
|
||||||
|
|
||||||
data.planned_end_time = get_datetime(data.planned_start_time) + relativedelta(minutes = data.time_in_mins)
|
|
||||||
|
|
||||||
if data.planned_start_time == data.planned_end_time:
|
|
||||||
frappe.throw(_("Capacity Planning Error"))
|
|
||||||
|
|
||||||
def get_start_end_time(self, timesheet, operation_id):
|
|
||||||
for data in timesheet.time_logs:
|
|
||||||
if data.operation_id == operation_id:
|
|
||||||
return data.from_time, data.to_time
|
|
||||||
|
|
||||||
def check_operation_fits_in_working_hours(self, d):
|
|
||||||
"""Raises expection if operation is longer than working hours in the given workstation."""
|
|
||||||
from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours
|
|
||||||
check_if_within_operating_hours(d.workstation, d.operation, d.planned_start_time, d.planned_end_time)
|
|
||||||
|
|
||||||
def update_operation_status(self):
|
|
||||||
for d in self.get("operations"):
|
|
||||||
if not d.completed_qty:
|
|
||||||
d.status = "Pending"
|
|
||||||
elif flt(d.completed_qty) < flt(self.qty):
|
|
||||||
d.status = "Work in Progress"
|
|
||||||
elif flt(d.completed_qty) == flt(self.qty):
|
|
||||||
d.status = "Completed"
|
|
||||||
else:
|
|
||||||
frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'"))
|
|
||||||
|
|
||||||
def set_actual_dates(self):
|
|
||||||
self.actual_start_date = None
|
|
||||||
self.actual_end_date = None
|
|
||||||
if self.get("operations"):
|
|
||||||
actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time]
|
|
||||||
if actual_start_dates:
|
|
||||||
self.actual_start_date = min(actual_start_dates)
|
|
||||||
|
|
||||||
actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time]
|
|
||||||
if actual_end_dates:
|
|
||||||
self.actual_end_date = max(actual_end_dates)
|
|
||||||
|
|
||||||
def delete_timesheet(self):
|
|
||||||
for timesheet in frappe.get_all("Timesheet", ["name"], {"production_order": self.name}):
|
|
||||||
frappe.delete_doc("Timesheet", timesheet.name)
|
|
||||||
|
|
||||||
def validate_production_item(self):
|
|
||||||
if frappe.db.get_value("Item", self.production_item, "has_variants"):
|
|
||||||
frappe.throw(_("Production Order cannot be raised against a Item Template"), ItemHasVariantError)
|
|
||||||
|
|
||||||
if self.production_item:
|
|
||||||
validate_end_of_life(self.production_item)
|
|
||||||
|
|
||||||
def validate_qty(self):
|
|
||||||
if not self.qty > 0:
|
|
||||||
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
|
|
||||||
|
|
||||||
def validate_operation_time(self):
|
|
||||||
for d in self.operations:
|
|
||||||
if not d.time_in_mins > 0:
|
|
||||||
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}".format(d.operation)))
|
|
||||||
|
|
||||||
def update_required_items(self):
|
|
||||||
'''
|
|
||||||
update bin reserved_qty_for_production
|
|
||||||
called from Stock Entry for production, after submit, cancel
|
|
||||||
'''
|
|
||||||
if self.docstatus==1:
|
|
||||||
# calculate transferred qty based on submitted stock entries
|
|
||||||
self.update_transaferred_qty_for_required_items()
|
|
||||||
|
|
||||||
# update in bin
|
|
||||||
self.update_reserved_qty_for_production()
|
|
||||||
|
|
||||||
def update_reserved_qty_for_production(self, items=None):
|
|
||||||
'''update reserved_qty_for_production in bins'''
|
|
||||||
for d in self.required_items:
|
|
||||||
if d.source_warehouse:
|
|
||||||
stock_bin = get_bin(d.item_code, d.source_warehouse)
|
|
||||||
stock_bin.update_reserved_qty_for_production()
|
|
||||||
|
|
||||||
def get_items_and_operations_from_bom(self):
|
|
||||||
self.set_required_items()
|
|
||||||
self.set_production_order_operations()
|
|
||||||
|
|
||||||
return check_if_scrap_warehouse_mandatory(self.bom_no)
|
|
||||||
|
|
||||||
def set_available_qty(self):
|
|
||||||
for d in self.get("required_items"):
|
|
||||||
if d.source_warehouse:
|
|
||||||
d.available_qty_at_source_warehouse = get_latest_stock_qty(d.item_code, d.source_warehouse)
|
|
||||||
|
|
||||||
if self.wip_warehouse:
|
|
||||||
d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse)
|
|
||||||
|
|
||||||
def set_required_items(self, reset_only_qty=False):
|
|
||||||
'''set required_items for production to keep track of reserved qty'''
|
|
||||||
if not reset_only_qty:
|
|
||||||
self.required_items = []
|
|
||||||
|
|
||||||
if self.bom_no and self.qty:
|
|
||||||
item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty,
|
|
||||||
fetch_exploded = self.use_multi_level_bom)
|
|
||||||
|
|
||||||
if reset_only_qty:
|
|
||||||
for d in self.get("required_items"):
|
|
||||||
if item_dict.get(d.item_code):
|
|
||||||
d.required_qty = item_dict.get(d.item_code).get("qty")
|
|
||||||
else:
|
|
||||||
for item in sorted(item_dict.values(), key=lambda d: d['idx']):
|
|
||||||
self.append('required_items', {
|
|
||||||
'item_code': item.item_code,
|
|
||||||
'item_name': item.item_name,
|
|
||||||
'description': item.description,
|
|
||||||
'required_qty': item.qty,
|
|
||||||
'source_warehouse': item.source_warehouse or item.default_warehouse
|
|
||||||
})
|
|
||||||
|
|
||||||
self.set_available_qty()
|
|
||||||
|
|
||||||
def update_transaferred_qty_for_required_items(self):
|
|
||||||
'''update transferred qty from submitted stock entries for that item against
|
|
||||||
the production order'''
|
|
||||||
|
|
||||||
for d in self.required_items:
|
|
||||||
transferred_qty = frappe.db.sql('''select sum(qty)
|
|
||||||
from `tabStock Entry` entry, `tabStock Entry Detail` detail
|
|
||||||
where
|
|
||||||
entry.production_order = %s
|
|
||||||
and entry.purpose = "Material Transfer for Manufacture"
|
|
||||||
and entry.docstatus = 1
|
|
||||||
and detail.parent = entry.name
|
|
||||||
and detail.item_code = %s''', (self.name, d.item_code))[0][0]
|
|
||||||
|
|
||||||
d.db_set('transferred_qty', flt(transferred_qty), update_modified = False)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_item_details(item, project = None):
|
|
||||||
res = frappe.db.sql("""
|
|
||||||
select stock_uom, description
|
|
||||||
from `tabItem`
|
|
||||||
where disabled=0
|
|
||||||
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
|
|
||||||
and name=%s
|
|
||||||
""", (nowdate(), item), as_dict=1)
|
|
||||||
|
|
||||||
if not res:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
res = res[0]
|
|
||||||
|
|
||||||
filters = {"item": item, "is_default": 1}
|
|
||||||
|
|
||||||
if project:
|
|
||||||
filters = {"item": item, "project": project}
|
|
||||||
|
|
||||||
res["bom_no"] = frappe.db.get_value("BOM", filters = filters)
|
|
||||||
|
|
||||||
if not res["bom_no"]:
|
|
||||||
variant_of= frappe.db.get_value("Item", item, "variant_of")
|
|
||||||
|
|
||||||
if variant_of:
|
|
||||||
res["bom_no"] = frappe.db.get_value("BOM", filters={"item": variant_of, "is_default": 1})
|
|
||||||
|
|
||||||
if not res["bom_no"]:
|
|
||||||
if project:
|
|
||||||
res = get_item_details(item)
|
|
||||||
frappe.msgprint(_("Default BOM not found for Item {0} and Project {1}").format(item, project), alert=1)
|
|
||||||
else:
|
|
||||||
frappe.throw(_("Default BOM for {0} not found").format(item))
|
|
||||||
|
|
||||||
res['project'] = project or frappe.db.get_value('BOM', res['bom_no'], 'project')
|
|
||||||
res.update(check_if_scrap_warehouse_mandatory(res["bom_no"]))
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def check_if_scrap_warehouse_mandatory(bom_no):
|
|
||||||
res = {"set_scrap_wh_mandatory": False }
|
|
||||||
if bom_no:
|
|
||||||
bom = frappe.get_doc("BOM", bom_no)
|
|
||||||
|
|
||||||
if len(bom.scrap_items) > 0:
|
|
||||||
res["set_scrap_wh_mandatory"] = True
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def set_production_order_ops(name):
|
|
||||||
po = frappe.get_doc('Production Order', name)
|
|
||||||
po.set_production_order_operations()
|
|
||||||
po.save()
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def make_stock_entry(production_order_id, purpose, qty=None):
|
|
||||||
production_order = frappe.get_doc("Production Order", production_order_id)
|
|
||||||
if not frappe.db.get_value("Warehouse", production_order.wip_warehouse, "is_group") \
|
|
||||||
and not production_order.skip_transfer:
|
|
||||||
wip_warehouse = production_order.wip_warehouse
|
|
||||||
else:
|
|
||||||
wip_warehouse = None
|
|
||||||
|
|
||||||
stock_entry = frappe.new_doc("Stock Entry")
|
|
||||||
stock_entry.purpose = purpose
|
|
||||||
stock_entry.production_order = production_order_id
|
|
||||||
stock_entry.company = production_order.company
|
|
||||||
stock_entry.from_bom = 1
|
|
||||||
stock_entry.bom_no = production_order.bom_no
|
|
||||||
stock_entry.use_multi_level_bom = production_order.use_multi_level_bom
|
|
||||||
stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty))
|
|
||||||
stock_entry.set_stock_entry_type()
|
|
||||||
|
|
||||||
if purpose=="Material Transfer for Manufacture":
|
|
||||||
stock_entry.to_warehouse = wip_warehouse
|
|
||||||
stock_entry.project = production_order.project
|
|
||||||
else:
|
|
||||||
stock_entry.from_warehouse = wip_warehouse
|
|
||||||
stock_entry.to_warehouse = production_order.fg_warehouse
|
|
||||||
additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty)
|
|
||||||
stock_entry.project = production_order.project
|
|
||||||
stock_entry.set("additional_costs", additional_costs)
|
|
||||||
|
|
||||||
stock_entry.get_items()
|
|
||||||
return stock_entry.as_dict()
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def make_timesheet(production_order, company):
|
|
||||||
timesheet = frappe.new_doc("Timesheet")
|
|
||||||
timesheet.employee = ""
|
|
||||||
timesheet.production_order = production_order
|
|
||||||
timesheet.company = company
|
|
||||||
return timesheet
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def add_timesheet_detail(timesheet, args):
|
|
||||||
if isinstance(timesheet, text_type):
|
|
||||||
timesheet = frappe.get_doc('Timesheet', timesheet)
|
|
||||||
|
|
||||||
if isinstance(args, text_type):
|
|
||||||
args = json.loads(args)
|
|
||||||
|
|
||||||
timesheet.append('time_logs', args)
|
|
||||||
return timesheet
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_default_warehouse():
|
|
||||||
wip_warehouse = frappe.db.get_single_value("Manufacturing Settings",
|
|
||||||
"default_wip_warehouse")
|
|
||||||
fg_warehouse = frappe.db.get_single_value("Manufacturing Settings",
|
|
||||||
"default_fg_warehouse")
|
|
||||||
return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def make_new_timesheet(source_name, target_doc=None):
|
|
||||||
po = frappe.get_doc('Production Order', source_name)
|
|
||||||
ts = po.make_time_logs(open_new=True)
|
|
||||||
|
|
||||||
if not ts or not ts.get('time_logs'):
|
|
||||||
frappe.throw(_("Already completed"))
|
|
||||||
|
|
||||||
return ts
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def stop_unstop(production_order, status):
|
|
||||||
""" Called from client side on Stop/Unstop event"""
|
|
||||||
|
|
||||||
if not frappe.has_permission("Production Order", "write"):
|
|
||||||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
|
||||||
|
|
||||||
pro_order = frappe.get_doc("Production Order", production_order)
|
|
||||||
pro_order.update_status(status)
|
|
||||||
pro_order.update_planned_qty()
|
|
||||||
frappe.msgprint(_("Production Order has been {0}").format(status))
|
|
||||||
pro_order.notify_update()
|
|
||||||
|
|
||||||
return pro_order.status
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def query_sales_order(production_item):
|
|
||||||
out = frappe.db.sql_list("""
|
|
||||||
select distinct so.name from `tabSales Order` so, `tabSales Order Item` so_item
|
|
||||||
where so_item.parent=so.name and so_item.item_code=%s and so.docstatus=1
|
|
||||||
union
|
|
||||||
select distinct so.name from `tabSales Order` so, `tabPacked Item` pi_item
|
|
||||||
where pi_item.parent=so.name and pi_item.item_code=%s and so.docstatus=1
|
|
||||||
""", (production_item, production_item))
|
|
||||||
|
|
||||||
return out
|
|
@ -99,7 +99,7 @@ class ProductionPlan(Document):
|
|||||||
self.get_mr_items()
|
self.get_mr_items()
|
||||||
|
|
||||||
def get_so_items(self):
|
def get_so_items(self):
|
||||||
so_list = [d.sales_order for d in self.sales_orders if d.sales_order]
|
so_list = [d.sales_order for d in self.get("sales_orders", []) if d.sales_order]
|
||||||
if not so_list:
|
if not so_list:
|
||||||
msgprint(_("Please enter Sales Orders in the above table"))
|
msgprint(_("Please enter Sales Orders in the above table"))
|
||||||
return []
|
return []
|
||||||
@ -134,7 +134,7 @@ class ProductionPlan(Document):
|
|||||||
self.calculate_total_planned_qty()
|
self.calculate_total_planned_qty()
|
||||||
|
|
||||||
def get_mr_items(self):
|
def get_mr_items(self):
|
||||||
mr_list = [d.material_request for d in self.material_requests if d.material_request]
|
mr_list = [d.material_request for d in self.get("material_requests", []) if d.material_request]
|
||||||
if not mr_list:
|
if not mr_list:
|
||||||
msgprint(_("Please enter Material Requests in the above table"))
|
msgprint(_("Please enter Material Requests in the above table"))
|
||||||
return []
|
return []
|
||||||
|
@ -632,3 +632,4 @@ execute:frappe.reload_doc('desk', 'doctype','dashboard_chart')
|
|||||||
erpnext.patches.v12_0.add_default_dashboards
|
erpnext.patches.v12_0.add_default_dashboards
|
||||||
erpnext.patches.v12_0.remove_bank_remittance_custom_fields
|
erpnext.patches.v12_0.remove_bank_remittance_custom_fields
|
||||||
erpnext.patches.v12_0.generate_leave_ledger_entries
|
erpnext.patches.v12_0.generate_leave_ledger_entries
|
||||||
|
erpnext.patches.v12_0.move_credit_limit_to_customer_credit_limit
|
@ -0,0 +1,46 @@
|
|||||||
|
# Copyright (c) 2019, Frappe and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
''' Move credit limit and bypass credit limit to the child table of customer credit limit '''
|
||||||
|
frappe.reload_doc("Selling", "doctype", "Customer Credit Limit")
|
||||||
|
frappe.reload_doc("Selling", "doctype", "Customer")
|
||||||
|
frappe.reload_doc("Setup", "doctype", "Customer Group")
|
||||||
|
|
||||||
|
if frappe.db.a_row_exists("Customer Credit Limit"):
|
||||||
|
return
|
||||||
|
|
||||||
|
move_credit_limit_to_child_table()
|
||||||
|
|
||||||
|
def move_credit_limit_to_child_table():
|
||||||
|
''' maps data from old field to the new field in the child table '''
|
||||||
|
|
||||||
|
companies = frappe.get_all("Company", 'name')
|
||||||
|
for doctype in ("Customer", "Customer Group"):
|
||||||
|
fields = ""
|
||||||
|
if doctype == "Customer" \
|
||||||
|
and frappe.db.has_column('Customer', 'bypass_credit_limit_check_at_sales_order'):
|
||||||
|
fields = ", bypass_credit_limit_check_at_sales_order"
|
||||||
|
|
||||||
|
credit_limit_records = frappe.db.sql('''
|
||||||
|
SELECT name, credit_limit {0}
|
||||||
|
FROM `tab{1}` where credit_limit > 0
|
||||||
|
'''.format(fields, doctype), as_dict=1) #nosec
|
||||||
|
|
||||||
|
for record in credit_limit_records:
|
||||||
|
doc = frappe.get_doc(doctype, record.name)
|
||||||
|
for company in companies:
|
||||||
|
row = frappe._dict({
|
||||||
|
'credit_limit': record.credit_limit,
|
||||||
|
'company': company.name
|
||||||
|
})
|
||||||
|
if doctype == "Customer":
|
||||||
|
row.bypass_credit_limit_check = record.bypass_credit_limit_check_at_sales_order
|
||||||
|
|
||||||
|
doc.append("credit_limits", row)
|
||||||
|
|
||||||
|
for row in doc.credit_limits:
|
||||||
|
row.db_insert()
|
@ -82,7 +82,7 @@ def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_
|
|||||||
account_name = " - ".join(parts[:-1])
|
account_name = " - ".join(parts[:-1])
|
||||||
company = frappe.db.get_value("Company", filters={"abbr": parts[-1]})
|
company = frappe.db.get_value("Company", filters={"abbr": parts[-1]})
|
||||||
parent_account = frappe.db.get_value("Account",
|
parent_account = frappe.db.get_value("Account",
|
||||||
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0}, fieldname="parent_account")
|
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
|
||||||
|
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Account",
|
"doctype": "Account",
|
||||||
|
@ -147,6 +147,15 @@ frappe.ui.form.on("Timesheet Detail", {
|
|||||||
calculate_time_and_amount(frm);
|
calculate_time_and_amount(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
task: (frm, cdt, cdn) => {
|
||||||
|
let row = frm.selected_doc;
|
||||||
|
if (row.task) {
|
||||||
|
frappe.db.get_value("Task", row.task, "project", (r) => {
|
||||||
|
frappe.model.set_value(cdt, cdn, "project", r.project);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
from_time: function(frm, cdt, cdn) {
|
from_time: function(frm, cdt, cdn) {
|
||||||
calculate_end_time(frm, cdt, cdn);
|
calculate_end_time(frm, cdt, cdn);
|
||||||
},
|
},
|
||||||
@ -200,9 +209,6 @@ frappe.ui.form.on("Timesheet Detail", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
activity_type: function(frm, cdt, cdn) {
|
activity_type: function(frm, cdt, cdn) {
|
||||||
frm.script_manager.copy_from_first_row('time_logs', frm.selected_doc,
|
|
||||||
'project');
|
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost",
|
method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost",
|
||||||
args: {
|
args: {
|
||||||
|
@ -145,12 +145,17 @@ class Timesheet(Document):
|
|||||||
def validate_time_logs(self):
|
def validate_time_logs(self):
|
||||||
for data in self.get('time_logs'):
|
for data in self.get('time_logs'):
|
||||||
self.validate_overlap(data)
|
self.validate_overlap(data)
|
||||||
|
self.validate_task_project()
|
||||||
|
|
||||||
def validate_overlap(self, data):
|
def validate_overlap(self, data):
|
||||||
settings = frappe.get_single('Projects Settings')
|
settings = frappe.get_single('Projects Settings')
|
||||||
self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
|
self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
|
||||||
self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
|
self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
|
||||||
|
|
||||||
|
def validate_task_project(self):
|
||||||
|
for log in self.time_logs:
|
||||||
|
log.project = log.project or frappe.db.get_value("Task", log.task, "project")
|
||||||
|
|
||||||
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
|
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
|
||||||
if not value or ignore_validation:
|
if not value or ignore_validation:
|
||||||
return
|
return
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
|
|
||||||
erpnext.get_purchase_trends_filters = function() {
|
erpnext.get_purchase_trends_filters = function() {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
"fieldname":"company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": frappe.defaults.get_user_default("Company")
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"period",
|
"fieldname":"period",
|
||||||
"label": __("Period"),
|
"label": __("Period"),
|
||||||
@ -15,6 +23,23 @@ erpnext.get_purchase_trends_filters = function() {
|
|||||||
],
|
],
|
||||||
"default": "Monthly"
|
"default": "Monthly"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"fiscal_year",
|
||||||
|
"label": __("Fiscal Year"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options":'Fiscal Year',
|
||||||
|
"default": frappe.sys_defaults.fiscal_year
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"period_based_on",
|
||||||
|
"label": __("Period based On"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{ "value": "posting_date", "label": __("Posting Date") },
|
||||||
|
{ "value": "bill_date", "label": __("Billing Date") },
|
||||||
|
],
|
||||||
|
"default": "posting_date"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"based_on",
|
"fieldname":"based_on",
|
||||||
"label": __("Based On"),
|
"label": __("Based On"),
|
||||||
@ -39,19 +64,5 @@ erpnext.get_purchase_trends_filters = function() {
|
|||||||
],
|
],
|
||||||
"default": ""
|
"default": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname":"fiscal_year",
|
|
||||||
"label": __("Fiscal Year"),
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options":'Fiscal Year',
|
|
||||||
"default": frappe.sys_defaults.fiscal_year
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname":"company",
|
|
||||||
"label": __("Company"),
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Company",
|
|
||||||
"default": frappe.defaults.get_user_default("Company")
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,19 @@ class QualityProcedure(NestedSet):
|
|||||||
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
|
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
|
||||||
if parent is None or parent == "All Quality Procedures":
|
if parent is None or parent == "All Quality Procedures":
|
||||||
parent = ""
|
parent = ""
|
||||||
return frappe.get_all(doctype, fields=["name as value", "is_group as expandable"], filters={"parent_quality_procedure": parent})
|
|
||||||
|
return frappe.db.sql("""
|
||||||
|
select
|
||||||
|
name as value,
|
||||||
|
is_group as expandable
|
||||||
|
from
|
||||||
|
`tab{doctype}`
|
||||||
|
where
|
||||||
|
ifnull(parent_quality_procedure, "")={parent}
|
||||||
|
""".format(
|
||||||
|
doctype = doctype,
|
||||||
|
parent=frappe.db.escape(parent)
|
||||||
|
), as_dict=1)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_node():
|
def add_node():
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
frappe.provide("frappe.treeview_settings");
|
|
||||||
|
|
||||||
frappe.treeview_settings["Quality Procedure"] = {
|
frappe.treeview_settings["Quality Procedure"] = {
|
||||||
ignore_fields:["parent_quality_procedure"],
|
ignore_fields:["parent_quality_procedure"],
|
||||||
get_tree_nodes: 'erpnext.quality_management.doctype.quality_procedure.quality_procedure.get_children',
|
get_tree_nodes: 'erpnext.quality_management.doctype.quality_procedure.quality_procedure.get_children',
|
||||||
@ -19,7 +17,7 @@ frappe.treeview_settings["Quality Procedure"] = {
|
|||||||
],
|
],
|
||||||
breadcrumb: "Setup",
|
breadcrumb: "Setup",
|
||||||
root_label: "All Quality Procedures",
|
root_label: "All Quality Procedures",
|
||||||
get_tree_root: true,
|
get_tree_root: false,
|
||||||
menu_items: [
|
menu_items: [
|
||||||
{
|
{
|
||||||
label: __("New Quality Procedure"),
|
label: __("New Quality Procedure"),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -167,13 +167,18 @@ class Customer(TransactionBase):
|
|||||||
frappe.throw(_("A Customer Group exists with same name please change the Customer name or rename the Customer Group"), frappe.NameError)
|
frappe.throw(_("A Customer Group exists with same name please change the Customer name or rename the Customer Group"), frappe.NameError)
|
||||||
|
|
||||||
def validate_credit_limit_on_change(self):
|
def validate_credit_limit_on_change(self):
|
||||||
if self.get("__islocal") or not self.credit_limit \
|
if self.get("__islocal") or not self.credit_limits:
|
||||||
or self.credit_limit == frappe.db.get_value("Customer", self.name, "credit_limit"):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
for company in frappe.get_all("Company"):
|
company_record = []
|
||||||
outstanding_amt = get_customer_outstanding(self.name, company.name)
|
for limit in self.credit_limits:
|
||||||
if flt(self.credit_limit) < outstanding_amt:
|
if limit.company in company_record:
|
||||||
|
frappe.throw(_("Credit limit is already defined for the Company {0}").format(limit.company, self.name))
|
||||||
|
else:
|
||||||
|
company_record.append(limit.company)
|
||||||
|
|
||||||
|
outstanding_amt = get_customer_outstanding(self.name, limit.company)
|
||||||
|
if flt(limit.credit_limit) < outstanding_amt:
|
||||||
frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt))
|
frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt))
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
@ -322,11 +327,13 @@ def get_credit_limit(customer, company):
|
|||||||
credit_limit = None
|
credit_limit = None
|
||||||
|
|
||||||
if customer:
|
if customer:
|
||||||
credit_limit, customer_group = frappe.get_cached_value("Customer",
|
credit_limit = frappe.db.get_value("Customer Credit Limit",
|
||||||
customer, ["credit_limit", "customer_group"])
|
{'parent': customer, 'parenttype': 'Customer', 'company': company}, 'credit_limit')
|
||||||
|
|
||||||
if not credit_limit:
|
if not credit_limit:
|
||||||
credit_limit = frappe.get_cached_value("Customer Group", customer_group, "credit_limit")
|
customer_group = frappe.get_cached_value("Customer", customer, 'customer_group')
|
||||||
|
credit_limit = frappe.db.get_value("Customer Credit Limit",
|
||||||
|
{'parent': customer_group, 'parenttype': 'Customer Group', 'company': company}, 'credit_limit')
|
||||||
|
|
||||||
if not credit_limit:
|
if not credit_limit:
|
||||||
credit_limit = frappe.get_cached_value('Company', company, "credit_limit")
|
credit_limit = frappe.get_cached_value('Company', company, "credit_limit")
|
||||||
|
@ -9,7 +9,7 @@ def get_data():
|
|||||||
'heatmap_message': _('This is based on transactions against this Customer. See timeline below for details'),
|
'heatmap_message': _('This is based on transactions against this Customer. See timeline below for details'),
|
||||||
'fieldname': 'customer',
|
'fieldname': 'customer',
|
||||||
'non_standard_fieldnames': {
|
'non_standard_fieldnames': {
|
||||||
'Payment Entry': 'party_name',
|
'Payment Entry': 'party',
|
||||||
'Quotation': 'party_name',
|
'Quotation': 'party_name',
|
||||||
'Opportunity': 'party_name'
|
'Opportunity': 'party_name'
|
||||||
},
|
},
|
||||||
|
@ -25,7 +25,7 @@ class TestCustomer(unittest.TestCase):
|
|||||||
make_test_records('Item')
|
make_test_records('Item')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.set_value("Customer", '_Test Customer', 'credit_limit', 0.0)
|
set_credit_limit('_Test Customer', '_Test Company', 0)
|
||||||
|
|
||||||
def test_party_details(self):
|
def test_party_details(self):
|
||||||
from erpnext.accounts.party import get_party_details
|
from erpnext.accounts.party import get_party_details
|
||||||
@ -225,8 +225,8 @@ class TestCustomer(unittest.TestCase):
|
|||||||
item_qty = int((abs(outstanding_amt) + 200)/100)
|
item_qty = int((abs(outstanding_amt) + 200)/100)
|
||||||
make_sales_order(qty=item_qty)
|
make_sales_order(qty=item_qty)
|
||||||
|
|
||||||
if credit_limit == 0.0:
|
if not credit_limit:
|
||||||
frappe.db.set_value("Customer", '_Test Customer', 'credit_limit', outstanding_amt - 50.0)
|
set_credit_limit('_Test Customer', '_Test Company', outstanding_amt - 50)
|
||||||
|
|
||||||
# Sales Order
|
# Sales Order
|
||||||
so = make_sales_order(do_not_submit=True)
|
so = make_sales_order(do_not_submit=True)
|
||||||
@ -241,7 +241,7 @@ class TestCustomer(unittest.TestCase):
|
|||||||
self.assertRaises(frappe.ValidationError, si.submit)
|
self.assertRaises(frappe.ValidationError, si.submit)
|
||||||
|
|
||||||
if credit_limit > outstanding_amt:
|
if credit_limit > outstanding_amt:
|
||||||
frappe.db.set_value("Customer", '_Test Customer', 'credit_limit', credit_limit)
|
set_credit_limit('_Test Customer', '_Test Company', credit_limit)
|
||||||
|
|
||||||
# Makes Sales invoice from Sales Order
|
# Makes Sales invoice from Sales Order
|
||||||
so.save(ignore_permissions=True)
|
so.save(ignore_permissions=True)
|
||||||
@ -252,7 +252,10 @@ class TestCustomer(unittest.TestCase):
|
|||||||
def test_customer_credit_limit_on_change(self):
|
def test_customer_credit_limit_on_change(self):
|
||||||
outstanding_amt = self.get_customer_outstanding_amount()
|
outstanding_amt = self.get_customer_outstanding_amount()
|
||||||
customer = frappe.get_doc("Customer", '_Test Customer')
|
customer = frappe.get_doc("Customer", '_Test Customer')
|
||||||
customer.credit_limit = flt(outstanding_amt - 100)
|
customer.append('credit_limits', {'credit_limit': flt(outstanding_amt - 100), 'company': '_Test Company'})
|
||||||
|
|
||||||
|
''' define new credit limit for same company '''
|
||||||
|
customer.append('credit_limits', {'credit_limit': flt(outstanding_amt - 100), 'company': '_Test Company'})
|
||||||
self.assertRaises(frappe.ValidationError, customer.save)
|
self.assertRaises(frappe.ValidationError, customer.save)
|
||||||
|
|
||||||
def test_customer_payment_terms(self):
|
def test_customer_payment_terms(self):
|
||||||
@ -292,3 +295,20 @@ def get_customer_dict(customer_name):
|
|||||||
"doctype": "Customer",
|
"doctype": "Customer",
|
||||||
"territory": "_Test Territory"
|
"territory": "_Test Territory"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def set_credit_limit(customer, company, credit_limit):
|
||||||
|
customer = frappe.get_doc("Customer", customer)
|
||||||
|
existing_row = None
|
||||||
|
for d in customer.credit_limits:
|
||||||
|
if d.company == company:
|
||||||
|
existing_row = d
|
||||||
|
d.credit_limit = credit_limit
|
||||||
|
d.db_update()
|
||||||
|
break
|
||||||
|
|
||||||
|
if not existing_row:
|
||||||
|
customer.append('credit_limits', {
|
||||||
|
'company': company,
|
||||||
|
'credit_limit': credit_limit
|
||||||
|
})
|
||||||
|
customer.credit_limits[-1].db_insert()
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"creation": "2019-08-28 17:29:42.115592",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"company",
|
||||||
|
"column_break_2",
|
||||||
|
"credit_limit",
|
||||||
|
"bypass_credit_limit_check"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"columns": 4,
|
||||||
|
"fieldname": "credit_limit",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Credit Limit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 4,
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "bypass_credit_limit_check",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Bypass credit limit_check"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"modified": "2019-08-29 20:46:36.073953",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Selling",
|
||||||
|
"name": "Customer Credit Limit",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC"
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class CustomerCreditLimit(Document):
|
||||||
|
pass
|
@ -208,7 +208,9 @@ class SalesOrder(SellingController):
|
|||||||
def check_credit_limit(self):
|
def check_credit_limit(self):
|
||||||
# if bypass credit limit check is set to true (1) at sales order level,
|
# if bypass credit limit check is set to true (1) at sales order level,
|
||||||
# then we need not to check credit limit and vise versa
|
# then we need not to check credit limit and vise versa
|
||||||
if not cint(frappe.get_cached_value("Customer", self.customer, "bypass_credit_limit_check_at_sales_order")):
|
if not cint(frappe.db.get_value("Customer Credit Limit",
|
||||||
|
{'parent': self.customer, 'parenttype': 'Customer', 'company': self.company},
|
||||||
|
"bypass_credit_limit_check")):
|
||||||
check_credit_limit(self.customer, self.company)
|
check_credit_limit(self.customer, self.company)
|
||||||
|
|
||||||
def check_nextdoc_docstatus(self):
|
def check_nextdoc_docstatus(self):
|
||||||
@ -518,7 +520,7 @@ def make_material_request(source_name, target_doc=None):
|
|||||||
"doctype": "Material Request Item",
|
"doctype": "Material Request Item",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"parent": "sales_order",
|
"parent": "sales_order",
|
||||||
"stock_uom": "uom"
|
"uom": "stock_uom"
|
||||||
},
|
},
|
||||||
"postprocess": update_item
|
"postprocess": update_item
|
||||||
},
|
},
|
||||||
|
@ -10,8 +10,10 @@ QUnit.test("test_sales_order_with_bypass_credit_limit_check", function(assert) {
|
|||||||
() => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
|
() => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => cur_frm.set_value("customer_name", "Test Customer 10"),
|
() => cur_frm.set_value("customer_name", "Test Customer 10"),
|
||||||
() => cur_frm.set_value("credit_limit", 100.00),
|
() => cur_frm.add_child('credit_limits', {
|
||||||
() => cur_frm.set_value("bypass_credit_limit_check_at_sales_order", 1),
|
'company': cur_frm.doc.company || '_Test Company'
|
||||||
|
'credit_limit': 1000,
|
||||||
|
'bypass_credit_limit_check': 1}),
|
||||||
// save form
|
// save form
|
||||||
() => cur_frm.save(),
|
() => cur_frm.save(),
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
|
@ -10,8 +10,10 @@ QUnit.test("test_sales_order_without_bypass_credit_limit_check", function(assert
|
|||||||
() => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
|
() => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => cur_frm.set_value("customer_name", "Test Customer 11"),
|
() => cur_frm.set_value("customer_name", "Test Customer 11"),
|
||||||
() => cur_frm.set_value("credit_limit", 100.00),
|
() => cur_frm.add_child('credit_limits', {
|
||||||
() => cur_frm.set_value("bypass_credit_limit_check_at_sales_order", 0),
|
'credit_limit': 1000,
|
||||||
|
'company': '_Test Company',
|
||||||
|
'bypass_credit_limit_check': 1}),
|
||||||
// save form
|
// save form
|
||||||
() => cur_frm.save(),
|
() => cur_frm.save(),
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
|
@ -29,7 +29,7 @@ def execute(filters=None):
|
|||||||
|
|
||||||
if customer_naming_type == "Naming Series":
|
if customer_naming_type == "Naming Series":
|
||||||
row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal,
|
row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal,
|
||||||
d.bypass_credit_limit_check_at_sales_order, d.is_frozen,
|
d.bypass_credit_limit_check, d.is_frozen,
|
||||||
d.disabled]
|
d.disabled]
|
||||||
else:
|
else:
|
||||||
row = [d.name, credit_limit, outstanding_amt, bal,
|
row = [d.name, credit_limit, outstanding_amt, bal,
|
||||||
@ -60,9 +60,15 @@ def get_details(filters):
|
|||||||
conditions = ""
|
conditions = ""
|
||||||
|
|
||||||
if filters.get("customer"):
|
if filters.get("customer"):
|
||||||
conditions += " where name = %(customer)s"
|
conditions += " AND c.name = " + filters.get("customer")
|
||||||
|
|
||||||
return frappe.db.sql("""select name, customer_name,
|
|
||||||
bypass_credit_limit_check_at_sales_order, is_frozen, disabled from `tabCustomer` %s
|
|
||||||
""" % conditions, filters, as_dict=1)
|
|
||||||
|
|
||||||
|
return frappe.db.sql("""SELECT
|
||||||
|
c.name, c.customer_name,
|
||||||
|
ccl.bypass_credit_limit_check,
|
||||||
|
c.is_frozen, c.disabled
|
||||||
|
FROM `tabCustomer` c, `tabCustomer Credit Limit` ccl
|
||||||
|
WHERE
|
||||||
|
c.name = ccl.parent
|
||||||
|
AND ccl.company = %s
|
||||||
|
{0}
|
||||||
|
""".format(conditions), (filters.get("company")), as_dict=1) #nosec
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
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,cstr
|
from frappe.utils import flt
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
@ -100,7 +100,7 @@ def get_data():
|
|||||||
so.transaction_date,
|
so.transaction_date,
|
||||||
so.customer,
|
so.customer,
|
||||||
so.territory,
|
so.territory,
|
||||||
sum(so_item.qty) as net_qty,
|
sum(so_item.qty) as total_qty,
|
||||||
so.company
|
so.company
|
||||||
FROM `tabSales Order` so, `tabSales Order Item` so_item
|
FROM `tabSales Order` so, `tabSales Order Item` so_item
|
||||||
WHERE
|
WHERE
|
||||||
@ -111,10 +111,15 @@ def get_data():
|
|||||||
so.name,so_item.item_code
|
so.name,so_item.item_code
|
||||||
""", as_dict = 1)
|
""", as_dict = 1)
|
||||||
|
|
||||||
|
sales_orders = [row.name for row in sales_order_entry]
|
||||||
mr_records = frappe.get_all("Material Request Item",
|
mr_records = frappe.get_all("Material Request Item",
|
||||||
{"sales_order_item": ("!=",""), "docstatus": 1},
|
{"sales_order": ("in", sales_orders), "docstatus": 1},
|
||||||
["parent", "qty", "sales_order", "item_code"])
|
["parent", "qty", "sales_order", "item_code"])
|
||||||
|
|
||||||
|
bundled_item_map = get_packed_items(sales_orders)
|
||||||
|
|
||||||
|
item_with_product_bundle = get_items_with_product_bundle([row.item_code for row in sales_order_entry])
|
||||||
|
|
||||||
materials_request_dict = {}
|
materials_request_dict = {}
|
||||||
|
|
||||||
for record in mr_records:
|
for record in mr_records:
|
||||||
@ -131,27 +136,64 @@ def get_data():
|
|||||||
if record.parent not in details.get('material_requests'):
|
if record.parent not in details.get('material_requests'):
|
||||||
details['material_requests'].append(record.parent)
|
details['material_requests'].append(record.parent)
|
||||||
|
|
||||||
pending_so=[]
|
pending_so = []
|
||||||
for so in sales_order_entry:
|
for so in sales_order_entry:
|
||||||
# fetch all the material request records for a sales order item
|
if so.item_code not in item_with_product_bundle:
|
||||||
key = (so.name, so.item_code)
|
material_requests_against_so = materials_request_dict.get((so.name, so.item_code)) or {}
|
||||||
materials_request = materials_request_dict.get(key) or {}
|
|
||||||
|
|
||||||
# check for pending sales order
|
# check for pending sales order
|
||||||
if cint(so.net_qty) > cint(materials_request.get('qty')):
|
if flt(so.total_qty) > flt(material_requests_against_so.get('qty')):
|
||||||
so_record = {
|
so_record = {
|
||||||
"item_code": so.item_code,
|
"item_code": so.item_code,
|
||||||
"item_name": so.item_name,
|
"item_name": so.item_name,
|
||||||
"description": so.description,
|
"description": so.description,
|
||||||
"sales_order_no": so.name,
|
"sales_order_no": so.name,
|
||||||
"date": so.transaction_date,
|
"date": so.transaction_date,
|
||||||
"material_request": ','.join(materials_request.get('material_requests', [])),
|
"material_request": ','.join(material_requests_against_so.get('material_requests', [])),
|
||||||
"customer": so.customer,
|
"customer": so.customer,
|
||||||
"territory": so.territory,
|
"territory": so.territory,
|
||||||
"so_qty": so.net_qty,
|
"so_qty": so.total_qty,
|
||||||
"requested_qty": cint(materials_request.get('qty')),
|
"requested_qty": material_requests_against_so.get('qty'),
|
||||||
"pending_qty": so.net_qty - cint(materials_request.get('qty')),
|
"pending_qty": so.total_qty - flt(material_requests_against_so.get('qty')),
|
||||||
"company": so.company
|
"company": so.company
|
||||||
}
|
}
|
||||||
pending_so.append(so_record)
|
pending_so.append(so_record)
|
||||||
|
else:
|
||||||
|
for item in bundled_item_map.get((so.name, so.item_code)):
|
||||||
|
material_requests_against_so = materials_request_dict.get((so.name, item.item_code)) or {}
|
||||||
|
if flt(item.qty) > flt(material_requests_against_so.get('qty')):
|
||||||
|
so_record = {
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"item_name": item.item_name,
|
||||||
|
"description": item.description,
|
||||||
|
"sales_order_no": so.name,
|
||||||
|
"date": so.transaction_date,
|
||||||
|
"material_request": ','.join(material_requests_against_so.get('material_requests', [])),
|
||||||
|
"customer": so.customer,
|
||||||
|
"territory": so.territory,
|
||||||
|
"so_qty": item.qty,
|
||||||
|
"requested_qty": material_requests_against_so.get('qty', 0),
|
||||||
|
"pending_qty": item.qty - flt(material_requests_against_so.get('qty', 0)),
|
||||||
|
"company": so.company
|
||||||
|
}
|
||||||
|
pending_so.append(so_record)
|
||||||
|
|
||||||
|
|
||||||
return pending_so
|
return pending_so
|
||||||
|
|
||||||
|
def get_items_with_product_bundle(item_list):
|
||||||
|
bundled_items = frappe.get_all("Product Bundle", filters = [
|
||||||
|
("new_item_code", "IN", item_list)
|
||||||
|
], fields = ["new_item_code"])
|
||||||
|
|
||||||
|
return [d.new_item_code for d in bundled_items]
|
||||||
|
|
||||||
|
def get_packed_items(sales_order_list):
|
||||||
|
packed_items = frappe.get_all("Packed Item", filters = [
|
||||||
|
("parent", "IN", sales_order_list)
|
||||||
|
], fields = ["parent_item", "item_code", "qty", "item_name", "description", "parent"])
|
||||||
|
|
||||||
|
bundled_item_map = frappe._dict()
|
||||||
|
for d in packed_items:
|
||||||
|
bundled_item_map.setdefault((d.parent, d.parent_item), []).append(d)
|
||||||
|
|
||||||
|
return bundled_item_map
|
||||||
|
@ -40,6 +40,16 @@ class Analytics(object):
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"width": 140
|
"width": 140
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.filters.tree_type == "Item":
|
||||||
|
self.columns.append({
|
||||||
|
"label": _("UOM"),
|
||||||
|
"fieldname": 'stock_uom',
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "UOM",
|
||||||
|
"width": 100
|
||||||
|
})
|
||||||
|
|
||||||
for end_date in self.periodic_daterange:
|
for end_date in self.periodic_daterange:
|
||||||
period = self.get_period(end_date)
|
period = self.get_period(end_date)
|
||||||
self.columns.append({
|
self.columns.append({
|
||||||
@ -129,7 +139,7 @@ class Analytics(object):
|
|||||||
value_field = 'qty'
|
value_field = 'qty'
|
||||||
|
|
||||||
self.entries = frappe.db.sql("""
|
self.entries = frappe.db.sql("""
|
||||||
select i.item_code as entity, i.item_name as entity_name, i.{value_field} as value_field, s.{date_field}
|
select i.item_code as entity, i.item_name as entity_name, i.stock_uom, i.{value_field} as value_field, s.{date_field}
|
||||||
from `tab{doctype} Item` i , `tab{doctype}` s
|
from `tab{doctype} Item` i , `tab{doctype}` s
|
||||||
where s.name = i.parent and i.docstatus = 1 and s.company = %s
|
where s.name = i.parent and i.docstatus = 1 and s.company = %s
|
||||||
and s.{date_field} between %s and %s
|
and s.{date_field} between %s and %s
|
||||||
@ -198,6 +208,10 @@ class Analytics(object):
|
|||||||
total += amount
|
total += amount
|
||||||
|
|
||||||
row["total"] = total
|
row["total"] = total
|
||||||
|
|
||||||
|
if self.filters.tree_type == "Item":
|
||||||
|
row["stock_uom"] = period_data.get("stock_uom")
|
||||||
|
|
||||||
self.data.append(row)
|
self.data.append(row)
|
||||||
|
|
||||||
def get_rows_by_group(self):
|
def get_rows_by_group(self):
|
||||||
@ -232,6 +246,9 @@ class Analytics(object):
|
|||||||
self.entity_periodic_data.setdefault(d.entity, frappe._dict()).setdefault(period, 0.0)
|
self.entity_periodic_data.setdefault(d.entity, frappe._dict()).setdefault(period, 0.0)
|
||||||
self.entity_periodic_data[d.entity][period] += flt(d.value_field)
|
self.entity_periodic_data[d.entity][period] += flt(d.value_field)
|
||||||
|
|
||||||
|
if self.filters.tree_type == "Item":
|
||||||
|
self.entity_periodic_data[d.entity]['stock_uom'] = d.stock_uom
|
||||||
|
|
||||||
def get_period(self, posting_date):
|
def get_period(self, posting_date):
|
||||||
if self.filters.range == 'Weekly':
|
if self.filters.range == 'Weekly':
|
||||||
period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year)
|
period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year)
|
||||||
|
@ -1,553 +1,194 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"_comments": "[]",
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:customer_group_name",
|
"autoname": "field:customer_group_name",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-01-10 16:34:23",
|
"creation": "2013-01-10 16:34:23",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 0,
|
"field_order": [
|
||||||
|
"customer_group_name",
|
||||||
|
"parent_customer_group",
|
||||||
|
"is_group",
|
||||||
|
"cb0",
|
||||||
|
"default_price_list",
|
||||||
|
"payment_terms",
|
||||||
|
"lft",
|
||||||
|
"rgt",
|
||||||
|
"old_parent",
|
||||||
|
"default_receivable_account",
|
||||||
|
"accounts",
|
||||||
|
"credit_limit_section",
|
||||||
|
"credit_limits"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "customer_group_name",
|
"fieldname": "customer_group_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Customer Group Name",
|
"label": "Customer Group Name",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "customer_group_name",
|
"oldfieldname": "customer_group_name",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "",
|
|
||||||
"fieldname": "parent_customer_group",
|
"fieldname": "parent_customer_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Parent Customer Group",
|
"label": "Parent Customer Group",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "parent_customer_group",
|
"oldfieldname": "parent_customer_group",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Customer Group",
|
"options": "Customer Group"
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
"collapsible": 0,
|
"default": "0",
|
||||||
"columns": 0,
|
|
||||||
"description": "Only leaf nodes are allowed in transaction",
|
"description": "Only leaf nodes are allowed in transaction",
|
||||||
"fieldname": "is_group",
|
"fieldname": "is_group",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Is Group",
|
"label": "Is Group",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "is_group",
|
"oldfieldname": "is_group",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select"
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "cb0",
|
"fieldname": "cb0",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "default_price_list",
|
"fieldname": "default_price_list",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Price List",
|
"label": "Default Price List",
|
||||||
"length": 0,
|
"options": "Price List"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Price List",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "",
|
|
||||||
"fieldname": "payment_terms",
|
"fieldname": "payment_terms",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Payment Terms Template",
|
"label": "Default Payment Terms Template",
|
||||||
"length": 0,
|
"options": "Payment Terms Template"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Payment Terms Template",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "credit_limit",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Credit Limit",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 1,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "lft",
|
"fieldname": "lft",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "lft",
|
"label": "lft",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "lft",
|
"oldfieldname": "lft",
|
||||||
"oldfieldtype": "Int",
|
"oldfieldtype": "Int",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 1,
|
"report_hide": 1,
|
||||||
"reqd": 0,
|
"search_index": 1
|
||||||
"search_index": 1,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "rgt",
|
"fieldname": "rgt",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "rgt",
|
"label": "rgt",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "rgt",
|
"oldfieldname": "rgt",
|
||||||
"oldfieldtype": "Int",
|
"oldfieldtype": "Int",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 1,
|
"report_hide": 1,
|
||||||
"reqd": 0,
|
"search_index": 1
|
||||||
"search_index": 1,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "",
|
|
||||||
"fieldname": "old_parent",
|
"fieldname": "old_parent",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "old_parent",
|
"label": "old_parent",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "old_parent",
|
"oldfieldname": "old_parent",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"options": "Customer Group",
|
"options": "Customer Group",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"report_hide": 1
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 1,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "default_receivable_account",
|
"fieldname": "default_receivable_account",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Default Receivable Account"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Receivable Account",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "eval:!doc.__islocal",
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"description": "Mention if non-standard receivable account applicable",
|
"description": "Mention if non-standard receivable account applicable",
|
||||||
"fieldname": "accounts",
|
"fieldname": "accounts",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Accounts",
|
"label": "Accounts",
|
||||||
"length": 0,
|
"options": "Party Account"
|
||||||
"no_copy": 0,
|
},
|
||||||
"options": "Party Account",
|
{
|
||||||
"permlevel": 0,
|
"fieldname": "credit_limit_section",
|
||||||
"print_hide": 0,
|
"fieldtype": "Section Break",
|
||||||
"print_hide_if_no_value": 0,
|
"label": "Credit Limits"
|
||||||
"read_only": 0,
|
},
|
||||||
"remember_last_selected_value": 0,
|
{
|
||||||
"report_hide": 0,
|
"fieldname": "credit_limits",
|
||||||
"reqd": 0,
|
"fieldtype": "Table",
|
||||||
"search_index": 0,
|
"label": "Credit Limit",
|
||||||
"set_only_once": 0,
|
"options": "Customer Credit Limit"
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-sitemap",
|
"icon": "fa fa-sitemap",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"modified": "2019-09-06 12:40:14.954697",
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-08-29 06:26:05.935871",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Customer Group",
|
"name": "Customer Group",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Sales Manager",
|
"role": "Sales Manager"
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Sales User",
|
"role": "Sales User"
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 1,
|
"import": 1,
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Sales Master Manager",
|
"role": "Sales Master Manager",
|
||||||
"set_user_permissions": 1,
|
"set_user_permissions": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 1,
|
"permlevel": 1,
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 0,
|
|
||||||
"role": "Sales Master Manager",
|
"role": "Sales Master Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 1,
|
"permlevel": 1,
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 0,
|
"role": "Sales User"
|
||||||
"role": "Sales User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 1,
|
"permlevel": 1,
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 0,
|
"role": "Sales Manager"
|
||||||
"role": "Sales Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"search_fields": "parent_customer_group",
|
"search_fields": "parent_customer_group",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC"
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
@ -4,6 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
import copy
|
import copy
|
||||||
|
from frappe import _
|
||||||
from frappe.utils import nowdate, cint, cstr
|
from frappe.utils import nowdate, cint, cstr
|
||||||
from frappe.utils.nestedset import NestedSet
|
from frappe.utils.nestedset import NestedSet
|
||||||
from frappe.website.website_generator import WebsiteGenerator
|
from frappe.website.website_generator import WebsiteGenerator
|
||||||
@ -28,7 +29,8 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
|||||||
super(ItemGroup, self).validate()
|
super(ItemGroup, self).validate()
|
||||||
|
|
||||||
if not self.parent_item_group and not frappe.flags.in_test:
|
if not self.parent_item_group and not frappe.flags.in_test:
|
||||||
self.parent_item_group = 'All Item Groups'
|
if frappe.db.exists("Item Group", _('All Item Groups'), cache=True):
|
||||||
|
self.parent_item_group = _('All Item Groups')
|
||||||
|
|
||||||
self.make_route()
|
self.make_route()
|
||||||
|
|
||||||
|
@ -336,19 +336,20 @@ def set_price_list_and_rate(quotation, cart_settings):
|
|||||||
|
|
||||||
def _set_price_list(quotation, cart_settings):
|
def _set_price_list(quotation, cart_settings):
|
||||||
"""Set price list based on customer or shopping cart default"""
|
"""Set price list based on customer or shopping cart default"""
|
||||||
if quotation.selling_price_list:
|
from erpnext.accounts.party import get_default_price_list
|
||||||
return
|
|
||||||
|
|
||||||
# check if customer price list exists
|
# check if customer price list exists
|
||||||
selling_price_list = None
|
selling_price_list = None
|
||||||
if quotation.party_name:
|
if quotation.party_name:
|
||||||
from erpnext.accounts.party import get_default_price_list
|
selling_price_list = frappe.db.get_value('Customer', quotation.party_name, 'default_price_list')
|
||||||
selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.party_name))
|
|
||||||
|
|
||||||
# else check for territory based price list
|
# else check for territory based price list
|
||||||
if not selling_price_list:
|
if not selling_price_list:
|
||||||
selling_price_list = cart_settings.price_list
|
selling_price_list = cart_settings.price_list
|
||||||
|
|
||||||
|
if not selling_price_list and quotation.party_name:
|
||||||
|
selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.party_name))
|
||||||
|
|
||||||
quotation.selling_price_list = selling_price_list
|
quotation.selling_price_list = selling_price_list
|
||||||
|
|
||||||
def set_taxes(quotation, cart_settings):
|
def set_taxes(quotation, cart_settings):
|
||||||
|
@ -234,8 +234,10 @@ class DeliveryNote(SellingController):
|
|||||||
|
|
||||||
extra_amount = 0
|
extra_amount = 0
|
||||||
validate_against_credit_limit = False
|
validate_against_credit_limit = False
|
||||||
bypass_credit_limit_check_at_sales_order = cint(frappe.db.get_value("Customer", self.customer,
|
bypass_credit_limit_check_at_sales_order = cint(frappe.db.get_value("Customer Credit Limit",
|
||||||
"bypass_credit_limit_check_at_sales_order"))
|
filters={'parent': self.customer, 'parenttype': 'Customer', 'company': self.company},
|
||||||
|
fieldname="bypass_credit_limit_check"))
|
||||||
|
|
||||||
if bypass_credit_limit_check_at_sales_order:
|
if bypass_credit_limit_check_at_sales_order:
|
||||||
validate_against_credit_limit = True
|
validate_against_credit_limit = True
|
||||||
extra_amount = self.base_grand_total
|
extra_amount = self.base_grand_total
|
||||||
|
@ -238,7 +238,7 @@ class DeliveryTrip(Document):
|
|||||||
try:
|
try:
|
||||||
directions = maps_client.directions(**directions_data)
|
directions = maps_client.directions(**directions_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.throw(_(e.message))
|
frappe.throw(_(e))
|
||||||
|
|
||||||
return directions[0] if directions else False
|
return directions[0] if directions else False
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class PackingSlip(Document):
|
|||||||
frappe.msgprint(_("Please specify a valid 'From Case No.'"), raise_exception=1)
|
frappe.msgprint(_("Please specify a valid 'From Case No.'"), raise_exception=1)
|
||||||
elif not self.to_case_no:
|
elif not self.to_case_no:
|
||||||
self.to_case_no = self.from_case_no
|
self.to_case_no = self.from_case_no
|
||||||
elif self.from_case_no > self.to_case_no:
|
elif cint(self.from_case_no) > cint(self.to_case_no):
|
||||||
frappe.msgprint(_("'To Case No.' cannot be less than 'From Case No.'"),
|
frappe.msgprint(_("'To Case No.' cannot be less than 'From Case No.'"),
|
||||||
raise_exception=1)
|
raise_exception=1)
|
||||||
|
|
||||||
|
@ -639,7 +639,7 @@ def validate_price_list(args):
|
|||||||
if not frappe.db.get_value("Price List",
|
if not frappe.db.get_value("Price List",
|
||||||
{"name": args.price_list, args.transaction_type: 1, "enabled": 1}):
|
{"name": args.price_list, args.transaction_type: 1, "enabled": 1}):
|
||||||
throw(_("Price List {0} is disabled or does not exist").format(args.price_list))
|
throw(_("Price List {0} is disabled or does not exist").format(args.price_list))
|
||||||
elif not args.get("supplier"):
|
elif args.get("customer"):
|
||||||
throw(_("Price List not selected"))
|
throw(_("Price List not selected"))
|
||||||
|
|
||||||
def validate_conversion_rate(args, meta):
|
def validate_conversion_rate(args, meta):
|
||||||
|
@ -181,9 +181,6 @@ class update_entries_after(object):
|
|||||||
# rounding as per precision
|
# rounding as per precision
|
||||||
self.stock_value = flt(self.stock_value, self.precision)
|
self.stock_value = flt(self.stock_value, self.precision)
|
||||||
|
|
||||||
if self.prev_stock_value < 0 and self.stock_value >= 0 and sle.voucher_type != 'Stock Reconciliation':
|
|
||||||
stock_value_difference = sle.actual_qty * self.valuation_rate
|
|
||||||
else:
|
|
||||||
stock_value_difference = self.stock_value - self.prev_stock_value
|
stock_value_difference = self.stock_value - self.prev_stock_value
|
||||||
|
|
||||||
self.prev_stock_value = self.stock_value
|
self.prev_stock_value = self.stock_value
|
||||||
|
@ -15,7 +15,7 @@ def get_stock_value_from_bin(warehouse=None, item_code=None):
|
|||||||
values = {}
|
values = {}
|
||||||
conditions = ""
|
conditions = ""
|
||||||
if warehouse:
|
if warehouse:
|
||||||
conditions += """ and warehouse in (
|
conditions += """ and `tabBin`.warehouse in (
|
||||||
select w2.name from `tabWarehouse` w1
|
select w2.name from `tabWarehouse` w1
|
||||||
join `tabWarehouse` w2 on
|
join `tabWarehouse` w2 on
|
||||||
w1.name = %(warehouse)s
|
w1.name = %(warehouse)s
|
||||||
@ -25,11 +25,12 @@ def get_stock_value_from_bin(warehouse=None, item_code=None):
|
|||||||
values['warehouse'] = warehouse
|
values['warehouse'] = warehouse
|
||||||
|
|
||||||
if item_code:
|
if item_code:
|
||||||
conditions += " and item_code = %(item_code)s"
|
conditions += " and `tabBin`.item_code = %(item_code)s"
|
||||||
|
|
||||||
values['item_code'] = item_code
|
values['item_code'] = item_code
|
||||||
|
|
||||||
query = "select sum(stock_value) from `tabBin` where 1 = 1 %s" % conditions
|
query = """select sum(stock_value) from `tabBin`, `tabItem` where 1 = 1
|
||||||
|
and `tabItem`.name = `tabBin`.item_code and ifnull(`tabItem`.disabled, 0) = 0 %s""" % conditions
|
||||||
|
|
||||||
stock_value = frappe.db.sql(query, values)
|
stock_value = frappe.db.sql(query, values)
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{% set domains = frappe.get_doc("Domain Settings").active_domains %}
|
{% set domains = frappe.get_doc("Domain Settings").active_domains %}
|
||||||
|
|
||||||
<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext - {{ domains[0].domain if domains else 'Open Source' }} ERP Software</a>
|
<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext - ERP Software {{ ('for ' + domains[0].domain + ' Companies') if domains else '' }}</a>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user