{%= frappe.boot.letter_heads[letterhead].header %}
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 09cf5b1d2fc..e9aecfb394e 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -126,7 +126,7 @@ def get_label(periodicity, from_date, to_date):
def get_data(
company, root_type, balance_must_be, period_list, filters=None,
accumulated_values=1, only_current_fiscal_year=True, ignore_closing_entries=False,
- ignore_accumulated_values_for_fy=False):
+ ignore_accumulated_values_for_fy=False , total = True):
accounts = get_accounts(company, root_type)
if not accounts:
@@ -154,7 +154,7 @@ def get_data(
out = prepare_data(accounts, balance_must_be, period_list, company_currency)
out = filter_out_zero_value_rows(out, parent_children_map)
- if out:
+ if out and total:
add_total_row(out, root_type, balance_must_be, period_list, company_currency)
return out
@@ -218,6 +218,9 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency):
"year_start_date": year_start_date,
"year_end_date": year_end_date,
"currency": company_currency,
+ "include_in_gross": d.include_in_gross,
+ "account_type": d.account_type,
+ "is_group": d.is_group,
"opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be=="Debit" else -1),
"account_name": ('%s - %s' %(_(d.account_number), _(d.account_name))
if d.account_number else _(d.account_name))
@@ -270,7 +273,7 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency
for period in period_list:
total_row.setdefault(period.key, 0.0)
total_row[period.key] += row.get(period.key, 0.0)
- row[period.key] = 0.0
+ row[period.key] = row.get(period.key, 0.0)
total_row.setdefault("total", 0.0)
total_row["total"] += flt(row["total"])
@@ -285,7 +288,7 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency
def get_accounts(company, root_type):
return frappe.db.sql("""
- select name, account_number, parent_account, lft, rgt, root_type, report_type, account_name
+ select name, account_number, parent_account, lft, rgt, root_type, report_type, account_name, include_in_gross, account_type, is_group, lft, rgt
from `tabAccount`
where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index be6633282ac..ecb18f78b18 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -53,7 +53,7 @@ def validate_filters(filters, account_details):
frappe.throw(_("Can not filter based on Account, if grouped by Account"))
if (filters.get("voucher_no")
- and filters.get("group_by") in [_('Group by Voucher'), _('Group by Voucher (Consolidated)')]):
+ and filters.get("group_by") in [_('Group by Voucher')]):
frappe.throw(_("Can not filter based on Voucher No, if grouped by Voucher"))
if filters.from_date > filters.to_date:
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/__init__.py b/erpnext/accounts/report/gross_and_net_profit_report/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html
new file mode 100644
index 00000000000..40ba20c4ac6
--- /dev/null
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html
@@ -0,0 +1 @@
+{% include "accounts/report/financial_statements.html" %}
\ No newline at end of file
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js
new file mode 100644
index 00000000000..63ac281cdb5
--- /dev/null
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js
@@ -0,0 +1,51 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Gross and Net Profit Report"] = {
+ "filters": [
+
+ ]
+}
+frappe.require("assets/erpnext/js/financial_statements.js", function() {
+ frappe.query_reports["Gross and Net Profit Report"] = $.extend({},
+ erpnext.financial_statements);
+
+ frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
+ {
+ "fieldname":"project",
+ "label": __("Project"),
+ "fieldtype": "MultiSelect",
+ get_data: function() {
+ var projects = frappe.query_report.get_filter_value("project") || "";
+
+ const values = projects.split(/\s*,\s*/).filter(d => d);
+ const txt = projects.match(/[^,\s*]*$/)[0] || '';
+ let data = [];
+
+ frappe.call({
+ type: "GET",
+ method:'frappe.desk.search.search_link',
+ async: false,
+ no_spinner: true,
+ args: {
+ doctype: "Project",
+ txt: txt,
+ filters: {
+ "name": ["not in", values]
+ }
+ },
+ callback: function(r) {
+ data = r.results;
+ }
+ });
+ return data;
+ }
+ },
+ {
+ "fieldname": "accumulated_values",
+ "label": __("Accumulated Values"),
+ "fieldtype": "Check"
+ }
+ );
+});
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.json b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.json
new file mode 100644
index 00000000000..994b47faef2
--- /dev/null
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.json
@@ -0,0 +1,30 @@
+{
+ "add_total_row": 0,
+ "creation": "2019-02-08 10:58:55.763090",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2019-02-08 10:58:55.763090",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Gross and Net Profit Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "Gross and Net Profit Report",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Auditor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
new file mode 100644
index 00000000000..6550981a146
--- /dev/null
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import flt
+from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
+import copy
+
+
+def execute(filters=None):
+ period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
+ filters.periodicity, filters.accumulated_values, filters.company)
+
+ columns, data = [], []
+
+ income = get_data(filters.company, "Income", "Credit", period_list, filters = filters,
+ accumulated_values=filters.accumulated_values,
+ ignore_closing_entries=True, ignore_accumulated_values_for_fy= True, total= False)
+
+ expense = get_data(filters.company, "Expense", "Debit", period_list, filters=filters,
+ accumulated_values=filters.accumulated_values,
+ ignore_closing_entries=True, ignore_accumulated_values_for_fy= True, total= False)
+
+ columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
+
+
+ gross_income = get_revenue(income, period_list)
+
+ gross_expense = get_revenue(expense, period_list)
+
+ if(len(gross_income)==0 and len(gross_expense)== 0):
+ data.append({"account_name": "'" + _("Nothing is included in gross") + "'",
+ "account": "'" + _("Nothing is included in gross") + "'"})
+
+ return columns, data
+
+ data.append({"account_name": "'" + _("Included in Gross Profit") + "'",
+ "account": "'" + _("Included in Gross Profit") + "'"})
+
+ data.append({})
+ data.extend(gross_income or [])
+
+ data.append({})
+ data.extend(gross_expense or [])
+
+ data.append({})
+ gross_profit = get_profit(gross_income, gross_expense, period_list, filters.company, 'Gross Profit',filters.presentation_currency)
+ data.append(gross_profit)
+
+ non_gross_income = get_revenue(income, period_list, 0)
+ data.append({})
+ data.extend(non_gross_income or [])
+
+ non_gross_expense = get_revenue(expense, period_list, 0)
+ data.append({})
+ data.extend(non_gross_expense or [])
+
+ net_profit = get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expense, period_list, filters.company,filters.presentation_currency)
+ data.append({})
+ data.append(net_profit)
+
+ return columns, data
+
+def get_revenue(data, period_list, include_in_gross=1):
+ revenue = [item for item in data if item['include_in_gross']==include_in_gross or item['is_group']==1]
+
+ data_to_be_removed =True
+ while data_to_be_removed:
+ revenue, data_to_be_removed = remove_parent_with_no_child(revenue, period_list)
+ revenue = adjust_account(revenue, period_list)
+ return copy.deepcopy(revenue)
+
+def remove_parent_with_no_child(data, period_list):
+ data_to_be_removed = False
+ for parent in data:
+ if 'is_group' in parent and parent.get("is_group") == 1:
+ have_child = False
+ for child in data:
+ if 'parent_account' in child and child.get("parent_account") == parent.get("account"):
+ have_child = True
+ break
+
+ if not have_child:
+ data_to_be_removed = True
+ data.remove(parent)
+
+ return data, data_to_be_removed
+
+def adjust_account(data, period_list, consolidated= False):
+ leaf_nodes = [item for item in data if item['is_group'] == 0]
+ totals = {}
+ for node in leaf_nodes:
+ set_total(node, node["total"], data, totals)
+ for d in data:
+ for period in period_list:
+ key = period if consolidated else period.key
+ d[key] = totals[d["account"]]
+ d['total'] = totals[d["account"]]
+ return data
+
+def set_total(node, value, complete_list, totals):
+ if not totals.get(node['account']):
+ totals[node["account"]] = 0
+ totals[node["account"]] += value
+
+ parent = node['parent_account']
+ if not parent == '':
+ return set_total(next(item for item in complete_list if item['account'] == parent), value, complete_list, totals)
+
+
+def get_profit(gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False):
+
+ profit_loss = {
+ "account_name": "'" + _(profit_type) + "'",
+ "account": "'" + _(profit_type) + "'",
+ "warn_if_negative": True,
+ "currency": currency or frappe.get_cached_value('Company', company, "default_currency")
+ }
+
+ has_value = False
+
+ for period in period_list:
+ key = period if consolidated else period.key
+ profit_loss[key] = flt(gross_income[0].get(key, 0)) - flt(gross_expense[0].get(key, 0))
+
+ if profit_loss[key]:
+ has_value=True
+
+ if has_value:
+ return profit_loss
+
+def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expense, period_list, company, currency=None, consolidated=False):
+ profit_loss = {
+ "account_name": "'" + _("Net Profit") + "'",
+ "account": "'" + _("Net Profit") + "'",
+ "warn_if_negative": True,
+ "currency": currency or frappe.get_cached_value('Company', company, "default_currency")
+ }
+
+ has_value = False
+
+ for period in period_list:
+ key = period if consolidated else period.key
+ total_income = flt(gross_income[0].get(key, 0)) + flt(non_gross_income[0].get(key, 0))
+ total_expense = flt(gross_expense[0].get(key, 0)) + flt(non_gross_expense[0].get(key, 0))
+ profit_loss[key] = flt(total_income) - flt(total_expense)
+
+ if profit_loss[key]:
+ has_value=True
+
+ if has_value:
+ return profit_loss
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 01211a976f3..e5aaafaff49 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -6,7 +6,7 @@ import frappe
from frappe import _, scrub
from erpnext.stock.utils import get_incoming_rate
from erpnext.controllers.queries import get_match_cond
-from frappe.utils import flt
+from frappe.utils import flt, cint
def execute(filters=None):
@@ -106,11 +106,14 @@ class GrossProfitGenerator(object):
self.grouped = {}
self.grouped_data = []
+ self.currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
+ self.float_precision = cint(frappe.db.get_default("float_precision")) or 2
+
for row in self.si_list:
if self.skip_row(row, self.product_bundles):
continue
- row.base_amount = flt(row.base_net_amount)
+ row.base_amount = flt(row.base_net_amount, self.currency_precision)
product_bundles = []
if row.update_stock:
@@ -122,22 +125,23 @@ class GrossProfitGenerator(object):
# get buying amount
if row.item_code in product_bundles:
- row.buying_amount = self.get_buying_amount_from_product_bundle(row,
- product_bundles[row.item_code])
+ row.buying_amount = flt(self.get_buying_amount_from_product_bundle(row,
+ product_bundles[row.item_code]), self.currency_precision)
else:
- row.buying_amount = self.get_buying_amount(row, row.item_code)
+ row.buying_amount = flt(self.get_buying_amount(row, row.item_code),
+ self.currency_precision)
# get buying rate
if row.qty:
- row.buying_rate = row.buying_amount / row.qty
- row.base_rate = row.base_amount / row.qty
+ row.buying_rate = flt(row.buying_amount / row.qty, self.float_precision)
+ row.base_rate = flt(row.base_amount / row.qty, self.float_precision)
else:
row.buying_rate, row.base_rate = 0.0, 0.0
# calculate gross profit
- row.gross_profit = row.base_amount - row.buying_amount
+ row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
if row.base_amount:
- row.gross_profit_percent = (row.gross_profit / row.base_amount) * 100.0
+ row.gross_profit_percent = flt((row.gross_profit / row.base_amount) * 100.0, self.currency_precision)
else:
row.gross_profit_percent = 0.0
@@ -156,8 +160,8 @@ class GrossProfitGenerator(object):
new_row = row
else:
new_row.qty += row.qty
- new_row.buying_amount += row.buying_amount
- new_row.base_amount += row.base_amount
+ new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
+ new_row.base_amount += flt(row.base_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)
else:
@@ -167,18 +171,19 @@ class GrossProfitGenerator(object):
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
for returned_item_row in returned_item_rows:
row.qty += returned_item_row.qty
- row.base_amount += returned_item_row.base_amount
- row.buying_amount = row.qty * row.buying_rate
+ row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
+ row.buying_amount = flt(row.qty * row.buying_rate, self.currency_precision)
if row.qty or row.base_amount:
row = self.set_average_rate(row)
self.grouped_data.append(row)
def set_average_rate(self, new_row):
- new_row.gross_profit = new_row.base_amount - new_row.buying_amount
- new_row.gross_profit_percent = ((new_row.gross_profit / new_row.base_amount) * 100.0) \
+ new_row.gross_profit = flt(new_row.base_amount - new_row.buying_amount, self.currency_precision)
+ new_row.gross_profit_percent = flt(((new_row.gross_profit / new_row.base_amount) * 100.0), self.currency_precision) \
if new_row.base_amount else 0
- new_row.buying_rate = (new_row.buying_amount / new_row.qty) if new_row.qty else 0
- new_row.base_rate = (new_row.base_amount / new_row.qty) if new_row.qty else 0
+ new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
+ new_row.base_rate = flt(new_row.base_amount / new_row.qty, self.float_precision) if new_row.qty else 0
+
return new_row
def get_returned_invoice_items(self):
@@ -211,7 +216,7 @@ class GrossProfitGenerator(object):
if packed_item.get("parent_detail_docname")==row.item_row:
buying_amount += self.get_buying_amount(row, packed_item.item_code)
- return buying_amount
+ return flt(buying_amount, self.currency_precision)
def get_buying_amount(self, row, item_code):
# IMP NOTE
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index 88c612e7f6c..e8b19b40249 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -54,8 +54,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
delivery_note, d.income_account, d.cost_center, d.stock_qty, d.stock_uom
]
- row += [(d.base_net_rate * d.qty)/d.stock_qty, d.base_net_amount] \
- if d.stock_uom != d.uom and d.stock_qty != 0 else [d.base_net_rate, d.base_net_amount]
+ if d.stock_uom != d.uom and d.stock_qty:
+ row += [(d.base_net_rate * d.qty)/d.stock_qty, d.base_net_amount]
+ else:
+ row += [d.base_net_rate, d.base_net_amount]
total_tax = 0
for tax in tax_columns:
@@ -108,13 +110,13 @@ def get_conditions(filters):
conditions += """ and exists(select name from `tabSales Invoice Payment`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
-
+
if filters.get("warehouse"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s)"""
-
+
if filters.get("brand"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
@@ -131,10 +133,10 @@ def get_conditions(filters):
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
match_conditions = frappe.build_match_conditions("Sales Invoice")
-
+
if match_conditions:
match_conditions = " and {0} ".format(match_conditions)
-
+
if additional_query_columns:
additional_query_columns = ', ' + ', '.join(additional_query_columns)
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 39706acde0f..a0d8c5f0e4e 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -133,6 +133,13 @@ def get_columns(filters):
"options": filters.get("based_on"),
"width": 300
},
+ {
+ "fieldname": "currency",
+ "label": _("Currency"),
+ "fieldtype": "Link",
+ "options": "Currency",
+ "hidden": 1
+ },
{
"fieldname": "income",
"label": _("Income"),
@@ -153,13 +160,6 @@ def get_columns(filters):
"fieldtype": "Currency",
"options": "currency",
"width": 120
- },
- {
- "fieldname": "currency",
- "label": _("Currency"),
- "fieldtype": "Link",
- "options": "Currency",
- "hidden": 1
}
]
@@ -191,4 +191,4 @@ def set_gl_entries_by_account(company, from_date, to_date, based_on, gl_entries_
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.based_on, []).append(entry)
- return gl_entries_by_account
\ No newline at end of file
+ return gl_entries_by_account
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py
index e33b90d0196..3f8abb76e26 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.py
+++ b/erpnext/accounts/report/purchase_register/purchase_register.py
@@ -66,8 +66,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
total_tax += tax_amount
row.append(tax_amount)
- # total tax, grand total, outstanding amount & rounded total
- row += [total_tax, inv.base_grand_total, flt(inv.base_grand_total, 2), inv.outstanding_amount]
+ # total tax, grand total, rounded total & outstanding amount
+ row += [total_tax, inv.base_grand_total, flt(inv.base_grand_total, 0), inv.outstanding_amount]
data.append(row)
return columns, data
diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js
index 6fd16f20908..f81297760ed 100644
--- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js
+++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js
@@ -35,9 +35,9 @@ frappe.query_reports["Supplier Ledger Summary"] = {
},
{
"fieldname":"party",
- "label": __("Customer"),
+ "label": __("Supplier"),
"fieldtype": "Link",
- "options": "Customer",
+ "options": "Supplier",
on_change: () => {
var party = frappe.query_report.get_filter_value('party');
if (party) {
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index e55c022452d..8ca466e4b88 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -55,12 +55,15 @@ def get_result(filters):
supplier = supplier_map[d]
tds_doc = tds_docs[supplier.tax_withholding_category]
- account = [i.account for i in tds_doc.accounts if i.company == filters.company][0]
+ account_list = [i.account for i in tds_doc.accounts if i.company == filters.company]
+
+ if account_list:
+ account = account_list[0]
for k in gle_map[d]:
if k.party == supplier_map[d] and k.credit > 0:
total_amount_credited += k.credit
- elif k.account == account and k.credit > 0:
+ elif account_list and k.account == account and k.credit > 0:
tds_deducted = k.credit
total_amount_credited += k.credit
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 226776e9858..e9ae6ee21ac 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -544,14 +544,14 @@ def fix_total_debit_credit():
(dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
(d.diff, d.voucher_type, d.voucher_no))
-def get_stock_and_account_difference(account_list=None, posting_date=None):
+def get_stock_and_account_difference(account_list=None, posting_date=None, company=None):
from erpnext.stock.utils import get_stock_value_on
from erpnext.stock import get_warehouse_account_map
if not posting_date: posting_date = nowdate()
difference = {}
- warehouse_account = get_warehouse_account_map()
+ warehouse_account = get_warehouse_account_map(company)
for warehouse, account_data in iteritems(warehouse_account):
if account_data.get('account') in account_list:
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index ed02d87e446..18ba8f9b5cb 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -31,7 +31,7 @@ frappe.ui.form.on('Asset', {
}
};
});
-
+
frm.set_query("cost_center", function() {
return {
"filters": {
@@ -206,12 +206,10 @@ frappe.ui.form.on('Asset', {
erpnext.asset.set_accululated_depreciation(frm);
},
- depreciation_method: function(frm) {
- frm.events.make_schedules_editable(frm);
- },
-
make_schedules_editable: function(frm) {
- var is_editable = frm.doc.depreciation_method==="Manual" ? true : false;
+ var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
+ ? true : false;
+
frm.toggle_enable("schedules", is_editable);
frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable);
frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable);
@@ -296,6 +294,44 @@ frappe.ui.form.on('Asset', {
})
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
+ },
+
+ set_depreciation_rate: function(frm, row) {
+ if (row.total_number_of_depreciations && row.frequency_of_depreciation) {
+ frappe.call({
+ method: "get_depreciation_rate",
+ doc: frm.doc,
+ args: row,
+ callback: function(r) {
+ if (r.message) {
+ frappe.model.set_value(row.doctype, row.name, "rate_of_depreciation", r.message);
+ }
+ }
+ });
+ }
+ }
+});
+
+frappe.ui.form.on('Asset Finance Book', {
+ depreciation_method: function(frm, cdt, cdn) {
+ const row = locals[cdt][cdn];
+ frm.events.set_depreciation_rate(frm, row);
+ frm.events.make_schedules_editable(frm);
+ },
+
+ expected_value_after_useful_life: function(frm, cdt, cdn) {
+ const row = locals[cdt][cdn];
+ frm.events.set_depreciation_rate(frm, row);
+ },
+
+ frequency_of_depreciation: function(frm, cdt, cdn) {
+ const row = locals[cdt][cdn];
+ frm.events.set_depreciation_rate(frm, row);
+ },
+
+ total_number_of_depreciations: function(frm, cdt, cdn) {
+ const row = locals[cdt][cdn];
+ frm.events.set_depreciation_rate(frm, row);
}
});
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index a38b40bc60b..8011038b1b1 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -3,8 +3,9 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe, erpnext
+import frappe, erpnext, math, json
from frappe import _
+from six import string_types
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff
from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
@@ -20,6 +21,7 @@ class Asset(AccountsController):
self.validate_item()
self.set_missing_values()
if self.calculate_depreciation:
+ self.set_depreciation_rate()
self.make_depreciation_schedule()
self.set_accumulated_depreciation()
else:
@@ -33,7 +35,7 @@ class Asset(AccountsController):
self.validate_in_use_date()
self.set_status()
self.update_stock_movement()
- if not self.booked_fixed_asset:
+ if not self.booked_fixed_asset and not is_cwip_accounting_disabled():
self.make_gl_entries()
def on_cancel(self):
@@ -71,14 +73,15 @@ class Asset(AccountsController):
if not flt(self.gross_purchase_amount):
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
- if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
- frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}").
- format(self.item_code))
+ if not is_cwip_accounting_disabled():
+ if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
+ frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}").
+ format(self.item_code))
- if (not self.purchase_receipt and self.purchase_invoice
- and not frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')):
- frappe.throw(_("Update stock must be enable for the purchase invoice {0}").
- format(self.purchase_invoice))
+ if (not self.purchase_receipt and self.purchase_invoice
+ and not frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')):
+ frappe.throw(_("Update stock must be enable for the purchase invoice {0}").
+ format(self.purchase_invoice))
if not self.calculate_depreciation:
return
@@ -88,17 +91,22 @@ class Asset(AccountsController):
if self.is_existing_asset:
return
- date = nowdate()
docname = self.purchase_receipt or self.purchase_invoice
if docname:
doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
date = frappe.db.get_value(doctype, docname, 'posting_date')
- if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(date):
+ if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
frappe.throw(_("Available-for-use Date should be after purchase date"))
+ def set_depreciation_rate(self):
+ for d in self.get("finance_books"):
+ d.rate_of_depreciation = self.get_depreciation_rate(d)
+
def make_depreciation_schedule(self):
- if self.depreciation_method != 'Manual':
+ depreciation_method = [d.depreciation_method for d in self.finance_books]
+
+ if 'Manual' not in depreciation_method:
self.schedules = []
if not self.get("schedules") and self.available_for_use_date:
@@ -253,14 +261,16 @@ class Asset(AccountsController):
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
- percentage_value = 100.0 if row.depreciation_method == 'Written Down Value' else 200.0
+ if row.depreciation_method in ["Straight Line", "Manual"]:
+ amt = (flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) -
+ flt(self.opening_accumulated_depreciation))
- factor = percentage_value / total_number_of_depreciations
- depreciation_amount = flt(depreciable_value * factor / 100, 0)
-
- value_after_depreciation = flt(depreciable_value) - depreciation_amount
- if value_after_depreciation < flt(row.expected_value_after_useful_life):
- depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
+ depreciation_amount = amt * row.rate_of_depreciation
+ else:
+ depreciation_amount = flt(depreciable_value) * (flt(row.rate_of_depreciation) / 100)
+ value_after_depreciation = flt(depreciable_value) - depreciation_amount
+ if value_after_depreciation < flt(row.expected_value_after_useful_life):
+ depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
return depreciation_amount
@@ -275,7 +285,7 @@ class Asset(AccountsController):
flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) -
cint(self.number_of_depreciations_booked)) * prorata_temporis
else:
- depreciation_amount = self.get_depreciation_amount(depreciable_value, row)
+ depreciation_amount = self.get_depreciation_amount(depreciable_value, row.total_number_of_depreciations, row)
return depreciation_amount
@@ -393,6 +403,32 @@ class Asset(AccountsController):
make_gl_entries(gl_entries)
self.db_set('booked_fixed_asset', 1)
+ def get_depreciation_rate(self, args):
+ if isinstance(args, string_types):
+ args = json.loads(args)
+
+ number_of_depreciations_booked = 0
+ if self.is_existing_asset:
+ number_of_depreciations_booked = self.number_of_depreciations_booked
+
+ float_precision = cint(frappe.db.get_default("float_precision")) or 2
+ tot_no_of_depreciation = flt(args.get("total_number_of_depreciations")) - flt(number_of_depreciations_booked)
+
+ if args.get("depreciation_method") in ["Straight Line", "Manual"]:
+ return 1.0 / tot_no_of_depreciation
+
+ if args.get("depreciation_method") == 'Double Declining Balance':
+ return 200.0 / args.get("total_number_of_depreciations")
+
+ if args.get("depreciation_method") == "Written Down Value" and not args.get("rate_of_depreciation"):
+ no_of_years = flt(args.get("total_number_of_depreciations") * flt(args.get("frequency_of_depreciation"))) / 12
+ value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
+
+ # square root of flt(salvage_value) / flt(asset_cost)
+ depreciation_rate = math.pow(value, 1.0/flt(no_of_years, 2))
+
+ return 100 * (1 - flt(depreciation_rate, float_precision))
+
def update_maintenance_status():
assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1})
@@ -404,6 +440,9 @@ def update_maintenance_status():
asset.set_status('Out of Order')
def make_post_gl_entry():
+ if is_cwip_accounting_disabled():
+ return
+
assets = frappe.db.sql_list(""" select name from `tabAsset`
where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate())
@@ -476,7 +515,6 @@ def create_asset_adjustment(asset, asset_category, company):
@frappe.whitelist()
def transfer_asset(args):
- import json
args = json.loads(args)
if args.get('serial_no'):
@@ -551,3 +589,6 @@ def make_journal_entry(asset_name):
})
return je
+
+def is_cwip_accounting_disabled():
+ return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index a12348e451b..985097b447d 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -160,9 +160,9 @@ class TestAsset(unittest.TestCase):
asset.save()
expected_schedules = [
- ["2020-06-06", 66667.0, 66667.0],
- ["2021-04-06", 22222.0, 88889.0],
- ["2022-02-06", 1111.0, 90000.0]
+ ["2020-06-06", 66666.67, 66666.67],
+ ["2021-04-06", 22222.22, 88888.89],
+ ["2022-02-06", 1111.11, 90000.0]
]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -192,8 +192,8 @@ class TestAsset(unittest.TestCase):
asset.save()
expected_schedules = [
- ["2020-06-06", 33333.0, 83333.0],
- ["2021-04-06", 6667.0, 90000.0]
+ ["2020-06-06", 33333.33, 83333.33],
+ ["2021-04-06", 6666.67, 90000.0]
]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -209,7 +209,7 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
- asset.purchase_date = '2020-06-06'
+ asset.purchase_date = '2020-01-30'
asset.is_existing_asset = 0
asset.available_for_use_date = "2020-01-30"
asset.append("finance_books", {
@@ -244,7 +244,7 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
- asset.purchase_date = '2020-06-06'
+ asset.purchase_date = '2020-01-30'
asset.available_for_use_date = "2020-01-30"
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
@@ -277,6 +277,37 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 0)
+ def test_depreciation_entry_for_wdv(self):
+ pr = make_purchase_receipt(item_code="Macbook Pro",
+ qty=1, rate=8000.0, location="Test Location")
+
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
+ asset = frappe.get_doc('Asset', asset_name)
+ asset.calculate_depreciation = 1
+ asset.available_for_use_date = '2030-06-06'
+ asset.purchase_date = '2030-06-06'
+ asset.append("finance_books", {
+ "expected_value_after_useful_life": 1000,
+ "depreciation_method": "Written Down Value",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 12,
+ "depreciation_start_date": "2030-12-31"
+ })
+ asset.save(ignore_permissions=True)
+
+ self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
+
+ expected_schedules = [
+ ["2030-12-31", 4000.0, 4000.0],
+ ["2031-12-31", 2000.0, 6000.0],
+ ["2032-12-31", 1000.0, 7000.0],
+ ]
+
+ schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
def test_depreciation_entry_cancellation(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=100000.0, location="Test Location")
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index f75c8510dcb..c80f95e1555 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -1,5 +1,6 @@
{
"allow_copy": 0,
+ "allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@@ -14,11 +15,13 @@
"fields": [
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "finance_book",
"fieldtype": "Link",
"hidden": 0,
@@ -42,14 +45,17 @@
"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,
+ "fetch_if_empty": 0,
"fieldname": "depreciation_method",
"fieldtype": "Select",
"hidden": 0,
@@ -73,14 +79,17 @@
"reqd": 1,
"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,
+ "fetch_if_empty": 0,
"fieldname": "total_number_of_depreciations",
"fieldtype": "Int",
"hidden": 0,
@@ -103,14 +112,17 @@
"reqd": 1,
"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,
+ "fetch_if_empty": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
@@ -133,14 +145,17 @@
"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,
+ "fetch_if_empty": 0,
"fieldname": "frequency_of_depreciation",
"fieldtype": "Int",
"hidden": 0,
@@ -163,15 +178,18 @@
"reqd": 1,
"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:parent.doctype == 'Asset'",
+ "fetch_if_empty": 0,
"fieldname": "depreciation_start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -194,16 +212,19 @@
"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,
"default": "0",
"depends_on": "eval:parent.doctype == 'Asset'",
+ "fetch_if_empty": 0,
"fieldname": "expected_value_after_useful_life",
"fieldtype": "Currency",
"hidden": 0,
@@ -227,14 +248,17 @@
"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,
+ "fetch_if_empty": 0,
"fieldname": "value_after_depreciation",
"fieldtype": "Currency",
"hidden": 1,
@@ -258,20 +282,54 @@
"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.depreciation_method == 'Written Down Value'",
+ "description": "In Percentage",
+ "fetch_if_empty": 0,
+ "fieldname": "rate_of_depreciation",
+ "fieldtype": "Percent",
+ "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": "Rate of Depreciation",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
- "hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
- "image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-05-12 14:56:44.800046",
+ "modified": "2019-04-09 19:45:14.523488",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",
@@ -280,10 +338,10 @@
"permissions": [],
"quick_entry": 1,
"read_only": 0,
- "read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.json b/erpnext/assets/doctype/asset_settings/asset_settings.json
index d6ddd33c302..a3fee96f4ee 100644
--- a/erpnext/assets/doctype/asset_settings/asset_settings.json
+++ b/erpnext/assets/doctype/asset_settings/asset_settings.json
@@ -1,5 +1,6 @@
{
"allow_copy": 0,
+ "allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@@ -14,10 +15,12 @@
"fields": [
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "depreciation_options",
"fieldtype": "Section Break",
"hidden": 0,
@@ -40,14 +43,17 @@
"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,
+ "fetch_if_empty": 0,
"fieldname": "schedule_based_on_fiscal_year",
"fieldtype": "Check",
"hidden": 0,
@@ -70,10 +76,12 @@
"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,
@@ -81,6 +89,7 @@
"default": "360",
"depends_on": "eval:doc.schedule_based_on_fiscal_year",
"description": "This value is used for pro-rata temporis calculation",
+ "fetch_if_empty": 0,
"fieldname": "number_of_days_in_fiscal_year",
"fieldtype": "Data",
"hidden": 0,
@@ -103,6 +112,40 @@
"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,
+ "fetch_if_empty": 0,
+ "fieldname": "disable_cwip_accounting",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Disable CWIP Accounting",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
"unique": 0
}
],
@@ -116,7 +159,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-01-05 10:10:39.803255",
+ "modified": "2019-03-08 10:44:41.924547",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Settings",
@@ -125,7 +168,6 @@
"permissions": [
{
"amend": 0,
- "apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
@@ -145,7 +187,6 @@
},
{
"amend": 0,
- "apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
@@ -171,5 +212,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index a4a636d6baa..e259c04acec 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -379,7 +379,9 @@ def make_purchase_invoice(source_name, target_doc=None):
def postprocess(source, target):
set_missing_values(source, target)
#Get the advance paid Journal Entries in Purchase Invoice Advance
- target.set_advances()
+
+ if target.get("allocate_advances_automatically"):
+ target.set_advances()
def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt)
@@ -388,9 +390,10 @@ def make_purchase_invoice(source_name, target_doc=None):
item = get_item_defaults(target.item_code, source_parent.company)
item_group = get_item_group_defaults(target.item_code, source_parent.company)
- target.cost_center = frappe.db.get_value("Project", obj.project, "cost_center") \
- or item.get("buying_cost_center") \
- or item_group.get("buying_cost_center")
+ target.cost_center = (obj.cost_center
+ or frappe.db.get_value("Project", obj.project, "cost_center")
+ or item.get("buying_cost_center")
+ or item_group.get("buying_cost_center"))
doc = get_mapped_doc("Purchase Order", source_name, {
"Purchase Order": {
diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py
index 94f31028311..99b8ce0e5ec 100644
--- a/erpnext/config/selling.py
+++ b/erpnext/config/selling.py
@@ -325,7 +325,7 @@ def get_data():
{
"type": "help",
"label": _("Sales Order to Payment"),
- "youtube_id": "7AMq4lqkN4A"
+ "youtube_id": "1eP90MWoDQM"
},
{
"type": "help",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index c10e6dcda74..2e3d9724dd9 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -116,6 +116,12 @@ class AccountsController(TransactionBase):
self.validate_non_invoice_documents_schedule()
def before_print(self):
+ if self.doctype in ['Journal Entry', 'Payment Entry', 'Sales Invoice', 'Purchase Invoice']:
+ self.gl_entries = frappe.get_list("GL Entry", filters={
+ "voucher_type": self.doctype,
+ "voucher_no": self.name
+ }, fields=["account", "party_type", "party", "debit", "credit", "remarks"])
+
if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
if self.get("group_same_items"):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 4c16323ca3d..f8f1e544596 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -172,8 +172,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
or tabItem.item_code LIKE %(txt)s
or tabItem.item_group LIKE %(txt)s
or tabItem.item_name LIKE %(txt)s
- or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s
- {description_cond}))
+ or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
+ {description_cond})
{fcond} {mcond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 15294f633f5..f2b15ad20ce 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -2,8 +2,9 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
from frappe import _
+from frappe.model.meta import get_field_precision
from frappe.utils import flt, get_datetime, format_datetime
class StockOverReturnError(frappe.ValidationError): pass
@@ -116,6 +117,10 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
already_returned_data = already_returned_items.get(args.item_code) or {}
+ company_currency = erpnext.get_company_currency(doc.company)
+ stock_qty_precision = get_field_precision(frappe.get_meta(doc.doctype + " Item")
+ .get_field("stock_qty"), company_currency)
+
for column in fields:
returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
@@ -126,7 +131,7 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
- max_returnable_qty = flt(reference_qty) - returned_qty
+ max_returnable_qty = flt(reference_qty, stock_qty_precision) - returned_qty
label = column.replace('_', ' ').title()
if reference_qty:
@@ -135,7 +140,7 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
elif returned_qty >= reference_qty and args.get(column):
frappe.throw(_("Item {0} has already been returned")
.format(args.item_code), StockOverReturnError)
- elif abs(current_stock_qty) > max_returnable_qty:
+ elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
.format(args.idx, max_returnable_qty, args.item_code), StockOverReturnError)
@@ -239,6 +244,10 @@ def make_return_doc(doctype, source_name, target_doc=None):
doc.paid_amount = -1 * source.paid_amount
doc.base_paid_amount = -1 * source.base_paid_amount
+ if doc.get("is_return") and hasattr(doc, "packed_items"):
+ for d in doc.get("packed_items"):
+ d.qty = d.qty * -1
+
doc.discount_amount = -1 * source.discount_amount
doc.run_method("calculate_taxes_and_totals")
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 0a3cd3499b1..d1ffd7db66d 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -26,7 +26,7 @@ class StockController(AccountsController):
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if cint(erpnext.is_perpetual_inventory_enabled(self.company)):
- warehouse_account = get_warehouse_account_map()
+ warehouse_account = get_warehouse_account_map(self.company)
if self.docstatus==1:
if not gl_entries:
@@ -36,7 +36,7 @@ class StockController(AccountsController):
if repost_future_gle:
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
- warehouse_account)
+ warehouse_account, company=self.company)
elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries)
@@ -46,7 +46,7 @@ class StockController(AccountsController):
default_cost_center=None):
if not warehouse_account:
- warehouse_account = get_warehouse_account_map()
+ warehouse_account = get_warehouse_account_map(self.company)
sle_map = self.get_stock_ledger_details()
voucher_details = self.get_voucher_details(default_expense_account, default_cost_center, sle_map)
@@ -199,7 +199,8 @@ class StockController(AccountsController):
def make_adjustment_entry(self, expected_gle, voucher_obj):
from erpnext.accounts.utils import get_stock_and_account_difference
account_list = [d.account for d in expected_gle]
- acc_diff = get_stock_and_account_difference(account_list, expected_gle[0].posting_date)
+ acc_diff = get_stock_and_account_difference(account_list,
+ expected_gle[0].posting_date, self.company)
cost_center = self.get_company_default("cost_center")
stock_adjustment_account = self.get_company_default("stock_adjustment_account")
@@ -361,13 +362,13 @@ class StockController(AccountsController):
frappe.get_doc("Blanket Order", blanket_order).update_ordered_qty()
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
- warehouse_account=None):
+ warehouse_account=None, company=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
- warehouse_account = get_warehouse_account_map()
+ warehouse_account = get_warehouse_account_map(company)
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
diff --git a/erpnext/controllers/tests/test_item_variant.py b/erpnext/controllers/tests/test_item_variant.py
index d4b5dd2e1ef..c257215e718 100644
--- a/erpnext/controllers/tests/test_item_variant.py
+++ b/erpnext/controllers/tests/test_item_variant.py
@@ -42,10 +42,10 @@ def create_variant_with_tables(item, args):
return variant
def make_item_variant():
- frappe.delete_doc_if_exists("Item", "_Test Variant Item-S", force=1)
- variant = create_variant_with_tables("_Test Variant Item", '{"Test Size": "Small"}')
- variant.item_code = "_Test Variant Item-S"
- variant.item_name = "_Test Variant Item-S"
+ frappe.delete_doc_if_exists("Item", "_Test Variant Item-XSL", force=1)
+ variant = create_variant_with_tables("_Test Variant Item", '{"Test Size": "Extra Small"}')
+ variant.item_code = "_Test Variant Item-XSL"
+ variant.item_name = "_Test Variant Item-XSL"
variant.save()
return variant
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
index 3234e7a332a..124910e35de 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
@@ -162,6 +162,8 @@ def create_item_code(amazon_item_json, sku):
igroup.parent_item_group = mws_settings.item_group
igroup.insert()
+ item.append("item_defaults", {'company':mws_settings.company})
+
item.insert(ignore_permissions=True)
create_item_price(amazon_item_json, item.item_code)
@@ -213,7 +215,7 @@ def get_orders(after_date):
fulfillment_channels=["MFN", "AFN"],
lastupdatedafter=after_date,
orderstatus=statuses,
- max_results='20')
+ max_results='50')
while True:
orders_list = []
@@ -432,8 +434,8 @@ def get_order_items(market_place_order_id):
return final_order_items
def get_item_code(order_item):
- asin = order_item.ASIN
- item_code = frappe.db.get_value("Item", {"amazon_item_code": asin}, "item_code")
+ sku = order_item.SellerSKU
+ item_code = frappe.db.get_value("Item", {"item_code": sku}, "item_code")
if item_code:
return item_code
@@ -451,11 +453,16 @@ def get_charges_and_fees(market_place_order_id):
shipment_item_list = return_as_list(shipment_event.ShipmentEvent.ShipmentItemList.ShipmentItem)
for shipment_item in shipment_item_list:
- charges = return_as_list(shipment_item.ItemChargeList.ChargeComponent)
- fees = return_as_list(shipment_item.ItemFeeList.FeeComponent)
+ charges, fees = []
+
+ if 'ItemChargeList' in shipment_item.keys():
+ charges = return_as_list(shipment_item.ItemChargeList.ChargeComponent)
+
+ if 'ItemFeeList' in shipment_item.keys():
+ fees = return_as_list(shipment_item.ItemFeeList.FeeComponent)
for charge in charges:
- if(charge.ChargeType != "Principal"):
+ if(charge.ChargeType != "Principal") and float(charge.ChargeAmount.CurrencyAmount) != 0:
charge_account = get_account(charge.ChargeType)
charges_fees.get("charges").append({
"charge_type":"Actual",
@@ -465,13 +472,14 @@ def get_charges_and_fees(market_place_order_id):
})
for fee in fees:
- fee_account = get_account(fee.FeeType)
- charges_fees.get("fees").append({
- "charge_type":"Actual",
- "account_head": fee_account,
- "tax_amount": fee.FeeAmount.CurrencyAmount,
- "description": fee.FeeType + " for " + shipment_item.SellerSKU
- })
+ if float(fee.FeeAmount.CurrencyAmount) != 0:
+ fee_account = get_account(fee.FeeType)
+ charges_fees.get("fees").append({
+ "charge_type":"Actual",
+ "account_head": fee_account,
+ "tax_amount": fee.FeeAmount.CurrencyAmount,
+ "description": fee.FeeType + " for " + shipment_item.SellerSKU
+ })
return charges_fees
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
index 1edc1029565..10e307134d0 100644
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
+++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
@@ -65,7 +65,7 @@ class WoocommerceSettings(Document):
if not frappe.get_value("Item Group",{"name": "WooCommerce Products"}):
item_group = frappe.new_doc("Item Group")
item_group.item_group_name = "WooCommerce Products"
- item_group.parent_item_group = "All Item Groups"
+ item_group.parent_item_group = _("All Item Groups")
item_group.save()
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
index 84255b2930b..288ebc40b4e 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
@@ -49,7 +49,7 @@ frappe.ui.form.on('Healthcare Service Unit Type', {
var disable = function(frm){
var doc = frm.doc;
frappe.call({
- method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable",
+ method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable",
args: {status: 1, doc_name: doc.name, item: doc.item, is_billable: doc.is_billable},
callback: function(){
cur_frm.reload_doc();
@@ -60,7 +60,7 @@ var disable = function(frm){
var enable = function(frm){
var doc = frm.doc;
frappe.call({
- method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable",
+ method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable",
args: {status: 0, doc_name: doc.name, item: doc.item, is_billable: doc.is_billable},
callback: function(){
cur_frm.reload_doc();
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
index 727d035a80f..650499454b9 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
@@ -111,7 +111,7 @@ def change_item_code(item, item_code, doc_name):
frappe.db.set_value("Healthcare Service Unit Type", doc_name, "item_code", item_code)
@frappe.whitelist()
-def disable_enable(status, doc_name, item, is_billable):
+def disable_enable(status, doc_name, item=None, is_billable=None):
frappe.db.set_value("Healthcare Service Unit Type", doc_name, "disabled", status)
if(is_billable == 1):
frappe.db.set_value("Item", item, "disabled", status)
diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py
index 6f87954f502..968a1c4571c 100644
--- a/erpnext/hr/doctype/additional_salary/additional_salary.py
+++ b/erpnext/hr/doctype/additional_salary/additional_salary.py
@@ -11,16 +11,14 @@ from frappe.utils import getdate, date_diff
class AdditionalSalary(Document):
def validate(self):
self.validate_dates()
- if self.amount <= 0:
- frappe.throw(_("Amount should be greater than zero."))
+ if self.amount < 0:
+ frappe.throw(_("Amount should not be less than zero."))
def validate_dates(self):
date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
if date_of_joining and getdate(self.payroll_date) < getdate(date_of_joining):
frappe.throw(_("Payroll date can not be less than employee's joining date"))
- elif relieving_date and getdate(self.payroll_date) > getdate(relieving_date):
- frappe.throw(_("To date can not greater than employee's relieving date"))
def get_amount(self, sal_start_date, sal_end_date):
start_date = getdate(sal_start_date)
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index 6bdab61d9df..6da718402bf 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -35,6 +35,7 @@ $.extend(cur_frm.cscript, new erpnext.hr.ExpenseClaimController({frm: cur_frm}))
cur_frm.add_fetch('employee', 'company', 'company');
cur_frm.add_fetch('employee','employee_name','employee_name');
+cur_frm.add_fetch('expense_type','description','description');
cur_frm.cscript.onload = function(doc) {
if (doc.__islocal) {
diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
index b8089908502..d4e70575e91 100644
--- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
+++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
@@ -1,366 +1,378 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-02-22 01:27:46",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "allow_copy": 0,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2013-02-22 01:27:46",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "editable_grid": 1,
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expense_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Expense Date",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "expense_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "expense_date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Expense Date",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "expense_date",
+ "oldfieldtype": "Date",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "print_width": "150px",
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"translatable": 0,
- "unique": 0,
+ "unique": 0,
"width": "150px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "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,
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_2",
+ "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,
+ "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expense_type",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Expense Claim Type",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "expense_type",
- "oldfieldtype": "Link",
- "options": "Expense Claim Type",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "expense_type",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Expense Claim Type",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "expense_type",
+ "oldfieldtype": "Link",
+ "options": "Expense Claim Type",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "print_width": "150px",
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
"translatable": 0,
- "unique": 0,
+ "unique": 0,
"width": "150px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "expense_type",
- "fieldname": "default_account",
- "fieldtype": "Link",
- "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": "Default Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "expense_type",
+ "fieldname": "default_account",
+ "fieldtype": "Link",
+ "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": "Default Account",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Account",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"translatable": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"translatable": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "expense_type.description",
- "fieldname": "description",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "description",
- "oldfieldtype": "Small Text",
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "300px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_from": "",
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Description",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "description",
+ "oldfieldtype": "Small Text",
+ "options": "",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "print_width": "300px",
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"translatable": 0,
- "unique": 0,
+ "unique": 0,
"width": "300px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"translatable": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "claim_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Claim Amount",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "claim_amount",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "claim_amount",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Claim Amount",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "claim_amount",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "print_width": "150px",
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
"translatable": 0,
- "unique": 0,
+ "unique": 0,
"width": "150px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_8",
- "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,
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_8",
+ "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,
+ "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sanctioned_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Sanctioned Amount",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "sanctioned_amount",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "sanctioned_amount",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Sanctioned Amount",
+ "length": 0,
+ "no_copy": 1,
+ "oldfieldname": "sanctioned_amount",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "print_width": "150px",
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"translatable": 0,
- "unique": 0,
+ "unique": 0,
"width": "150px"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-05-16 22:42:56.727863",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Expense Claim Detail",
- "owner": "harshada@webnotestech.com",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 1,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2019-02-24 08:41:36.122565",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Expense Claim Detail",
+ "owner": "harshada@webnotestech.com",
+ "permissions": [],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 0,
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 6b7c0f7e79d..560dd1d3bfd 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -399,6 +399,19 @@ def get_leave_balance_on(employee, leave_type, date, allocation_records=None, do
return flt(allocation.total_leaves_allocated) - (flt(leaves_taken) + flt(leaves_encashed))
+def get_total_allocated_leaves(employee, leave_type, date):
+ filters= {
+ 'from_date': ['<=', date],
+ 'to_date': ['>=', date],
+ 'docstatus': 1,
+ 'leave_type': leave_type,
+ 'employee': employee
+ }
+
+ leave_allocation_records = frappe.db.get_all('Leave Allocation', filters=filters, fields=['total_leaves_allocated'])
+
+ return flt(leave_allocation_records[0]['total_leaves_allocated']) if leave_allocation_records else flt(0)
+
def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docname=None):
leave_applications = frappe.db.sql("""
select name, employee, leave_type, from_date, to_date, total_leave_days
@@ -487,7 +500,6 @@ def get_events(start, end, filters=None):
add_block_dates(events, start, end, employee, company)
add_holidays(events, start, end, employee, company)
-
return events
def add_department_leaves(events, start, end, employee, company):
@@ -500,19 +512,32 @@ def add_department_leaves(events, start, end, employee, company):
department_employees = frappe.db.sql_list("""select name from tabEmployee where department=%s
and company=%s""", (department, company))
- match_conditions = "and employee in (\"%s\")" % '", "'.join(department_employees)
- add_leaves(events, start, end, match_conditions=match_conditions)
+ filter_conditions = " and employee in (\"%s\")" % '", "'.join(department_employees)
+ add_leaves(events, start, end, filter_conditions=filter_conditions)
+
+def add_leaves(events, start, end, filter_conditions=None):
+ conditions = []
+
+
+ if not cint(frappe.db.get_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar")):
+ from frappe.desk.reportview import build_match_conditions
+ match_conditions = build_match_conditions("Leave Application")
+
+ if match_conditions:
+ conditions.append(match_conditions)
-def add_leaves(events, start, end, match_conditions=None):
query = """select name, from_date, to_date, employee_name, half_day,
status, employee, docstatus
from `tabLeave Application` where
from_date <= %(end)s and to_date >= %(start)s <= to_date
and docstatus < 2
- and status!="Rejected" """
+ and status!='Rejected' """
- if match_conditions:
- query += match_conditions
+ if conditions:
+ query += ' and ' + ' and '.join(conditions)
+
+ if filter_conditions:
+ query += filter_conditions
for d in frappe.db.sql(query, {"start":start, "end": end}, as_dict=True):
e = {
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 3fd266b4bba..684f3483f06 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -441,7 +441,7 @@ class SalarySlip(TransactionBase):
def calculate_net_pay(self):
if self.salary_structure:
self.calculate_component_amounts()
-
+
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
precision = frappe.defaults.get_global_default("currency_precision")
self.total_deduction = 0
@@ -452,10 +452,10 @@ class SalarySlip(TransactionBase):
self.set_loan_repayment()
- self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
+ self.net_pay = (flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))) * flt(self.payment_days / self.total_working_days)
self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0)
-
+
if self.net_pay < 0:
frappe.throw(_("Net Pay cannnot be negative"))
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 70e185cde56..83e53135ef4 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -20,6 +20,7 @@ class StaffingPlan(Document):
self.total_estimated_budget = 0
for detail in self.get("staffing_details"):
+ self.set_vacancies(detail)
self.validate_overlap(detail)
self.validate_with_subsidiary_plans(detail)
self.validate_with_parent_plan(detail)
@@ -39,6 +40,15 @@ class StaffingPlan(Document):
else: detail.vacancies = detail.number_of_positions = detail.total_estimated_cost = 0
self.total_estimated_budget += detail.total_estimated_cost
+ def set_vacancies(self, row):
+ if not row.vacancies:
+ current_openings = 0
+ for field in ['current_count', 'current_openings']:
+ if row.get(field):
+ current_openings += row.get(field)
+
+ row.vacancies = row.number_of_positions - current_openings
+
def validate_overlap(self, staffing_plan_detail):
# Validate if any submitted Staffing Plan exist for any Designations in this plan
# and spd.vacancies>0 ?
diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
index 66d9cdd07a5..22dba99af00 100644
--- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
@@ -18,7 +18,7 @@ class TestStaffingPlan(unittest.TestCase):
if frappe.db.exists("Staffing Plan", "Test"):
return
staffing_plan = frappe.new_doc("Staffing Plan")
- staffing_plan.company = "_Test Company 3"
+ staffing_plan.company = "_Test Company 10"
staffing_plan.name = "Test"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
@@ -67,7 +67,7 @@ class TestStaffingPlan(unittest.TestCase):
if frappe.db.exists("Staffing Plan", "Test 1"):
return
staffing_plan = frappe.new_doc("Staffing Plan")
- staffing_plan.company = "_Test Company 3"
+ staffing_plan.company = "_Test Company 10"
staffing_plan.name = "Test 1"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
@@ -85,11 +85,11 @@ def _set_up():
make_company()
def make_company():
- if frappe.db.exists("Company", "_Test Company 3"):
+ if frappe.db.exists("Company", "_Test Company 10"):
return
company = frappe.new_doc("Company")
- company.company_name = "_Test Company 3"
- company.abbr = "_TC3"
+ company.company_name = "_Test Company 10"
+ company.abbr = "_TC10"
company.parent_company = "_Test Company"
company.default_currency = "INR"
company.country = "India"
diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.py b/erpnext/hr/doctype/training_feedback/training_feedback.py
index b7eae38ae48..b47e2b4be88 100644
--- a/erpnext/hr/doctype/training_feedback/training_feedback.py
+++ b/erpnext/hr/doctype/training_feedback/training_feedback.py
@@ -15,9 +15,11 @@ class TrainingFeedback(Document):
def on_submit(self):
training_event = frappe.get_doc("Training Event", self.training_event)
+ status = None
for e in training_event.employees:
if e.employee == self.employee:
- training_event.status = 'Feedback Submitted'
+ status = 'Feedback Submitted'
break
- training_event.save()
+ if status:
+ frappe.db.set_value("Training Event", self.training_event, "status", status)
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index ed44d639f52..95cb30b7918 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -5,21 +5,21 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.hr.doctype.leave_application.leave_application \
- import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period
+ import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period, get_total_allocated_leaves
def execute(filters=None):
leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
-
+
columns = get_columns(leave_types)
data = get_data(filters, leave_types)
-
+
return columns, data
-
+
def get_columns(leave_types):
columns = [
- _("Employee") + ":Link/Employee:150",
- _("Employee Name") + "::200",
+ _("Employee") + ":Link/Employee:150",
+ _("Employee Name") + "::200",
_("Department") +"::150"
]
@@ -27,18 +27,18 @@ def get_columns(leave_types):
columns.append(_(leave_type) + " " + _("Opening") + ":Float:160")
columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
-
+
return columns
-
+
def get_data(filters, leave_types):
user = frappe.session.user
allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date)
allocation_records_based_on_from_date = get_leave_allocation_records(filters.from_date)
- active_employees = frappe.get_all("Employee",
- filters = { "status": "Active", "company": filters.company},
+ active_employees = frappe.get_all("Employee",
+ filters = { "status": "Active", "company": filters.company},
fields = ["name", "employee_name", "department", "user_id"])
-
+
data = []
for employee in active_employees:
leave_approvers = get_approvers(employee.department)
@@ -51,8 +51,7 @@ def get_data(filters, leave_types):
filters.from_date, filters.to_date)
# opening balance
- opening = get_leave_balance_on(employee.name, leave_type, filters.from_date,
- allocation_records_based_on_from_date.get(employee.name, frappe._dict()))
+ opening = get_total_allocated_leaves(employee.name, leave_type, filters.to_date)
# closing balance
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date,
@@ -61,7 +60,7 @@ def get_data(filters, leave_types):
row += [opening, leaves_taken, closing]
data.append(row)
-
+
return data
def get_approvers(department):
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index 6d082bd2005..2f2ad00e023 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -41,12 +41,19 @@ class MaintenanceVisit(TransactionBase):
work_done = nm and nm[0][3] or ''
else:
status = 'Open'
- mntc_date = ''
- service_person = ''
- work_done = ''
+ mntc_date = None
+ service_person = None
+ work_done = None
- frappe.db.sql("update `tabWarranty Claim` set resolution_date=%s, resolved_by=%s, resolution_details=%s, status=%s where name =%s",(mntc_date,service_person,work_done,status,d.prevdoc_docname))
+ wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname)
+ wc_doc.update({
+ 'resolution_date': mntc_date,
+ 'resolved_by': service_person,
+ 'resolution_details': work_done,
+ 'status': status
+ })
+ wc_doc.db_update()
def check_if_last_visit(self):
"""check if last maintenance visit against same sales order/ Warranty Claim"""
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 88d346ff0f3..c65693bddf6 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -580,7 +580,7 @@ def get_list_context(context):
context.title = _("Bill of Materials")
# context.introduction = _('Boms')
-def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_items=0, include_non_stock_items=False):
+def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_items=0, include_non_stock_items=False, fetch_qty_in_stock_uom=True):
item_dict = {}
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
@@ -588,7 +588,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
bom_item.item_code,
bom_item.idx,
item.item_name,
- sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(qty)s as qty,
+ sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * %(qty)s as qty,
item.description,
item.image,
item.stock_uom,
@@ -616,16 +616,18 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
query = query.format(table="BOM Explosion Item",
where_conditions="",
is_stock_item=is_stock_item,
+ qty_field="stock_qty",
select_columns = """, bom_item.source_warehouse, bom_item.operation, bom_item.include_item_in_manufacturing,
- (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s ) as idx""")
+ (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""")
items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
elif fetch_scrap_items:
- query = query.format(table="BOM Scrap Item", where_conditions="", select_columns=", bom_item.idx", is_stock_item=is_stock_item)
+ query = query.format(table="BOM Scrap Item", where_conditions="", select_columns=", bom_item.idx", is_stock_item=is_stock_item, qty_field="stock_qty")
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
else:
query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item,
- select_columns = ", bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing")
+ qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty",
+ select_columns = ", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing")
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
for item in items:
@@ -706,8 +708,11 @@ def get_children(doctype, parent=None, is_root=False, **filters):
def get_boms_in_bottom_up_order(bom_no=None):
def _get_parent(bom_no):
- return frappe.db.sql_list("""select distinct parent from `tabBOM Item`
- where bom_no = %s and docstatus=1 and parenttype='BOM'""", bom_no)
+ return frappe.db.sql_list("""
+ select distinct bom_item.parent from `tabBOM Item` bom_item
+ where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
+ and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
+ """, bom_no)
count = 0
bom_list = []
@@ -715,9 +720,10 @@ def get_boms_in_bottom_up_order(bom_no=None):
bom_list.append(bom_no)
else:
# get all leaf BOMs
- bom_list = frappe.db.sql_list("""select name from `tabBOM` bom where docstatus=1
- and not exists(select bom_no from `tabBOM Item`
- where parent=bom.name and ifnull(bom_no, '')!='')""")
+ bom_list = frappe.db.sql_list("""select name from `tabBOM` bom
+ where docstatus=1 and is_active=1
+ and not exists(select bom_no from `tabBOM Item`
+ where parent=bom.name and ifnull(bom_no, '')!='')""")
while(count < len(bom_list)):
for child_bom in _get_parent(bom_list[count]):
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index 22583605777..febf315988c 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -78,6 +78,39 @@
"translatable": 0,
"unique": 0
},
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "operation",
+ "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": "Item operation",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Operation",
+ "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,
@@ -893,6 +926,38 @@
"translatable": 0,
"unique": 0
},
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "allow_alternative_item",
+ "fieldtype": "Check",
+ "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": "Allow Alternative Item",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@@ -958,71 +1023,6 @@
"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": "operation",
- "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": "Item operation",
- "length": 0,
- "no_copy": 0,
- "options": "Operation",
- "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": "allow_alternative_item",
- "fieldtype": "Check",
- "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": "Allow Alternative Item",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
}
],
"has_web_view": 0,
@@ -1035,7 +1035,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-11-23 15:05:55.187136",
+ "modified": "2019-02-21 19:19:54.872459",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
@@ -1050,4 +1050,4 @@
"track_changes": 0,
"track_seen": 0,
"track_views": 0
-}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index 3fe9b8af30d..95549d5a248 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -18,20 +18,27 @@ frappe.ui.form.on('Job Card', {
}
if (frm.doc.docstatus == 0) {
- if (!frm.doc.actual_start_date || !frm.doc.actual_end_date) {
- frm.trigger("make_dashboard");
- }
+ frm.trigger("make_dashboard");
- if (!frm.doc.actual_start_date) {
+ if (!frm.doc.job_started) {
frm.add_custom_button(__("Start Job"), () => {
- frm.set_value('actual_start_date', frappe.datetime.now_datetime());
+ let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs');
+ row.from_time = frappe.datetime.now_datetime();
+ frm.set_value('job_started', 1);
+ frm.set_value('started_time' , row.from_time);
frm.save();
});
- } else if (!frm.doc.actual_end_date) {
+ } else {
frm.add_custom_button(__("Complete Job"), () => {
- frm.set_value('actual_end_date', frappe.datetime.now_datetime());
- frm.save();
- frm.savesubmit();
+ let completed_time = frappe.datetime.now_datetime();
+ frm.doc.time_logs.forEach(d => {
+ if (d.from_time && !d.to_time) {
+ d.to_time = completed_time;
+ frm.set_value('started_time' , '');
+ frm.set_value('job_started', 0);
+ frm.save();
+ }
+ })
});
}
}
@@ -53,8 +60,8 @@ frappe.ui.form.on('Job Card', {
var section = frm.dashboard.add_section(timer);
- if (frm.doc.actual_start_date) {
- let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.actual_start_date),"seconds");
+ if (frm.doc.started_time) {
+ let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.started_time),"seconds");
initialiseTimer();
function initialiseTimer() {
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index b020c89053c..39c5cce313b 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -21,6 +21,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "work_order",
"fieldtype": "Link",
"hidden": 0,
@@ -54,6 +55,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "bom_no",
"fieldtype": "Link",
"hidden": 0,
@@ -87,6 +89,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "workstation",
"fieldtype": "Link",
"hidden": 0,
@@ -120,6 +123,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "operation",
"fieldtype": "Link",
"hidden": 0,
@@ -153,6 +157,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
@@ -185,6 +190,7 @@
"collapsible": 0,
"columns": 0,
"default": "Today",
+ "fetch_if_empty": 0,
"fieldname": "posting_date",
"fieldtype": "Date",
"hidden": 0,
@@ -217,6 +223,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -250,6 +257,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "for_quantity",
"fieldtype": "Float",
"hidden": 0,
@@ -282,6 +290,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "wip_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -315,6 +324,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "timing_detail",
"fieldtype": "Section Break",
"hidden": 0,
@@ -347,6 +357,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
@@ -380,7 +391,74 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "time_in_mins",
+ "fetch_if_empty": 0,
+ "fieldname": "time_logs",
+ "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": "Time Logs",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Job Card Time Log",
+ "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,
+ "fetch_if_empty": 0,
+ "fieldname": "section_break_13",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "total_completed_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -389,7 +467,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Time In Mins",
+ "label": "Total Completed Qty",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -412,7 +490,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "column_break_13",
+ "fetch_if_empty": 0,
+ "fieldname": "column_break_15",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -443,8 +522,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "actual_start_date",
- "fieldtype": "Datetime",
+ "fetch_if_empty": 0,
+ "fieldname": "total_time_in_mins",
+ "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -452,46 +532,14 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Actual Start Date",
+ "label": "Total Time in Mins",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "actual_end_date",
- "fieldtype": "Datetime",
- "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": "Actual End Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -507,6 +555,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
@@ -539,6 +588,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "items",
"fieldtype": "Table",
"hidden": 0,
@@ -572,6 +622,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "more_information",
"fieldtype": "Section Break",
"hidden": 0,
@@ -604,6 +655,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "operation_id",
"fieldtype": "Data",
"hidden": 1,
@@ -637,6 +689,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
+ "fetch_if_empty": 0,
"fieldname": "transferred_qty",
"fieldtype": "Float",
"hidden": 0,
@@ -670,6 +723,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
+ "fetch_if_empty": 0,
"fieldname": "requested_qty",
"fieldtype": "Float",
"hidden": 0,
@@ -702,6 +756,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "project",
"fieldtype": "Link",
"hidden": 0,
@@ -735,6 +790,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "remarks",
"fieldtype": "Small Text",
"hidden": 0,
@@ -767,6 +823,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_20",
"fieldtype": "Column Break",
"hidden": 0,
@@ -799,6 +856,7 @@
"collapsible": 0,
"columns": 0,
"default": "Open",
+ "fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
@@ -832,6 +890,73 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "job_started",
+ "fieldtype": "Check",
+ "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": "Job Started",
+ "length": 0,
+ "no_copy": 1,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "started_time",
+ "fieldtype": "Datetime",
+ "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": "Started Time",
+ "length": 0,
+ "no_copy": 1,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -868,7 +993,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-12-13 17:23:57.986381",
+ "modified": "2019-03-10 17:38:37.499871",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index ea9f714fc84..23a4e5105c0 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -11,44 +11,56 @@ from frappe.model.document import Document
class JobCard(Document):
def validate(self):
- self.validate_actual_dates()
- self.set_time_in_mins()
+ self.validate_time_logs()
self.set_status()
- def validate_actual_dates(self):
- if get_datetime(self.actual_start_date) > get_datetime(self.actual_end_date):
- frappe.throw(_("Actual start date must be less than actual end date"))
+ def validate_time_logs(self):
+ self.total_completed_qty = 0.0
+ self.total_time_in_mins = 0.0
- if not (self.employee and self.actual_start_date and self.actual_end_date):
- return
+ for d in self.get('time_logs'):
+ if get_datetime(d.from_time) > get_datetime(d.to_time):
+ frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx))
- data = frappe.db.sql(""" select name from `tabJob Card`
- where
- ((%(actual_start_date)s > actual_start_date and %(actual_start_date)s < actual_end_date) or
- (%(actual_end_date)s > actual_start_date and %(actual_end_date)s < actual_end_date) or
- (%(actual_start_date)s <= actual_start_date and %(actual_end_date)s >= actual_end_date)) and
- name != %(name)s and employee = %(employee)s and docstatus =1
- """, {
- 'actual_start_date': self.actual_start_date,
- 'actual_end_date': self.actual_end_date,
- 'employee': self.employee,
- 'name': self.name
- }, as_dict=1)
+ data = self.get_overlap_for(d)
+ if data:
+ frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
+ .format(d.idx, self.name, data.name))
- if data:
- frappe.throw(_("Start date and end date is overlapping with the job card
{1}")
- .format(data[0].name, data[0].name))
+ if d.from_time and d.to_time:
+ d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
+ self.total_time_in_mins += d.time_in_mins
- def set_time_in_mins(self):
- if self.actual_start_date and self.actual_end_date:
- self.time_in_mins = time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60
+ if d.completed_qty:
+ self.total_completed_qty += d.completed_qty
+
+ def get_overlap_for(self, args):
+ existing = frappe.db.sql("""select jc.name as name from
+ `tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and
+ (
+ (%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or
+ (%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or
+ (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time))
+ and jctl.name!=%(name)s
+ and jc.name!=%(parent)s
+ and jc.docstatus < 2
+ and jc.employee = %(employee)s """,
+ {
+ "from_time": args.from_time,
+ "to_time": args.to_time,
+ "name": args.name or "No Name",
+ "parent": args.parent or "No Name",
+ "employee": self.employee
+ }, as_dict=True)
+
+ return existing[0] if existing else None
def get_required_items(self):
if not self.get('work_order'):
return
doc = frappe.get_doc('Work Order', self.get('work_order'))
- if doc.transfer_material_against == 'Work Order' and doc.skip_transfer:
+ if doc.transfer_material_against == 'Work Order' or doc.skip_transfer:
return
for d in doc.required_items:
@@ -67,36 +79,51 @@ class JobCard(Document):
})
def on_submit(self):
- self.validate_dates()
+ self.validate_job_card()
self.update_work_order()
self.set_transferred_qty()
- def validate_dates(self):
- if not self.actual_start_date and not self.actual_end_date:
- frappe.throw(_("Actual start date and actual end date is mandatory"))
-
def on_cancel(self):
self.update_work_order()
self.set_transferred_qty()
+ def validate_job_card(self):
+ if not self.time_logs:
+ frappe.throw(_("Time logs are required for job card {0}").format(self.name))
+
+ if self.total_completed_qty <= 0.0:
+ frappe.throw(_("Total completed qty must be greater than zero"))
+
+ if self.total_completed_qty > self.for_quantity:
+ frappe.throw(_("Total completed qty can not be greater than for quantity"))
+
def update_work_order(self):
if not self.work_order:
return
- data = frappe.db.get_value("Job Card", {'docstatus': 1, 'operation_id': self.operation_id},
- ['sum(time_in_mins)', 'min(actual_start_date)', 'max(actual_end_date)', 'sum(for_quantity)'])
+ for_quantity, time_in_mins = 0, 0
+ from_time_list, to_time_list = [], []
- if data:
- time_in_mins, actual_start_date, actual_end_date, for_quantity = data
+ for d in frappe.get_all('Job Card',
+ filters = {'docstatus': 1, 'operation_id': self.operation_id}):
+ doc = frappe.get_doc('Job Card', d.name)
+
+ for_quantity += doc.total_completed_qty
+ time_in_mins += doc.total_time_in_mins
+ for time_log in doc.time_logs:
+ from_time_list.append(time_log.from_time)
+ to_time_list.append(time_log.to_time)
+
+ if for_quantity:
wo = frappe.get_doc('Work Order', self.work_order)
for data in wo.operations:
if data.name == self.operation_id:
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
- data.actual_start_time = actual_start_date
- data.actual_end_time = actual_end_date
+ data.actual_start_time = min(from_time_list)
+ data.actual_end_time = max(to_time_list)
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()
@@ -132,9 +159,11 @@ class JobCard(Document):
break
if completed:
- job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order,
+ job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order,
'docstatus': ('!=', 2)}, fields = 'sum(transferred_qty) as qty', group_by='operation_id')
- qty = min([d.qty for d in job_cards])
+
+ if job_cards:
+ qty = min([d.qty for d in job_cards])
doc.db_set('material_transferred_for_manufacturing', qty)
@@ -147,7 +176,7 @@ class JobCard(Document):
2: "Cancelled"
}[self.docstatus or 0]
- if self.actual_start_date:
+ if self.time_logs:
self.status = 'Work In Progress'
if (self.docstatus == 1 and
diff --git a/erpnext/manufacturing/doctype/job_card_time_log/__init__.py b/erpnext/manufacturing/doctype/job_card_time_log/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
new file mode 100644
index 00000000000..2aab71dee4f
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
@@ -0,0 +1,208 @@
+{
+ "allow_copy": 0,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2019-03-08 23:56:43.187569",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "From Time",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "To Time",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "column_break_2",
+ "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,
+ "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,
+ "fetch_if_empty": 0,
+ "fieldname": "time_in_mins",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Time In Mins",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fetch_if_empty": 0,
+ "fieldname": "completed_qty",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Completed Qty",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2019-03-10 17:08:46.504910",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Time Log",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "track_changes": 1,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py
new file mode 100644
index 00000000000..3dc66891216
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe.model.document import Document
+
+class JobCardTimeLog(Document):
+ pass
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 69381c53b3e..b292047aa63 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -302,6 +302,19 @@ class TestWorkOrder(unittest.TestCase):
self.assertEqual(len(ste.additional_costs), 1)
self.assertEqual(ste.total_additional_costs, 1000)
+ def test_job_card(self):
+ data = frappe.get_cached_value('BOM',
+ {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+
+ if data:
+ bom, bom_item = data
+
+ bom_doc = frappe.get_doc('BOM', bom)
+ work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
+
+ job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order})
+ self.assertEqual(len(job_cards), len(bom_doc.operations))
+
def test_work_order_with_non_transfer_item(self):
items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
for item, allow_transfer in items.items():
@@ -346,7 +359,7 @@ def make_wo_order_test_record(**args):
wo_order = frappe.new_doc("Work Order")
wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item"
- wo_order.bom_no = frappe.db.get_value("BOM", {"item": wo_order.production_item,
+ wo_order.bom_no = args.bom_no or frappe.db.get_value("BOM", {"item": wo_order.production_item,
"is_active": 1, "is_default": 1})
wo_order.qty = args.qty or 10
wo_order.wip_warehouse = args.wip_warehouse or "_Test Warehouse - _TC"
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 9873efa124b..947d6931e21 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -282,6 +282,10 @@ class WorkOrder(Document):
total_bundle_qty = frappe.db.sql(""" select sum(qty) from
`tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0]
+ if not total_bundle_qty:
+ # product bundle is 0 (product bundle allows 0 qty for items)
+ total_bundle_qty = 1
+
cond = "product_bundle_item = %s" if self.product_bundle_item else "production_item = %s"
qty = frappe.db.sql(""" select sum(qty) from
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index b1a393bdf3e..9dcb285a0a3 100755
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -571,7 +571,7 @@ execute:frappe.delete_doc_if_exists("Page", "sales-analytics")
execute:frappe.delete_doc_if_exists("Page", "purchase-analytics")
execute:frappe.delete_doc_if_exists("Page", "stock-analytics")
execute:frappe.delete_doc_if_exists("Page", "production-analytics")
-erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09
+erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09 #2019-04-01
erpnext.patches.v11_0.drop_column_max_days_allowed
erpnext.patches.v11_0.change_healthcare_desktop_icons
erpnext.patches.v10_0.update_user_image_in_employee
@@ -587,4 +587,6 @@ erpnext.patches.v11_1.setup_guardian_role
execute:frappe.delete_doc('DocType', 'Notification Control')
erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
-erpnext.patches.v11_0.make_italian_localization_fields
+erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019
+erpnext.patches.v11_1.make_job_card_time_logs
+erpnext.patches.v11_1.set_variant_based_on
\ No newline at end of file
diff --git a/erpnext/patches/v10_0/item_barcode_childtable_migrate.py b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
index bc6005677db..e30e0a74c00 100644
--- a/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
+++ b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
@@ -27,5 +27,5 @@ def execute():
'parent': item.name,
'parentfield': 'barcodes'
}).insert()
- except frappe.DuplicateEntryError:
+ except (frappe.DuplicateEntryError, frappe.UniqueValidationError):
continue
diff --git a/erpnext/patches/v11_0/make_italian_localization_fields.py b/erpnext/patches/v11_0/make_italian_localization_fields.py
index cb65f23ab12..79958b9189c 100644
--- a/erpnext/patches/v11_0/make_italian_localization_fields.py
+++ b/erpnext/patches/v11_0/make_italian_localization_fields.py
@@ -6,23 +6,32 @@ from erpnext.regional.italy.setup import make_custom_fields, setup_report
from erpnext.regional.italy import state_codes
import frappe
-
def execute():
+ company = frappe.get_all('Company', filters = {'country': 'Italy'})
+ if not company:
+ return
- company = frappe.get_all('Company', filters = {'country': 'Italy'})
- if not company:
- return
+ frappe.reload_doc('regional', 'report', 'electronic_invoice_register')
+ make_custom_fields()
+ setup_report()
- make_custom_fields()
- setup_report()
+ # Set state codes
+ condition = ""
+ for state, code in state_codes.items():
+ condition += " when '{0}' then '{1}'".format(frappe.db.escape(state), frappe.db.escape(code))
- # Set state codes
- condition = ""
- for state, code in state_codes.items():
- condition += " when '{0}' then '{1}'".format(frappe.db.escape(state), frappe.db.escape(code))
+ if condition:
+ condition = "state_code = (case state {0} end),".format(condition)
- if condition:
- frappe.db.sql("""
- UPDATE tabAddress set state_code = (case state {condition} end)
- WHERE country in ('Italy', 'Italia', 'Italian Republic', 'Repubblica Italiana')
- """.format(condition=condition))
+ frappe.db.sql("""
+ UPDATE tabAddress set {condition} country_code = UPPER(ifnull((select code
+ from `tabCountry` where name = `tabAddress`.country), ''))
+ where country_code is null and state_code is null
+ """.format(condition=condition))
+
+ frappe.db.sql("""
+ UPDATE `tabSales Invoice Item` si, `tabSales Order` so
+ set si.customer_po_no = so.po_no, si.customer_po_date = so.po_date
+ WHERE
+ si.sales_order = so.name and so.po_no is not null
+ """)
diff --git a/erpnext/patches/v11_1/make_job_card_time_logs.py b/erpnext/patches/v11_1/make_job_card_time_logs.py
new file mode 100644
index 00000000000..6e708df48d8
--- /dev/null
+++ b/erpnext/patches/v11_1/make_job_card_time_logs.py
@@ -0,0 +1,29 @@
+# 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
+
+def execute():
+ frappe.reload_doc('manufacturing', 'doctype', 'job_card_time_log')
+
+ if (frappe.db.table_exists("Job Card")
+ and frappe.get_meta("Job Card").has_field("actual_start_date")):
+ time_logs = []
+ for d in frappe.get_all('Job Card',
+ fields = ["actual_start_date", "actual_end_date", "time_in_mins", "name", "for_quantity"],
+ filters = {'docstatus': ("<", 2)}):
+ if d.actual_start_date:
+ time_logs.append([d.actual_start_date, d.actual_end_date, d.time_in_mins,
+ d.for_quantity, d.name, 'Job Card', 'time_logs', frappe.generate_hash("", 10)])
+
+ if time_logs:
+ frappe.db.sql(""" INSERT INTO
+ `tabJob Card Time Log`
+ (from_time, to_time, time_in_mins, completed_qty, parent, parenttype, parentfield, name)
+ values {values}
+ """.format(values = ','.join(['%s'] * len(time_logs))), tuple(time_logs))
+
+ frappe.reload_doc('manufacturing', 'doctype', 'job_card')
+ frappe.db.sql(""" update `tabJob Card` set total_completed_qty = for_quantity,
+ total_time_in_mins = time_in_mins where docstatus < 2 """)
\ No newline at end of file
diff --git a/erpnext/patches/v11_1/set_variant_based_on.py b/erpnext/patches/v11_1/set_variant_based_on.py
new file mode 100644
index 00000000000..019eefd68f4
--- /dev/null
+++ b/erpnext/patches/v11_1/set_variant_based_on.py
@@ -0,0 +1,11 @@
+# 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
+
+def execute():
+ frappe.db.sql("""update tabItem set variant_based_on = 'Item Attribute'
+ where ifnull(variant_based_on, '') = ''
+ and (has_variants=1 or ifnull(variant_of, '') != '')
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v7_0/repost_future_gle_for_purchase_invoice.py b/erpnext/patches/v7_0/repost_future_gle_for_purchase_invoice.py
index 22f9db6b2be..9e21fb699b9 100644
--- a/erpnext/patches/v7_0/repost_future_gle_for_purchase_invoice.py
+++ b/erpnext/patches/v7_0/repost_future_gle_for_purchase_invoice.py
@@ -10,14 +10,15 @@ from erpnext.controllers.stock_controller import update_gl_entries_after
def execute():
company_list = frappe.db.sql_list("""Select name from tabCompany where enable_perpetual_inventory = 1""")
frappe.reload_doc('accounts', 'doctype', 'sales_invoice')
-
- frappe.reload_doctype("Purchase Invoice")
+
+ frappe.reload_doctype("Purchase Invoice")
wh_account = get_warehouse_account_map()
-
+
for pi in frappe.get_all("Purchase Invoice", fields=["name", "company"], filters={"docstatus": 1, "update_stock": 1}):
if pi.company in company_list:
pi_doc = frappe.get_doc("Purchase Invoice", pi.name)
items, warehouses = pi_doc.get_items_and_warehouses()
- update_gl_entries_after(pi_doc.posting_date, pi_doc.posting_time, warehouses, items, wh_account)
-
+ update_gl_entries_after(pi_doc.posting_date, pi_doc.posting_time,
+ warehouses, items, wh_account, company = pi.company)
+
frappe.db.commit()
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index fffa9c1657c..12302789a9b 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -159,6 +159,13 @@ class Task(NestedSet):
self.update_nsm_model()
+ def update_status(self):
+ if self.status not in ('Cancelled', 'Closed') and self.exp_end_date:
+ from datetime import datetime
+ if self.exp_end_date < datetime.now().date():
+ self.db_set('status', 'Overdue')
+ self.update_project()
+
@frappe.whitelist()
def check_if_child_exists(name):
child_tasks = frappe.get_all("Task", filters={"parent_task": name})
@@ -186,10 +193,9 @@ def set_multiple_status(names, status):
task.save()
def set_tasks_as_overdue():
- frappe.db.sql("""update tabTask set `status`='Overdue'
- where exp_end_date is not null
- and exp_end_date < CURDATE()
- and `status` not in ('Closed', 'Cancelled')""")
+ tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Closed']]})
+ for task in tasks:
+ frappe.get_doc("Task", task.name).update_status()
@frappe.whitelist()
def get_children(doctype, parent, task=None, project=None, is_root=False):
diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py
index 9971946cb4f..b733f67a980 100644
--- a/erpnext/projects/doctype/task/test_task.py
+++ b/erpnext/projects/doctype/task/test_task.py
@@ -117,4 +117,4 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None, sa
if save:
task.save()
- return task
\ No newline at end of file
+ return task
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index 8811ab95438..8ffc10ee972 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -10,8 +10,8 @@ frappe.ui.form.on("Timesheet", {
filters:{
'status': 'Active'
}
- }
- }
+ };
+ };
frm.fields_dict['time_logs'].grid.get_field('task').get_query = function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
@@ -20,33 +20,37 @@ frappe.ui.form.on("Timesheet", {
'project': child.project,
'status': ["!=", "Cancelled"]
}
- }
- }
+ };
+ };
frm.fields_dict['time_logs'].grid.get_field('project').get_query = function() {
return{
filters: {
'company': frm.doc.company
}
- }
- }
+ };
+ };
},
onload: function(frm){
if (frm.doc.__islocal && frm.doc.time_logs) {
calculate_time_and_amount(frm);
}
+
+ if (frm.is_new()) {
+ set_employee_and_company(frm);
+ }
},
refresh: function(frm) {
if(frm.doc.docstatus==1) {
if(frm.doc.per_billed < 100 && frm.doc.total_billable_hours && frm.doc.total_billable_hours > frm.doc.total_billed_hours){
- frm.add_custom_button(__("Make Sales Invoice"), function() { frm.trigger("make_invoice") },
+ frm.add_custom_button(__("Make Sales Invoice"), function() { frm.trigger("make_invoice"); },
"fa fa-file-alt");
}
if(!frm.doc.salary_slip && frm.doc.employee){
- frm.add_custom_button(__("Make Salary Slip"), function() { frm.trigger("make_salary_slip") },
+ frm.add_custom_button(__("Make Salary Slip"), function() { frm.trigger("make_salary_slip"); },
"fa fa-file-alt");
}
}
@@ -58,7 +62,7 @@ frappe.ui.form.on("Timesheet", {
if ((row.from_time <= frappe.datetime.now_datetime()) && !row.completed) {
button = 'Resume Timer';
}
- })
+ });
frm.add_custom_button(__(button), function() {
var flag = true;
@@ -77,7 +81,7 @@ frappe.ui.form.on("Timesheet", {
erpnext.timesheet.timer(frm, row, timestamp);
flag = false;
}
- })
+ });
// If no activities found to start a timer, create new
if (flag) {
erpnext.timesheet.timer(frm);
@@ -94,7 +98,7 @@ frappe.ui.form.on("Timesheet", {
frappe.db.get_value('Company', { 'company_name' : frm.doc.company }, 'standard_working_hours')
.then(({ message }) => {
(frappe.working_hours = message.standard_working_hours || 0);
- });
+ });
},
make_invoice: function(frm) {
@@ -125,8 +129,8 @@ frappe.ui.form.on("Timesheet", {
frappe.set_route("Form", r.message.doctype, r.message.name);
}
}
- })
- })
+ });
+ });
dialog.show();
},
@@ -136,7 +140,7 @@ frappe.ui.form.on("Timesheet", {
frm: frm
});
},
-})
+});
frappe.ui.form.on("Timesheet Detail", {
time_logs_remove: function(frm) {
@@ -171,22 +175,22 @@ frappe.ui.form.on("Timesheet Detail", {
.find('[data-fieldname="timer"]')
.append(frappe.render_template("timesheet"));
frm.trigger("control_timer");
- })
+ });
},
hours: function(frm, cdt, cdn) {
- calculate_end_time(frm, cdt, cdn)
+ calculate_end_time(frm, cdt, cdn);
},
billing_hours: function(frm, cdt, cdn) {
- calculate_billing_costing_amount(frm, cdt, cdn)
+ calculate_billing_costing_amount(frm, cdt, cdn);
},
billing_rate: function(frm, cdt, cdn) {
- calculate_billing_costing_amount(frm, cdt, cdn)
+ calculate_billing_costing_amount(frm, cdt, cdn);
},
costing_rate: function(frm, cdt, cdn) {
- calculate_billing_costing_amount(frm, cdt, cdn)
+ calculate_billing_costing_amount(frm, cdt, cdn);
},
billable: function(frm, cdt, cdn) {
@@ -212,7 +216,7 @@ frappe.ui.form.on("Timesheet Detail", {
calculate_billing_costing_amount(frm, cdt, cdn);
}
}
- })
+ });
}
});
@@ -240,23 +244,23 @@ var calculate_end_time = function(frm, cdt, cdn) {
frm._setting_hours = true;
frappe.model.set_value(cdt, cdn, "to_time",
d.format(frappe.defaultDatetimeFormat)).then(() => {
- frm._setting_hours = false;
- });
+ frm._setting_hours = false;
+ });
}
}
-}
+};
var update_billing_hours = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
if(!child.billable) frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
-}
+};
var update_time_rates = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
if(!child.billable){
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
}
-}
+};
var calculate_billing_costing_amount = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
@@ -270,7 +274,7 @@ var calculate_billing_costing_amount = function(frm, cdt, cdn){
frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount);
frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount);
calculate_time_and_amount(frm);
-}
+};
var calculate_time_and_amount = function(frm) {
var tl = frm.doc.time_logs || [];
@@ -294,4 +298,17 @@ var calculate_time_and_amount = function(frm) {
frm.set_value("total_hours", total_working_hr);
frm.set_value("total_billable_amount", total_billable_amount);
frm.set_value("total_costing_amount", total_costing_amount);
-}
\ No newline at end of file
+};
+
+// set employee (and company) to the one that's currently logged in
+const set_employee_and_company = function(frm) {
+ const options = { user_id: frappe.session.user };
+ const fields = ['name', 'company'];
+ frappe.db.get_value('Employee', options, fields).then(({ message }) => {
+ if (message) {
+ // there is an employee with the currently logged in user_id
+ frm.set_value("employee", message.name);
+ frm.set_value("company", message.company);
+ }
+ });
+};
diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json
index 5ad2ab3a379..c29c11b7465 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.json
+++ b/erpnext/projects/doctype/timesheet/timesheet.json
@@ -739,7 +739,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "total_costing_amount",
- "fieldtype": "Float",
+ "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -805,7 +805,7 @@
"depends_on": "",
"description": "",
"fieldname": "total_billable_amount",
- "fieldtype": "Float",
+ "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -837,7 +837,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "total_billed_amount",
- "fieldtype": "Float",
+ "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -1000,7 +1000,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2019-01-30 11:28:18.518590",
+ "modified": "2019-03-05 21:54:02.654690",
"modified_by": "Administrator",
"module": "Projects",
"name": "Timesheet",
diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py
new file mode 100644
index 00000000000..80bf926a54c
--- /dev/null
+++ b/erpnext/projects/report/billing_summary.py
@@ -0,0 +1,147 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import time_diff_in_hours
+
+def get_columns():
+ return [
+ {
+ "label": _("Employee ID"),
+ "fieldtype": "Link",
+ "fieldname": "employee",
+ "options": "Employee",
+ "width": 300
+ },
+ {
+ "label": _("Employee Name"),
+ "fieldtype": "data",
+ "fieldname": "employee_name",
+ "hidden": 1,
+ "width": 200
+ },
+ {
+ "label": _("Timesheet"),
+ "fieldtype": "Link",
+ "fieldname": "timesheet",
+ "options": "Timesheet",
+ "width": 150
+ },
+ {
+ "label": _("Billable Hours"),
+ "fieldtype": "Float",
+ "fieldname": "total_billable_hours",
+ "width": 50
+ },
+ {
+ "label": _("Working Hours"),
+ "fieldtype": "Float",
+ "fieldname": "total_hours",
+ "width": 50
+ },
+ {
+ "label": _("Amount"),
+ "fieldtype": "Currency",
+ "fieldname": "amount",
+ "width": 100
+ }
+ ]
+
+def get_data(filters):
+ data = []
+ record = get_records(filters)
+
+ billable_hours_worked = 0
+ hours_worked = 0
+ working_cost = 0
+ for entries in record:
+ total_hours = 0
+ total_billable_hours = 0
+ total_amount = 0
+ entries_exists = False
+ timesheet_details = get_timesheet_details(filters, entries.name)
+
+ for activity in timesheet_details:
+ entries_exists = True
+ time_start = activity.from_time
+ time_end = frappe.utils.add_to_date(activity.from_time, hours=activity.hours)
+ from_date = frappe.utils.get_datetime(filters.from_date)
+ to_date = frappe.utils.get_datetime(filters.to_date)
+
+ if time_start <= from_date and time_end >= from_date:
+ total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
+ time_end, from_date, total_hours, total_billable_hours, total_amount)
+
+ billable_hours_worked += total_billable_hours
+ hours_worked += total_hours
+ working_cost += total_amount
+ elif time_start >= from_date and time_end >= to_date:
+ total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
+ to_date, time_start, total_hours, total_billable_hours, total_amount)
+
+ billable_hours_worked += total_billable_hours
+ hours_worked += total_hours
+ working_cost += total_amount
+ elif time_start >= from_date and time_end <= to_date:
+ total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
+ time_end, time_start, total_hours, total_billable_hours, total_amount)
+
+ billable_hours_worked += total_billable_hours
+ hours_worked += total_hours
+ working_cost += total_amount
+
+ row = {
+ "employee": entries.employee,
+ "employee_name": entries.employee_name,
+ "timesheet": entries.name,
+ "total_billable_hours": total_billable_hours,
+ "total_hours": total_hours,
+ "amount": total_amount
+ }
+
+ if entries_exists:
+ data.append(row)
+ entries_exists = False
+
+ total = {
+ "total_billable_hours": billable_hours_worked,
+ "total_hours": hours_worked,
+ "amount": working_cost
+ }
+ if billable_hours_worked !=0 or hours_worked !=0 or working_cost !=0:
+ data.append(total)
+ return data
+
+def get_records(filters):
+ record_filters = [
+ ["start_date", "<=", filters.to_date],
+ ["end_date", ">=", filters.from_date],
+ ["docstatus", "=", 1]
+ ]
+
+ if "employee" in filters:
+ record_filters.append(["employee", "=", filters.employee])
+
+ return frappe.get_all("Timesheet", filters=record_filters, fields=[" * "] )
+
+def get_billable_and_total_hours(activity, end, start, total_hours, total_billable_hours, total_amount):
+ total_hours += abs(time_diff_in_hours(end, start))
+ if activity.billable:
+ total_billable_hours += abs(time_diff_in_hours(end, start))
+ total_amount += total_billable_hours * activity.billing_rate
+ return total_hours, total_billable_hours, total_amount
+
+def get_timesheet_details(filters, parent):
+ timesheet_details_filter = {"parent": parent}
+
+ if "project" in filters:
+ timesheet_details_filter["project"] = filters.project
+
+ return frappe.get_all(
+ "Timesheet Detail",
+ filters = timesheet_details_filter,
+ fields=["*"]
+ )
diff --git a/erpnext/projects/report/employee_billing_summary/__init__.py b/erpnext/projects/report/employee_billing_summary/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
new file mode 100644
index 00000000000..13f49ed6bed
--- /dev/null
+++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Employee Billing Summary"] = {
+ "filters": [
+ {
+ fieldname: "employee",
+ label: __("Employee"),
+ fieldtype: "Link",
+ options: "Employee",
+ reqd: 1
+ },
+ {
+ fieldname:"from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
+ reqd: 1
+ },
+ {
+ fieldname:"to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_days(frappe.datetime.month_start(), -1),
+ reqd: 1
+ },
+ ]
+}
diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json
new file mode 100644
index 00000000000..433ebac5ddf
--- /dev/null
+++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json
@@ -0,0 +1,36 @@
+{
+ "add_total_row": 0,
+ "creation": "2019-03-08 15:08:19.929728",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2019-03-08 15:08:19.929728",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Employee Billing Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Timesheet",
+ "report_name": "Employee Billing Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Projects User"
+ },
+ {
+ "role": "HR User"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Employee"
+ },
+ {
+ "role": "Accounts User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py
new file mode 100644
index 00000000000..cd5ad7803a5
--- /dev/null
+++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from erpnext.projects.report.billing_summary import get_columns, get_data
+
+def execute(filters=None):
+ filters = frappe._dict(filters or {})
+ columns = get_columns()
+
+ data = get_data(filters)
+ return columns, data
\ No newline at end of file
diff --git a/erpnext/projects/report/project_billing_summary/__init__.py b/erpnext/projects/report/project_billing_summary/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js
new file mode 100644
index 00000000000..caac1d86b45
--- /dev/null
+++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Project Billing Summary"] = {
+ "filters": [
+ {
+ fieldname: "project",
+ label: __("Project"),
+ fieldtype: "Link",
+ options: "Project",
+ reqd: 1
+ },
+ {
+ fieldname:"from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
+ reqd: 1
+ },
+ {
+ fieldname:"to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
+ reqd: 1
+ },
+ ]
+}
diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.json b/erpnext/projects/report/project_billing_summary/project_billing_summary.json
new file mode 100644
index 00000000000..a3f91c802d5
--- /dev/null
+++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.json
@@ -0,0 +1,36 @@
+{
+ "add_total_row": 0,
+ "creation": "2019-03-11 16:22:39.460524",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2019-03-11 16:22:39.460524",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Project Billing Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Timesheet",
+ "report_name": "Project Billing Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Projects User"
+ },
+ {
+ "role": "HR User"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Employee"
+ },
+ {
+ "role": "Accounts User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.py b/erpnext/projects/report/project_billing_summary/project_billing_summary.py
new file mode 100644
index 00000000000..cd5ad7803a5
--- /dev/null
+++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from erpnext.projects.report.billing_summary import get_columns, get_data
+
+def execute(filters=None):
+ filters = frappe._dict(filters or {})
+ columns = get_columns()
+
+ data = get_data(filters)
+ return columns, data
\ No newline at end of file
diff --git a/erpnext/public/js/conf.js b/erpnext/public/js/conf.js
index 56e6bdb8dbe..477781bc805 100644
--- a/erpnext/public/js/conf.js
+++ b/erpnext/public/js/conf.js
@@ -27,8 +27,6 @@ $(document).bind('toolbar_setup', function() {
target="_blank">'+__('Documentation')+'').insertBefore($help_menu);
$('
'+__('User Forum')+'').insertBefore($help_menu);
- $('
'+__('Gitter Chat')+'').insertBefore($help_menu);
$('
'+__('Report an Issue')+'').insertBefore($help_menu);
diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js
index 1b8e0791323..1c12c352ed4 100644
--- a/erpnext/public/js/controllers/stock_controller.js
+++ b/erpnext/public/js/controllers/stock_controller.js
@@ -73,7 +73,7 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({
from_date: me.frm.doc.posting_date,
to_date: me.frm.doc.posting_date,
company: me.frm.doc.company,
- group_by: ""
+ group_by: "Group by Voucher (Consolidated)"
};
frappe.set_route("query-report", "General Ledger");
}, __("View"));
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index cf62af7b70e..168a727f270 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -314,14 +314,21 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
show_description(row_to_modify.idx, row_to_modify.item_code);
+ this.frm.from_barcode = true;
frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
item_code: data.item_code,
qty: (row_to_modify.qty || 0) + 1
});
- this.frm.refresh_field('items');
+ ['serial_no', 'batch_no', 'barcode'].forEach(field => {
+ if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) {
+ frappe.model.set_value(row_to_modify.doctype,
+ row_to_modify.name, field, data[field]);
+ }
+ });
+
+ scan_barcode_field.set_value('');
});
- scan_barcode_field.set_value('');
}
return false;
},
@@ -384,10 +391,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
// barcode cleared, remove item
d.item_code = "";
}
- this.item_code(doc, cdt, cdn, true);
+
+ this.frm.from_barcode = true;
+ this.item_code(doc, cdt, cdn);
},
- item_code: function(doc, cdt, cdn, from_barcode) {
+ item_code: function(doc, cdt, cdn) {
var me = this;
var item = frappe.get_doc(cdt, cdn);
var update_stock = 0, show_batch_dialog = 0;
@@ -400,9 +409,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
show_batch_dialog = 1;
}
// clear barcode if setting item (else barcode will take priority)
- if(!from_barcode) {
+ if(!this.frm.from_barcode) {
item.barcode = null;
}
+
+ this.frm.from_barcode = false;
if(item.item_code || item.barcode || item.serial_no) {
if(!this.validate_company_and_party()) {
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 64085a83cdf..389e25ece10 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -255,11 +255,16 @@ $.extend(erpnext.utils, {
// get valid options for tree based on user permission & locals dict
let unscrub_option = frappe.model.unscrub(option);
let user_permission = frappe.defaults.get_user_permissions();
+ let options;
+
if(user_permission && user_permission[unscrub_option]) {
- return user_permission[unscrub_option].map(perm => perm.doc);
+ options = user_permission[unscrub_option].map(perm => perm.doc);
} else {
- return $.map(locals[`:${unscrub_option}`], function(c) { return c.name; }).sort();
+ options = $.map(locals[`:${unscrub_option}`], function(c) { return c.name; }).sort();
}
+
+ // filter unique values, as there may be multiple user permissions for any value
+ return options.filter((value, index, self) => self.indexOf(value) === index);
},
get_tree_default: function(option) {
// set default for a field based on user permission
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index c5498c77700..34d5991e7e0 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -93,7 +93,7 @@ def add_print_formats():
def make_custom_fields(update=True):
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
- allow_on_submit=1, print_hide=1)
+ allow_on_submit=1, print_hide=1, fetch_if_empty=1)
invoice_gst_fields = [
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break',
insert_after='language', print_hide=1, collapsible=1),
@@ -243,6 +243,7 @@ def make_custom_fields(update=True):
'Purchase Order Item': [hsn_sac_field],
'Purchase Receipt Item': [hsn_sac_field],
'Purchase Invoice Item': [hsn_sac_field],
+ 'Material Request Item': [hsn_sac_field],
'Employee': [
dict(fieldname='ifsc_code', label='IFSC Code',
fieldtype='Data', insert_after='bank_ac_no', print_hide=1,
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index e7d0d5052ec..9747b245694 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -9,6 +9,8 @@ from erpnext.hr.utils import get_salary_assignment
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
def validate_gstin_for_india(doc, method):
+ if hasattr(doc, 'gst_state') and doc.gst_state:
+ doc.gst_state_number = state_numbers[doc.gst_state]
if not hasattr(doc, 'gstin') or not doc.gstin:
return
diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml
index 34053bdc99a..b725b964f2e 100644
--- a/erpnext/regional/italy/e-invoice.xml
+++ b/erpnext/regional/italy/e-invoice.xml
@@ -9,7 +9,7 @@
{%- if address.state_code %}
{{ address.state_code }}
{%- endif %}
-
{{ address.country_code|upper }}
+
{{ address.country_code }}
{%- endmacro %}
{%- macro render_discount_or_margin(item) -%}
@@ -32,15 +32,15 @@
{%- endmacro -%}
-
- {{ doc.company_address_data.country_code|upper or "IT" }}
+ {{ doc.company_address_data.country_code }}
{{ doc.company_fiscal_code or doc.company_tax_id | replace("IT","") }}
{{ doc.progressive_number }}
@@ -56,7 +56,7 @@
- {{ doc.company_address_data.country_code|upper or "IT"}}
+ {{ doc.company_address_data.country_code }}
{{ doc.company_tax_id | replace("IT","") }}
{%- if doc.company_fiscal_code %}
@@ -95,13 +95,12 @@
{{ doc.customer_data.last_name }}
{%- else %}
- {%- if doc.customer_data.is_public_administration %}
- {{ doc.customer_data.fiscal_code }}
- {%- else %}
- {{ doc.customer_address_data.country_code|upper or "IT" }}
+ {{ doc.customer_address_data.country_code }}
{{ doc.tax_id | replace("IT","") }}
+ {%- if doc.customer_data.fiscal_code %}
+ {{ doc.customer_data.fiscal_code }}
{%- endif %}
{{ doc.customer_name }}
@@ -128,22 +127,42 @@
{{ format_float(doc.stamp_duty) }}
{%- endif %}
- {{ format_float(doc.grand_total) }}
+ {%- if doc.discount_amount %}
+
+ {%- if doc.discount_amount > 0.0 %}
+ SC
+ {%- else %}
+ MG
+ {%- endif %}
+ {%- if doc.additional_discount_percentage > 0.0 %}
+ {{ format_float(doc.additional_discount_percentage) }}
+ {%- endif %}
+ {{ format_float(doc.discount_amount) }}
+
+ {%- endif %}
+ {{ format_float(doc.rounded_total or doc.grand_total) }}
VENDITA
- {%- if doc.po_no %}
-
- {{ doc.po_no }}
- {%- if doc.po_date %}
- {{ doc.po_date }}
- {%- endif %}
-
- {%- endif %}
+ {%- for po_no, po_date in doc.customer_po_data.items() %}
+
+ {{ po_no }}
+ {{ po_date }}
+
+ {%- endfor %}
{%- if doc.is_return and doc.return_against_unamended %}
{{ doc.return_against_unamended }}
{%- endif %}
+ {%- for row in doc.e_invoice_items %}
+ {%- if row.delivery_note %}
+
+ {{ row.delivery_note }}
+ {{ frappe.db.get_value('Delivery Note', row.delivery_note, 'posting_date') }}
+ {{ row.idx }}
+
+ {%- endif %}
+ {%- endfor %}
{%- if doc.shipping_address_data %}
@@ -160,7 +179,7 @@
CODICE
{{ item.item_code }}
- {{ item.description or item.item_name }}
+ {{ html2text(item.description or '') or item.item_name }}
{{ format_float(item.qty) }}
{{ item.stock_uom }}
{{ format_float(item.price_list_rate or item.rate) }}
@@ -199,7 +218,17 @@
{{ payment_term.mode_of_payment_code.split("-")[0] }}
{{ payment_term.due_date }}
{{ format_float(payment_term.payment_amount) }}
- {%- if payment_term.bank_account_iban %}{{ payment_term.bank_account_iban }}{%- endif %}
+ {%- if payment_term.bank_account_name %}
+ {{ payment_term.bank_account_name }}
+ {%- endif %}
+ {%- if payment_term.bank_account_iban %}
+ {{ payment_term.bank_account_iban }}
+ {{ payment_term.bank_account_iban[5:10] }}
+ {{ payment_term.bank_account_iban[10:15] }}
+ {%- endif %}
+ {%- if payment_term.bank_account_swift_number %}
+ {{ payment_term.bank_account_swift_number }}
+ {%- endif %}
{%- endfor %}
diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js
new file mode 100644
index 00000000000..586a52937b5
--- /dev/null
+++ b/erpnext/regional/italy/sales_invoice.js
@@ -0,0 +1,30 @@
+erpnext.setup_e_invoice_button = (doctype) => {
+ frappe.ui.form.on(doctype, {
+ refresh: (frm) => {
+ if(frm.doc.docstatus == 1) {
+ frm.add_custom_button('Generate E-Invoice', () => {
+ frm.call({
+ method: "erpnext.regional.italy.utils.generate_single_invoice",
+ args: {
+ docname: frm.doc.name
+ },
+ callback: function(r) {
+ frm.reload_doc();
+ if(r.message) {
+ var w = window.open(
+ frappe.urllib.get_full_url(
+ "/api/method/erpnext.regional.italy.utils.download_e_invoice_file?"
+ + "file_name=" + r.message
+ )
+ )
+ if (!w) {
+ frappe.msgprint(__("Please enable pop-ups")); return;
+ }
+ }
+ }
+ });
+ });
+ }
+ }
+ });
+};
diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py
index 3e9d7a29e50..1526d6f62f0 100644
--- a/erpnext/regional/italy/setup.py
+++ b/erpnext/regional/italy/setup.py
@@ -10,12 +10,12 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.regional.italy import fiscal_regimes, tax_exemption_reasons, mode_of_payment_codes, vat_collectability_options
def setup(company=None, patch=True):
- make_custom_fields()
- setup_report()
+ make_custom_fields()
+ setup_report()
def make_custom_fields(update=True):
- invoice_item_fields = [
- dict(fieldname='tax_rate', label='Tax Rate',
+ invoice_item_fields = [
+ dict(fieldname='tax_rate', label='Tax Rate',
fieldtype='Float', insert_after='description',
print_hide=1, hidden=1, read_only=1),
dict(fieldname='tax_amount', label='Tax Amount',
@@ -24,135 +24,154 @@ def make_custom_fields(update=True):
dict(fieldname='total_amount', label='Total Amount',
fieldtype='Currency', insert_after='tax_amount',
print_hide=1, hidden=1, read_only=1, options="currency")
- ]
+ ]
- custom_fields = {
- 'Company': [
- dict(fieldname='sb_e_invoicing', label='E-Invoicing',
- fieldtype='Section Break', insert_after='date_of_establishment', print_hide=1),
- dict(fieldname='fiscal_regime', label='Fiscal Regime',
- fieldtype='Select', insert_after='sb_e_invoicing', print_hide=1,
- options="\n".join(map(lambda x: x.decode('utf-8'), fiscal_regimes))),
- dict(fieldname='fiscal_code', label='Fiscal Code', fieldtype='Data', insert_after='fiscal_regime', print_hide=1,
- description=_("Applicable if the company is an Individual or a Proprietorship")),
- dict(fieldname='vat_collectability', label='VAT Collectability',
- fieldtype='Select', insert_after='fiscal_code', print_hide=1,
- options="\n".join(map(lambda x: x.decode('utf-8'), vat_collectability_options))),
- dict(fieldname='cb_e_invoicing1', fieldtype='Column Break', insert_after='vat_collectability', print_hide=1),
- dict(fieldname='registrar_office_province', label='Province of the Registrar Office',
- fieldtype='Data', insert_after='cb_e_invoicing1', print_hide=1, length=2),
- dict(fieldname='registration_number', label='Registration Number',
- fieldtype='Data', insert_after='registrar_office_province', print_hide=1, length=20),
- dict(fieldname='share_capital_amount', label='Share Capital',
- fieldtype='Currency', insert_after='registration_number', print_hide=1,
- description=_('Applicable if the company is SpA, SApA or SRL')),
- dict(fieldname='no_of_members', label='No of Members',
- fieldtype='Select', insert_after='share_capital_amount', print_hide=1,
- options="\nSU-Socio Unico\nSM-Piu Soci", description=_("Applicable if the company is a limited liability company")),
- dict(fieldname='liquidation_state', label='Liquidation State',
- fieldtype='Select', insert_after='no_of_members', print_hide=1,
- options="\nLS-In Liquidazione\nLN-Non in Liquidazione")
- ],
- 'Sales Taxes and Charges': [
- dict(fieldname='tax_exemption_reason', label='Tax Exemption Reason',
- fieldtype='Select', insert_after='included_in_print_rate', print_hide=1,
- depends_on='eval:doc.charge_type!="Actual" && doc.rate==0.0',
- options="\n" + "\n".join(map(lambda x: x.decode('utf-8'), tax_exemption_reasons))),
- dict(fieldname='tax_exemption_law', label='Tax Exempt Under',
- fieldtype='Text', insert_after='tax_exemption_reason', print_hide=1,
- depends_on='eval:doc.charge_type!="Actual" && doc.rate==0.0')
- ],
- 'Customer': [
- dict(fieldname='fiscal_code', label='Fiscal Code', fieldtype='Data', insert_after='tax_id', print_hide=1),
- dict(fieldname='recipient_code', label='Recipient Code',
- fieldtype='Data', insert_after='fiscal_code', print_hide=1, default="0000000"),
- dict(fieldname='pec', label='Recipient PEC',
- fieldtype='Data', insert_after='fiscal_code', print_hide=1),
- dict(fieldname='is_public_administration', label='Is Public Administration',
- fieldtype='Check', insert_after='is_internal_customer', print_hide=1,
- description=_("Set this if the customer is a Public Administration company."),
- depends_on='eval:doc.customer_type=="Company"'),
- dict(fieldname='first_name', label='First Name', fieldtype='Data',
- insert_after='salutation', print_hide=1, depends_on='eval:doc.customer_type!="Company"'),
- dict(fieldname='last_name', label='Last Name', fieldtype='Data',
- insert_after='first_name', print_hide=1, depends_on='eval:doc.customer_type!="Company"')
- ],
- 'Mode of Payment': [
- dict(fieldname='mode_of_payment_code', label='Code',
- fieldtype='Select', insert_after='included_in_print_rate', print_hide=1,
- options="\n".join(map(lambda x: x.decode('utf-8'), mode_of_payment_codes)))
- ],
- 'Payment Schedule': [
- dict(fieldname='mode_of_payment_code', label='Code',
- fieldtype='Select', insert_after='mode_of_payment', print_hide=1,
- options="\n".join(map(lambda x: x.decode('utf-8'), mode_of_payment_codes)),
- fetch_from="mode_of_payment.mode_of_payment_code", read_only=1),
- dict(fieldname='bank_account', label='Bank Account',
- fieldtype='Link', insert_after='mode_of_payment_code', print_hide=1,
- options="Bank Account"),
- dict(fieldname='bank_account_name', label='Bank Account Name',
- fieldtype='Data', insert_after='bank_account', print_hide=1,
- fetch_from="bank_account.account_name", read_only=1),
- dict(fieldname='bank_account_no', label='Bank Account No',
- fieldtype='Data', insert_after='bank_account_name', print_hide=1,
- fetch_from="bank_account.bank_account_no", read_only=1),
- dict(fieldname='bank_account_iban', label='IBAN',
- fieldtype='Data', insert_after='bank_account_name', print_hide=1,
- fetch_from="bank_account.iban", read_only=1),
- ],
- "Sales Invoice": [
- dict(fieldname='vat_collectability', label='VAT Collectability',
- fieldtype='Select', insert_after='taxes_and_charges', print_hide=1,
- options="\n".join(map(lambda x: x.decode('utf-8'), vat_collectability_options)),
- fetch_from="company.vat_collectability"),
- dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing',
- fieldtype='Section Break', insert_after='pos_total_qty', print_hide=1),
- dict(fieldname='company_tax_id', label='Company Tax ID',
- fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
- fetch_from="company.tax_id"),
- dict(fieldname='company_fiscal_code', label='Company Fiscal Code',
- fieldtype='Data', insert_after='company_tax_id', print_hide=1, read_only=1,
- fetch_from="company.fiscal_code"),
- dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime',
- fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1,
- fetch_from="company.fiscal_regime"),
- dict(fieldname='cb_e_invoicing_reference', fieldtype='Column Break',
- insert_after='company_fiscal_regime', print_hide=1),
- dict(fieldname='customer_fiscal_code', label='Customer Fiscal Code',
- fieldtype='Data', insert_after='cb_e_invoicing_reference', read_only=1,
- fetch_from="customer.fiscal_code"),
- ],
- 'Purchase Invoice Item': invoice_item_fields,
+ customer_po_fields = [
+ dict(fieldname='customer_po_details', label='Customer PO',
+ fieldtype='Section Break', insert_after='image'),
+ dict(fieldname='customer_po_no', label='Customer PO No',
+ fieldtype='Data', insert_after='customer_po_details',
+ fetch_from = 'sales_order.po_no',
+ print_hide=1, allow_on_submit=1, fetch_if_empty= 1, read_only=1, no_copy=1),
+ dict(fieldname='customer_po_clm_brk', label='',
+ fieldtype='Column Break', insert_after='customer_po_no',
+ print_hide=1, read_only=1),
+ dict(fieldname='customer_po_date', label='Customer PO Date',
+ fieldtype='Date', insert_after='customer_po_clm_brk',
+ fetch_from = 'sales_order.po_date',
+ print_hide=1, allow_on_submit=1, fetch_if_empty= 1, read_only=1, no_copy=1)
+ ]
+
+ custom_fields = {
+ 'Company': [
+ dict(fieldname='sb_e_invoicing', label='E-Invoicing',
+ fieldtype='Section Break', insert_after='date_of_establishment', print_hide=1),
+ dict(fieldname='fiscal_regime', label='Fiscal Regime',
+ fieldtype='Select', insert_after='sb_e_invoicing', print_hide=1,
+ options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), fiscal_regimes))),
+ dict(fieldname='fiscal_code', label='Fiscal Code', fieldtype='Data', insert_after='fiscal_regime', print_hide=1,
+ description=_("Applicable if the company is an Individual or a Proprietorship")),
+ dict(fieldname='vat_collectability', label='VAT Collectability',
+ fieldtype='Select', insert_after='fiscal_code', print_hide=1,
+ options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), vat_collectability_options))),
+ dict(fieldname='cb_e_invoicing1', fieldtype='Column Break', insert_after='vat_collectability', print_hide=1),
+ dict(fieldname='registrar_office_province', label='Province of the Registrar Office',
+ fieldtype='Data', insert_after='cb_e_invoicing1', print_hide=1, length=2),
+ dict(fieldname='registration_number', label='Registration Number',
+ fieldtype='Data', insert_after='registrar_office_province', print_hide=1, length=20),
+ dict(fieldname='share_capital_amount', label='Share Capital',
+ fieldtype='Currency', insert_after='registration_number', print_hide=1,
+ description=_('Applicable if the company is SpA, SApA or SRL')),
+ dict(fieldname='no_of_members', label='No of Members',
+ fieldtype='Select', insert_after='share_capital_amount', print_hide=1,
+ options="\nSU-Socio Unico\nSM-Piu Soci", description=_("Applicable if the company is a limited liability company")),
+ dict(fieldname='liquidation_state', label='Liquidation State',
+ fieldtype='Select', insert_after='no_of_members', print_hide=1,
+ options="\nLS-In Liquidazione\nLN-Non in Liquidazione")
+ ],
+ 'Sales Taxes and Charges': [
+ dict(fieldname='tax_exemption_reason', label='Tax Exemption Reason',
+ fieldtype='Select', insert_after='included_in_print_rate', print_hide=1,
+ depends_on='eval:doc.charge_type!="Actual" && doc.rate==0.0',
+ options="\n" + "\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), tax_exemption_reasons))),
+ dict(fieldname='tax_exemption_law', label='Tax Exempt Under',
+ fieldtype='Text', insert_after='tax_exemption_reason', print_hide=1,
+ depends_on='eval:doc.charge_type!="Actual" && doc.rate==0.0')
+ ],
+ 'Customer': [
+ dict(fieldname='fiscal_code', label='Fiscal Code', fieldtype='Data', insert_after='tax_id', print_hide=1),
+ dict(fieldname='recipient_code', label='Recipient Code',
+ fieldtype='Data', insert_after='fiscal_code', print_hide=1, default="0000000"),
+ dict(fieldname='pec', label='Recipient PEC',
+ fieldtype='Data', insert_after='fiscal_code', print_hide=1),
+ dict(fieldname='is_public_administration', label='Is Public Administration',
+ fieldtype='Check', insert_after='is_internal_customer', print_hide=1,
+ description=_("Set this if the customer is a Public Administration company."),
+ depends_on='eval:doc.customer_type=="Company"'),
+ dict(fieldname='first_name', label='First Name', fieldtype='Data',
+ insert_after='salutation', print_hide=1, depends_on='eval:doc.customer_type!="Company"'),
+ dict(fieldname='last_name', label='Last Name', fieldtype='Data',
+ insert_after='first_name', print_hide=1, depends_on='eval:doc.customer_type!="Company"')
+ ],
+ 'Mode of Payment': [
+ dict(fieldname='mode_of_payment_code', label='Code',
+ fieldtype='Select', insert_after='included_in_print_rate', print_hide=1,
+ options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), mode_of_payment_codes)))
+ ],
+ 'Payment Schedule': [
+ dict(fieldname='mode_of_payment_code', label='Code',
+ fieldtype='Select', insert_after='mode_of_payment', print_hide=1,
+ options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), mode_of_payment_codes)),
+ fetch_from="mode_of_payment.mode_of_payment_code", read_only=1),
+ dict(fieldname='bank_account', label='Bank Account',
+ fieldtype='Link', insert_after='mode_of_payment_code', print_hide=1,
+ options="Bank Account"),
+ dict(fieldname='bank_account_name', label='Bank Name',
+ fieldtype='Data', insert_after='bank_account', print_hide=1,
+ fetch_from="bank_account.bank", read_only=1),
+ dict(fieldname='bank_account_no', label='Bank Account No',
+ fieldtype='Data', insert_after='bank_account_name', print_hide=1,
+ fetch_from="bank_account.bank_account_no", read_only=1),
+ dict(fieldname='bank_account_iban', label='IBAN',
+ fieldtype='Data', insert_after='bank_account_name', print_hide=1,
+ fetch_from="bank_account.iban", read_only=1),
+ dict(fieldname='bank_account_swift_number', label='Swift Code (BIC)',
+ fieldtype='Data', insert_after='bank_account_iban', print_hide=1,
+ fetch_from="bank_account.swift_number", read_only=1),
+ ],
+ "Sales Invoice": [
+ dict(fieldname='vat_collectability', label='VAT Collectability',
+ fieldtype='Select', insert_after='taxes_and_charges', print_hide=1,
+ options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), vat_collectability_options)),
+ fetch_from="company.vat_collectability"),
+ dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing',
+ fieldtype='Section Break', insert_after='pos_total_qty', print_hide=1),
+ dict(fieldname='company_tax_id', label='Company Tax ID',
+ fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
+ fetch_from="company.tax_id"),
+ dict(fieldname='company_fiscal_code', label='Company Fiscal Code',
+ fieldtype='Data', insert_after='company_tax_id', print_hide=1, read_only=1,
+ fetch_from="company.fiscal_code"),
+ dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime',
+ fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1,
+ fetch_from="company.fiscal_regime"),
+ dict(fieldname='cb_e_invoicing_reference', fieldtype='Column Break',
+ insert_after='company_fiscal_regime', print_hide=1),
+ dict(fieldname='customer_fiscal_code', label='Customer Fiscal Code',
+ fieldtype='Data', insert_after='cb_e_invoicing_reference', read_only=1,
+ fetch_from="customer.fiscal_code"),
+ ],
+ 'Purchase Invoice Item': invoice_item_fields,
'Sales Order Item': invoice_item_fields,
'Delivery Note Item': invoice_item_fields,
- 'Sales Invoice Item': invoice_item_fields,
+ 'Sales Invoice Item': invoice_item_fields + customer_po_fields,
'Quotation Item': invoice_item_fields,
'Purchase Order Item': invoice_item_fields,
'Purchase Receipt Item': invoice_item_fields,
'Supplier Quotation Item': invoice_item_fields,
- 'Address': [
- dict(fieldname='country_code', label='Country Code',
- fieldtype='Data', insert_after='country', print_hide=1, read_only=1,
- fetch_from="country.code"),
- dict(fieldname='state_code', label='State Code',
- fieldtype='Data', insert_after='state', print_hide=1)
- ]
- }
+ 'Address': [
+ dict(fieldname='country_code', label='Country Code',
+ fieldtype='Data', insert_after='country', print_hide=1, read_only=0,
+ fetch_from="country.code"),
+ dict(fieldname='state_code', label='State Code',
+ fieldtype='Data', insert_after='state', print_hide=1)
+ ]
+ }
- create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch, update=update)
+ create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch, update=update)
def setup_report():
- report_name = 'Electronic Invoice Register'
+ report_name = 'Electronic Invoice Register'
- frappe.db.sql(""" update `tabReport` set disabled = 0 where
- name = %s """, report_name)
+ frappe.db.sql(""" update `tabReport` set disabled = 0 where
+ name = %s """, report_name)
- if not frappe.db.get_value('Custom Role', dict(report=report_name)):
- frappe.get_doc(dict(
- doctype='Custom Role',
- report=report_name,
- roles= [
- dict(role='Accounts User'),
- dict(role='Accounts Manager')
- ]
- )).insert()
+ if not frappe.db.get_value('Custom Role', dict(report=report_name)):
+ frappe.get_doc(dict(
+ doctype='Custom Role',
+ report=report_name,
+ roles= [
+ dict(role='Accounts User'),
+ dict(role='Accounts Manager')
+ ]
+ )).insert()
diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py
index c86ad78cbc9..5fae858efeb 100644
--- a/erpnext/regional/italy/utils.py
+++ b/erpnext/regional/italy/utils.py
@@ -4,6 +4,7 @@ import frappe, json, os
from frappe.utils import flt, cstr
from erpnext.controllers.taxes_and_totals import get_itemised_tax
from frappe import _
+from six import string_types
from frappe.utils.file_manager import save_file, remove_file
from frappe.desk.form.load import get_attachments
from erpnext.regional.italy import state_codes
@@ -82,6 +83,14 @@ def prepare_invoice(invoice, progressive_number):
if item.tax_rate == 0.0 and item.tax_amount == 0.0:
item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"]
+ customer_po_data = {}
+ for d in invoice.e_invoice_items:
+ if (d.customer_po_no and d.customer_po_date
+ and d.customer_po_no not in customer_po_data):
+ customer_po_data[d.customer_po_no] = d.customer_po_date
+
+ invoice.customer_po_data = customer_po_data
+
return invoice
def get_conditions(filters):
@@ -134,6 +143,7 @@ def get_invoice_summary(items, taxes):
idx=len(items)+1,
item_code=reference_row.description,
item_name=reference_row.description,
+ description=reference_row.description,
rate=reference_row.tax_amount,
qty=1.0,
amount=reference_row.tax_amount,
@@ -142,7 +152,7 @@ def get_invoice_summary(items, taxes):
tax_amount=(reference_row.tax_amount * tax.rate) / 100,
net_amount=reference_row.tax_amount,
taxable_amount=reference_row.tax_amount,
- item_tax_rate="{}",
+ item_tax_rate={tax.account_head: tax.rate},
charges=True
)
)
@@ -150,10 +160,16 @@ def get_invoice_summary(items, taxes):
#Check item tax rates if tax rate is zero.
if tax.rate == 0:
for item in items:
- item_tax_rate = json.loads(item.item_tax_rate)
- if tax.account_head in item_tax_rate:
+ item_tax_rate = item.item_tax_rate
+ if isinstance(item.item_tax_rate, string_types):
+ item_tax_rate = json.loads(item.item_tax_rate)
+
+ if item_tax_rate and tax.account_head in item_tax_rate:
key = cstr(item_tax_rate[tax.account_head])
- summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0, "tax_exemption_reason": "", "tax_exemption_law": ""})
+ if key not in summary_data:
+ summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0,
+ "tax_exemption_reason": "", "tax_exemption_law": ""})
+
summary_data[key]["tax_amount"] += item.tax_amount
summary_data[key]["taxable_amount"] += item.net_amount
if key == "0.0":
@@ -189,28 +205,41 @@ def sales_invoice_validate(doc):
if not doc.company_address:
frappe.throw(_("Please set an Address on the Company '%s'" % doc.company), title=_("E-Invoicing Information Missing"))
else:
- validate_address(doc.company_address, "Company")
+ validate_address(doc.company_address)
+ company_fiscal_regime = frappe.get_cached_value("Company", doc.company, 'fiscal_regime')
+ if not company_fiscal_regime:
+ frappe.throw(_("Fiscal Regime is mandatory, kindly set the fiscal regime in the company {0}")
+ .format(doc.company))
+ else:
+ doc.company_fiscal_regime = company_fiscal_regime
+
+ doc.company_tax_id = frappe.get_cached_value("Company", doc.company, 'tax_id')
+ doc.company_fiscal_code = frappe.get_cached_value("Company", doc.company, 'fiscal_code')
if not doc.company_tax_id and not doc.company_fiscal_code:
frappe.throw(_("Please set either the Tax ID or Fiscal Code on Company '%s'" % doc.company), title=_("E-Invoicing Information Missing"))
#Validate customer details
- customer_type, is_public_administration = frappe.db.get_value("Customer", doc.customer, ["customer_type", "is_public_administration"])
- if customer_type == _("Individual"):
+ customer = frappe.get_doc("Customer", doc.customer)
+
+ if customer.customer_type == _("Individual"):
+ doc.customer_fiscal_code = customer.fiscal_code
if not doc.customer_fiscal_code:
frappe.throw(_("Please set Fiscal Code for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
else:
- if is_public_administration:
+ if customer.is_public_administration:
+ doc.customer_fiscal_code = customer.fiscal_code
if not doc.customer_fiscal_code:
frappe.throw(_("Please set Fiscal Code for the public administration '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
else:
+ doc.tax_id = customer.tax_id
if not doc.tax_id:
frappe.throw(_("Please set Tax ID for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
if not doc.customer_address:
frappe.throw(_("Please set the Customer Address"), title=_("E-Invoicing Information Missing"))
else:
- validate_address(doc.customer_address, "Customer")
+ validate_address(doc.customer_address)
if not len(doc.taxes):
frappe.throw(_("Please set at least one row in the Taxes and Charges Table"), title=_("E-Invoicing Information Missing"))
@@ -220,6 +249,10 @@ def sales_invoice_validate(doc):
frappe.throw(_("Row {0}: Please set at Tax Exemption Reason in Sales Taxes and Charges".format(row.idx)),
title=_("E-Invoicing Information Missing"))
+ for schedule in doc.payment_schedule:
+ if schedule.mode_of_payment and not schedule.mode_of_payment_code:
+ schedule.mode_of_payment_code = frappe.get_cached_value('Mode of Payment',
+ schedule.mode_of_payment, 'mode_of_payment_code')
#Ensure payment details are valid for e-invoice.
def sales_invoice_on_submit(doc, method):
@@ -241,14 +274,34 @@ def sales_invoice_on_submit(doc, method):
prepare_and_attach_invoice(doc)
-def prepare_and_attach_invoice(doc):
- progressive_name, progressive_number = get_progressive_name_and_number(doc)
+def prepare_and_attach_invoice(doc, replace=False):
+ progressive_name, progressive_number = get_progressive_name_and_number(doc, replace)
invoice = prepare_invoice(doc, progressive_number)
invoice_xml = frappe.render_template('erpnext/regional/italy/e-invoice.xml', context={"doc": invoice}, is_path=True)
+ invoice_xml = invoice_xml.replace("&", "&")
xml_filename = progressive_name + ".xml"
- save_file(xml_filename, invoice_xml, dt=doc.doctype, dn=doc.name, is_private=True)
+ return save_file(xml_filename, invoice_xml, dt=doc.doctype, dn=doc.name, is_private=True)
+
+@frappe.whitelist()
+def generate_single_invoice(docname):
+ doc = frappe.get_doc("Sales Invoice", docname)
+
+
+ e_invoice = prepare_and_attach_invoice(doc, True)
+
+ return e_invoice.file_name
+
+@frappe.whitelist()
+def download_e_invoice_file(file_name):
+ content = None
+ with open(frappe.get_site_path('private', 'files', file_name), "r") as f:
+ content = f.read()
+
+ frappe.local.response.filename = file_name
+ frappe.local.response.filecontent = content
+ frappe.local.response.type = "download"
#Delete e-invoice attachment on cancel.
def sales_invoice_on_cancel(doc, method):
@@ -268,18 +321,19 @@ def get_e_invoice_attachments(invoice):
company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id
for attachment in attachments:
- if attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"):
+ if attachment.file_name and attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"):
out.append(attachment)
return out
-def validate_address(address_name, address_context):
- pincode, city = frappe.db.get_value("Address", address_name, ["pincode", "city"])
- if not pincode:
- frappe.throw(_("Please set pin code on %s Address" % address_context), title=_("E-Invoicing Information Missing"))
- if not city:
- frappe.throw(_("Please set city on %s Address" % address_context), title=_("E-Invoicing Information Missing"))
+def validate_address(address_name):
+ fields = ["pincode", "city", "country_code"]
+ data = frappe.get_cached_value("Address", address_name, fields, as_dict=1) or {}
+ for field in fields:
+ if not data.get(field):
+ frappe.throw(_("Please set {0} for address {1}".format(field.replace('-',''), address_name)),
+ title=_("E-Invoicing Information Missing"))
def get_unamended_name(doc):
attributes = ["naming_series", "amended_from"]
@@ -292,7 +346,13 @@ def get_unamended_name(doc):
else:
return doc.name
-def get_progressive_name_and_number(doc):
+def get_progressive_name_and_number(doc, replace=False):
+ if replace:
+ for attachment in get_e_invoice_attachments(doc):
+ remove_file(attachment.name, attached_to_doctype=doc.doctype, attached_to_name=doc.name)
+ filename = attachment.file_name.split(".xml")[0]
+ return filename, filename.split("_")[1]
+
company_tax_id = doc.company_tax_id if doc.company_tax_id.startswith("IT") else "IT" + doc.company_tax_id
progressive_name = frappe.model.naming.make_autoname(company_tax_id + "_.#####")
progressive_number = progressive_name.split("_")[1]
@@ -300,8 +360,17 @@ def get_progressive_name_and_number(doc):
return progressive_name, progressive_number
def set_state_code(doc, method):
+ if doc.get('country_code'):
+ doc.country_code = doc.country_code.upper()
+
+ if not doc.get('state'):
+ return
+
if not (hasattr(doc, "state_code") and doc.country in ["Italy", "Italia", "Italian Republic", "Repubblica Italiana"]):
return
state_codes_lower = {key.lower():value for key,value in state_codes.items()}
- doc.state_code = state_codes_lower.get(doc.get('state','').lower())
+
+ state = doc.get('state','').lower()
+ if state_codes_lower.get(state):
+ doc.state_code = state_codes_lower.get(state)
diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py
index 5fbf7007f4c..6b8d3f0139e 100644
--- a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py
+++ b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py
@@ -87,8 +87,8 @@ def get_gl_entries(filters):
left join `tabPurchase Invoice` pur on gl.voucher_no = pur.name
left join `tabJournal Entry` jnl on gl.voucher_no = jnl.name
left join `tabPayment Entry` pay on gl.voucher_no = pay.name
- left join `tabCustomer` cus on gl.party = cus.customer_name
- left join `tabSupplier` sup on gl.party = sup.supplier_name
+ left join `tabCustomer` cus on gl.party = cus.name
+ left join `tabSupplier` sup on gl.party = sup.name
where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s
{group_by_condition}
order by GlPostDate, voucher_no"""\
diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py
index 3c8328b1074..250659e54da 100644
--- a/erpnext/regional/united_arab_emirates/setup.py
+++ b/erpnext/regional/united_arab_emirates/setup.py
@@ -28,24 +28,24 @@ def make_custom_fields():
purchase_invoice_fields = [
dict(fieldname='company_trn', label='Company TRN',
fieldtype='Read Only', insert_after='shipping_address',
- options='company.tax_id', print_hide=1),
+ fetch_from='company.tax_id', print_hide=1),
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
fieldtype='Read Only', insert_after='supplier_name',
- options='supplier.supplier_name_in_arabic', print_hide=1)
+ fetch_from='supplier.supplier_name_in_arabic', print_hide=1)
]
sales_invoice_fields = [
dict(fieldname='company_trn', label='Company TRN',
fieldtype='Read Only', insert_after='company_address',
- options='company.tax_id', print_hide=1),
+ fetch_from='company.tax_id', print_hide=1),
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
fieldtype='Read Only', insert_after='customer_name',
- options='customer.customer_name_in_arabic', print_hide=1),
+ fetch_from='customer.customer_name_in_arabic', print_hide=1),
]
invoice_item_fields = [
dict(fieldname='tax_code', label='Tax Code',
- fieldtype='Read Only', options='item_code.tax_code', insert_after='description',
+ fieldtype='Read Only', fetch_from='item_code.tax_code', insert_after='description',
allow_on_submit=1, print_hide=1),
dict(fieldname='tax_rate', label='Tax Rate',
fieldtype='Float', insert_after='tax_code',
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index b589cdeaa43..8e81c13357c 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -388,6 +388,7 @@ class SalesOrder(SellingController):
items.append(dict(
name= i.name,
item_code= i.item_code,
+ description= i.description,
bom = bom,
warehouse = i.warehouse,
pending_qty = pending_qty,
@@ -398,6 +399,7 @@ class SalesOrder(SellingController):
items.append(dict(
name= i.name,
item_code= i.item_code,
+ description= i.description,
bom = '',
warehouse = i.warehouse,
pending_qty = pending_qty,
@@ -610,7 +612,8 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
def postprocess(source, target):
set_missing_values(source, target)
#Get the advance paid Journal Entries in Sales Invoice Advance
- target.set_advances()
+ if target.get("allocate_advances_automatically"):
+ target.set_advances()
def set_missing_values(source, target):
target.is_pos = 0
@@ -901,7 +904,8 @@ def make_work_orders(items, sales_order, company, project=None):
sales_order=sales_order,
sales_order_item=i['sales_order_item'],
project=project,
- fg_warehouse=i['warehouse']
+ fg_warehouse=i['warehouse'],
+ description=i['description']
)).insert()
work_order.set_work_order_operations()
work_order.save()
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 0eb19e3417c..f270938ad3a 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -573,7 +573,8 @@ class TestSalesOrder(unittest.TestCase):
"item_code": item.get("item_code"),
"pending_qty": item.get("pending_qty"),
"sales_order_item": item.get("sales_order_item"),
- "bom": item.get("bom")
+ "bom": item.get("bom"),
+ "description": item.get("description")
})
so_item_name[item.get("sales_order_item")]= item.get("pending_qty")
make_work_orders(json.dumps({"items":po_items}), so.name, so.company)
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index c54430fd560..26cadae4ef9 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -446,16 +446,15 @@ erpnext.pos.PointOfSale = class PointOfSale {
}
setup_company() {
- this.company = frappe.sys_defaults.company;
return new Promise(resolve => {
- if(!this.company) {
+ if(!frappe.sys_defaults.company) {
frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link",
label: __("Select Company"), reqd: 1}, (data) => {
this.company = data.company;
resolve(this.company);
}, __("Select Company"));
} else {
- resolve(this.company);
+ resolve();
}
})
}
@@ -509,7 +508,9 @@ erpnext.pos.PointOfSale = class PointOfSale {
}
set_pos_profile_data() {
- this.frm.doc.company = this.company;
+ if (this.company) {
+ this.frm.doc.company = this.company;
+ }
return new Promise(resolve => {
return this.frm.call({
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 56d91518d8c..e088679917b 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -39,7 +39,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
if display_items_in_stock == 0:
- res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image,
+ res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image, i.idx as idx,
i.is_stock_item, item_det.price_list_rate, item_det.currency
from `tabItem` i LEFT JOIN
(select item_code, price_list_rate, currency from
@@ -49,7 +49,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
- and {condition} limit {start}, {page_length}""".format(start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition),
+ and {condition} order by idx desc limit {start}, {page_length}""".format(start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition),
{
'item_code': item_code,
'price_list': price_list
@@ -60,7 +60,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
}
elif display_items_in_stock == 1:
- query = """select i.name as item_code, i.item_name, i.image as item_image,
+ query = """select i.name as item_code, i.item_name, i.image as item_image, i.idx as idx,
i.is_stock_item, item_det.price_list_rate, item_det.currency
from `tabItem` i LEFT JOIN
(select item_code, price_list_rate, currency from
@@ -79,7 +79,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
- and {condition} limit {start}, {page_length}""".format
+ and {condition} order by idx desc limit {start}, {page_length}""".format
(start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition),
{
'item_code': item_code,
diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.py b/erpnext/selling/report/address_and_contacts/address_and_contacts.py
index eb242d0a737..a9e43034b48 100644
--- a/erpnext/selling/report/address_and_contacts/address_and_contacts.py
+++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.py
@@ -102,8 +102,7 @@ def get_party_details(party_type, party_list, doctype, party_details):
records = frappe.get_list(doctype, filters=filters, fields=fields, as_list=True)
for d in records:
details = party_details.get(d[0])
- if details:
- details.setdefault(frappe.scrub(doctype), []).append(d[1:])
+ details.setdefault(frappe.scrub(doctype), []).append(d[1:])
return party_details
diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js
index 67051c8b5e5..b236151bad9 100644
--- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js
+++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js
@@ -33,7 +33,8 @@ frappe.query_reports["Sales Person-wise Transaction Summary"] = {
label: __("Company"),
fieldtype: "Link",
options: "Company",
- default: frappe.defaults.get_user_default("Company")
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
},
{
fieldname:"item_group",
diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
index db11ae95c2f..41c7f765175 100644
--- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
+++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
@@ -15,7 +15,7 @@ def execute(filters=None):
item_details = get_item_details()
data = []
- company_currency = get_company_currency(filters["company"])
+ company_currency = get_company_currency(filters.get("company"))
for d in entries:
if d.stock_qty > 0 or filters.get('show_return_entries', 0):
diff --git a/erpnext/setup/doctype/brand/test_records.json b/erpnext/setup/doctype/brand/test_records.json
index d2a4ad4e238..e4f892eef50 100644
--- a/erpnext/setup/doctype/brand/test_records.json
+++ b/erpnext/setup/doctype/brand/test_records.json
@@ -1,6 +1,6 @@
[
{
- "brand": "_Test Brand",
+ "brand": "_Test Brand",
"doctype": "Brand"
}
]
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 70e047a4e80..aff4baf9314 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -16,6 +16,12 @@ frappe.ui.form.on("Company", {
filters: {"is_additional_component": 1}
}
});
+
+ frm.set_query("parent_company", function() {
+ return {
+ filters: {"is_group": 1}
+ }
+ });
},
company_name: function(frm) {
@@ -28,6 +34,13 @@ frappe.ui.form.on("Company", {
}
},
+ parent_company: function(frm) {
+ var bool = frm.doc.parent_company ? true : false;
+ frm.set_value('create_chart_of_accounts_based_on', bool ? "Existing Company" : "");
+ frm.set_value('existing_company', bool ? frm.doc.parent_company : "");
+ disbale_coa_fields(frm, bool);
+ },
+
date_of_commencement: function(frm) {
if(frm.doc.date_of_commencement date_sub(curdate(), interval 1 year)
+ st.sales_person = %s and st.parent = dt.name and dt.transaction_date > date_sub(curdate(), interval 1 year)
group by dt.transaction_date ''', name)))
sales_invoice = dict(frappe.db.sql('''select
@@ -75,7 +76,7 @@ def get_timeline_data(doctype, name):
where
st.sales_person = %s and st.parent = dt.name and dt.posting_date > date_sub(curdate(), interval 1 year)
group by dt.posting_date ''', name))
-
+
for key in sales_invoice:
if out.get(key):
out[key] += sales_invoice[key]
@@ -97,5 +98,3 @@ def get_timeline_data(doctype, name):
out[key] = delivery_note[key]
return out
-
-
diff --git a/erpnext/setup/doctype/setup_progress/setup_progress.py b/erpnext/setup/doctype/setup_progress/setup_progress.py
index 9187eb79962..e1402f5844b 100644
--- a/erpnext/setup/doctype/setup_progress/setup_progress.py
+++ b/erpnext/setup/doctype/setup_progress/setup_progress.py
@@ -16,8 +16,9 @@ def get_setup_progress():
return frappe.local.setup_progress
def get_action_completed_state(action_name):
- return [d.is_completed for d in get_setup_progress().actions
- if d.action_name == action_name][0]
+ for d in get_setup_progress().actions:
+ if d.action_name == action_name:
+ return d.is_completed
def update_action_completed_state(action_name):
action_table_doc = [d for d in get_setup_progress().actions
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index 62c9e7b90c5..8a164f9d136 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -33,7 +33,7 @@ def boot_session(bootinfo):
tabCompany limit 1""") and 'Yes' or 'No'
bootinfo.docs += frappe.db.sql("""select name, default_currency, cost_center, default_terms,
- default_letter_head, default_bank_account, enable_perpetual_inventory from `tabCompany`""",
+ default_letter_head, default_bank_account, enable_perpetual_inventory, country from `tabCompany`""",
as_dict=1, update={"doctype":":Company"})
party_account_types = frappe.db.sql(""" select name, ifnull(account_type, '') from `tabParty Type`""")
diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py
index ea3d1036d2d..32a03e7373c 100644
--- a/erpnext/stock/__init__.py
+++ b/erpnext/stock/__init__.py
@@ -8,16 +8,21 @@ install_docs = [
{"doctype":"Role", "role_name":"Stock User", "name":"Stock User"},
{"doctype":"Role", "role_name":"Quality Manager", "name":"Quality Manager"},
{"doctype":"Item Group", "item_group_name":"All Item Groups", "is_group": 1},
- {"doctype":"Item Group", "item_group_name":"Default",
+ {"doctype":"Item Group", "item_group_name":"Default",
"parent_item_group":"All Item Groups", "is_group": 0},
]
-def get_warehouse_account_map():
+def get_warehouse_account_map(company=None):
if not frappe.flags.warehouse_account_map or frappe.flags.in_test:
warehouse_account = frappe._dict()
+ filters = {}
+ if company:
+ filters['company'] = company
+
for d in frappe.get_all('Warehouse',
fields = ["name", "account", "parent_warehouse", "company"],
+ filters = filters,
order_by="lft, rgt"):
if not d.account:
d.account = get_warehouse_account(d, warehouse_account)
@@ -57,6 +62,6 @@ def get_warehouse_account(warehouse, warehouse_account=None):
frappe.throw(_("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}")
.format(warehouse.name, warehouse.company))
return account
-
+
def get_company_default_inventory_account(company):
return frappe.get_cached_value('Company', company, 'default_inventory_account')
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 6242fa767ef..487c7656595 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -28,7 +28,7 @@ def get_data(item_code=None, warehouse=None, item_group=None,
# user does not have access on warehouse
return []
- return frappe.db.get_all('Bin', fields=['item_code', 'warehouse', 'projected_qty',
+ items = frappe.db.get_all('Bin', fields=['item_code', 'warehouse', 'projected_qty',
'reserved_qty', 'reserved_qty_for_production', 'reserved_qty_for_sub_contract', 'actual_qty', 'valuation_rate'],
or_filters={
'projected_qty': ['!=', 0],
@@ -41,3 +41,10 @@ def get_data(item_code=None, warehouse=None, item_group=None,
order_by=sort_by + ' ' + sort_order,
limit_start=start,
limit_page_length='21')
+
+ for item in items:
+ item.update({
+ 'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name')
+ })
+
+ return items
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 50996edddaa..1eb2b0985ac 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -391,7 +391,7 @@ def get_invoiced_qty_map(delivery_note):
return invoiced_qty_map
-def get_returned_qty_map(sales_orders):
+def get_returned_qty_map_against_so(sales_orders):
"""returns a map: {so_detail: returned_qty}"""
returned_qty_map = {}
@@ -403,12 +403,25 @@ def get_returned_qty_map(sales_orders):
return returned_qty_map
+def get_returned_qty_map_against_dn(delivery_note):
+ """returns a map: {so_detail: returned_qty}"""
+ returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty
+ from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
+ where dn.name = dn_item.parent
+ and dn.docstatus = 1
+ and dn.is_return = 1
+ and dn.return_against = %s
+ group by dn_item.item_code
+ """, delivery_note))
+
+ return returned_qty_map
+
@frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None):
doc = frappe.get_doc('Delivery Note', source_name)
sales_orders = [d.against_sales_order for d in doc.items]
- returned_qty_map = get_returned_qty_map(sales_orders)
-
+ returned_qty_map_against_so = get_returned_qty_map_against_so(sales_orders)
+ returned_qty_map_against_dn = get_returned_qty_map_against_dn(source_name)
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
@@ -428,13 +441,26 @@ def make_sales_invoice(source_name, target_doc=None):
target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
def update_item(source_doc, target_doc, source_parent):
- target_doc.qty = (source_doc.qty -
- invoiced_qty_map.get(source_doc.name, 0) - returned_qty_map.get(source_doc.so_detail, 0))
+ target_doc.qty, returned_qty = get_pending_qty(source_doc)
+ if not source_doc.so_detail:
+ returned_qty_map_against_dn[source_doc.item_code] = returned_qty
if source_doc.serial_no and source_parent.per_billed > 0:
target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
target_doc.qty, source_parent.name)
+ def get_pending_qty(item_row):
+ pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) - returned_qty_map_against_so.get(item_row.so_detail, 0)
+ returned_qty = flt(returned_qty_map_against_dn.get(item_row.item_code, 0))
+ if not item_row.so_detail:
+ if returned_qty >= pending_qty:
+ pending_qty = 0
+ returned_qty -= pending_qty
+ else:
+ pending_qty -= returned_qty
+ returned_qty = 0
+ return pending_qty, returned_qty
+
doc = get_mapped_doc("Delivery Note", source_name, {
"Delivery Note": {
"doctype": "Sales Invoice",
@@ -453,7 +479,7 @@ def make_sales_invoice(source_name, target_doc=None):
"cost_center": "cost_center"
},
"postprocess": update_item,
- "filter": lambda d: abs(d.qty) - abs(invoiced_qty_map.get(d.name, 0))<=0
+ "filter": lambda d: get_pending_qty(d)[0]<=0
},
"Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges",
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 0c5a71c30c0..5c031213d8a 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -636,7 +636,7 @@ class TestDeliveryNote(unittest.TestCase):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
set_perpetual_inventory(0, company)
-
+
def test_make_sales_invoice_from_dn_for_returned_qty(self):
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
@@ -655,6 +655,33 @@ class TestDeliveryNote(unittest.TestCase):
si = make_sales_invoice(dn.name)
self.assertEquals(si.items[0].qty, 1)
+ def test_make_sales_invoice_from_dn_with_returned_qty_against_dn(self):
+ from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
+
+ dn = create_delivery_note(qty=8, do_not_submit=True)
+ dn.append("items", {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "rate": 100,
+ "conversion_factor": 1.0,
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC"
+ })
+ dn.submit()
+
+ si1 = make_sales_invoice(dn.name)
+ si1.items[0].qty = 4
+ si1.items.pop(1)
+ si1.save()
+ si1.submit()
+
+ create_delivery_note(is_return=1, return_against=dn.name, qty=-2)
+
+ si2 = make_sales_invoice(dn.name)
+ self.assertEquals(si2.items[0].qty, 2)
+ self.assertEquals(si2.items[1].qty, 1)
+
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 58c907793a7..2d9f6e9acd9 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -23,6 +23,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "name_and_description_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -57,6 +58,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 0,
@@ -91,6 +93,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "item_code",
"fieldtype": "Data",
"hidden": 0,
@@ -127,6 +130,7 @@
"columns": 0,
"depends_on": "variant_of",
"description": "If item is a variant of another item then description, image, pricing, taxes etc will be set from the template unless explicitly specified",
+ "fetch_if_empty": 0,
"fieldname": "variant_of",
"fieldtype": "Link",
"hidden": 0,
@@ -160,6 +164,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "item_name",
"fieldtype": "Data",
"hidden": 0,
@@ -195,6 +200,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "item_group",
"fieldtype": "Link",
"hidden": 0,
@@ -230,6 +236,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "is_item_from_hub",
"fieldtype": "Check",
"hidden": 0,
@@ -263,6 +270,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "stock_uom",
"fieldtype": "Link",
"hidden": 0,
@@ -298,6 +306,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -329,6 +338,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
@@ -361,6 +371,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"hidden": 0,
@@ -395,6 +406,7 @@
"columns": 0,
"default": "1",
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "is_stock_item",
"fieldtype": "Check",
"hidden": 0,
@@ -431,6 +443,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
+ "fetch_if_empty": 0,
"fieldname": "include_item_in_manufacturing",
"fieldtype": "Check",
"hidden": 0,
@@ -464,6 +477,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.__islocal&&doc.is_stock_item && !doc.has_serial_no && !doc.has_batch_no)",
+ "fetch_if_empty": 0,
"fieldname": "opening_stock",
"fieldtype": "Float",
"hidden": 0,
@@ -497,6 +511,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"hidden": 0,
@@ -530,6 +545,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.__islocal",
+ "fetch_if_empty": 0,
"fieldname": "standard_rate",
"fieldtype": "Currency",
"hidden": 0,
@@ -562,6 +578,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
"hidden": 0,
@@ -595,6 +612,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_fixed_asset",
+ "fetch_if_empty": 0,
"fieldname": "asset_category",
"fieldtype": "Link",
"hidden": 0,
@@ -629,6 +647,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_fixed_asset",
+ "fetch_if_empty": 0,
"fieldname": "asset_naming_series",
"fieldtype": "Select",
"hidden": 0,
@@ -663,6 +682,7 @@
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "tolerance",
"fieldtype": "Float",
"hidden": 0,
@@ -697,6 +717,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
@@ -730,6 +751,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "section_break_11",
"fieldtype": "Section Break",
"hidden": 0,
@@ -762,6 +784,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "brand",
"fieldtype": "Link",
"hidden": 0,
@@ -797,6 +820,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -831,6 +855,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "sb_barcodes",
"fieldtype": "Section Break",
"hidden": 0,
@@ -863,6 +888,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "barcodes",
"fieldtype": "Table",
"hidden": 0,
@@ -898,6 +924,7 @@
"collapsible_depends_on": "is_stock_item",
"columns": 0,
"depends_on": "is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "inventory_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -932,6 +959,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "shelf_life_in_days",
"fieldtype": "Int",
"hidden": 0,
@@ -966,6 +994,7 @@
"columns": 0,
"default": "2099-12-31",
"depends_on": "is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "end_of_life",
"fieldtype": "Date",
"hidden": 0,
@@ -1001,6 +1030,7 @@
"collapsible": 0,
"columns": 0,
"default": "Purchase",
+ "fetch_if_empty": 0,
"fieldname": "default_material_request_type",
"fieldtype": "Select",
"hidden": 0,
@@ -1035,6 +1065,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "valuation_method",
"fieldtype": "Select",
"hidden": 0,
@@ -1069,6 +1100,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1103,6 +1135,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "warranty_period",
"fieldtype": "Data",
"hidden": 0,
@@ -1139,6 +1172,7 @@
"columns": 0,
"depends_on": "is_stock_item",
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "weight_per_unit",
"fieldtype": "Float",
"hidden": 0,
@@ -1172,6 +1206,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "weight_uom",
"fieldtype": "Link",
"hidden": 0,
@@ -1207,6 +1242,7 @@
"columns": 0,
"depends_on": "is_stock_item",
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "reorder_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1242,6 +1278,7 @@
"columns": 0,
"depends_on": "",
"description": "Will also apply for variants unless overrridden",
+ "fetch_if_empty": 0,
"fieldname": "reorder_levels",
"fieldtype": "Table",
"hidden": 0,
@@ -1276,6 +1313,7 @@
"collapsible": 1,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "unit_of_measure_conversion",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1310,6 +1348,7 @@
"columns": 0,
"depends_on": "",
"description": "Will also apply for variants",
+ "fetch_if_empty": 0,
"fieldname": "uoms",
"fieldtype": "Table",
"hidden": 0,
@@ -1347,6 +1386,7 @@
"collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no || doc.is_fixed_asset",
"columns": 0,
"depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
+ "fetch_if_empty": 0,
"fieldname": "serial_nos_and_batches",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1381,6 +1421,7 @@
"columns": 0,
"default": "",
"depends_on": "eval:doc.is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "has_batch_no",
"fieldtype": "Check",
"hidden": 0,
@@ -1418,6 +1459,7 @@
"columns": 0,
"depends_on": "has_batch_no",
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "create_new_batch",
"fieldtype": "Check",
"hidden": 0,
@@ -1452,6 +1494,7 @@
"columns": 0,
"depends_on": "eval:doc.has_batch_no==1 && doc.create_new_batch==1",
"description": "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions,then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item,leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings.",
+ "fetch_if_empty": 0,
"fieldname": "batch_number_series",
"fieldtype": "Data",
"hidden": 0,
@@ -1485,6 +1528,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "has_batch_no",
+ "fetch_if_empty": 0,
"fieldname": "has_expiry_date",
"fieldtype": "Check",
"hidden": 0,
@@ -1518,6 +1562,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "has_batch_no",
+ "fetch_if_empty": 0,
"fieldname": "retain_sample",
"fieldtype": "Check",
"hidden": 0,
@@ -1552,6 +1597,7 @@
"columns": 0,
"depends_on": "eval: (doc.retain_sample && doc.has_batch_no)",
"description": "Maximum sample quantity that can be retained",
+ "fetch_if_empty": 0,
"fieldname": "sample_quantity",
"fieldtype": "Int",
"hidden": 0,
@@ -1584,6 +1630,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_37",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1618,6 +1665,7 @@
"default": "",
"depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "has_serial_no",
"fieldtype": "Check",
"hidden": 0,
@@ -1655,6 +1703,7 @@
"columns": 0,
"depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions,then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
+ "fetch_if_empty": 0,
"fieldname": "serial_no_series",
"fieldtype": "Data",
"hidden": 0,
@@ -1689,6 +1738,7 @@
"collapsible_depends_on": "attributes",
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "variants_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1724,6 +1774,7 @@
"default": "0",
"depends_on": "eval:!doc.variant_of",
"description": "If this item has variants,then it cannot be selected in sales orders etc.",
+ "fetch_if_empty": 0,
"fieldname": "has_variants",
"fieldtype": "Check",
"hidden": 0,
@@ -1759,6 +1810,7 @@
"columns": 0,
"default": "Item Attribute",
"depends_on": "has_variants",
+ "fetch_if_empty": 0,
"fieldname": "variant_based_on",
"fieldtype": "Select",
"hidden": 0,
@@ -1781,7 +1833,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
- "set_only_once": 1,
+ "set_only_once": 0,
"translatable": 0,
"unique": 0
},
@@ -1793,6 +1845,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'",
+ "fetch_if_empty": 0,
"fieldname": "attributes",
"fieldtype": "Table",
"hidden": 1,
@@ -1826,6 +1879,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "defaults",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1858,6 +1912,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "item_defaults",
"fieldtype": "Table",
"hidden": 0,
@@ -1891,6 +1946,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "purchase_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1926,6 +1982,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
+ "fetch_if_empty": 0,
"fieldname": "is_purchase_item",
"fieldtype": "Check",
"hidden": 0,
@@ -1958,6 +2015,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "purchase_uom",
"fieldtype": "Link",
"hidden": 0,
@@ -1994,6 +2052,7 @@
"default": "0.00",
"depends_on": "is_stock_item",
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "min_order_qty",
"fieldtype": "Float",
"hidden": 0,
@@ -2028,6 +2087,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "safety_stock",
"fieldtype": "Float",
"hidden": 0,
@@ -2060,6 +2120,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "purchase_details_cb",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2094,6 +2155,7 @@
"columns": 0,
"depends_on": "",
"description": "Average time taken by the supplier to deliver",
+ "fetch_if_empty": 0,
"fieldname": "lead_time_days",
"fieldtype": "Int",
"hidden": 0,
@@ -2129,6 +2191,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "last_purchase_rate",
"fieldtype": "Float",
"hidden": 0,
@@ -2164,6 +2227,7 @@
"collapsible": 1,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "supplier_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2196,6 +2260,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "delivered_by_supplier",
"fieldtype": "Check",
"hidden": 0,
@@ -2229,6 +2294,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "manufacturer",
"fieldtype": "Link",
"hidden": 0,
@@ -2263,6 +2329,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "manufacturer_part_no",
"fieldtype": "Data",
"hidden": 0,
@@ -2296,6 +2363,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "column_break2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2331,6 +2399,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "supplier_items",
"fieldtype": "Table",
"hidden": 0,
@@ -2364,6 +2433,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "foreign_trade_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2396,6 +2466,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "country_of_origin",
"fieldtype": "Link",
"hidden": 0,
@@ -2429,6 +2500,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_59",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2460,6 +2532,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "customs_tariff_number",
"fieldtype": "Link",
"hidden": 0,
@@ -2493,6 +2566,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "sales_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2527,6 +2601,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "sales_uom",
"fieldtype": "Link",
"hidden": 0,
@@ -2561,6 +2636,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
+ "fetch_if_empty": 0,
"fieldname": "is_sales_item",
"fieldtype": "Check",
"hidden": 0,
@@ -2594,6 +2670,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "column_break3",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2629,6 +2706,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "max_discount",
"fieldtype": "Float",
"hidden": 0,
@@ -2663,6 +2741,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "deferred_revenue",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2696,6 +2775,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_revenue",
+ "fetch_if_empty": 0,
"fieldname": "deferred_revenue_account",
"fieldtype": "Link",
"hidden": 0,
@@ -2729,6 +2809,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "enable_deferred_revenue",
"fieldtype": "Check",
"hidden": 0,
@@ -2761,6 +2842,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_85",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2793,6 +2875,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_revenue",
+ "fetch_if_empty": 0,
"fieldname": "no_of_months",
"fieldtype": "Int",
"hidden": 0,
@@ -2825,6 +2908,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "deferred_expense_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2858,6 +2942,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_expense",
+ "fetch_if_empty": 0,
"fieldname": "deferred_expense_account",
"fieldtype": "Link",
"hidden": 0,
@@ -2891,6 +2976,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "enable_deferred_expense",
"fieldtype": "Check",
"hidden": 0,
@@ -2923,6 +3009,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_88",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2955,6 +3042,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_expense",
+ "fetch_if_empty": 0,
"fieldname": "no_of_months_exp",
"fieldtype": "Int",
"hidden": 0,
@@ -2987,6 +3075,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "customer_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3021,6 +3110,7 @@
"columns": 0,
"depends_on": "",
"description": "",
+ "fetch_if_empty": 0,
"fieldname": "customer_items",
"fieldtype": "Table",
"hidden": 0,
@@ -3054,6 +3144,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "item_tax_section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3089,6 +3180,7 @@
"collapsible": 0,
"columns": 0,
"description": "Will also apply for variants",
+ "fetch_if_empty": 0,
"fieldname": "taxes",
"fieldtype": "Table",
"hidden": 0,
@@ -3124,6 +3216,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "inspection_criteria",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3159,6 +3252,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
+ "fetch_if_empty": 0,
"fieldname": "inspection_required_before_purchase",
"fieldtype": "Check",
"hidden": 0,
@@ -3194,6 +3288,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "inspection_required_before_delivery",
"fieldtype": "Check",
"hidden": 0,
@@ -3227,6 +3322,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.inspection_required_before_purchase || doc.inspection_required_before_delivery)",
+ "fetch_if_empty": 0,
"fieldname": "quality_inspection_template",
"fieldtype": "Link",
"hidden": 0,
@@ -3261,6 +3357,7 @@
"collapsible": 1,
"columns": 0,
"depends_on": "is_stock_item",
+ "fetch_if_empty": 0,
"fieldname": "manufacturing",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3296,6 +3393,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fetch_if_empty": 0,
"fieldname": "default_bom",
"fieldtype": "Link",
"hidden": 0,
@@ -3333,6 +3431,7 @@
"columns": 0,
"default": "",
"description": "If subcontracted to a vendor",
+ "fetch_if_empty": 0,
"fieldname": "is_sub_contracted_item",
"fieldtype": "Check",
"hidden": 0,
@@ -3368,6 +3467,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_74",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3399,6 +3499,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "customer_code",
"fieldtype": "Data",
"hidden": 1,
@@ -3431,6 +3532,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "website_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3465,6 +3567,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.variant_of",
+ "fetch_if_empty": 0,
"fieldname": "show_in_website",
"fieldtype": "Check",
"hidden": 0,
@@ -3498,6 +3601,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "variant_of",
+ "fetch_if_empty": 0,
"fieldname": "show_variant_in_website",
"fieldtype": "Check",
"hidden": 0,
@@ -3531,6 +3635,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
+ "fetch_if_empty": 0,
"fieldname": "route",
"fieldtype": "Small Text",
"hidden": 0,
@@ -3565,6 +3670,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "Items with higher weightage will be shown higher",
+ "fetch_if_empty": 0,
"fieldname": "weightage",
"fieldtype": "Int",
"hidden": 0,
@@ -3599,6 +3705,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "Show a slideshow at the top of the page",
+ "fetch_if_empty": 0,
"fieldname": "slideshow",
"fieldtype": "Link",
"hidden": 0,
@@ -3634,6 +3741,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "Item Image (if not slideshow)",
+ "fetch_if_empty": 0,
"fieldname": "website_image",
"fieldtype": "Attach",
"hidden": 0,
@@ -3667,6 +3775,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "thumbnail",
"fieldtype": "Data",
"hidden": 0,
@@ -3699,6 +3808,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "cb72",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3732,6 +3842,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "Show \"In Stock\" or \"Not in Stock\" based on stock available in this warehouse.",
+ "fetch_if_empty": 0,
"fieldname": "website_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -3767,6 +3878,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "List this Item in multiple groups on the website.",
+ "fetch_if_empty": 0,
"fieldname": "website_item_groups",
"fieldtype": "Table",
"hidden": 0,
@@ -3802,6 +3914,7 @@
"collapsible_depends_on": "website_specifications",
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
+ "fetch_if_empty": 0,
"fieldname": "sb72",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3835,6 +3948,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
+ "fetch_if_empty": 0,
"fieldname": "copy_from_item_group",
"fieldtype": "Button",
"hidden": 0,
@@ -3868,6 +3982,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
+ "fetch_if_empty": 0,
"fieldname": "website_specifications",
"fieldtype": "Table",
"hidden": 0,
@@ -3902,6 +4017,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
+ "fetch_if_empty": 0,
"fieldname": "web_long_description",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -3934,6 +4050,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "total_projected_qty",
"fieldtype": "Float",
"hidden": 1,
@@ -3967,6 +4084,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(!doc.is_item_from_hub)",
+ "fetch_if_empty": 0,
"fieldname": "hub_publishing_sb",
"fieldtype": "Section Break",
"hidden": 0,
@@ -4001,6 +4119,7 @@
"columns": 0,
"default": "0",
"description": "Publish Item to hub.erpnext.com",
+ "fetch_if_empty": 0,
"fieldname": "publish_in_hub",
"fieldtype": "Check",
"hidden": 0,
@@ -4033,6 +4152,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "hub_category_to_publish",
"fieldtype": "Data",
"hidden": 0,
@@ -4067,6 +4187,7 @@
"collapsible": 0,
"columns": 0,
"description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.",
+ "fetch_if_empty": 0,
"fieldname": "hub_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -4101,6 +4222,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
+ "fetch_if_empty": 0,
"fieldname": "synced_with_hub",
"fieldtype": "Check",
"hidden": 0,
@@ -4139,7 +4261,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
- "modified": "2019-02-16 17:43:56.039611",
+ "modified": "2019-04-05 12:03:24.530849",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index bd06688caa2..e93e7d9b615 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -9,11 +9,11 @@ import erpnext
import frappe
import copy
from erpnext.controllers.item_variant import (ItemVariantExistsError,
- copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
+ copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
from frappe import _, msgprint
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
- now_datetime, random_string, strip)
+ now_datetime, random_string, strip)
from frappe.utils.html_utils import clean_html
from frappe.website.doctype.website_slideshow.website_slideshow import \
get_slideshow
@@ -49,9 +49,6 @@ class Item(WebsiteGenerator):
self.set_onload('stock_exists', self.stock_ledger_created())
self.set_asset_naming_series()
- if self.is_fixed_asset:
- asset = self.asset_exists()
- self.set_onload("asset_exists", True if asset else False)
def set_asset_naming_series(self):
if not hasattr(self, '_asset_naming_series'):
@@ -118,9 +115,9 @@ class Item(WebsiteGenerator):
self.validate_has_variants()
self.validate_stock_exists_for_template_item()
- self.validate_asset_exists_for_serialized_asset()
self.validate_attributes()
self.validate_variant_attributes()
+ self.validate_variant_based_on_change()
self.validate_website_image()
self.make_thumbnail()
self.validate_fixed_asset()
@@ -128,6 +125,7 @@ class Item(WebsiteGenerator):
self.validate_uom_conversion_factor()
self.validate_item_defaults()
self.update_defaults_from_item_group()
+ self.validate_stock_for_has_batch_and_has_serial()
if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@@ -151,7 +149,7 @@ class Item(WebsiteGenerator):
'''Add a new price'''
if not price_list:
price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
- or frappe.db.get_value('Price List', _('Standard Selling')))
+ or frappe.db.get_value('Price List', _('Standard Selling')))
if price_list:
item_price = frappe.get_doc({
"doctype": "Item Price",
@@ -190,7 +188,7 @@ class Item(WebsiteGenerator):
def make_route(self):
if not self.route:
return cstr(frappe.db.get_value('Item Group', self.item_group,
- 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
+ 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
def validate_website_image(self):
"""Validate if the website image is a public file"""
@@ -213,7 +211,7 @@ class Item(WebsiteGenerator):
if not file_doc:
if not auto_set_website_image:
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
- .format(self.website_image, self.name))
+ .format(self.website_image, self.name))
self.website_image = None
@@ -314,8 +312,8 @@ class Item(WebsiteGenerator):
# load variants
# also used in set_attribute_context
context.variants = frappe.get_all("Item",
- filters={"variant_of": self.name, "show_variant_in_website": 1},
- order_by="name asc")
+ filters={"variant_of": self.name, "show_variant_in_website": 1},
+ order_by="name asc")
variant = frappe.form_dict.variant
if not variant and context.variants:
@@ -326,7 +324,7 @@ class Item(WebsiteGenerator):
context.variant = frappe.get_doc("Item", variant)
for fieldname in ("website_image", "web_long_description", "description",
- "website_specifications"):
+ "website_specifications"):
if context.variant.get(fieldname):
value = context.variant.get(fieldname)
if isinstance(value, list):
@@ -349,7 +347,7 @@ class Item(WebsiteGenerator):
# load attributes
for v in context.variants:
v.attributes = frappe.get_all("Item Variant Attribute",
- fields=["attribute", "attribute_value"],
+ fields=["attribute", "attribute_value"],
filters={"parent": v.name})
for attr in v.attributes:
@@ -530,7 +528,7 @@ class Item(WebsiteGenerator):
warehouse += [d.get("warehouse")]
else:
frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
- .format(d.idx, d.warehouse), DuplicateReorderRows)
+ .format(d.idx, d.warehouse), DuplicateReorderRows)
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
@@ -550,7 +548,7 @@ class Item(WebsiteGenerator):
def update_item_price(self):
frappe.db.sql("""update `tabItem Price` set item_name=%s,
item_description=%s, brand=%s where item_code=%s""",
- (self.item_name, self.description, self.brand, self.name))
+ (self.item_name, self.description, self.brand, self.name))
def on_trash(self):
super(Item, self).on_trash()
@@ -572,7 +570,7 @@ class Item(WebsiteGenerator):
new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
if new_properties != [cstr(self.get(fld)) for fld in field_list]:
frappe.throw(_("To merge, following properties must be same for both items")
- + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
+ + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
def after_rename(self, old_name, new_name, merge):
if self.route:
@@ -595,7 +593,7 @@ class Item(WebsiteGenerator):
item_wise_tax_detail.pop(old_name)
frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
- json.dumps(item_wise_tax_detail), update_modified=False)
+ json.dumps(item_wise_tax_detail), update_modified=False)
def set_last_purchase_rate(self, new_name):
last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
@@ -623,7 +621,7 @@ class Item(WebsiteGenerator):
self.set("website_specifications", [])
if self.item_group:
for label, desc in frappe.db.get_values("Item Website Specification",
- {"parent": self.item_group}, ["label", "description"]):
+ {"parent": self.item_group}, ["label", "description"]):
row = self.append("website_specifications")
row.label = label
row.description = desc
@@ -697,7 +695,7 @@ class Item(WebsiteGenerator):
def update_variants(self):
if self.flags.dont_update_variants or \
- frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
+ frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
return
if self.has_variants:
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name})
@@ -726,17 +724,10 @@ class Item(WebsiteGenerator):
frappe.throw(
_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
- def validate_asset_exists_for_serialized_asset(self):
- if (not self.get("__islocal") and self.asset_exists() and
- cint(self.has_serial_no) != cint(frappe.db.get_value('Item', self.name, 'has_serial_no'))):
- frappe.throw(_("Asset is already exists against the item {0}, you cannot change the has serial no value")
- .format(self.name))
-
- def asset_exists(self):
- if not hasattr(self, '_asset_created'):
- self._asset_created = frappe.db.get_all("Asset",
- filters={"item_code": self.name, "docstatus": 1}, limit=1)
- return self._asset_created
+ def validate_variant_based_on_change(self):
+ if not self.is_new() and (self.variant_of or (self.has_variants and frappe.get_all("Item", {"variant_of": self.name}))):
+ if self.variant_based_on != frappe.db.get_value("Item", self.name, "variant_based_on"):
+ frappe.throw(_("Variant Based On cannot be changed"))
def validate_uom(self):
if not self.get("__islocal"):
@@ -748,7 +739,7 @@ class Item(WebsiteGenerator):
template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
if template_uom != self.stock_uom:
frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
- .format(self.stock_uom, template_uom))
+ .format(self.stock_uom, template_uom))
def validate_uom_conversion_factor(self):
if self.uoms:
@@ -758,7 +749,13 @@ class Item(WebsiteGenerator):
d.conversion_factor = value
def validate_attributes(self):
- if (self.has_variants or self.variant_of) and self.variant_based_on == 'Item Attribute':
+ if not (self.has_variants or self.variant_of):
+ return
+
+ if not self.variant_based_on:
+ self.variant_based_on = 'Item Attribute'
+
+ if self.variant_based_on == 'Item Attribute':
attributes = []
if not self.attributes:
frappe.throw(_("Attribute table is mandatory"))
@@ -780,10 +777,15 @@ class Item(WebsiteGenerator):
variant = get_variant(self.variant_of, args, self.name)
if variant:
frappe.throw(_("Item variant {0} exists with same attributes")
- .format(variant), ItemVariantExistsError)
+ .format(variant), ItemVariantExistsError)
validate_item_variant_attributes(self, args)
+ def validate_stock_for_has_batch_and_has_serial(self):
+ if self.stock_ledger_created():
+ for value in ["has_batch_no", "has_serial_no"]:
+ if frappe.db.get_value("Item", self.name, value) != self.get_value(value):
+ frappe.throw(_("Cannot change {0} as Stock Transaction for Item {1} exist.".format(value, self.name)))
def get_timeline_data(doctype, name):
'''returns timeline data based on stock ledger entry'''
@@ -863,18 +865,18 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
limit 1""", (item_code, cstr(doc_name)), as_dict=1)
purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
- or "1900-01-01")
+ or "1900-01-01")
purchase_receipt_date = getdate(last_purchase_receipt and
- last_purchase_receipt[0].posting_date or "1900-01-01")
+ last_purchase_receipt[0].posting_date or "1900-01-01")
if (purchase_order_date > purchase_receipt_date) or \
- (last_purchase_order and not last_purchase_receipt):
+ (last_purchase_order and not last_purchase_receipt):
# use purchase order
last_purchase = last_purchase_order[0]
purchase_date = purchase_order_date
elif (purchase_receipt_date > purchase_order_date) or \
- (last_purchase_receipt and not last_purchase_order):
+ (last_purchase_receipt and not last_purchase_order):
# use purchase receipt
last_purchase = last_purchase_receipt[0]
purchase_date = purchase_receipt_date
@@ -904,7 +906,7 @@ def invalidate_cache_for_item(doc):
invalidate_cache_for(doc, doc.item_group)
website_item_groups = list(set((doc.get("old_website_item_groups") or [])
- + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
+ + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
for item_group in website_item_groups:
invalidate_cache_for(doc, item_group)
@@ -919,7 +921,7 @@ def check_stock_uom_with_bin(item, stock_uom):
matched = True
ref_uom = frappe.db.get_value("Stock Ledger Entry",
- {"item_code": item}, "stock_uom")
+ {"item_code": item}, "stock_uom")
if ref_uom:
if cstr(ref_uom) != cstr(stock_uom):
@@ -928,7 +930,7 @@ def check_stock_uom_with_bin(item, stock_uom):
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
for bin in bin_list:
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0
- or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
+ or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
matched = False
break
@@ -1005,4 +1007,4 @@ def update_variants(variants, template, publish_progress=True):
variant.save()
count+=1
if publish_progress:
- frappe.publish_progress(count*100/len(variants), title = _("Updating Variants..."))
\ No newline at end of file
+ frappe.publish_progress(count*100/len(variants), title = _("Updating Variants..."))
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index d02559ebcb1..ac499e0965e 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -17,7 +17,7 @@ from erpnext.stock.get_item_details import get_item_details
from six import iteritems
test_ignore = ["BOM"]
-test_dependencies = ["Warehouse", "Item Group"]
+test_dependencies = ["Warehouse", "Item Group", "Brand"]
def make_item(item_code, properties=None):
if frappe.db.exists("Item", item_code):
@@ -393,3 +393,6 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None,
"company": "_Test Company"
})
item.save()
+ else:
+ item = frappe.get_doc("Item", item_code)
+ return item
diff --git a/erpnext/stock/doctype/item_attribute/test_records.json b/erpnext/stock/doctype/item_attribute/test_records.json
index 0208c176d32..d346979496f 100644
--- a/erpnext/stock/doctype/item_attribute/test_records.json
+++ b/erpnext/stock/doctype/item_attribute/test_records.json
@@ -6,7 +6,8 @@
"item_attribute_values": [
{"attribute_value": "Small", "abbr": "S"},
{"attribute_value": "Medium", "abbr": "M"},
- {"attribute_value": "Large", "abbr": "L"}
+ {"attribute_value": "Large", "abbr": "L"},
+ {"attribute_value": "Extra Small", "abbr": "XSL"}
]
},
{
diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py
index 455dff4e154..3782f540cf2 100644
--- a/erpnext/stock/doctype/item_price/test_item_price.py
+++ b/erpnext/stock/doctype/item_price/test_item_price.py
@@ -11,7 +11,7 @@ from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem
class TestItemPrice(unittest.TestCase):
def setUp(self):
- frappe.db.sql("delete from `tabItem Price`")
+ frappe.db.sql("delete from `tabItem Price`")
make_test_records_for_doctype("Item Price", force=True)
def test_duplicate_item(self):
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index f006d00176e..ed7f2ca4323 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -13,7 +13,7 @@ from erpnext.controllers.buying_controller import BuyingController
from erpnext.accounts.utils import get_account_currency
from frappe.desk.notifications import clear_doctype_notifications
from erpnext.buying.utils import check_for_closed_status
-from erpnext.assets.doctype.asset.asset import get_asset_account
+from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled
from six import iteritems
form_grid_templates = {
@@ -125,7 +125,7 @@ class PurchaseReceipt(BuyingController):
else:
self.status = "Completed"
-
+
# Updating stock ledger should always be called after updating prevdoc status,
# because updating ordered qty, reserved_qty_for_subcontract in bin
# depends upon updated ordered qty in PO
@@ -258,7 +258,8 @@ class PurchaseReceipt(BuyingController):
d.rejected_warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(d.warehouse)
- self.get_asset_gl_entry(gl_entries)
+ if not is_cwip_accounting_disabled():
+ self.get_asset_gl_entry(gl_entries)
# Cost center-wise amount breakup for other charges included for valuation
valuation_tax = {}
for tax in self.get("taxes"):
@@ -311,7 +312,7 @@ class PurchaseReceipt(BuyingController):
"\n".join(warehouse_with_no_account))
return process_gl_map(gl_entries)
-
+
def get_asset_gl_entry(self, gl_entries):
for d in self.get("items"):
if d.is_fixed_asset:
@@ -405,6 +406,11 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True):
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
+ doc = frappe.get_doc('Purchase Receipt', source_name)
+ purchase_orders = [d.purchase_order for d in doc.items]
+ returned_qty_map_against_po = get_returned_qty_map_against_po(purchase_orders)
+ returned_qty_map_against_pr = get_returned_qty_map_against_pr(source_name)
+
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
@@ -417,7 +423,23 @@ def make_purchase_invoice(source_name, target_doc=None):
doc.run_method("calculate_taxes_and_totals")
def update_item(source_doc, target_doc, source_parent):
- target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0)
+ target_doc.qty, returned_qty = get_pending_qty(source_doc)
+ if not source_doc.purchase_order_item:
+ returned_qty_map_against_pr[source_doc.item_code] = returned_qty
+
+ def get_pending_qty(item_row):
+ pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) \
+ - returned_qty_map_against_po.get(item_row.purchase_order_item, 0)
+ returned_qty = flt(returned_qty_map_against_pr.get(item_row.item_code, 0))
+ if not item_row.purchase_order_item:
+ if returned_qty >= pending_qty:
+ pending_qty = 0
+ returned_qty -= pending_qty
+ else:
+ pending_qty -= returned_qty
+ returned_qty = 0
+ return pending_qty, returned_qty
+
doclist = get_mapped_doc("Purchase Receipt", source_name, {
"Purchase Receipt": {
@@ -440,7 +462,7 @@ def make_purchase_invoice(source_name, target_doc=None):
"asset": "asset",
},
"postprocess": update_item,
- "filter": lambda d: abs(d.qty) - abs(invoiced_qty_map.get(d.name, 0))<=0
+ "filter": lambda d: get_pending_qty(d)[0]<=0
},
"Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges",
@@ -462,6 +484,31 @@ def get_invoiced_qty_map(purchase_receipt):
return invoiced_qty_map
+def get_returned_qty_map_against_po(purchase_orders):
+ """returns a map: {so_detail: returned_qty}"""
+ returned_qty_map = {}
+
+ for name, returned_qty in frappe.get_all('Purchase Order Item', fields = ["name", "returned_qty"],
+ filters = {'parent': ('in', purchase_orders), 'docstatus': 1}, as_list=1):
+ if not returned_qty_map.get(name):
+ returned_qty_map[name] = 0
+ returned_qty_map[name] += returned_qty
+
+ return returned_qty_map
+
+def get_returned_qty_map_against_pr(purchase_receipt):
+ """returns a map: {so_detail: returned_qty}"""
+ returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty
+ from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
+ where pr.name = pr_item.parent
+ and pr.docstatus = 1
+ and pr.is_return = 1
+ and pr.return_against = %s
+ group by pr_item.item_code
+ """, purchase_receipt))
+
+ return returned_qty_map
+
@frappe.whitelist()
def make_purchase_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 472083b5a11..c5ae838c8f0 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -404,6 +404,44 @@ class TestPurchaseReceipt(unittest.TestCase):
set_perpetual_inventory(0, pr.company)
+ def test_make_purchase_invoice_from_pr_for_returned_qty(self):
+ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, create_pr_against_po
+
+ po = create_purchase_order()
+ pr = create_pr_against_po(po.name)
+
+ pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True)
+ pr1.items[0].purchase_order = po.name
+ pr1.items[0].purchase_order_item = po.items[0].name
+ pr1.submit()
+
+ pi = make_purchase_invoice(pr.name)
+ self.assertEquals(pi.items[0].qty, 3)
+
+ def test_make_purchase_invoice_from_dn_with_returned_qty_against_dn(self):
+ pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
+ pr1.append("items", {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "received_qty": 1,
+ "rate": 100,
+ "conversion_factor": 1.0,
+ })
+ pr1.submit()
+
+ pi1 = make_purchase_invoice(pr1.name)
+ pi1.items[0].qty = 4
+ pi1.items.pop(1)
+ pi1.save()
+ pi1.submit()
+
+ make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2)
+
+ pi2 = make_purchase_invoice(pr1.name)
+ self.assertEquals(pi2.items[0].qty, 2)
+ self.assertEquals(pi2.items[1].qty, 1)
+
def get_gl_entries(voucher_type, voucher_no):
return frappe.db.sql("""select account, debit, credit, cost_center
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 3a5253019af..dc9c4fc3fe8 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -802,7 +802,7 @@ class StockEntry(StockController):
# item dict = { item_code: {qty, description, stock_uom} }
item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty,
- fetch_exploded = self.use_multi_level_bom)
+ fetch_exploded = self.use_multi_level_bom, fetch_qty_in_stock_uom=False)
used_alternative_items = get_used_alternative_items(work_order = self.work_order)
for item in itervalues(item_dict):
@@ -1031,7 +1031,7 @@ class StockEntry(StockController):
se_child.item_code = item_dict[d].get('item_code') or cstr(d)
se_child.item_name = item_dict[d]["item_name"]
se_child.description = item_dict[d]["description"]
- se_child.uom = stock_uom
+ se_child.uom = item_dict[d]["uom"] if item_dict[d].get("uom") else stock_uom
se_child.stock_uom = stock_uom
se_child.qty = flt(item_dict[d]["qty"], se_child.precision("qty"))
se_child.expense_account = item_dict[d].get("expense_account") or expense_account
@@ -1049,8 +1049,9 @@ class StockEntry(StockController):
se_child.t_warehouse = self.to_warehouse
# in stock uom
- se_child.transfer_qty = flt(item_dict[d]["qty"], se_child.precision("qty"))
- se_child.conversion_factor = 1.00
+ se_child.conversion_factor = flt(item_dict[d].get("conversion_factor")) or 1
+ se_child.transfer_qty = flt(item_dict[d]["qty"]*se_child.conversion_factor, se_child.precision("qty"))
+
# to be assigned for finished item
se_child.bom_no = bom_no
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 0d60a5ca384..63e374f6d78 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -51,6 +51,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "description": "If blank, parent Warehouse Account or company default will be considered",
"fieldname": "warehouse_name",
"fieldtype": "Data",
"hidden": 0,
@@ -870,7 +871,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-29 06:26:48.647225",
+ "modified": "2018-08-29 06:26:49.647225",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 74f35953e2a..8b81972b4d1 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -490,7 +490,7 @@ def get_price_list_rate_for(args, item_code):
price_list_rate = get_item_price(item_price_args, item_code)
if price_list_rate:
desired_qty = args.get("qty")
- if check_packing_list(price_list_rate[0][0], desired_qty, item_code):
+ if desired_qty and check_packing_list(price_list_rate[0][0], desired_qty, item_code):
item_price_data = price_list_rate
else:
for field in ["customer", "supplier", "min_qty"]:
@@ -521,12 +521,15 @@ def check_packing_list(price_list_rate_name, desired_qty, item_code):
:param qty: Derised Qt
"""
+ flag = True
item_price = frappe.get_doc("Item Price", price_list_rate_name)
- if desired_qty and item_price.packing_unit:
+ if item_price.packing_unit:
packing_increment = desired_qty % item_price.packing_unit
- if packing_increment == 0:
- return True
+ if packing_increment != 0:
+ flag = False
+
+ return flag
def validate_price_list(args):
if args.get("price_list"):
@@ -603,28 +606,39 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa
@frappe.whitelist()
def get_pos_profile(company, pos_profile=None, user=None):
- if pos_profile:
- return frappe.get_cached_doc('POS Profile', pos_profile)
+ if pos_profile: return frappe.get_cached_doc('POS Profile', pos_profile)
if not user:
user = frappe.session['user']
+ condition = "pfu.user = %(user)s AND pfu.default=1"
+ if user and company:
+ condition = "pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1"
+
pos_profile = frappe.db.sql("""SELECT pf.*
FROM
`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
ON
pf.name = pfu.parent
WHERE
- (
- (pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1)
- OR (pfu.user = %(user)s AND pfu.default=1)
- OR (ifnull(pfu.user, '') = '' AND pf.company = %(company)s)
- ) AND pf.disabled = 0
- """, {
+ {cond} AND pf.disabled = 0
+ """.format(cond = condition), {
'user': user,
'company': company
}, as_dict=1)
+ if not pos_profile and company:
+ pos_profile = frappe.db.sql("""SELECT pf.*
+ FROM
+ `tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
+ ON
+ pf.name = pfu.parent
+ WHERE
+ pf.company = %(company)s AND pf.disabled = 0
+ """, {
+ 'company': company
+ }, as_dict=1)
+
return pos_profile and pos_profile[0] or None
def get_serial_nos_by_fifo(args, sales_order=None):
diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.js b/erpnext/stock/report/total_stock_summary/total_stock_summary.js
index 223a603004c..b7461c485f6 100644
--- a/erpnext/stock/report/total_stock_summary/total_stock_summary.js
+++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.js
@@ -18,7 +18,9 @@ frappe.query_reports["Total Stock Summary"] = {
"label": __("Company"),
"fieldtype": "Link",
"width": "80",
- "options": "Company"
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
},
]
}
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index fb0c05fcddb..04d737f0557 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -60,7 +60,7 @@
{{doc.terms}}