diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index d031bc5bb1..f40b957563 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '12.1.8'
+__version__ = '12.2.0'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
index bc07b6d807..43acded3a9 100644
--- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe, json
+from frappe import _
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate
from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
@@ -18,12 +19,20 @@ def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_d
else:
chart = frappe._dict(frappe.parse_json(chart))
timespan = chart.timespan
+
+ if chart.timespan == 'Select Date Range':
+ from_date = chart.from_date
+ to_date = chart.to_date
+
timegrain = chart.time_interval
filters = frappe.parse_json(chart.filters_json)
account = filters.get("account")
company = filters.get("company")
+ if not account and chart:
+ frappe.throw(_("Account is not set for the dashboard chart {0}").format(chart))
+
if not to_date:
to_date = nowdate()
if not from_date:
@@ -84,7 +93,8 @@ def get_gl_entries(account, to_date):
fields = ['posting_date', 'debit', 'credit'],
filters = [
dict(posting_date = ('<', to_date)),
- dict(account = ('in', child_accounts))
+ dict(account = ('in', child_accounts)),
+ dict(voucher_type = ('!=', 'Period Closing Voucher'))
],
order_by = 'posting_date asc')
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 32485a3469..62a8f05c65 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -174,6 +174,8 @@ def make_gl_entries(doc, credit_account, debit_account, against,
# GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries
+ if amount == 0: return
+
gl_entries = []
gl_entries.append(
doc.get_gl_dict({
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 7cca8d2003..cccced8e0b 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -117,7 +117,7 @@ class Account(NestedSet):
if not parent_acc_name_map: return
- self.create_account_for_child_company(parent_acc_name_map, descendants)
+ self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
def validate_group_or_ledger(self):
if self.get("__islocal"):
@@ -159,7 +159,7 @@ class Account(NestedSet):
if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
- def create_account_for_child_company(self, parent_acc_name_map, descendants):
+ def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
for company in descendants:
if not parent_acc_name_map.get(company):
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index 4ee55736fe..dc23b2b2d0 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -160,7 +160,7 @@ def _make_test_records(verbose):
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"]
]
- for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"]]:
+ for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]:
test_objects = make_test_objects("Account", [{
"doctype": "Account",
"account_name": account_name,
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index af51fc5d8e..522ed4ffa4 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -24,6 +24,11 @@ class AccountingDimension(Document):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)
+ exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
+
+ if exists and self.is_new():
+ frappe.throw("Document Type already used as a dimension")
+
def after_insert(self):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
@@ -60,7 +65,8 @@ def make_dimension_in_accounting_doctypes(doc):
"label": doc.label,
"fieldtype": "Link",
"options": doc.document_type,
- "insert_after": insert_after_field
+ "insert_after": insert_after_field,
+ "owner": "Administrator"
}
if doctype == "Budget":
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 3222aeb085..2473d715d0 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -15,8 +15,8 @@ class AccountsSettings(Document):
frappe.clear_cache()
def validate(self):
- for f in ["add_taxes_from_item_tax_template"]:
- frappe.db.set_default(f, self.get(f, ""))
+ frappe.db.set_default("add_taxes_from_item_tax_template",
+ self.get("add_taxes_from_item_tax_template", 0))
self.validate_stale_days()
self.enable_payment_schedule_in_print()
diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.js b/erpnext/accounts/doctype/coupon_code/coupon_code.js
index 0bf097f8d5..da3a9f8132 100644
--- a/erpnext/accounts/doctype/coupon_code/coupon_code.js
+++ b/erpnext/accounts/doctype/coupon_code/coupon_code.js
@@ -2,6 +2,15 @@
// For license information, please see license.txt
frappe.ui.form.on('Coupon Code', {
+ setup: function(frm) {
+ frm.set_query("pricing_rule", function() {
+ return {
+ filters: [
+ ["Pricing Rule","coupon_code_based", "=", "1"]
+ ]
+ };
+ });
+ },
coupon_name:function(frm){
if (frm.doc.__islocal===1) {
frm.trigger("make_coupon_code");
diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.json b/erpnext/accounts/doctype/coupon_code/coupon_code.json
index fafc63531f..7dc5e9dc78 100644
--- a/erpnext/accounts/doctype/coupon_code/coupon_code.json
+++ b/erpnext/accounts/doctype/coupon_code/coupon_code.json
@@ -24,6 +24,7 @@
],
"fields": [
{
+ "description": "e.g. \"Summer Holiday 2019 Offer 20\"",
"fieldname": "coupon_name",
"fieldtype": "Data",
"label": "Coupon Name",
@@ -50,7 +51,7 @@
"fieldtype": "Column Break"
},
{
- "description": "To be used to get discount",
+ "description": "unique e.g. SAVE20 To be used to get discount",
"fieldname": "coupon_code",
"fieldtype": "Data",
"label": "Coupon Code",
@@ -62,12 +63,13 @@
"fieldname": "pricing_rule",
"fieldtype": "Link",
"label": "Pricing Rule",
- "options": "Pricing Rule"
+ "options": "Pricing Rule",
+ "reqd": 1
},
{
"fieldname": "uses",
"fieldtype": "Section Break",
- "label": "Uses"
+ "label": "Validity and Usage"
},
{
"fieldname": "valid_from",
@@ -113,7 +115,7 @@
"read_only": 1
}
],
- "modified": "2019-10-15 14:12:22.686986",
+ "modified": "2019-10-19 14:48:14.602481",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Coupon Code",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 11d847d821..221e3a7280 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -570,7 +570,7 @@ $.extend(erpnext.journal_entry, {
},
{fieldtype: "Date", fieldname: "posting_date", label: __("Date"), reqd: 1,
default: frm.doc.posting_date},
- {fieldtype: "Small Text", fieldname: "user_remark", label: __("User Remark"), reqd: 1},
+ {fieldtype: "Small Text", fieldname: "user_remark", label: __("User Remark")},
{fieldtype: "Select", fieldname: "naming_series", label: __("Series"), reqd: 1,
options: naming_series_options, default: naming_series_default},
]
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
index 4a7406e0cb..341884c190 100644
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -8,10 +8,12 @@ import unittest
from frappe.utils import today, cint, flt, getdate
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
from erpnext.accounts.party import get_dashboard_info
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
class TestLoyaltyProgram(unittest.TestCase):
@classmethod
def setUpClass(self):
+ set_perpetual_inventory(0)
# create relevant item, customer, loyalty program, etc
create_records()
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index ce8aba75b2..54464e71c4 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -32,8 +32,10 @@ class OpeningInvoiceCreationTool(Document):
})
invoices_summary.update({company: _summary})
- paid_amount.append(invoice.paid_amount)
- outstanding_amount.append(invoice.outstanding_amount)
+ if invoice.paid_amount:
+ paid_amount.append(invoice.paid_amount)
+ if invoice.outstanding_amount:
+ outstanding_amount.append(invoice.outstanding_amount)
if paid_amount or outstanding_amount:
max_count.update({
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 1e0b1bcbf1..adf47ed276 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -554,7 +554,7 @@ frappe.ui.form.on('Payment Entry', {
frappe.flags.allocate_payment_amount = true;
frm.events.validate_filters_data(frm, filters);
frm.events.get_outstanding_documents(frm, filters);
- }, __("Filters"), __("Get Outstanding Invoices"));
+ }, __("Filters"), __("Get Outstanding Documents"));
},
validate_filters_data: function(frm, filters) {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index a85eccd30a..acfc660c4f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -62,6 +62,7 @@
"dimension_col_break",
"cost_center",
"section_break_12",
+ "status",
"remarks",
"column_break_16",
"letter_head",
@@ -563,10 +564,18 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "\nDraft\nSubmitted\nCancelled",
+ "read_only": 1
}
],
"is_submittable": 1,
- "modified": "2019-05-27 15:53:21.108857",
+ "modified": "2019-11-06 12:59:43.151721",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 89aaffbc2d..bf7e833285 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -61,6 +61,7 @@ class PaymentEntry(AccountsController):
self.validate_duplicate_entry()
self.validate_allocated_amount()
self.ensure_supplier_is_not_blocked()
+ self.set_status()
def on_submit(self):
self.setup_party_account_field()
@@ -70,6 +71,7 @@ class PaymentEntry(AccountsController):
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_expense_claim()
+ self.set_status()
def on_cancel(self):
@@ -79,6 +81,7 @@ class PaymentEntry(AccountsController):
self.update_advance_paid()
self.update_expense_claim()
self.delink_advance_entry_references()
+ self.set_status()
def update_outstanding_amounts(self):
self.set_missing_ref_details(force=True)
@@ -275,6 +278,14 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
.format(d.reference_name, dr_or_cr))
+ def set_status(self):
+ if self.docstatus == 2:
+ self.status = 'Cancelled'
+ elif self.docstatus == 1:
+ self.status = 'Submitted'
+ else:
+ self.status = 'Draft'
+
def set_amounts(self):
self.set_amounts_in_company_currency()
self.set_total_allocated_amount()
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 4665d75510..d85344e8b7 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -90,7 +90,8 @@ class PaymentReconciliation(Document):
FROM `tab{doc}`, `tabGL Entry`
WHERE
(`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no)
- and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)s
+ and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL
+ and `tabGL Entry`.against_voucher_type = %(voucher_type)s
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
GROUP BY `tab{doc}`.name
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index f4b656d3f6..e4e2c7b10f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -331,15 +331,15 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
})
},
- asset: function(frm, cdt, cdn) {
+ item_code: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
- if(row.asset) {
+ if(row.item_code) {
frappe.call({
method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account",
args: {
- "asset": row.asset,
+ "item": row.item_code,
"fieldname": "fixed_asset_account",
- "account": row.expense_account
+ "company": frm.doc.company
},
callback: function(r, rt) {
frappe.model.set_value(cdt, cdn, "expense_account", r.message);
@@ -430,19 +430,7 @@ cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn)
cur_frm.set_query("expense_account", "items", function(doc) {
return {
query: "erpnext.controllers.queries.get_expense_account",
- filters: {'company': doc.company}
- }
-});
-
-cur_frm.set_query("asset", "items", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- return {
- filters: {
- 'item_code': d.item_code,
- 'docstatus': 1,
- 'company': doc.company,
- 'status': 'Submitted'
- }
+ filters: {'company': doc.company }
}
});
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 4ea9b1c6c9..3bb3df8dbd 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -18,13 +18,14 @@ from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entri
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
-from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled
+from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
from frappe.model.mapper import get_mapped_doc
from six import iteritems
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
unlink_inter_company_doc
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from erpnext.accounts.deferred_revenue import validate_service_stop_date
+from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -97,7 +98,6 @@ class PurchaseInvoice(BuyingController):
self.set_against_expense_account()
self.validate_write_off_account()
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
- self.validate_fixed_asset()
self.create_remarks()
self.set_status()
self.validate_purchase_receipt_if_update_stock()
@@ -225,6 +225,8 @@ class PurchaseInvoice(BuyingController):
# in case of auto inventory accounting,
# expense account is always "Stock Received But Not Billed" for a stock item
# except epening entry, drop-ship entry and fixed asset items
+ if item.item_code:
+ asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if auto_accounting_for_stock and item.item_code in stock_items \
and self.is_opening == 'No' and not item.is_fixed_asset \
@@ -235,12 +237,8 @@ class PurchaseInvoice(BuyingController):
item.expense_account = warehouse_account[item.warehouse]["account"]
else:
item.expense_account = stock_not_billed_account
- elif item.is_fixed_asset and is_cwip_accounting_disabled():
- if not item.asset:
- frappe.throw(_("Row {0}: asset is required for item {1}")
- .format(item.idx, item.item_code))
-
- item.expense_account = get_asset_category_account(item.asset, 'fixed_asset_account',
+ elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
+ item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
company = self.company)
elif item.is_fixed_asset and item.pr_detail:
item.expense_account = asset_received_but_not_billed
@@ -391,7 +389,8 @@ class PurchaseInvoice(BuyingController):
self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries)
- if not is_cwip_accounting_disabled():
+
+ if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
@@ -404,6 +403,15 @@ class PurchaseInvoice(BuyingController):
return gl_entries
+ def check_asset_cwip_enabled(self):
+ # Check if there exists any item with cwip accounting enabled in it's asset category
+ for item in self.get("items"):
+ if item.item_code and item.is_fixed_asset:
+ asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
+ if is_cwip_accounting_enabled(asset_category):
+ return 1
+ return 0
+
def make_supplier_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
@@ -436,15 +444,23 @@ class PurchaseInvoice(BuyingController):
if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
+ landed_cost_entries = get_item_account_wise_additional_cost(self.name)
+
voucher_wise_stock_value = {}
if self.update_stock:
for d in frappe.get_all('Stock Ledger Entry',
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
+ valuation_tax_accounts = [d.account_head for d in self.get("taxes")
+ if d.category in ('Valuation', 'Total and Valuation')
+ and flt(d.base_tax_amount_after_discount_amount)]
+
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
+ if item.item_code:
+ asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
# warehouse account
@@ -463,15 +479,16 @@ class PurchaseInvoice(BuyingController):
)
# Amount added through landed-cost-voucher
- if flt(item.landed_cost_voucher_amount):
- gl_entries.append(self.get_gl_dict({
- "account": expenses_included_in_valuation,
- "against": item.expense_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(item.landed_cost_voucher_amount),
- "project": item.project
- }, item=item))
+ if landed_cost_entries:
+ for account, amount in iteritems(landed_cost_entries[(item.item_code, item.name)]):
+ gl_entries.append(self.get_gl_dict({
+ "account": account,
+ "against": item.expense_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(amount),
+ "project": item.project
+ }, item=item))
# sub-contracting warehouse
if flt(item.rm_supp_cost):
@@ -486,31 +503,61 @@ class PurchaseInvoice(BuyingController):
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.rm_supp_cost)
}, warehouse_account[self.supplier_warehouse]["account_currency"], item=item))
- elif not item.is_fixed_asset or (item.is_fixed_asset and is_cwip_accounting_disabled()):
+ elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)):
expense_account = (item.expense_account
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
- gl_entries.append(
- self.get_gl_dict({
+ if not item.is_fixed_asset:
+ amount = flt(item.base_net_amount, item.precision("base_net_amount"))
+ else:
+ amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
+
+ gl_entries.append(self.get_gl_dict({
"account": expense_account,
"against": self.supplier,
- "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
- "debit_in_account_currency": (flt(item.base_net_amount,
- item.precision("base_net_amount")) if account_currency==self.company_currency
- else flt(item.net_amount, item.precision("net_amount"))),
+ "debit": amount,
"cost_center": item.cost_center,
"project": item.project
- }, account_currency, item=item)
- )
+ }, account_currency, item=item))
+
+ # If asset is bought through this document and not linked to PR
+ if self.update_stock and item.landed_cost_voucher_amount:
+ expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
+ # Amount added through landed-cost-voucher
+ gl_entries.append(self.get_gl_dict({
+ "account": expenses_included_in_asset_valuation,
+ "against": expense_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(item.landed_cost_voucher_amount),
+ "project": item.project
+ }, item=item))
+
+ gl_entries.append(self.get_gl_dict({
+ "account": expense_account,
+ "against": expenses_included_in_asset_valuation,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": flt(item.landed_cost_voucher_amount),
+ "project": item.project
+ }, item=item))
+
+ # update gross amount of asset bought through this document
+ assets = frappe.db.get_all('Asset',
+ filters={ 'purchase_invoice': self.name, 'item_code': item.item_code }
+ )
+ for asset in assets:
+ frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
+ frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
if self.auto_accounting_for_stock and self.is_opening == "No" and \
item.item_code in stock_items and item.item_tax_amount:
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
- if item.purchase_receipt:
+ if item.purchase_receipt and valuation_tax_accounts:
negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry`
- where voucher_type='Purchase Receipt' and voucher_no=%s and account=%s""",
- (item.purchase_receipt, self.expenses_included_in_valuation))
+ where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""",
+ (item.purchase_receipt, valuation_tax_accounts))
if not negative_expense_booked_in_pr:
gl_entries.append(
@@ -527,27 +574,27 @@ class PurchaseInvoice(BuyingController):
item.precision("item_tax_amount"))
def get_asset_gl_entry(self, gl_entries):
+ arbnb_account = self.get_company_default("asset_received_but_not_billed")
+ eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
+
for item in self.get("items"):
if item.is_fixed_asset:
- eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
-
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
- if (not item.expense_account or frappe.db.get_value('Account',
- item.expense_account, 'account_type') not in ['Asset Received But Not Billed', 'Fixed Asset']):
- arbnb_account = self.get_company_default("asset_received_but_not_billed")
+ item_exp_acc_type = frappe.db.get_value('Account', item.expense_account, 'account_type')
+ if (not item.expense_account or item_exp_acc_type not in ['Asset Received But Not Billed', 'Fixed Asset']):
item.expense_account = arbnb_account
if not self.update_stock:
- asset_rbnb_currency = get_account_currency(item.expense_account)
+ arbnb_currency = get_account_currency(item.expense_account)
gl_entries.append(self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount
- if asset_rbnb_currency == self.company_currency else asset_amount),
+ if arbnb_currency == self.company_currency else asset_amount),
"cost_center": item.cost_center
}, item=item))
@@ -564,8 +611,7 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount / self.conversion_rate)
}, item=item))
else:
- cwip_account = get_asset_account("capital_work_in_progress_account",
- item.asset, company = self.company)
+ cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
cwip_account_currency = get_account_currency(cwip_account)
gl_entries.append(self.get_gl_dict({
@@ -591,6 +637,36 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount / self.conversion_rate)
}, item=item))
+ # When update stock is checked
+ # Assets are bought through this document then it will be linked to this document
+ if self.update_stock:
+ if flt(item.landed_cost_voucher_amount):
+ gl_entries.append(self.get_gl_dict({
+ "account": eiiav_account,
+ "against": cwip_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(item.landed_cost_voucher_amount),
+ "project": item.project
+ }, item=item))
+
+ gl_entries.append(self.get_gl_dict({
+ "account": cwip_account,
+ "against": eiiav_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": flt(item.landed_cost_voucher_amount),
+ "project": item.project
+ }, item=item))
+
+ # update gross amount of assets bought through this document
+ assets = frappe.db.get_all('Asset',
+ filters={ 'purchase_invoice': self.name, 'item_code': item.item_code }
+ )
+ for asset in assets:
+ frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
+ frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
+
return gl_entries
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
@@ -641,14 +717,14 @@ class PurchaseInvoice(BuyingController):
if account_currency==self.company_currency \
else tax.tax_amount_after_discount_amount,
"cost_center": tax.cost_center
- }, account_currency)
+ }, account_currency, item=tax)
)
# accumulate valuation tax
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
- valuation_tax.setdefault(tax.cost_center, 0)
- valuation_tax[tax.cost_center] += \
+ valuation_tax.setdefault(tax.name, 0)
+ valuation_tax[tax.name] += \
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount)
if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax:
@@ -658,36 +734,38 @@ class PurchaseInvoice(BuyingController):
total_valuation_amount = sum(valuation_tax.values())
amount_including_divisional_loss = self.negative_expense_to_be_booked
i = 1
- for cost_center, amount in iteritems(valuation_tax):
- if i == len(valuation_tax):
- applicable_amount = amount_including_divisional_loss
- else:
- applicable_amount = self.negative_expense_to_be_booked * (amount / total_valuation_amount)
- amount_including_divisional_loss -= applicable_amount
+ for tax in self.get("taxes"):
+ if valuation_tax.get(tax.name):
+ if i == len(valuation_tax):
+ applicable_amount = amount_including_divisional_loss
+ else:
+ applicable_amount = self.negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
+ amount_including_divisional_loss -= applicable_amount
- gl_entries.append(
- self.get_gl_dict({
- "account": self.expenses_included_in_valuation,
- "cost_center": cost_center,
- "against": self.supplier,
- "credit": applicable_amount,
- "remarks": self.remarks or "Accounting Entry for Stock"
- })
- )
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "against": self.supplier,
+ "credit": applicable_amount,
+ "remarks": self.remarks or _("Accounting Entry for Stock"),
+ }, item=tax)
+ )
- i += 1
+ i += 1
if self.auto_accounting_for_stock and self.update_stock and valuation_tax:
- for cost_center, amount in iteritems(valuation_tax):
- gl_entries.append(
- self.get_gl_dict({
- "account": self.expenses_included_in_valuation,
- "cost_center": cost_center,
- "against": self.supplier,
- "credit": amount,
- "remarks": self.remarks or "Accounting Entry for Stock"
- })
- )
+ for tax in self.get("taxes"):
+ if valuation_tax.get(tax.name):
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "against": self.supplier,
+ "credit": valuation_tax[tax.name],
+ "remarks": self.remarks or "Accounting Entry for Stock"
+ }, item=tax)
+ )
def make_payment_gl_entries(self, gl_entries):
# Make Cash GL Entries
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
index 4e76a8d955..800ed921bd 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
@@ -6,8 +6,8 @@ frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold"],
get_indicator: function(doc) {
- if(flt(doc.outstanding_amount) < 0 && doc.docstatus == 1) {
- return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<,0"]
+ if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
+ return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"];
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 6deee38148..e41ad42846 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -10,7 +10,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent
from frappe.utils import cint, flt, today, nowdate, add_days
import frappe.defaults
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
- test_records as pr_test_records
+ test_records as pr_test_records, make_purchase_receipt, get_taxes
from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.exceptions import InvalidCurrency
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
@@ -57,16 +57,11 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
def test_gl_entries_with_perpetual_inventory(self):
- pi = frappe.copy_doc(test_records[1])
- set_perpetual_inventory(1, pi.company)
+ pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10)
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
- pi.insert()
- pi.submit()
self.check_gle_for_pi(pi.name)
- set_perpetual_inventory(0, pi.company)
-
def test_terms_added_after_save(self):
pi = frappe.copy_doc(test_records[1])
pi.insert()
@@ -196,32 +191,33 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(pi.on_hold, 0)
def test_gl_entries_with_perpetual_inventory_against_pr(self):
- pr = frappe.copy_doc(pr_test_records[0])
- set_perpetual_inventory(1, pr.company)
- self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
- pr.submit()
- pi = frappe.copy_doc(test_records[1])
- for d in pi.get("items"):
+ pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,)
+
+ self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
+
+ pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True")
+
+ for d in pi.items:
d.purchase_receipt = pr.name
+
pi.insert()
pi.submit()
self.check_gle_for_pi(pi.name)
- set_perpetual_inventory(0, pr.company)
-
def check_gle_for_pi(self, pi):
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- order by account asc""", pi, as_dict=1)
+ group by account""", pi, as_dict=1)
+
self.assertTrue(gl_entries)
expected_values = dict((d[0], d) for d in [
- ["_Test Payable - _TC", 0, 720],
- ["Stock Received But Not Billed - _TC", 500.0, 0],
- ["_Test Account Shipping Charges - _TC", 100.0, 0],
- ["_Test Account VAT - _TC", 120.0, 0],
+ ["Creditors - TCP1", 0, 720],
+ ["Stock Received But Not Billed - TCP1", 500.0, 0],
+ ["_Test Account Shipping Charges - TCP1", 100.0, 0.0],
+ ["_Test Account VAT - TCP1", 120.0, 0]
])
for i, gle in enumerate(gl_entries):
@@ -524,10 +520,9 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertFalse(gle)
def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self):
- set_perpetual_inventory()
pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime())
+ posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
@@ -548,9 +543,9 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_gl_entries[gle.account][2], gle.credit)
def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self):
- set_perpetual_inventory()
+
pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - _TC", is_paid=1)
+ posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", is_paid=1, company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
gl_entries = frappe.db.sql("""select account, account_currency, sum(debit) as debit,
sum(credit) as credit, debit_in_account_currency, credit_in_account_currency
@@ -563,7 +558,7 @@ class TestPurchaseInvoice(unittest.TestCase):
expected_gl_entries = dict((d[0], d) for d in [
[pi.credit_to, 250.0, 250.0],
[stock_in_hand_account, 250.0, 0.0],
- ["Cash - _TC", 0.0, 250.0]
+ ["Cash - TCP1", 0.0, 250.0]
])
for i, gle in enumerate(gl_entries):
@@ -630,6 +625,7 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
def test_rejected_serial_no(self):
+ set_perpetual_inventory(0)
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
rejected_qty=1, rate=500, update_stock=1,
rejected_warehouse = "_Test Rejected Warehouse - _TC")
@@ -881,7 +877,7 @@ def make_purchase_invoice(**args):
pi.is_return = args.is_return
pi.return_against = args.return_against
pi.is_subcontracted = args.is_subcontracted or "No"
- pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
+ pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
pi.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
@@ -890,14 +886,21 @@ def make_purchase_invoice(**args):
"received_qty": args.received_qty or 0,
"rejected_qty": args.rejected_qty or 0,
"rate": args.rate or 50,
+ 'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
"conversion_factor": 1.0,
"serial_no": args.serial_no,
"stock_uom": "_Test UOM",
- "cost_center": "_Test Cost Center - _TC",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
"project": args.project,
"rejected_warehouse": args.rejected_warehouse or "",
"rejected_serial_no": args.rejected_serial_no or ""
})
+
+ if args.get_taxes_and_charges:
+ taxes = get_taxes()
+ for tax in taxes:
+ pi.append("taxes", tax)
+
if not args.do_not_save:
pi.insert()
if not args.do_not_submit:
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 3a19bb1b6b..27d8233a44 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -71,8 +71,8 @@
"expense_account",
"col_break5",
"is_fixed_asset",
- "asset",
"asset_location",
+ "asset_category",
"deferred_expense_section",
"deferred_expense_account",
"service_stop_date",
@@ -116,6 +116,8 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "item_code.item_name",
+ "fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Data",
"in_global_search": 1,
@@ -414,6 +416,7 @@
"print_hide": 1
},
{
+ "depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
@@ -425,12 +428,14 @@
"fieldtype": "Column Break"
},
{
+ "depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1
},
{
+ "depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "rejected_serial_no",
"fieldtype": "Text",
"label": "Rejected Serial No",
@@ -615,6 +620,7 @@
},
{
"default": "0",
+ "fetch_from": "item_code.is_fixed_asset",
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
"hidden": 1,
@@ -623,14 +629,6 @@
"print_hide": 1,
"read_only": 1
},
- {
- "depends_on": "is_fixed_asset",
- "fieldname": "asset",
- "fieldtype": "Link",
- "label": "Asset",
- "no_copy": 1,
- "options": "Asset"
- },
{
"depends_on": "is_fixed_asset",
"fieldname": "asset_location",
@@ -676,7 +674,7 @@
"fieldname": "pr_detail",
"fieldtype": "Data",
"hidden": 1,
- "label": "PR Detail",
+ "label": "Purchase Receipt Detail",
"no_copy": 1,
"oldfieldname": "pr_detail",
"oldfieldtype": "Data",
@@ -754,11 +752,21 @@
"fieldtype": "Data",
"label": "Manufacturer Part Number",
"read_only": 1
+ },
+ {
+ "depends_on": "is_fixed_asset",
+ "fetch_from": "item_code.asset_category",
+ "fieldname": "asset_category",
+ "fieldtype": "Data",
+ "in_preview": 1,
+ "label": "Asset Category",
+ "options": "Asset Category",
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-09-17 22:32:05.984240",
+ "modified": "2019-11-21 16:27:52.043744",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index 7d4fc63955..ed45b2cc2c 100755
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -402,14 +402,21 @@ def make_invoice(doc_list={}, email_queue_list={}, customers_list={}):
for docs in doc_list:
for name, doc in iteritems(docs):
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):
- validate_records(doc)
- si_doc = frappe.new_doc('Sales Invoice')
- si_doc.offline_pos_name = name
- si_doc.update(doc)
- si_doc.set_posting_time = 1
- si_doc.customer = get_customer_id(doc)
- si_doc.due_date = doc.get('posting_date')
- name_list = submit_invoice(si_doc, name, doc, name_list)
+ if isinstance(doc, dict):
+ validate_records(doc)
+ si_doc = frappe.new_doc('Sales Invoice')
+ si_doc.offline_pos_name = name
+ si_doc.update(doc)
+ si_doc.set_posting_time = 1
+ si_doc.customer = get_customer_id(doc)
+ si_doc.due_date = doc.get('posting_date')
+ name_list = submit_invoice(si_doc, name, doc, name_list)
+ else:
+ doc.due_date = doc.get('posting_date')
+ doc.customer = get_customer_id(doc)
+ doc.set_posting_time = 1
+ doc.offline_pos_name = name
+ name_list = submit_invoice(doc, name, doc, name_list)
else:
name_list.append(name)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e1256a78d9..70a80ca184 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -136,6 +136,16 @@ class SalesInvoice(SellingController):
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
validate_loyalty_points(self, self.loyalty_points)
+ def validate_fixed_asset(self):
+ for d in self.get("items"):
+ if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
+ asset = frappe.get_doc("Asset", d.asset)
+ if self.doctype == "Sales Invoice" and self.docstatus == 1:
+ if self.update_stock:
+ frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
+
+ elif asset.status in ("Scrapped", "Cancelled", "Sold"):
+ frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
def before_save(self):
set_account_for_mode_of_payment(self)
@@ -686,7 +696,6 @@ class SalesInvoice(SellingController):
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False):
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
-
if not gl_entries:
gl_entries = self.get_gl_entries()
@@ -992,10 +1001,8 @@ class SalesInvoice(SellingController):
continue
for serial_no in item.serial_no.split("\n"):
- if serial_no and frappe.db.exists('Serial No', serial_no):
- sno = frappe.get_doc('Serial No', serial_no)
- sno.sales_invoice = invoice
- sno.db_update()
+ if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
+ frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
def validate_serial_numbers(self):
"""
@@ -1041,8 +1048,9 @@ class SalesInvoice(SellingController):
continue
for serial_no in item.serial_no.split("\n"):
- sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice")
- if sales_invoice and self.name != sales_invoice:
+ sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no,
+ ["sales_invoice", "item_code"])
+ if sales_invoice and item_code == item.item_code and self.name != sales_invoice:
sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company")
if sales_invoice_company == self.company:
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"
@@ -1231,7 +1239,8 @@ class SalesInvoice(SellingController):
self.status = "Unpaid and Discounted"
elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()):
self.status = "Unpaid"
- elif flt(self.outstanding_amount) < 0 and self.is_return==0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
+ #Check if outstanding amount is 0 due to credit note issued against invoice
+ elif flt(self.outstanding_amount) <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Credit Note Issued"
elif self.is_return == 1:
self.status = "Return"
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index 9c8de7d5a2..ebe6e3da8d 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -68,8 +68,6 @@
"selling_price_list": "_Test Price List",
"territory": "_Test Territory"
},
-
-
{
"company": "_Test Company",
"conversion_rate": 1.0,
@@ -276,7 +274,6 @@
"uom": "_Test UOM 1",
"conversion_factor": 1,
"stock_uom": "_Test UOM 1"
-
},
{
"cost_center": "_Test Cost Center - _TC",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 4f253b69f7..530bd893c0 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -20,6 +20,9 @@ from erpnext.stock.doctype.item.test_item import create_item
from six import iteritems
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
from erpnext.regional.india.utils import get_ewb_data
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
class TestSalesInvoice(unittest.TestCase):
def make(self):
@@ -550,7 +553,6 @@ class TestSalesInvoice(unittest.TestCase):
si.get("taxes")[6].tax_amount = 2
si.insert()
- print(si.name)
expected_values = [
{
@@ -679,56 +681,67 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(gle)
def test_pos_gl_entry_with_perpetual_inventory(self):
- set_perpetual_inventory()
make_pos_profile()
- self._insert_purchase_receipt()
- pos = copy.deepcopy(test_records[1])
- pos["is_pos"] = 1
- pos["update_stock"] = 1
- pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300},
- {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300}]
+ pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
+
+ pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
+
+ pos.is_pos = 1
+ pos.update_stock = 1
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50})
+
+ taxes = get_taxes_and_charges()
+ pos.taxes = []
+ for tax in taxes:
+ pos.append("taxes", tax)
si = frappe.copy_doc(pos)
si.insert()
si.submit()
+ self.assertEqual(si.paid_amount, 100.0)
- self.assertEqual(si.paid_amount, 600.0)
-
- self.pos_gl_entry(si, pos, 300)
+ self.pos_gl_entry(si, pos, 50)
def test_pos_change_amount(self):
- set_perpetual_inventory()
make_pos_profile()
- self._insert_purchase_receipt()
- pos = copy.deepcopy(test_records[1])
- pos["is_pos"] = 1
- pos["update_stock"] = 1
- pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300},
- {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 340}]
+ pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
- si = frappe.copy_doc(pos)
- si.change_amount = 5.0
- si.insert()
- si.submit()
+ pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
- self.assertEqual(si.grand_total, 630.0)
- self.assertEqual(si.write_off_amount, -5)
+ pos.is_pos = 1
+ pos.update_stock = 1
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60})
+
+ pos.change_amount = 5.0
+ pos.insert()
+ pos.submit()
+
+ self.assertEqual(pos.grand_total, 100.0)
+ self.assertEqual(pos.write_off_amount, -5)
def test_make_pos_invoice(self):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
- set_perpetual_inventory()
-
make_pos_profile()
- self._insert_purchase_receipt()
+ pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
+ pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
- pos = copy.deepcopy(test_records[1])
- pos["is_pos"] = 1
- pos["update_stock"] = 1
- pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300},
- {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 330}]
+ pos.is_pos = 1
+ pos.update_stock = 1
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50})
+
+ taxes = get_taxes_and_charges()
+ pos.taxes = []
+ for tax in taxes:
+ pos.append("taxes", tax)
invoice_data = [{'09052016142': pos}]
si = make_invoice(invoice_data).get('invoice')
@@ -736,16 +749,15 @@ class TestSalesInvoice(unittest.TestCase):
sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': '09052016142', 'docstatus': 1})
si = frappe.get_doc('Sales Invoice', sales_invoice[0].name)
- self.assertEqual(si.grand_total, 630.0)
- self.pos_gl_entry(si, pos, 330)
+ self.assertEqual(si.grand_total, 100)
+
+ self.pos_gl_entry(si, pos, 50)
def test_make_pos_invoice_in_draft(self):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
from erpnext.stock.doctype.item.test_item import make_item
- set_perpetual_inventory()
-
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
if allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
@@ -789,7 +801,7 @@ class TestSalesInvoice(unittest.TestCase):
si.name, as_dict=1)[0]
self.assertTrue(sle)
self.assertEqual([sle.item_code, sle.warehouse, sle.actual_qty],
- ["_Test Item", "_Test Warehouse - _TC", -1.0])
+ ['_Test FG Item', 'Stores - TCP1', -1.0])
# check gl entries
gl_entries = frappe.db.sql("""select account, debit, credit
@@ -797,19 +809,19 @@ class TestSalesInvoice(unittest.TestCase):
order by account asc, debit asc, credit asc""", si.name, as_dict=1)
self.assertTrue(gl_entries)
- stock_in_hand = get_inventory_account('_Test Company')
-
+ stock_in_hand = get_inventory_account('_Test Company with perpetual inventory')
expected_gl_entries = sorted([
- [si.debit_to, 630.0, 0.0],
- [pos["items"][0]["income_account"], 0.0, 500.0],
- [pos["taxes"][0]["account_head"], 0.0, 80.0],
- [pos["taxes"][1]["account_head"], 0.0, 50.0],
+ [si.debit_to, 100.0, 0.0],
+ [pos.items[0].income_account, 0.0, 89.09],
+ ['Round Off - TCP1', 0.0, 0.01],
+ [pos.taxes[0].account_head, 0.0, 10.69],
+ [pos.taxes[1].account_head, 0.0, 0.21],
[stock_in_hand, 0.0, abs(sle.stock_value_difference)],
- [pos["items"][0]["expense_account"], abs(sle.stock_value_difference), 0.0],
- [si.debit_to, 0.0, 300.0],
+ [pos.items[0].expense_account, abs(sle.stock_value_difference), 0.0],
+ [si.debit_to, 0.0, 50.0],
[si.debit_to, 0.0, cash_amount],
- ["_Test Bank - _TC", 300.0, 0.0],
- ["Cash - _TC", cash_amount, 0.0]
+ ["_Test Bank - TCP1", 50, 0.0],
+ ["Cash - TCP1", cash_amount, 0.0]
])
for i, gle in enumerate(sorted(gl_entries, key=lambda gle: gle.account)):
@@ -823,9 +835,9 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(gle)
- set_perpetual_inventory(0)
frappe.db.sql("delete from `tabPOS Profile`")
+ si.delete()
def test_pos_si_without_payment(self):
set_perpetual_inventory()
@@ -1008,7 +1020,6 @@ class TestSalesInvoice(unittest.TestCase):
"""
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
- from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item()
@@ -1023,14 +1034,17 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.get("items")[0].serial_no, dn.get("items")[0].serial_no)
def test_return_sales_invoice(self):
- set_perpetual_inventory()
- make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
+ make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
- actual_qty_0 = get_qty_after_transaction()
+ actual_qty_0 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
- si = create_sales_invoice(qty=5, rate=500, update_stock=1)
+ si = create_sales_invoice(qty = 5, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1")
+
+
+ actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
+
+ frappe.db.commit()
- actual_qty_1 = get_qty_after_transaction()
self.assertEqual(actual_qty_0 - 5, actual_qty_1)
# outgoing_rate
@@ -1038,10 +1052,9 @@ class TestSalesInvoice(unittest.TestCase):
"voucher_no": si.name}, "stock_value_difference") / 5
# return entry
- si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1)
-
- actual_qty_2 = get_qty_after_transaction()
+ si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1")
+ actual_qty_2 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
self.assertEqual(actual_qty_1 + 2, actual_qty_2)
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
@@ -1049,7 +1062,7 @@ class TestSalesInvoice(unittest.TestCase):
["incoming_rate", "stock_value_difference"])
self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
- stock_in_hand_account = get_inventory_account('_Test Company', si1.items[0].warehouse)
+ stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory', si1.items[0].warehouse)
# Check gl entry
gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
@@ -1058,7 +1071,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(gle_warehouse_amount, stock_value_difference)
party_credited = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
- "voucher_no": si1.name, "account": "Debtors - _TC", "party": "_Test Customer"}, "credit")
+ "voucher_no": si1.name, "account": "Debtors - TCP1", "party": "_Test Customer"}, "credit")
self.assertEqual(party_credited, 1000)
@@ -1066,7 +1079,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(si1.outstanding_amount)
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
- set_perpetual_inventory(0)
def test_discount_on_net_total(self):
si = frappe.copy_doc(test_records[2])
@@ -1524,6 +1536,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.total_taxes_and_charges, 577.05)
self.assertEqual(si.grand_total, 1827.05)
+
+
def test_create_invoice_without_terms(self):
si = create_sales_invoice(do_not_save=1)
self.assertFalse(si.get('payment_schedule'))
@@ -1930,4 +1944,29 @@ def get_outstanding_amount(against_voucher_type, against_voucher, account, party
if against_voucher_type == 'Purchase Invoice':
bal = bal * -1
- return bal
\ No newline at end of file
+ return bal
+
+def get_taxes_and_charges():
+ return [{
+ "account_head": "_Test Account Excise Duty - TCP1",
+ "charge_type": "On Net Total",
+ "cost_center": "Main - TCP1",
+ "description": "Excise Duty",
+ "doctype": "Sales Taxes and Charges",
+ "idx": 1,
+ "included_in_print_rate": 1,
+ "parentfield": "taxes",
+ "rate": 12
+ },
+ {
+ "account_head": "_Test Account Education Cess - TCP1",
+ "charge_type": "On Previous Row Amount",
+ "cost_center": "Main - TCP1",
+ "description": "Education Cess",
+ "doctype": "Sales Taxes and Charges",
+ "idx": 2,
+ "included_in_print_rate": 1,
+ "parentfield": "taxes",
+ "rate": 2,
+ "row_id": 1
+ }]
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.js b/erpnext/accounts/doctype/share_transfer/share_transfer.js
index 364ca6fd28..1cad4dfae3 100644
--- a/erpnext/accounts/doctype/share_transfer/share_transfer.js
+++ b/erpnext/accounts/doctype/share_transfer/share_transfer.js
@@ -21,6 +21,8 @@ frappe.ui.form.on('Share Transfer', {
erpnext.share_transfer.make_jv(frm);
});
}
+
+ frm.toggle_reqd("asset_account", frm.doc.transfer_type != "Transfer");
},
no_of_shares: (frm) => {
if (frm.doc.rate != undefined || frm.doc.rate != null){
@@ -56,6 +58,10 @@ frappe.ui.form.on('Share Transfer', {
};
});
}
+ },
+
+ transfer_type: function(frm) {
+ frm.toggle_reqd("asset_account", frm.doc.transfer_type != "Transfer");
}
});
diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.json b/erpnext/accounts/doctype/share_transfer/share_transfer.json
index 24c4569b00..f17bf04caf 100644
--- a/erpnext/accounts/doctype/share_transfer/share_transfer.json
+++ b/erpnext/accounts/doctype/share_transfer/share_transfer.json
@@ -1,881 +1,239 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "ACC-SHT-.YYYY.-.#####",
- "beta": 0,
- "creation": "2017-12-25 17:18:03.143726",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "autoname": "ACC-SHT-.YYYY.-.#####",
+ "creation": "2017-12-25 17:18:03.143726",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "transfer_type",
+ "column_break_1",
+ "date",
+ "section_break_1",
+ "from_shareholder",
+ "from_folio_no",
+ "column_break_3",
+ "to_shareholder",
+ "to_folio_no",
+ "section_break_10",
+ "equity_or_liability_account",
+ "column_break_12",
+ "asset_account",
+ "section_break_4",
+ "share_type",
+ "from_no",
+ "rate",
+ "column_break_8",
+ "no_of_shares",
+ "to_no",
+ "amount",
+ "section_break_11",
+ "company",
+ "section_break_6",
+ "remarks",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "transfer_type",
- "fieldtype": "Select",
- "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": "Transfer Type",
- "length": 0,
- "no_copy": 0,
- "options": "\nIssue\nPurchase\nTransfer",
- "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
- },
+ "fieldname": "transfer_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Transfer Type",
+ "options": "\nIssue\nPurchase\nTransfer",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_1",
- "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
- },
+ "fieldname": "column_break_1",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "date",
- "fieldtype": "Date",
- "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": "Date",
- "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
- },
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "label": "Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_1",
- "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
- },
+ "fieldname": "section_break_1",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.transfer_type != 'Issue'",
- "fieldname": "from_shareholder",
- "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": "From Shareholder",
- "length": 0,
- "no_copy": 0,
- "options": "Shareholder",
- "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
- },
+ "depends_on": "eval:doc.transfer_type != 'Issue'",
+ "fieldname": "from_shareholder",
+ "fieldtype": "Link",
+ "label": "From Shareholder",
+ "options": "Shareholder"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.transfer_type != 'Issue'",
- "fetch_from": "from_shareholder.folio_no",
- "fieldname": "from_folio_no",
- "fieldtype": "Data",
- "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": "From Folio No",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "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
- },
+ "depends_on": "eval:doc.transfer_type != 'Issue'",
+ "fetch_from": "from_shareholder.folio_no",
+ "fieldname": "from_folio_no",
+ "fieldtype": "Data",
+ "label": "From Folio No"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.company",
- "fieldname": "equity_or_liability_account",
- "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": "Equity/Liability Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "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
- },
+ "depends_on": "eval:doc.company",
+ "fieldname": "equity_or_liability_account",
+ "fieldtype": "Link",
+ "label": "Equity/Liability Account",
+ "options": "Account",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:(doc.transfer_type != 'Transfer') && (doc.company)",
- "fieldname": "asset_account",
- "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": "Asset Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "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
- },
+ "depends_on": "eval:(doc.transfer_type != 'Transfer') && (doc.company)",
+ "fieldname": "asset_account",
+ "fieldtype": "Link",
+ "label": "Asset Account",
+ "options": "Account"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "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
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.transfer_type != 'Purchase'",
- "fieldname": "to_shareholder",
- "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": "To Shareholder",
- "length": 0,
- "no_copy": 0,
- "options": "Shareholder",
- "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
- },
+ "depends_on": "eval:doc.transfer_type != 'Purchase'",
+ "fieldname": "to_shareholder",
+ "fieldtype": "Link",
+ "label": "To Shareholder",
+ "options": "Shareholder"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.transfer_type != 'Purchase'",
- "fetch_from": "to_shareholder.folio_no",
- "fieldname": "to_folio_no",
- "fieldtype": "Data",
- "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": "To Folio No",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "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
- },
+ "depends_on": "eval:doc.transfer_type != 'Purchase'",
+ "fetch_from": "to_shareholder.folio_no",
+ "fieldname": "to_folio_no",
+ "fieldtype": "Data",
+ "label": "To Folio No"
+ },
{
- "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
- },
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "share_type",
- "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": "Share Type",
- "length": 0,
- "no_copy": 0,
- "options": "Share Type",
- "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
- },
+ "fieldname": "share_type",
+ "fieldtype": "Link",
+ "label": "Share Type",
+ "options": "Share Type",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "(including)",
- "fieldname": "from_no",
- "fieldtype": "Int",
- "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": "From No",
- "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
- },
+ "description": "(including)",
+ "fieldname": "from_no",
+ "fieldtype": "Int",
+ "label": "From No",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Rate",
- "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
- },
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "label": "Rate",
+ "reqd": 1
+ },
{
- "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
- },
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "no_of_shares",
- "fieldtype": "Int",
- "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": "No of Shares",
- "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
- },
+ "fieldname": "no_of_shares",
+ "fieldtype": "Int",
+ "label": "No of Shares",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "(including)",
- "fieldname": "to_no",
- "fieldtype": "Int",
- "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": "To No",
- "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
- },
+ "description": "(including)",
+ "fieldname": "to_no",
+ "fieldtype": "Int",
+ "label": "To No",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amount",
- "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
- },
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "label": "Amount",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_11",
- "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
- },
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "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": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "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
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "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
- },
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "remarks",
- "fieldtype": "Long Text",
- "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": "Remarks",
- "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
- },
+ "fieldname": "remarks",
+ "fieldtype": "Long Text",
+ "label": "Remarks"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "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": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Share Transfer",
- "permlevel": 0,
- "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
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Share Transfer",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_10",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-09-18 14:14:46.233568",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Share Transfer",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "modified": "2019-11-07 13:31:17.999744",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Share Transfer",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "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_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
index 582ecb2e16..abc6ab82d3 100644
--- a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
@@ -14,13 +14,13 @@ class TestShippingRule(unittest.TestCase):
shipping_rule.name = test_records[0].get('name')
shipping_rule.get("conditions")[0].from_value = 101
self.assertRaises(FromGreaterThanToError, shipping_rule.insert)
-
+
def test_many_zero_to_values(self):
shipping_rule = frappe.copy_doc(test_records[0])
shipping_rule.name = test_records[0].get('name')
shipping_rule.get("conditions")[0].to_value = 0
self.assertRaises(ManyBlankToValuesError, shipping_rule.insert)
-
+
def test_overlapping_conditions(self):
for range_a, range_b in [
((50, 150), (0, 100)),
@@ -38,6 +38,10 @@ class TestShippingRule(unittest.TestCase):
self.assertRaises(OverlappingConditionError, shipping_rule.insert)
def create_shipping_rule(shipping_rule_type, shipping_rule_name):
+
+ if frappe.db.exists("Shipping Rule", shipping_rule_name):
+ return frappe.get_doc("Shipping Rule", shipping_rule_name)
+
sr = frappe.new_doc("Shipping Rule")
sr.account = "_Test Account Shipping Charges - _TC"
sr.calculate_based_on = "Net Total"
@@ -70,4 +74,4 @@ def create_shipping_rule(shipping_rule_type, shipping_rule_name):
})
sr.insert(ignore_permissions=True)
sr.submit()
- return sr
+ return sr
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 5c9e93d019..2ba319d05e 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -3,8 +3,9 @@
from __future__ import unicode_literals
import frappe, erpnext
-from frappe.utils import flt, cstr, cint
+from frappe.utils import flt, cstr, cint, comma_and
from frappe import _
+from erpnext.accounts.utils import get_stock_and_account_balance
from frappe.model.meta import get_field_precision
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
@@ -12,6 +13,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass
+class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
if gl_map:
@@ -115,11 +117,9 @@ def check_if_in_list(gle, gl_map, dimensions=None):
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
- validate_account_for_perpetual_inventory(gl_map)
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map)
-
for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding, from_repost)
@@ -127,6 +127,10 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_expense_against_budget(entry)
+ if not from_repost:
+ validate_account_for_perpetual_inventory(gl_map)
+
+
def make_entry(args, adv_adj, update_outstanding, from_repost=False):
args.update({"doctype": "GL Entry"})
gle = frappe.get_doc(args)
@@ -137,25 +141,66 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle.submit()
def validate_account_for_perpetual_inventory(gl_map):
- if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)) \
- and gl_map[0].voucher_type=="Journal Entry":
- aii_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
- where account_type = 'Stock' and is_group=0""")]
+ if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
+ account_list = [gl_entries.account for gl_entries in gl_map]
- for entry in gl_map:
- if entry.account in aii_accounts:
+ aii_accounts = [d.name for d in frappe.get_all("Account",
+ filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})]
+
+ for account in account_list:
+ if account not in aii_accounts:
+ continue
+
+ account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
+ gl_map[0].posting_date, gl_map[0].company)
+
+ if gl_map[0].voucher_type=="Journal Entry":
+ # In case of Journal Entry, there are no corresponding SL entries,
+ # hence deducting currency amount
+ account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit)
+ if account_bal == stock_bal:
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
- .format(entry.account), StockAccountInvalidTransaction)
+ .format(account), StockAccountInvalidTransaction)
+
+ elif account_bal != stock_bal:
+ precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
+ currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
+
+ diff = flt(stock_bal - account_bal, precision)
+ error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
+ stock_bal, account_bal, frappe.bold(account))
+ error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
+ stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
+
+ db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
+ db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
+
+ journal_entry_args = {
+ 'accounts':[
+ {'account': account, db_or_cr_warehouse_account : abs(diff)},
+ {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
+ }
+
+ frappe.msgprint(msg="""{0}
{1}
""".format(error_reason, error_resolution),
+ raise_exception=StockValueAndAccountBalanceOutOfSync,
+ title=_('Values Out Of Sync'),
+ primary_action={
+ 'label': _('Make Journal Entry'),
+ 'client_action': 'erpnext.route_to_adjustment_jv',
+ 'args': journal_entry_args
+ })
def validate_cwip_accounts(gl_map):
- if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \
- and gl_map[0].voucher_type == "Journal Entry":
+ cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
+
+ if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
where account_type = 'Capital Work in Progress' and is_group=0""")]
for entry in gl_map:
if entry.account in cwip_accounts:
- frappe.throw(_("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
+ frappe.throw(
+ _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
def round_off_debit_credit(gl_map):
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
index 6eafa0d231..efc76f9158 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
@@ -139,15 +139,11 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
}
make() {
- const me = this;
- frappe.upload.make({
- args: {
- method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
- allow_multiple: 0
- },
- no_socketio: true,
- sample_url: "e.g. http://example.com/somefile.csv",
- callback: function(attachment, r) {
+ const me = this;
+ new frappe.ui.FileUploader({
+ method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
+ allow_multiple: 0,
+ on_success: function(attachment, r) {
if (!r.exc && r.message) {
me.data = r.message;
me.setup_transactions_dom();
@@ -533,9 +529,16 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
frappe.db.get_doc(dt, event.value)
.then(doc => {
let displayed_docs = []
+ let payment = []
if (dt === "Payment Entry") {
payment.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency;
payment.doctype = dt
+ payment.posting_date = doc.posting_date;
+ payment.party = doc.party;
+ payment.reference_no = doc.reference_no;
+ payment.reference_date = doc.reference_date;
+ payment.paid_amount = doc.paid_amount;
+ payment.name = doc.name;
displayed_docs.push(payment);
} else if (dt === "Journal Entry") {
doc.accounts.forEach(payment => {
@@ -568,11 +571,11 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper;
details_wrapper.append(frappe.render_template("linked_payment_header"));
- displayed_docs.forEach(values => {
- details_wrapper.append(frappe.render_template("linked_payment_row", values));
+ displayed_docs.forEach(payment => {
+ details_wrapper.append(frappe.render_template("linked_payment_row", payment));
})
})
}
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 228be18d21..9b4dda2f69 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -79,13 +79,20 @@ frappe.query_reports["Accounts Receivable"] = {
"options": "Customer",
on_change: () => {
var customer = frappe.query_report.get_filter_value('customer');
+ var company = frappe.query_report.get_filter_value('company');
if (customer) {
- frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "credit_limit", "payment_terms"], function(value) {
+ frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "payment_terms"], function(value) {
frappe.query_report.set_filter_value('tax_id', value["tax_id"]);
frappe.query_report.set_filter_value('customer_name', value["customer_name"]);
- frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]);
frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]);
});
+
+ frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company},
+ ["credit_limit"], function(value) {
+ if (value) {
+ frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]);
+ }
+ }, "Customer");
} else {
frappe.query_report.set_filter_value('tax_id', "");
frappe.query_report.set_filter_value('customer_name', "");
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index bcbd427186..14906f2c2e 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -188,7 +188,11 @@ class ReceivablePayableReport(object):
self.data.append(row)
def set_invoice_details(self, row):
- row.update(self.invoice_details.get(row.voucher_no, {}))
+ invoice_details = self.invoice_details.get(row.voucher_no, {})
+ if row.due_date:
+ invoice_details.pop("due_date", None)
+ row.update(invoice_details)
+
if row.voucher_type == 'Sales Invoice':
if self.filters.show_delivery_notes:
self.set_delivery_notes(row)
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index b90a7a9501..8955830e09 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -36,6 +36,9 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.filters.report_date) or {}
for party, party_dict in iteritems(self.party_total):
+ if party_dict.outstanding <= 0:
+ continue
+
row = frappe._dict()
row.party = party
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js
index 4bc29da2c7..8c11514aa6 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.js
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js
@@ -2,7 +2,7 @@
// License: GNU General Public License v3. See license.txt
frappe.require("assets/erpnext/js/financial_statements.js", function() {
- frappe.query_reports["Balance Sheet"] = erpnext.financial_statements;
+ frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements);
frappe.query_reports["Balance Sheet"]["filters"].push({
"fieldname": "accumulated_values",
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
index 146c10c222..8d65ac8714 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -69,7 +69,7 @@ def get_columns(filters):
for year in fiscal_year:
for from_date, to_date in get_period_date_ranges(filters["period"], year[0]):
if filters["period"] == "Yearly":
- labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Varaiance ") + " " + str(year[0])]
+ labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Variance ") + " " + str(year[0])]
for label in labels:
columns.append(label+":Float:150")
else:
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 10e977acbf..faeee0f76a 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -76,8 +76,7 @@ def get_data(filters):
accumulate_values_into_parents(accounts, accounts_by_name)
data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency)
- data = filter_out_zero_value_rows(data, parent_children_map,
- show_zero_values=filters.get("show_zero_values"))
+ data = filter_out_zero_value_rows(data, parent_children_map, show_zero_values=filters.get("show_zero_values"))
return data
@@ -187,33 +186,11 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters,
d["closing_debit"] = d["opening_debit"] + d["debit"]
d["closing_credit"] = d["opening_credit"] + d["credit"]
- total_row["debit"] += d["debit"]
- total_row["credit"] += d["credit"]
- if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense":
- d["opening_debit"] -= d["opening_credit"]
- d["closing_debit"] -= d["closing_credit"]
+ prepare_opening_closing(d)
- # For opening
- check_opening_closing_has_negative_value(d, "opening_debit", "opening_credit")
-
- # For closing
- check_opening_closing_has_negative_value(d, "closing_debit", "closing_credit")
-
- if d["root_type"] == "Liability" or d["root_type"] == "Income":
- d["opening_credit"] -= d["opening_debit"]
- d["closing_credit"] -= d["closing_debit"]
-
- # For opening
- check_opening_closing_has_negative_value(d, "opening_credit", "opening_debit")
-
- # For closing
- check_opening_closing_has_negative_value(d, "closing_credit", "closing_debit")
-
- total_row["opening_debit"] += d["opening_debit"]
- total_row["closing_debit"] += d["closing_debit"]
- total_row["opening_credit"] += d["opening_credit"]
- total_row["closing_credit"] += d["closing_credit"]
+ for field in value_fields:
+ total_row[field] += d[field]
return total_row
@@ -227,6 +204,10 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr
data = []
for d in accounts:
+ # Prepare opening closing for group account
+ if parent_children_map.get(d.account):
+ prepare_opening_closing(d)
+
has_value = False
row = {
"account": d.name,
@@ -313,11 +294,16 @@ def get_columns():
}
]
-def check_opening_closing_has_negative_value(d, dr_or_cr, switch_to_column):
- # If opening debit has negetive value then move it to opening credit and vice versa.
+def prepare_opening_closing(row):
+ dr_or_cr = "debit" if row["root_type"] in ["Asset", "Equity", "Expense"] else "credit"
+ reverse_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
- if d[dr_or_cr] < 0:
- d[switch_to_column] = abs(d[dr_or_cr])
- d[dr_or_cr] = 0.0
- else:
- d[switch_to_column] = 0.0
+ for col_type in ["opening", "closing"]:
+ valid_col = col_type + "_" + dr_or_cr
+ reverse_col = col_type + "_" + reverse_dr_or_cr
+ row[valid_col] -= row[reverse_col]
+ if row[valid_col] < 0:
+ row[reverse_col] = abs(row[valid_col])
+ row[valid_col] = 0.0
+ else:
+ row[reverse_col] = 0.0
\ No newline at end of file
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index ac69fd3c96..94697be02f 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -13,6 +13,10 @@ from six import iteritems
# imported to enable erpnext.accounts.utils.get_account_currency
from erpnext.accounts.doctype.account.account import get_account_currency
+from erpnext.stock.utils import get_stock_value_on
+from erpnext.stock import get_warehouse_account_map
+
+
class FiscalYearError(frappe.ValidationError): pass
@frappe.whitelist()
@@ -560,23 +564,23 @@ 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, company=None):
- from erpnext.stock.utils import get_stock_value_on
- from erpnext.stock import get_warehouse_account_map
-
+def get_stock_and_account_balance(account=None, posting_date=None, company=None):
if not posting_date: posting_date = nowdate()
- difference = {}
warehouse_account = get_warehouse_account_map(company)
- for warehouse, account_data in iteritems(warehouse_account):
- if account_data.get('account') in account_list:
- account_balance = get_balance_on(account_data.get('account'), posting_date, in_account_currency=False)
- stock_value = get_stock_value_on(warehouse, posting_date)
- if abs(flt(stock_value) - flt(account_balance)) > 0.005:
- difference.setdefault(account_data.get('account'), flt(stock_value) - flt(account_balance))
+ account_balance = get_balance_on(account, posting_date, in_account_currency=False)
- return difference
+ related_warehouses = [wh for wh, wh_details in warehouse_account.items()
+ if wh_details.account == account and not wh_details.is_group]
+
+ total_stock_value = 0.0
+ for warehouse in related_warehouses:
+ value = get_stock_value_on(warehouse, posting_date)
+ total_stock_value += value
+
+ precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
+ return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
def get_currency_precision():
precision = cint(frappe.db.get_default("currency_precision"))
@@ -626,7 +630,7 @@ def get_held_invoices(party_type, party):
'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
as_dict=1
)
- held_invoices = [d['name'] for d in held_invoices]
+ held_invoices = set([d['name'] for d in held_invoices])
return held_invoices
@@ -635,14 +639,19 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
outstanding_invoices = []
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
- if erpnext.get_party_account_type(party_type) == 'Receivable':
+ if account:
+ root_type = frappe.get_cached_value("Account", account, "root_type")
+ party_account_type = "Receivable" if root_type == "Asset" else "Payable"
+ else:
+ party_account_type = erpnext.get_party_account_type(party_type)
+
+ if party_account_type == 'Receivable':
dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
else:
dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
payment_dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
- invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice'
held_invoices = get_held_invoices(party_type, party)
invoice_list = frappe.db.sql("""
@@ -661,7 +670,6 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
group by voucher_type, voucher_no
order by posting_date, name""".format(
dr_or_cr=dr_or_cr,
- invoice = invoice,
condition=condition or ""
), {
"party_type": party_type,
diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
index bb9045ca81..3e51933df7 100644
--- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
+++ b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
@@ -51,27 +51,25 @@ class CropCycle(Document):
self.create_task(disease_doc.treatment_task, self.name, start_date)
def create_project(self, period, crop_tasks):
- project = frappe.new_doc("Project")
- project.update({
+ project = frappe.get_doc({
+ "doctype": "Project",
"project_name": self.title,
"expected_start_date": self.start_date,
"expected_end_date": add_days(self.start_date, period - 1)
- })
- project.insert()
+ }).insert()
return project.name
def create_task(self, crop_tasks, project_name, start_date):
for crop_task in crop_tasks:
- task = frappe.new_doc("Task")
- task.update({
+ frappe.get_doc({
+ "doctype": "Task",
"subject": crop_task.get("task_name"),
"priority": crop_task.get("priority"),
"project": project_name,
"exp_start_date": add_days(start_date, crop_task.get("start_day") - 1),
"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1)
- })
- task.insert()
+ }).insert()
def reload_linked_analysis(self):
linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis']
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index c5cad73801..6b3f2c777c 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -41,6 +41,39 @@ frappe.ui.form.on('Asset', {
});
},
+ setup: function(frm) {
+ frm.make_methods = {
+ 'Asset Movement': () => {
+ frappe.call({
+ method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
+ freeze: true,
+ args:{
+ "assets": [{ name: cur_frm.doc.name }]
+ },
+ callback: function (r) {
+ if (r.message) {
+ var doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
+ },
+ }
+
+ frm.set_query("purchase_receipt", (doc) => {
+ return {
+ query: "erpnext.controllers.queries.get_purchase_receipts",
+ filters: { item_code: doc.item_code }
+ }
+ });
+ frm.set_query("purchase_invoice", (doc) => {
+ return {
+ query: "erpnext.controllers.queries.get_purchase_invoices",
+ filters: { item_code: doc.item_code }
+ }
+ });
+ },
+
refresh: function(frm) {
frappe.ui.form.trigger("Asset", "is_existing_asset");
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
@@ -78,11 +111,6 @@ frappe.ui.form.on('Asset', {
});
}
- if (frm.doc.status=='Submitted' && !frm.doc.is_existing_asset && !frm.doc.purchase_invoice) {
- frm.add_custom_button(__("Purchase Invoice"), function() {
- frm.trigger("make_purchase_invoice");
- }, __('Create'));
- }
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
frm.add_custom_button(__("Asset Maintenance"), function() {
frm.trigger("create_asset_maintenance");
@@ -104,11 +132,36 @@ frappe.ui.form.on('Asset', {
frm.trigger("setup_chart");
}
+ frm.trigger("toggle_reference_doc");
+
if (frm.doc.docstatus == 0) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
}
},
+ toggle_reference_doc: function(frm) {
+ if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
+ frm.set_df_property('purchase_invoice', 'read_only', 1);
+ frm.set_df_property('purchase_receipt', 'read_only', 1);
+ }
+ else if (frm.doc.purchase_receipt) {
+ // if purchase receipt link is set then set PI disabled
+ frm.toggle_reqd('purchase_invoice', 0);
+ frm.set_df_property('purchase_invoice', 'read_only', 1);
+ }
+ else if (frm.doc.purchase_invoice) {
+ // if purchase invoice link is set then set PR disabled
+ frm.toggle_reqd('purchase_receipt', 0);
+ frm.set_df_property('purchase_receipt', 'read_only', 1);
+ }
+ else {
+ frm.toggle_reqd('purchase_receipt', 1);
+ frm.set_df_property('purchase_receipt', 'read_only', 0);
+ frm.toggle_reqd('purchase_invoice', 1);
+ frm.set_df_property('purchase_invoice', 'read_only', 0);
+ }
+ },
+
make_journal_entry: function(frm) {
frappe.call({
method: "erpnext.assets.doctype.asset.asset.make_journal_entry",
@@ -176,21 +229,25 @@ frappe.ui.form.on('Asset', {
item_code: function(frm) {
if(frm.doc.item_code) {
- frappe.call({
- method: "erpnext.assets.doctype.asset.asset.get_item_details",
- args: {
- item_code: frm.doc.item_code,
- asset_category: frm.doc.asset_category
- },
- callback: function(r, rt) {
- if(r.message) {
- frm.set_value('finance_books', r.message);
- }
- }
- })
+ frm.trigger('set_finance_book');
}
},
+ set_finance_book: function(frm) {
+ frappe.call({
+ method: "erpnext.assets.doctype.asset.asset.get_item_details",
+ args: {
+ item_code: frm.doc.item_code,
+ asset_category: frm.doc.asset_category
+ },
+ callback: function(r, rt) {
+ if(r.message) {
+ frm.set_value('finance_books', r.message);
+ }
+ }
+ })
+ },
+
available_for_use_date: function(frm) {
$.each(frm.doc.finance_books || [], function(i, d) {
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
@@ -203,33 +260,18 @@ frappe.ui.form.on('Asset', {
},
opening_accumulated_depreciation: function(frm) {
- erpnext.asset.set_accululated_depreciation(frm);
+ erpnext.asset.set_accumulated_depreciation(frm);
},
make_schedules_editable: function(frm) {
- var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
- ? true : false;
+ if (frm.doc.finance_books) {
+ 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);
- },
-
- make_purchase_invoice: function(frm) {
- frappe.call({
- args: {
- "asset": frm.doc.name,
- "item_code": frm.doc.item_code,
- "gross_purchase_amount": frm.doc.gross_purchase_amount,
- "company": frm.doc.company,
- "posting_date": frm.doc.purchase_date
- },
- method: "erpnext.assets.doctype.asset.asset.make_purchase_invoice",
- callback: function(r) {
- var doclist = frappe.model.sync(r.message);
- frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
- }
- })
+ 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);
+ }
},
make_sales_invoice: function(frm) {
@@ -282,17 +324,6 @@ frappe.ui.form.on('Asset', {
},
calculate_depreciation: function(frm) {
- frappe.db.get_value("Asset Settings", {'name':"Asset Settings"}, 'schedule_based_on_fiscal_year', (data) => {
- if (data.schedule_based_on_fiscal_year == 1) {
- frm.set_df_property("depreciation_method", "options", "\nStraight Line\nManual");
- frm.toggle_reqd("available_for_use_date", true);
- frm.toggle_display("frequency_of_depreciation", false);
- frappe.db.get_value("Fiscal Year", {'name': frappe.sys_defaults.fiscal_year}, "year_end_date", (data) => {
- frm.set_value("next_depreciation_date", data.year_end_date);
- })
- }
- })
-
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
},
@@ -302,6 +333,65 @@ frappe.ui.form.on('Asset', {
})
},
+ purchase_receipt: function(frm) {
+ frm.trigger('toggle_reference_doc');
+
+ if (frm.doc.purchase_receipt) {
+ if (frm.doc.item_code) {
+ frappe.db.get_doc('Purchase Receipt', frm.doc.purchase_receipt).then(pr_doc => {
+ frm.set_value('company', pr_doc.company);
+ frm.set_value('purchase_date', pr_doc.posting_date);
+ const item = pr_doc.items.find(item => item.item_code === frm.doc.item_code);
+ if (!item) {
+ frm.set_value('purchase_receipt', '');
+ frappe.msgprint({
+ title: __('Invalid Purchase Receipt'),
+ message: __("The selected Purchase Receipt doesn't contains selected Asset Item."),
+ indicator: 'red'
+ });
+ }
+ frm.set_value('gross_purchase_amount', item.base_net_rate);
+ frm.set_value('location', item.asset_location);
+ });
+ } else {
+ frm.set_value('purchase_receipt', '');
+ frappe.msgprint({
+ title: __('Not Allowed'),
+ message: __("Please select Item Code first")
+ });
+ }
+ }
+ },
+
+ purchase_invoice: function(frm) {
+ frm.trigger('toggle_reference_doc');
+ if (frm.doc.purchase_invoice) {
+ if (frm.doc.item_code) {
+ frappe.db.get_doc('Purchase Invoice', frm.doc.purchase_invoice).then(pi_doc => {
+ frm.set_value('company', pi_doc.company);
+ frm.set_value('purchase_date', pi_doc.posting_date);
+ const item = pi_doc.items.find(item => item.item_code === frm.doc.item_code);
+ if (!item) {
+ frm.set_value('purchase_invoice', '');
+ frappe.msgprint({
+ title: __('Invalid Purchase Invoice'),
+ message: __("The selected Purchase Invoice doesn't contains selected Asset Item."),
+ indicator: 'red'
+ });
+ }
+ frm.set_value('gross_purchase_amount', item.base_net_rate);
+ frm.set_value('location', item.asset_location);
+ });
+ } else {
+ frm.set_value('purchase_invoice', '');
+ frappe.msgprint({
+ title: __('Not Allowed'),
+ message: __("Please select Item Code first")
+ });
+ }
+ }
+ },
+
set_depreciation_rate: function(frm, row) {
if (row.total_number_of_depreciations && row.frequency_of_depreciation
&& row.expected_value_after_useful_life) {
@@ -371,12 +461,12 @@ frappe.ui.form.on('Depreciation Schedule', {
},
depreciation_amount: function(frm, cdt, cdn) {
- erpnext.asset.set_accululated_depreciation(frm);
+ erpnext.asset.set_accumulated_depreciation(frm);
}
})
-erpnext.asset.set_accululated_depreciation = function(frm) {
+erpnext.asset.set_accumulated_depreciation = function(frm) {
if(frm.doc.depreciation_method != "Manual") return;
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
@@ -415,92 +505,19 @@ erpnext.asset.restore_asset = function(frm) {
})
};
-erpnext.asset.transfer_asset = function(frm) {
- var dialog = new frappe.ui.Dialog({
- title: __("Transfer Asset"),
- fields: [
- {
- "label": __("Target Location"),
- "fieldname": "target_location",
- "fieldtype": "Link",
- "options": "Location",
- "get_query": function () {
- return {
- filters: [
- ["Location", "is_group", "=", 0]
- ]
- }
- },
- "reqd": 1
- },
- {
- "label": __("Select Serial No"),
- "fieldname": "serial_nos",
- "fieldtype": "Link",
- "options": "Serial No",
- "get_query": function () {
- return {
- filters: {
- 'asset': frm.doc.name
- }
- }
- },
- "onchange": function() {
- let val = this.get_value();
- if (val) {
- let serial_nos = dialog.get_value("serial_no") || val;
- if (serial_nos) {
- serial_nos = serial_nos.split('\n');
- serial_nos.push(val);
-
- const unique_sn = serial_nos.filter(function(elem, index, self) {
- return index === self.indexOf(elem);
- });
-
- dialog.set_value("serial_no", unique_sn.join('\n'));
- dialog.set_value("serial_nos", "");
- }
- }
- }
- },
- {
- "label": __("Serial No"),
- "fieldname": "serial_no",
- "read_only": 1,
- "fieldtype": "Small Text"
- },
- {
- "label": __("Date"),
- "fieldname": "transfer_date",
- "fieldtype": "Datetime",
- "reqd": 1,
- "default": frappe.datetime.now_datetime()
+erpnext.asset.transfer_asset = function() {
+ frappe.call({
+ method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
+ freeze: true,
+ args:{
+ "assets": [{ name: cur_frm.doc.name }],
+ "purpose": "Transfer"
+ },
+ callback: function (r) {
+ if (r.message) {
+ var doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
}
- ]
+ }
});
-
- dialog.set_primary_action(__("Transfer"), function() {
- var args = dialog.get_values();
- if(!args) return;
- dialog.hide();
- return frappe.call({
- type: "GET",
- method: "erpnext.assets.doctype.asset.asset.transfer_asset",
- args: {
- args: {
- "asset": frm.doc.name,
- "transaction_date": args.transfer_date,
- "source_location": frm.doc.location,
- "target_location": args.target_location,
- "serial_no": args.serial_no,
- "company": frm.doc.company
- }
- },
- freeze: true,
- callback: function(r) {
- cur_frm.reload_doc();
- }
- })
- });
- dialog.show();
};
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index c60ec5ec3f..97165a31d2 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -1,497 +1,505 @@
{
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "naming_series:",
- "creation": "2016-03-01 17:01:27.920130",
- "doctype": "DocType",
- "document_type": "Document",
- "field_order": [
- "naming_series",
- "asset_name",
- "item_code",
- "item_name",
- "asset_category",
- "asset_owner",
- "asset_owner_company",
- "supplier",
- "customer",
- "image",
- "column_break_3",
- "company",
- "location",
- "custodian",
- "department",
- "purchase_date",
- "disposal_date",
- "journal_entry_for_scrap",
- "accounting_dimensions_section",
- "cost_center",
- "dimension_col_break",
- "section_break_5",
- "gross_purchase_amount",
- "available_for_use_date",
- "column_break_18",
- "calculate_depreciation",
- "is_existing_asset",
- "opening_accumulated_depreciation",
- "number_of_depreciations_booked",
- "section_break_23",
- "finance_books",
- "section_break_33",
- "depreciation_method",
- "value_after_depreciation",
- "total_number_of_depreciations",
- "column_break_24",
- "frequency_of_depreciation",
- "next_depreciation_date",
- "section_break_14",
- "schedules",
- "insurance_details",
- "policy_number",
- "insurer",
- "insured_value",
- "column_break_48",
- "insurance_start_date",
- "insurance_end_date",
- "comprehensive_insurance",
- "section_break_31",
- "maintenance_required",
- "other_details",
- "status",
- "booked_fixed_asset",
- "column_break_51",
- "purchase_receipt",
- "purchase_receipt_amount",
- "purchase_invoice",
- "default_finance_book",
- "amended_from"
- ],
- "fields": [
- {
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "label": "Naming Series",
- "options": "ACC-ASS-.YYYY.-"
- },
- {
- "fieldname": "asset_name",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Asset Name",
- "reqd": 1
- },
- {
- "fieldname": "item_code",
- "fieldtype": "Link",
- "in_standard_filter": 1,
- "label": "Item Code",
- "options": "Item",
- "reqd": 1
- },
- {
- "fetch_from": "item_code.item_name",
- "fieldname": "item_name",
- "fieldtype": "Read Only",
- "label": "Item Name"
- },
- {
- "fetch_from": "item_code.asset_category",
- "fieldname": "asset_category",
- "fieldtype": "Link",
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Asset Category",
- "options": "Asset Category",
- "read_only": 1
- },
- {
- "fieldname": "asset_owner",
- "fieldtype": "Select",
- "label": "Asset Owner",
- "options": "\nCompany\nSupplier\nCustomer"
- },
- {
- "depends_on": "eval:doc.asset_owner == \"Company\"",
- "fieldname": "asset_owner_company",
- "fieldtype": "Link",
- "label": "Asset Owner Company",
- "options": "Company"
- },
- {
- "depends_on": "eval:doc.asset_owner == \"Supplier\"",
- "fieldname": "supplier",
- "fieldtype": "Link",
- "label": "Supplier",
- "options": "Supplier"
- },
- {
- "depends_on": "eval:doc.asset_owner == \"Customer\"",
- "fieldname": "customer",
- "fieldtype": "Link",
- "label": "Customer",
- "options": "Customer"
- },
- {
- "allow_on_submit": 1,
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "hidden": 1,
- "label": "Image",
- "no_copy": 1,
- "print_hide": 1
- },
- {
- "fieldname": "column_break_3",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "company",
- "fieldtype": "Link",
- "label": "Company",
- "options": "Company",
- "remember_last_selected_value": 1,
- "reqd": 1
- },
- {
- "fieldname": "location",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Location",
- "options": "Location",
- "reqd": 1
- },
- {
- "fieldname": "custodian",
- "fieldtype": "Link",
- "ignore_user_permissions": 1,
- "label": "Custodian",
- "options": "Employee"
- },
- {
- "fieldname": "cost_center",
- "fieldtype": "Link",
- "label": "Cost Center",
- "options": "Cost Center"
- },
- {
- "fieldname": "department",
- "fieldtype": "Link",
- "label": "Department",
- "options": "Department"
- },
- {
- "fieldname": "purchase_date",
- "fieldtype": "Date",
- "label": "Purchase Date",
- "reqd": 1
- },
- {
- "fieldname": "disposal_date",
- "fieldtype": "Date",
- "label": "Disposal Date",
- "read_only": 1
- },
- {
- "fieldname": "journal_entry_for_scrap",
- "fieldtype": "Link",
- "label": "Journal Entry for Scrap",
- "no_copy": 1,
- "options": "Journal Entry",
- "print_hide": 1,
- "read_only": 1
- },
- {
- "fieldname": "section_break_5",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "gross_purchase_amount",
- "fieldtype": "Currency",
- "label": "Gross Purchase Amount",
- "options": "Company:company:default_currency",
- "reqd": 1
- },
- {
- "fieldname": "available_for_use_date",
- "fieldtype": "Date",
- "label": "Available-for-use Date"
- },
- {
- "fieldname": "column_break_18",
- "fieldtype": "Column Break"
- },
- {
- "default": "0",
- "fieldname": "calculate_depreciation",
- "fieldtype": "Check",
- "label": "Calculate Depreciation"
- },
- {
- "default": "0",
- "fieldname": "is_existing_asset",
- "fieldtype": "Check",
- "label": "Is Existing Asset"
- },
- {
- "depends_on": "is_existing_asset",
- "fieldname": "opening_accumulated_depreciation",
- "fieldtype": "Currency",
- "label": "Opening Accumulated Depreciation",
- "no_copy": 1,
- "options": "Company:company:default_currency"
- },
- {
- "depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)",
- "fieldname": "number_of_depreciations_booked",
- "fieldtype": "Int",
- "label": "Number of Depreciations Booked",
- "no_copy": 1
- },
- {
- "depends_on": "calculate_depreciation",
- "fieldname": "section_break_23",
- "fieldtype": "Section Break",
- "label": "Depreciation"
- },
- {
- "fieldname": "finance_books",
- "fieldtype": "Table",
- "label": "Finance Books",
- "options": "Asset Finance Book"
- },
- {
- "fieldname": "section_break_33",
- "fieldtype": "Section Break",
- "hidden": 1
- },
- {
- "fieldname": "depreciation_method",
- "fieldtype": "Select",
- "label": "Depreciation Method",
- "options": "\nStraight Line\nDouble Declining Balance\nManual"
- },
- {
- "fieldname": "value_after_depreciation",
- "fieldtype": "Currency",
- "hidden": 1,
- "label": "Value After Depreciation",
- "options": "Company:company:default_currency",
- "read_only": 1
- },
- {
- "fieldname": "total_number_of_depreciations",
- "fieldtype": "Int",
- "label": "Total Number of Depreciations"
- },
- {
- "fieldname": "column_break_24",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "frequency_of_depreciation",
- "fieldtype": "Int",
- "label": "Frequency of Depreciation (Months)"
- },
- {
- "fieldname": "next_depreciation_date",
- "fieldtype": "Date",
- "label": "Next Depreciation Date",
- "no_copy": 1
- },
- {
- "depends_on": "calculate_depreciation",
- "fieldname": "section_break_14",
- "fieldtype": "Section Break",
- "label": "Depreciation Schedule"
- },
- {
- "fieldname": "schedules",
- "fieldtype": "Table",
- "label": "Depreciation Schedules",
- "no_copy": 1,
- "options": "Depreciation Schedule"
- },
- {
- "collapsible": 1,
- "fieldname": "insurance_details",
- "fieldtype": "Section Break",
- "label": "Insurance details"
- },
- {
- "fieldname": "policy_number",
- "fieldtype": "Data",
- "label": "Policy number"
- },
- {
- "fieldname": "insurer",
- "fieldtype": "Data",
- "label": "Insurer"
- },
- {
- "fieldname": "insured_value",
- "fieldtype": "Data",
- "label": "Insured value"
- },
- {
- "fieldname": "column_break_48",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "insurance_start_date",
- "fieldtype": "Date",
- "label": "Insurance Start Date"
- },
- {
- "fieldname": "insurance_end_date",
- "fieldtype": "Date",
- "label": "Insurance End Date"
- },
- {
- "fieldname": "comprehensive_insurance",
- "fieldtype": "Data",
- "label": "Comprehensive Insurance"
- },
- {
- "fieldname": "section_break_31",
- "fieldtype": "Section Break",
- "label": "Maintenance"
- },
- {
- "allow_on_submit": 1,
- "default": "0",
- "description": "Check if Asset requires Preventive Maintenance or Calibration",
- "fieldname": "maintenance_required",
- "fieldtype": "Check",
- "label": "Maintenance Required"
- },
- {
- "collapsible": 1,
- "fieldname": "other_details",
- "fieldtype": "Section Break",
- "label": "Other Details"
- },
- {
- "allow_on_submit": 1,
- "default": "Draft",
- "fieldname": "status",
- "fieldtype": "Select",
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Status",
- "no_copy": 1,
- "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt",
- "read_only": 1
- },
- {
- "default": "0",
- "fieldname": "booked_fixed_asset",
- "fieldtype": "Check",
- "label": "Booked Fixed Asset",
- "no_copy": 1,
- "read_only": 1
- },
- {
- "fieldname": "column_break_51",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "purchase_receipt",
- "fieldtype": "Link",
- "label": "Purchase Receipt",
- "no_copy": 1,
- "options": "Purchase Receipt",
- "print_hide": 1,
- "read_only": 1
- },
- {
- "fieldname": "purchase_receipt_amount",
- "fieldtype": "Currency",
- "hidden": 1,
- "label": "Purchase Receipt Amount",
- "no_copy": 1,
- "print_hide": 1,
- "read_only": 1
- },
- {
- "fieldname": "purchase_invoice",
- "fieldtype": "Link",
- "label": "Purchase Invoice",
- "no_copy": 1,
- "options": "Purchase Invoice",
- "read_only": 1
- },
- {
- "fetch_from": "company.default_finance_book",
- "fieldname": "default_finance_book",
- "fieldtype": "Link",
- "hidden": 1,
- "label": "Default Finance Book",
- "options": "Finance Book",
- "read_only": 1
- },
- {
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "label": "Amended From",
- "no_copy": 1,
- "options": "Asset",
- "print_hide": 1,
- "read_only": 1
- },
- {
- "collapsible": 1,
- "fieldname": "accounting_dimensions_section",
- "fieldtype": "Section Break",
- "label": "Accounting Dimensions"
- },
- {
- "fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
- }
- ],
- "idx": 72,
- "image_field": "image",
- "is_submittable": 1,
- "modified": "2019-05-25 22:26:19.786201",
- "modified_by": "Administrator",
- "module": "Assets",
- "name": "Asset",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "import": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "share": 1,
- "submit": 1,
- "write": 1
- },
- {
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Quality Manager",
- "share": 1,
- "submit": 1,
- "write": 1
- }
- ],
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "asset_name"
- }
\ No newline at end of file
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "naming_series:",
+ "creation": "2016-03-01 17:01:27.920130",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "asset_name",
+ "item_code",
+ "item_name",
+ "asset_category",
+ "asset_owner",
+ "asset_owner_company",
+ "supplier",
+ "customer",
+ "image",
+ "purchase_invoice",
+ "column_break_3",
+ "company",
+ "location",
+ "custodian",
+ "department",
+ "purchase_date",
+ "disposal_date",
+ "journal_entry_for_scrap",
+ "purchase_receipt",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break",
+ "section_break_5",
+ "gross_purchase_amount",
+ "available_for_use_date",
+ "column_break_18",
+ "calculate_depreciation",
+ "allow_monthly_depreciation",
+ "is_existing_asset",
+ "opening_accumulated_depreciation",
+ "number_of_depreciations_booked",
+ "section_break_23",
+ "finance_books",
+ "section_break_33",
+ "depreciation_method",
+ "value_after_depreciation",
+ "total_number_of_depreciations",
+ "column_break_24",
+ "frequency_of_depreciation",
+ "next_depreciation_date",
+ "section_break_14",
+ "schedules",
+ "insurance_details",
+ "policy_number",
+ "insurer",
+ "insured_value",
+ "column_break_48",
+ "insurance_start_date",
+ "insurance_end_date",
+ "comprehensive_insurance",
+ "section_break_31",
+ "maintenance_required",
+ "other_details",
+ "status",
+ "booked_fixed_asset",
+ "column_break_51",
+
+ "purchase_receipt_amount",
+ "default_finance_book",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "ACC-ASS-.YYYY.-"
+ },
+ {
+ "fieldname": "asset_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Asset Name",
+ "reqd": 1
+ },
+ {
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Item Code",
+ "options": "Item",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "item_code.item_name",
+ "fieldname": "item_name",
+ "fieldtype": "Read Only",
+ "label": "Item Name"
+ },
+ {
+ "fetch_from": "item_code.asset_category",
+ "fieldname": "asset_category",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Asset Category",
+ "options": "Asset Category",
+ "read_only": 1
+ },
+ {
+ "fieldname": "asset_owner",
+ "fieldtype": "Select",
+ "label": "Asset Owner",
+ "options": "\nCompany\nSupplier\nCustomer"
+ },
+ {
+ "depends_on": "eval:doc.asset_owner == \"Company\"",
+ "fieldname": "asset_owner_company",
+ "fieldtype": "Link",
+ "label": "Asset Owner Company",
+ "options": "Company"
+ },
+ {
+ "depends_on": "eval:doc.asset_owner == \"Supplier\"",
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "label": "Supplier",
+ "options": "Supplier"
+ },
+ {
+ "depends_on": "eval:doc.asset_owner == \"Customer\"",
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "label": "Customer",
+ "options": "Customer"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "label": "Image",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "location",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Location",
+ "options": "Location",
+ "reqd": 1
+ },
+ {
+ "fieldname": "custodian",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "Custodian",
+ "options": "Employee"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "label": "Department",
+ "options": "Department"
+ },
+ {
+ "fieldname": "purchase_date",
+ "fieldtype": "Date",
+ "label": "Purchase Date",
+ "reqd": 1
+ },
+ {
+ "fieldname": "disposal_date",
+ "fieldtype": "Date",
+ "label": "Disposal Date",
+ "read_only": 1
+ },
+ {
+ "fieldname": "journal_entry_for_scrap",
+ "fieldtype": "Link",
+ "label": "Journal Entry for Scrap",
+ "no_copy": 1,
+ "options": "Journal Entry",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "gross_purchase_amount",
+ "fieldtype": "Currency",
+ "label": "Gross Purchase Amount",
+ "options": "Company:company:default_currency",
+ "reqd": 1
+ },
+ {
+ "fieldname": "available_for_use_date",
+ "fieldtype": "Date",
+ "label": "Available-for-use Date"
+ },
+ {
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "calculate_depreciation",
+ "fieldtype": "Check",
+ "label": "Calculate Depreciation"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_existing_asset",
+ "fieldtype": "Check",
+ "label": "Is Existing Asset"
+ },
+ {
+ "depends_on": "is_existing_asset",
+ "fieldname": "opening_accumulated_depreciation",
+ "fieldtype": "Currency",
+ "label": "Opening Accumulated Depreciation",
+ "no_copy": 1,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)",
+ "fieldname": "number_of_depreciations_booked",
+ "fieldtype": "Int",
+ "label": "Number of Depreciations Booked",
+ "no_copy": 1
+ },
+ {
+ "depends_on": "calculate_depreciation",
+ "fieldname": "section_break_23",
+ "fieldtype": "Section Break",
+ "label": "Depreciation"
+ },
+ {
+ "fieldname": "finance_books",
+ "fieldtype": "Table",
+ "label": "Finance Books",
+ "options": "Asset Finance Book"
+ },
+ {
+ "fieldname": "section_break_33",
+ "fieldtype": "Section Break",
+ "hidden": 1
+ },
+ {
+ "fieldname": "depreciation_method",
+ "fieldtype": "Select",
+ "label": "Depreciation Method",
+ "options": "\nStraight Line\nDouble Declining Balance\nManual"
+ },
+ {
+ "fieldname": "value_after_depreciation",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Value After Depreciation",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_number_of_depreciations",
+ "fieldtype": "Int",
+ "label": "Total Number of Depreciations"
+ },
+ {
+ "fieldname": "column_break_24",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "frequency_of_depreciation",
+ "fieldtype": "Int",
+ "label": "Frequency of Depreciation (Months)"
+ },
+ {
+ "fieldname": "next_depreciation_date",
+ "fieldtype": "Date",
+ "label": "Next Depreciation Date",
+ "no_copy": 1
+ },
+ {
+ "depends_on": "calculate_depreciation",
+ "fieldname": "section_break_14",
+ "fieldtype": "Section Break",
+ "label": "Depreciation Schedule"
+ },
+ {
+ "fieldname": "schedules",
+ "fieldtype": "Table",
+ "label": "Depreciation Schedules",
+ "no_copy": 1,
+ "options": "Depreciation Schedule"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "insurance_details",
+ "fieldtype": "Section Break",
+ "label": "Insurance details"
+ },
+ {
+ "fieldname": "policy_number",
+ "fieldtype": "Data",
+ "label": "Policy number"
+ },
+ {
+ "fieldname": "insurer",
+ "fieldtype": "Data",
+ "label": "Insurer"
+ },
+ {
+ "fieldname": "insured_value",
+ "fieldtype": "Data",
+ "label": "Insured value"
+ },
+ {
+ "fieldname": "column_break_48",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "insurance_start_date",
+ "fieldtype": "Date",
+ "label": "Insurance Start Date"
+ },
+ {
+ "fieldname": "insurance_end_date",
+ "fieldtype": "Date",
+ "label": "Insurance End Date"
+ },
+ {
+ "fieldname": "comprehensive_insurance",
+ "fieldtype": "Data",
+ "label": "Comprehensive Insurance"
+ },
+ {
+ "fieldname": "section_break_31",
+ "fieldtype": "Section Break",
+ "label": "Maintenance"
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "description": "Check if Asset requires Preventive Maintenance or Calibration",
+ "fieldname": "maintenance_required",
+ "fieldtype": "Check",
+ "label": "Maintenance Required"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "other_details",
+ "fieldtype": "Section Break",
+ "label": "Other Details"
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "booked_fixed_asset",
+ "fieldtype": "Check",
+ "label": "Booked Fixed Asset",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_51",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "purchase_receipt",
+ "fieldtype": "Link",
+ "label": "Purchase Receipt",
+ "no_copy": 1,
+ "options": "Purchase Receipt",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "purchase_receipt_amount",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Purchase Receipt Amount",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "purchase_invoice",
+ "fieldtype": "Link",
+ "label": "Purchase Invoice",
+ "no_copy": 1,
+ "options": "Purchase Invoice"
+ },
+ {
+ "fetch_from": "company.default_finance_book",
+ "fieldname": "default_finance_book",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Default Finance Book",
+ "options": "Finance Book",
+ "read_only": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Asset",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "depends_on": "calculate_depreciation",
+ "fieldname": "allow_monthly_depreciation",
+ "fieldtype": "Check",
+ "label": "Allow Monthly Depreciation"
+ }
+ ],
+ "idx": 72,
+ "image_field": "image",
+ "is_submittable": 1,
+ "modified": "2019-10-22 15:47:36.050828",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "import": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Quality Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "asset_name"
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 6e2bbc1626..56341ed1b1 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -6,7 +6,7 @@ from __future__ import unicode_literals
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, add_days
+from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days
from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \
@@ -18,6 +18,7 @@ from erpnext.controllers.accounts_controller import AccountsController
class Asset(AccountsController):
def validate(self):
self.validate_asset_values()
+ self.validate_asset_and_reference()
self.validate_item()
self.set_missing_values()
self.prepare_depreciation_data()
@@ -29,9 +30,12 @@ class Asset(AccountsController):
def on_submit(self):
self.validate_in_use_date()
self.set_status()
- self.update_stock_movement()
- if not self.booked_fixed_asset and not is_cwip_accounting_disabled():
+ self.make_asset_movement()
+ if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category):
self.make_gl_entries()
+
+ def before_cancel(self):
+ self.cancel_auto_gen_movement()
def on_cancel(self):
self.validate_cancellation()
@@ -39,6 +43,18 @@ class Asset(AccountsController):
self.set_status()
delete_gl_entries(voucher_type='Asset', voucher_no=self.name)
self.db_set('booked_fixed_asset', 0)
+
+ def validate_asset_and_reference(self):
+ if self.purchase_invoice or self.purchase_receipt:
+ reference_doc = 'Purchase Invoice' if self.purchase_invoice else 'Purchase Receipt'
+ reference_name = self.purchase_invoice or self.purchase_receipt
+ reference_doc = frappe.get_doc(reference_doc, reference_name)
+ if reference_doc.get('company') != self.company:
+ frappe.throw(_("Company of asset {0} and purchase document {1} doesn't matches.").format(self.name, reference_doc.get('name')))
+
+
+ if self.is_existing_asset and self.purchase_invoice:
+ frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
def prepare_depreciation_data(self):
if self.calculate_depreciation:
@@ -76,10 +92,13 @@ class Asset(AccountsController):
self.set('finance_books', finance_books)
def validate_asset_values(self):
+ if not self.asset_category:
+ self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
+
if not flt(self.gross_purchase_amount):
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
- if not is_cwip_accounting_disabled():
+ if is_cwip_accounting_enabled(self.asset_category):
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))
@@ -105,6 +124,38 @@ class Asset(AccountsController):
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 cancel_auto_gen_movement(self):
+ movements = frappe.db.sql(
+ """SELECT asm.name, asm.docstatus
+ FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
+ WHERE asm_item.parent=asm.name and asm_item.asset=%s and asm.docstatus=1""", self.name, as_dict=1)
+ if len(movements) > 1:
+ frappe.throw(_('Asset has multiple Asset Movement Entries which has to be \
+ cancelled manually to cancel this asset.'))
+ movement = frappe.get_doc('Asset Movement', movements[0].get('name'))
+ movement.flags.ignore_validate = True
+ movement.cancel()
+
+ def make_asset_movement(self):
+ reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
+ reference_docname = self.purchase_receipt or self.purchase_invoice
+ assets = [{
+ 'asset': self.name,
+ 'asset_name': self.asset_name,
+ 'target_location': self.location,
+ 'to_employee': self.custodian
+ }]
+ asset_movement = frappe.get_doc({
+ 'doctype': 'Asset Movement',
+ 'assets': assets,
+ 'purpose': 'Receipt',
+ 'company': self.company,
+ 'transaction_date': getdate(nowdate()),
+ 'reference_doctype': reference_doctype,
+ 'reference_name': reference_docname
+ }).insert()
+ asset_movement.submit()
+
def set_depreciation_rate(self):
for d in self.get("finance_books"):
d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True),
@@ -145,19 +196,31 @@ class Asset(AccountsController):
schedule_date = add_months(d.depreciation_start_date,
n * cint(d.frequency_of_depreciation))
+ # schedule date will be a year later from start date
+ # so monthly schedule date is calculated by removing 11 months from it
+ monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1)
+
# For first row
if has_pro_rata and n==0:
- depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount,
+ depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
self.available_for_use_date, d.depreciation_start_date)
+
+ # For first depr schedule date will be the start date
+ # so monthly schedule date is calculated by removing month difference between use date and start date
+ monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1)
+
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation))
- depreciation_amount, days = get_pro_rata_amt(d,
+ depreciation_amount, days, months = get_pro_rata_amt(d,
depreciation_amount, schedule_date, to_date)
+ monthly_schedule_date = add_months(schedule_date, 1)
+
schedule_date = add_days(schedule_date, days)
+ last_schedule_date = schedule_date
if not depreciation_amount: continue
value_after_depreciation -= flt(depreciation_amount,
@@ -171,13 +234,50 @@ class Asset(AccountsController):
skip_row = True
if depreciation_amount > 0:
- self.append("schedules", {
- "schedule_date": schedule_date,
- "depreciation_amount": depreciation_amount,
- "depreciation_method": d.depreciation_method,
- "finance_book": d.finance_book,
- "finance_book_id": d.idx
- })
+ # With monthly depreciation, each depreciation is divided by months remaining until next date
+ if self.allow_monthly_depreciation:
+ # month range is 1 to 12
+ # In pro rata case, for first and last depreciation, month range would be different
+ month_range = months \
+ if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
+ else d.frequency_of_depreciation
+
+ for r in range(month_range):
+ if (has_pro_rata and n == 0):
+ # For first entry of monthly depr
+ if r == 0:
+ days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
+ per_day_amt = depreciation_amount / days
+ depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
+ depreciation_amount -= depreciation_amount_for_current_month
+ date = monthly_schedule_date
+ amount = depreciation_amount_for_current_month
+ else:
+ date = add_months(monthly_schedule_date, r)
+ amount = depreciation_amount / (month_range - 1)
+ elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
+ # For last entry of monthly depr
+ date = last_schedule_date
+ amount = depreciation_amount / month_range
+ else:
+ date = add_months(monthly_schedule_date, r)
+ amount = depreciation_amount / month_range
+
+ self.append("schedules", {
+ "schedule_date": date,
+ "depreciation_amount": amount,
+ "depreciation_method": d.depreciation_method,
+ "finance_book": d.finance_book,
+ "finance_book_id": d.idx
+ })
+ else:
+ self.append("schedules", {
+ "schedule_date": schedule_date,
+ "depreciation_amount": depreciation_amount,
+ "depreciation_method": d.depreciation_method,
+ "finance_book": d.finance_book,
+ "finance_book_id": d.idx
+ })
def check_is_pro_rata(self, row):
has_pro_rata = False
@@ -196,7 +296,9 @@ class Asset(AccountsController):
.format(row.idx))
if not row.depreciation_start_date:
- frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
+ if not self.available_for_use_date:
+ frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
+ row.depreciation_start_date = self.available_for_use_date
if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0
@@ -345,22 +447,13 @@ class Asset(AccountsController):
if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1
- def update_stock_movement(self):
- asset_movement = frappe.db.get_value('Asset Movement',
- {'asset': self.name, 'reference_name': self.purchase_receipt, 'docstatus': 0}, 'name')
-
- if asset_movement:
- doc = frappe.get_doc('Asset Movement', asset_movement)
- doc.naming_series = 'ACC-ASM-.YYYY.-'
- doc.submit()
-
def make_gl_entries(self):
gl_entries = []
- if ((self.purchase_receipt or (self.purchase_invoice and
- frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')))
+ if ((self.purchase_receipt \
+ or (self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')))
and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
- fixed_aseet_account = get_asset_category_account(self.name, 'fixed_asset_account',
+ fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
asset_category = self.asset_category, company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account",
@@ -368,7 +461,7 @@ class Asset(AccountsController):
gl_entries.append(self.get_gl_dict({
"account": cwip_account,
- "against": fixed_aseet_account,
+ "against": fixed_asset_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount,
@@ -377,7 +470,7 @@ class Asset(AccountsController):
}))
gl_entries.append(self.get_gl_dict({
- "account": fixed_aseet_account,
+ "account": fixed_asset_account,
"against": cwip_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
@@ -424,7 +517,7 @@ def update_maintenance_status():
asset.set_status('Out of Order')
def make_post_gl_entry():
- if is_cwip_accounting_disabled():
+ if not is_cwip_accounting_enabled(self.asset_category):
return
assets = frappe.db.sql_list(""" select name from `tabAsset`
@@ -438,25 +531,6 @@ def get_asset_naming_series():
meta = frappe.get_meta('Asset')
return meta.get_field("naming_series").options
-@frappe.whitelist()
-def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, posting_date):
- pi = frappe.new_doc("Purchase Invoice")
- pi.company = company
- pi.currency = frappe.get_cached_value('Company', company, "default_currency")
- pi.set_posting_time = 1
- pi.posting_date = posting_date
- pi.append("items", {
- "item_code": item_code,
- "is_fixed_asset": 1,
- "asset": asset,
- "expense_account": get_asset_category_account(asset, 'fixed_asset_account'),
- "qty": 1,
- "price_list_rate": gross_purchase_amount,
- "rate": gross_purchase_amount
- })
- pi.set_missing_values()
- return pi
-
@frappe.whitelist()
def make_sales_invoice(asset, item_code, company, serial_no=None):
si = frappe.new_doc("Sales Invoice")
@@ -531,7 +605,7 @@ def get_item_details(item_code, asset_category):
def get_asset_account(account_name, asset=None, asset_category=None, company=None):
account = None
if asset:
- account = get_asset_category_account(asset, account_name,
+ account = get_asset_category_account(account_name, asset=asset,
asset_category = asset_category, company = company)
if not account:
@@ -574,17 +648,43 @@ 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"))
+@frappe.whitelist()
+def make_asset_movement(assets, purpose=None):
+ import json
+ from six import string_types
+
+ if isinstance(assets, string_types):
+ assets = json.loads(assets)
+
+ if len(assets) == 0:
+ frappe.throw(_('Atleast one asset has to be selected.'))
+
+ asset_movement = frappe.new_doc("Asset Movement")
+ asset_movement.quantity = len(assets)
+ for asset in assets:
+ asset = frappe.get_doc('Asset', asset.get('name'))
+ asset_movement.company = asset.get('company')
+ asset_movement.append("assets", {
+ 'asset': asset.get('name'),
+ 'source_location': asset.get('location'),
+ 'from_employee': asset.get('custodian')
+ })
+
+ if asset_movement.get('assets'):
+ return asset_movement.as_dict()
+
+def is_cwip_accounting_enabled(asset_category):
+ return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
days = date_diff(to_date, from_date)
+ months = month_diff(to_date, from_date)
total_days = get_total_days(to_date, row.frequency_of_depreciation)
- return (depreciation_amount * flt(days)) / flt(total_days), days
+ return (depreciation_amount * flt(days)) / flt(total_days), days, months
def get_total_days(date, frequency):
period_start_date = add_months(date,
cint(frequency) * -1)
- return date_diff(date, period_start_date)
\ No newline at end of file
+ return date_diff(date, period_start_date)
diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js
index 3b95a17afc..02f39e0e7f 100644
--- a/erpnext/assets/doctype/asset/asset_list.js
+++ b/erpnext/assets/doctype/asset/asset_list.js
@@ -30,8 +30,24 @@ frappe.listview_settings['Asset'] = {
} else if (doc.status === "Draft") {
return [__("Draft"), "red", "status,=,Draft"];
-
}
-
+ },
+ onload: function(me) {
+ me.page.add_action_item('Make Asset Movement', function() {
+ const assets = me.get_checked_items();
+ frappe.call({
+ method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
+ freeze: true,
+ args:{
+ "assets": assets
+ },
+ callback: function (r) {
+ if (r.message) {
+ var doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
+ });
},
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index c09b94fa8e..a56440de3d 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -7,14 +7,13 @@ import frappe
import unittest
from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, add_months
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset
-from erpnext.assets.doctype.asset.asset import make_sales_invoice, make_purchase_invoice
+from erpnext.assets.doctype.asset.asset import make_sales_invoice
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice
class TestAsset(unittest.TestCase):
def setUp(self):
set_depreciation_settings_in_company()
- remove_prorated_depreciation_schedule()
create_asset_data()
frappe.db.sql("delete from `tabTax Rule`")
@@ -40,15 +39,15 @@ class TestAsset(unittest.TestCase):
})
asset.submit()
- pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount,
- asset.company, asset.purchase_date)
+ pi = make_invoice(pr.name)
pi.supplier = "_Test Supplier"
pi.insert()
pi.submit()
asset.load_from_db()
self.assertEqual(asset.supplier, "_Test Supplier")
self.assertEqual(asset.purchase_date, getdate(purchase_date))
- self.assertEqual(asset.purchase_invoice, pi.name)
+ # Asset won't have reference to PI when purchased through PR
+ self.assertEqual(asset.purchase_receipt, pr.name)
expected_gle = (
("Asset Received But Not Billed - _TC", 100000.0, 0.0),
@@ -61,20 +60,23 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
pi.cancel()
-
+ asset.cancel()
asset.load_from_db()
- self.assertEqual(asset.supplier, None)
- self.assertEqual(asset.purchase_invoice, None)
+ pr.load_from_db()
+ pr.cancel()
+ self.assertEqual(asset.docstatus, 2)
self.assertFalse(frappe.db.get_value("GL Entry",
{"voucher_type": "Purchase Invoice", "voucher_no": pi.name}))
def test_is_fixed_asset_set(self):
+ asset = create_asset(is_existing_asset = 1)
doc = frappe.new_doc('Purchase Invoice')
doc.supplier = '_Test Supplier'
doc.append('items', {
'item_code': 'Macbook Pro',
- 'qty': 1
+ 'qty': 1,
+ 'asset': asset.name
})
doc.set_missing_values()
@@ -200,7 +202,6 @@ class TestAsset(unittest.TestCase):
self.assertEqual(schedules, expected_schedules)
def test_schedule_for_prorated_straight_line_method(self):
- set_prorated_depreciation_schedule()
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=100000.0, location="Test Location")
@@ -233,8 +234,6 @@ class TestAsset(unittest.TestCase):
self.assertEqual(schedules, expected_schedules)
- remove_prorated_depreciation_schedule()
-
def test_depreciation(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=100000.0, location="Test Location")
@@ -484,9 +483,6 @@ class TestAsset(unittest.TestCase):
self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule)
def test_cwip_accounting(self):
- from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
- make_purchase_invoice as make_purchase_invoice_from_pr)
-
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=5000, do_not_submit=True, location="Test Location")
@@ -515,13 +511,13 @@ class TestAsset(unittest.TestCase):
("CWIP Account - _TC", 5250.0, 0.0)
)
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no = %s
order by account""", pr.name)
- self.assertEqual(gle, expected_gle)
+ self.assertEqual(pr_gle, expected_gle)
- pi = make_purchase_invoice_from_pr(pr.name)
+ pi = make_invoice(pr.name)
pi.submit()
expected_gle = (
@@ -532,11 +528,11 @@ class TestAsset(unittest.TestCase):
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
)
- gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
+ pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no = %s
order by account""", pi.name)
- self.assertEqual(gle, expected_gle)
+ self.assertEqual(pi_gle, expected_gle)
asset = frappe.db.get_value('Asset',
{'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
@@ -565,6 +561,7 @@ class TestAsset(unittest.TestCase):
where voucher_type='Asset' and voucher_no = %s
order by account""", asset_doc.name)
+
self.assertEqual(gle, expected_gle)
def test_expense_head(self):
@@ -575,7 +572,6 @@ class TestAsset(unittest.TestCase):
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
-
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()
@@ -596,15 +592,15 @@ def create_asset(**args):
asset = frappe.get_doc({
"doctype": "Asset",
- "asset_name": "Macbook Pro 1",
+ "asset_name": args.asset_name or "Macbook Pro 1",
"asset_category": "Computers",
- "item_code": "Macbook Pro",
- "company": "_Test Company",
+ "item_code": args.item_code or "Macbook Pro",
+ "company": args.company or"_Test Company",
"purchase_date": "2015-01-01",
"calculate_depreciation": 0,
"gross_purchase_amount": 100000,
"expected_value_after_useful_life": 10000,
- "warehouse": "_Test Warehouse - _TC",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
"available_for_use_date": "2020-06-06",
"location": "Test Location",
"asset_owner": "Company",
@@ -616,6 +612,9 @@ def create_asset(**args):
except frappe.DuplicateEntryError:
pass
+ if args.submit:
+ asset.submit()
+
return asset
def create_asset_category():
@@ -623,6 +622,7 @@ def create_asset_category():
asset_category.asset_category_name = "Computers"
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
+ asset_category.enable_cwip_accounting = 1
asset_category.append("accounts", {
"company_name": "_Test Company",
"fixed_asset_account": "_Test Fixed Asset - _TC",
@@ -632,6 +632,8 @@ def create_asset_category():
asset_category.insert()
def create_fixed_asset_item():
+ meta = frappe.get_meta('Asset')
+ naming_series = meta.get_field("naming_series").options.splitlines()[0] or 'ACC-ASS-.YYYY.-'
try:
frappe.get_doc({
"doctype": "Item",
@@ -642,7 +644,9 @@ def create_fixed_asset_item():
"item_group": "All Item Groups",
"stock_uom": "Nos",
"is_stock_item": 0,
- "is_fixed_asset": 1
+ "is_fixed_asset": 1,
+ "auto_create_assets": 1,
+ "asset_naming_series": naming_series
}).insert()
except frappe.DuplicateEntryError:
pass
@@ -656,19 +660,4 @@ def set_depreciation_settings_in_company():
company.save()
# Enable booking asset depreciation entry automatically
- frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
-
-def remove_prorated_depreciation_schedule():
- asset_settings = frappe.get_doc("Asset Settings", "Asset Settings")
- asset_settings.schedule_based_on_fiscal_year = 0
- asset_settings.save()
-
- frappe.db.commit()
-
-def set_prorated_depreciation_schedule():
- asset_settings = frappe.get_doc("Asset Settings", "Asset Settings")
- asset_settings.schedule_based_on_fiscal_year = 1
- asset_settings.number_of_days_in_fiscal_year = 360
- asset_settings.save()
-
- frappe.db.commit()
+ frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_category/asset_category.json b/erpnext/assets/doctype/asset_category/asset_category.json
index 882cbe2eaa..7483b41d4d 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.json
+++ b/erpnext/assets/doctype/asset_category/asset_category.json
@@ -1,284 +1,115 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:asset_category_name",
- "beta": 0,
- "creation": "2016-03-01 17:41:39.778765",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:asset_category_name",
+ "creation": "2016-03-01 17:41:39.778765",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "asset_category_name",
+ "column_break_3",
+ "depreciation_options",
+ "enable_cwip_accounting",
+ "finance_book_detail",
+ "finance_books",
+ "section_break_2",
+ "accounts"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "asset_category_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Asset Category Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "asset_category_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Asset Category Name",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "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,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "finance_book_detail",
- "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,
- "label": "Finance Book Detail",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "finance_book_detail",
+ "fieldtype": "Section Break",
+ "label": "Finance Book Detail"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "finance_books",
- "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": "Finance Books",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Finance Book",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "finance_books",
+ "fieldtype": "Table",
+ "label": "Finance Books",
+ "options": "Asset Finance Book"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_2",
- "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,
- "label": "Accounts",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break",
+ "label": "Accounts"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "accounts",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Accounts",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Category Account",
- "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,
- "unique": 0
+ "fieldname": "accounts",
+ "fieldtype": "Table",
+ "label": "Accounts",
+ "options": "Asset Category Account",
+ "reqd": 1
+ },
+ {
+ "fieldname": "depreciation_options",
+ "fieldtype": "Section Break",
+ "label": "Depreciation Options"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_cwip_accounting",
+ "fieldtype": "Check",
+ "label": "Enable Capital Work in Progress Accounting"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-05-12 14:56:04.116425",
- "modified_by": "Administrator",
- "module": "Assets",
- "name": "Asset Category",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "modified": "2019-10-11 12:19:59.759136",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Category",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "import": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "import": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Quality Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Quality Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py
index bbdc6ec2cf..2a42894623 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.py
+++ b/erpnext/assets/doctype/asset_category/asset_category.py
@@ -10,14 +10,20 @@ from frappe.model.document import Document
class AssetCategory(Document):
def validate(self):
+ self.validate_finance_books()
+
+ def validate_finance_books(self):
for d in self.finance_books:
for field in ("Total Number of Depreciations", "Frequency of Depreciation"):
if cint(d.get(frappe.scrub(field)))<1:
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
@frappe.whitelist()
-def get_asset_category_account(asset, fieldname, account=None, asset_category = None, company = None):
- if not asset_category and company:
+def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None):
+ if item and frappe.db.get_value("Item", item, "is_fixed_asset"):
+ asset_category = frappe.db.get_value("Item", item, ["asset_category"])
+
+ elif not asset_category or not company:
if account:
if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset":
account=None
diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
index 735302a0c3..6c2fd67a9a 100644
--- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
@@ -73,8 +73,10 @@ def create_asset_data():
'doctype': 'Location',
'location_name': 'Test Location'
}).insert()
-
+
if not frappe.db.exists("Item", "Photocopier"):
+ meta = frappe.get_meta('Asset')
+ naming_series = meta.get_field("naming_series").options
frappe.get_doc({
"doctype": "Item",
"item_code": "Photocopier",
@@ -83,7 +85,9 @@ def create_asset_data():
"company": "_Test Company",
"is_fixed_asset": 1,
"is_stock_item": 0,
- "asset_category": "Equipment"
+ "asset_category": "Equipment",
+ "auto_create_assets": 1,
+ "asset_naming_series": naming_series
}).insert()
def create_maintenance_team():
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js
index 7ef6461b5a..06d8879091 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.js
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.js
@@ -2,27 +2,101 @@
// For license information, please see license.txt
frappe.ui.form.on('Asset Movement', {
- select_serial_no: function(frm) {
- if (frm.doc.select_serial_no) {
- let serial_no = frm.doc.serial_no
- ? frm.doc.serial_no + '\n' + frm.doc.select_serial_no : frm.doc.select_serial_no;
- frm.set_value("serial_no", serial_no);
- frm.set_value("quantity", serial_no.split('\n').length);
- }
- },
-
- serial_no: function(frm) {
- const qty = frm.doc.serial_no ? frm.doc.serial_no.split('\n').length : 0;
- frm.set_value("quantity", qty);
- },
-
- setup: function(frm) {
- frm.set_query("select_serial_no", function() {
+ setup: (frm) => {
+ frm.set_query("to_employee", "assets", (doc) => {
return {
filters: {
- "asset": frm.doc.asset
+ company: doc.company
}
};
+ })
+ frm.set_query("from_employee", "assets", (doc) => {
+ return {
+ filters: {
+ company: doc.company
+ }
+ };
+ })
+ frm.set_query("reference_name", (doc) => {
+ return {
+ filters: {
+ company: doc.company,
+ docstatus: 1
+ }
+ };
+ })
+ frm.set_query("reference_doctype", () => {
+ return {
+ filters: {
+ name: ["in", ["Purchase Receipt", "Purchase Invoice"]]
+ }
+ };
+ }),
+ frm.set_query("asset", "assets", () => {
+ return {
+ filters: {
+ status: ["not in", ["Draft"]]
+ }
+ }
+ })
+ },
+
+ onload: (frm) => {
+ frm.trigger('set_required_fields');
+ },
+
+ purpose: (frm) => {
+ frm.trigger('set_required_fields');
+ },
+
+ set_required_fields: (frm, cdt, cdn) => {
+ let fieldnames_to_be_altered;
+ if (frm.doc.purpose === 'Transfer') {
+ fieldnames_to_be_altered = {
+ target_location: { read_only: 0, reqd: 1 },
+ source_location: { read_only: 1, reqd: 1 },
+ from_employee: { read_only: 1, reqd: 0 },
+ to_employee: { read_only: 1, reqd: 0 }
+ };
+ }
+ else if (frm.doc.purpose === 'Receipt') {
+ fieldnames_to_be_altered = {
+ target_location: { read_only: 0, reqd: 1 },
+ source_location: { read_only: 1, reqd: 0 },
+ from_employee: { read_only: 0, reqd: 1 },
+ to_employee: { read_only: 1, reqd: 0 }
+ };
+ }
+ else if (frm.doc.purpose === 'Issue') {
+ fieldnames_to_be_altered = {
+ target_location: { read_only: 1, reqd: 0 },
+ source_location: { read_only: 1, reqd: 1 },
+ from_employee: { read_only: 1, reqd: 0 },
+ to_employee: { read_only: 0, reqd: 1 }
+ };
+ }
+ Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
+ let property_to_be_altered = fieldnames_to_be_altered[fieldname];
+ Object.keys(property_to_be_altered).forEach(property => {
+ let value = property_to_be_altered[property];
+ frm.set_df_property(fieldname, property, value, cdn, 'assets');
+ });
});
+ frm.refresh_field('assets');
}
});
+
+frappe.ui.form.on('Asset Movement Item', {
+ asset: function(frm, cdt, cdn) {
+ // on manual entry of an asset auto sets their source location / employee
+ const asset_name = locals[cdt][cdn].asset;
+ if (asset_name){
+ frappe.db.get_doc('Asset', asset_name).then((asset_doc) => {
+ if(asset_doc.location) frappe.model.set_value(cdt, cdn, 'source_location', asset_doc.location);
+ if(asset_doc.custodian) frappe.model.set_value(cdt, cdn, 'from_employee', asset_doc.custodian);
+ }).catch((err) => {
+ console.log(err); // eslint-disable-line
+ });
+ }
+ }
+});
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json
index 68076e1f74..3472ab5d7d 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.json
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.json
@@ -1,26 +1,19 @@
{
"allow_import": 1,
- "autoname": "naming_series:",
+ "autoname": "format:ACC-ASM-{YYYY}-{#####}",
"creation": "2016-04-25 18:00:23.559973",
"doctype": "DocType",
+ "engine": "InnoDB",
"field_order": [
- "naming_series",
"company",
"purpose",
- "asset",
- "transaction_date",
"column_break_4",
- "quantity",
- "select_serial_no",
- "serial_no",
- "section_break_7",
- "source_location",
- "target_location",
- "column_break_10",
- "from_employee",
- "to_employee",
+ "transaction_date",
+ "section_break_10",
+ "assets",
"reference",
"reference_doctype",
+ "column_break_9",
"reference_name",
"amended_from"
],
@@ -36,23 +29,12 @@
"reqd": 1
},
{
- "default": "Transfer",
"fieldname": "purpose",
"fieldtype": "Select",
"label": "Purpose",
"options": "\nIssue\nReceipt\nTransfer",
"reqd": 1
},
- {
- "fieldname": "asset",
- "fieldtype": "Link",
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Asset",
- "options": "Asset",
- "reqd": 1
- },
{
"fieldname": "transaction_date",
"fieldtype": "Datetime",
@@ -65,56 +47,7 @@
"fieldtype": "Column Break"
},
{
- "fieldname": "quantity",
- "fieldtype": "Float",
- "label": "Quantity"
- },
- {
- "fieldname": "select_serial_no",
- "fieldtype": "Link",
- "label": "Select Serial No",
- "options": "Serial No"
- },
- {
- "fieldname": "serial_no",
- "fieldtype": "Small Text",
- "label": "Serial No"
- },
- {
- "fieldname": "section_break_7",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "source_location",
- "fieldtype": "Link",
- "label": "Source Location",
- "options": "Location"
- },
- {
- "fieldname": "target_location",
- "fieldtype": "Link",
- "label": "Target Location",
- "options": "Location"
- },
- {
- "fieldname": "column_break_10",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "from_employee",
- "fieldtype": "Link",
- "ignore_user_permissions": 1,
- "label": "From Employee",
- "options": "Employee"
- },
- {
- "fieldname": "to_employee",
- "fieldtype": "Link",
- "ignore_user_permissions": 1,
- "label": "To Employee",
- "options": "Employee"
- },
- {
+ "collapsible": 1,
"fieldname": "reference",
"fieldtype": "Section Break",
"label": "Reference"
@@ -122,18 +55,16 @@
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
- "label": "Reference DocType",
+ "label": "Reference Document Type",
"no_copy": 1,
- "options": "DocType",
- "read_only": 1
+ "options": "DocType"
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
- "label": "Reference Name",
+ "label": "Reference Document Name",
"no_copy": 1,
- "options": "reference_doctype",
- "read_only": 1
+ "options": "reference_doctype"
},
{
"fieldname": "amended_from",
@@ -145,16 +76,23 @@
"read_only": 1
},
{
- "default": "ACC-ASM-.YYYY.-",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "label": "Series",
- "options": "ACC-ASM-.YYYY.-",
+ "fieldname": "section_break_10",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "assets",
+ "fieldtype": "Table",
+ "label": "Assets",
+ "options": "Asset Movement Item",
"reqd": 1
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
}
],
"is_submittable": 1,
- "modified": "2019-09-16 16:27:53.887634",
+ "modified": "2019-11-23 13:28:47.256935",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Movement",
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index a1d3308b4d..4e1822b2ce 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -5,101 +5,142 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from frappe.model.document import Document
class AssetMovement(Document):
def validate(self):
self.validate_asset()
self.validate_location()
+ self.validate_employee()
def validate_asset(self):
- status, company = frappe.db.get_value("Asset", self.asset, ["status", "company"])
- if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"):
- frappe.throw(_("{0} asset cannot be transferred").format(status))
+ for d in self.assets:
+ status, company = frappe.db.get_value("Asset", d.asset, ["status", "company"])
+ if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"):
+ frappe.throw(_("{0} asset cannot be transferred").format(status))
- if company != self.company:
- frappe.throw(_("Asset {0} does not belong to company {1}").format(self.asset, self.company))
+ if company != self.company:
+ frappe.throw(_("Asset {0} does not belong to company {1}").format(d.asset, self.company))
- if self.serial_no and len(get_serial_nos(self.serial_no)) != self.quantity:
- frappe.throw(_("Number of serial nos and quantity must be the same"))
-
- if not(self.source_location or self.target_location or self.from_employee or self.to_employee):
- frappe.throw(_("Either location or employee must be required"))
-
- if (not self.serial_no and
- frappe.db.get_value('Serial No', {'asset': self.asset}, 'name')):
- frappe.throw(_("Serial no is required for the asset {0}").format(self.asset))
+ if not (d.source_location or d.target_location or d.from_employee or d.to_employee):
+ frappe.throw(_("Either location or employee must be required"))
def validate_location(self):
- if self.purpose in ['Transfer', 'Issue']:
- if not self.serial_no and not (self.from_employee or self.to_employee):
- self.source_location = frappe.db.get_value("Asset", self.asset, "location")
+ for d in self.assets:
+ if self.purpose in ['Transfer', 'Issue']:
+ if not d.source_location:
+ d.source_location = frappe.db.get_value("Asset", d.asset, "location")
- if self.purpose == 'Issue' and not (self.source_location or self.from_employee):
- frappe.throw(_("Source Location is required for the asset {0}").format(self.asset))
+ if not d.source_location:
+ frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset))
- if self.serial_no and self.source_location:
- s_nos = get_serial_nos(self.serial_no)
- serial_nos = frappe.db.sql_list(""" select name from `tabSerial No` where location != '%s'
- and name in (%s)""" %(self.source_location, ','.join(['%s'] * len(s_nos))), tuple(s_nos))
+ if d.source_location:
+ current_location = frappe.db.get_value("Asset", d.asset, "location")
- if serial_nos:
- frappe.throw(_("Serial nos {0} does not belongs to the location {1}").
- format(','.join(serial_nos), self.source_location))
+ if current_location != d.source_location:
+ frappe.throw(_("Asset {0} does not belongs to the location {1}").
+ format(d.asset, d.source_location))
+
+ if self.purpose == 'Issue':
+ if d.target_location:
+ frappe.throw(_("Issuing cannot be done to a location. \
+ Please enter employee who has issued Asset {0}").format(d.asset), title="Incorrect Movement Purpose")
+ if not d.to_employee:
+ frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset))
+
+ if self.purpose == 'Transfer':
+ if d.to_employee:
+ frappe.throw(_("Transferring cannot be done to an Employee. \
+ Please enter location where Asset {0} has to be transferred").format(
+ d.asset), title="Incorrect Movement Purpose")
+ if not d.target_location:
+ frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset))
+ if d.source_location == d.target_location:
+ frappe.throw(_("Source and Target Location cannot be same"))
+
+ if self.purpose == 'Receipt':
+ # only when asset is bought and first entry is made
+ if not d.source_location and not (d.target_location or d.to_employee):
+ frappe.throw(_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset))
+ elif d.source_location:
+ # when asset is received from an employee
+ if d.target_location and not d.from_employee:
+ frappe.throw(_("From employee is required while receiving Asset {0} to a target location").format(d.asset))
+ if d.from_employee and not d.target_location:
+ frappe.throw(_("Target Location is required while receiving Asset {0} from an employee").format(d.asset))
+ if d.to_employee and d.target_location:
+ frappe.throw(_("Asset {0} cannot be received at a location and \
+ given to employee in a single movement").format(d.asset))
- if self.source_location and self.source_location == self.target_location and self.purpose == 'Transfer':
- frappe.throw(_("Source and Target Location cannot be same"))
+ def validate_employee(self):
+ for d in self.assets:
+ if d.from_employee:
+ current_custodian = frappe.db.get_value("Asset", d.asset, "custodian")
- if self.purpose == 'Receipt' and not (self.target_location or self.to_employee):
- frappe.throw(_("Target Location is required for the asset {0}").format(self.asset))
+ if current_custodian != d.from_employee:
+ frappe.throw(_("Asset {0} does not belongs to the custodian {1}").
+ format(d.asset, d.from_employee))
+
+ if d.to_employee and frappe.db.get_value("Employee", d.to_employee, "company") != self.company:
+ frappe.throw(_("Employee {0} does not belongs to the company {1}").
+ format(d.to_employee, self.company))
def on_submit(self):
self.set_latest_location_in_asset()
+
+ def before_cancel(self):
+ self.validate_last_movement()
def on_cancel(self):
self.set_latest_location_in_asset()
+
+ def validate_last_movement(self):
+ for d in self.assets:
+ auto_gen_movement_entry = frappe.db.sql(
+ """
+ SELECT asm.name
+ FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm
+ WHERE
+ asm.docstatus=1 and
+ asm_item.parent=asm.name and
+ asm_item.asset=%s and
+ asm.company=%s and
+ asm_item.source_location is NULL and
+ asm.purpose=%s
+ ORDER BY
+ asm.transaction_date asc
+ """, (d.asset, self.company, 'Receipt'), as_dict=1)
+ if auto_gen_movement_entry[0].get('name') == self.name:
+ frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \
+ auto generated for Asset {1}').format(self.name, d.asset))
def set_latest_location_in_asset(self):
- location, employee = '', ''
+ current_location, current_employee = '', ''
cond = "1=1"
- args = {
- 'asset': self.asset,
- 'company': self.company
- }
+ for d in self.assets:
+ args = {
+ 'asset': d.asset,
+ 'company': self.company
+ }
- if self.serial_no:
- cond = "serial_no like %(txt)s"
- args.update({
- 'txt': "%%%s%%" % self.serial_no
- })
+ # latest entry corresponds to current document's location, employee when transaction date > previous dates
+ # In case of cancellation it corresponds to previous latest document's location, employee
+ latest_movement_entry = frappe.db.sql(
+ """
+ SELECT asm_item.target_location, asm_item.to_employee
+ FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm
+ WHERE
+ asm_item.parent=asm.name and
+ asm_item.asset=%(asset)s and
+ asm.company=%(company)s and
+ asm.docstatus=1 and {0}
+ ORDER BY
+ asm.transaction_date desc limit 1
+ """.format(cond), args)
+ if latest_movement_entry:
+ current_location = latest_movement_entry[0][0]
+ current_employee = latest_movement_entry[0][1]
- latest_movement_entry = frappe.db.sql("""select target_location, to_employee from `tabAsset Movement`
- where asset=%(asset)s and docstatus=1 and company=%(company)s and {0}
- order by transaction_date desc limit 1""".format(cond), args)
-
- if latest_movement_entry:
- location = latest_movement_entry[0][0]
- employee = latest_movement_entry[0][1]
- elif self.purpose in ['Transfer', 'Receipt']:
- movement_entry = frappe.db.sql("""select source_location, from_employee from `tabAsset Movement`
- where asset=%(asset)s and docstatus=2 and company=%(company)s and {0}
- order by transaction_date asc limit 1""".format(cond), args)
- if movement_entry:
- location = movement_entry[0][0]
- employee = movement_entry[0][1]
-
- if not self.serial_no:
- frappe.db.set_value("Asset", self.asset, "location", location)
-
- if not employee and self.purpose in ['Receipt', 'Transfer']:
- employee = self.to_employee
-
- if self.serial_no:
- for d in get_serial_nos(self.serial_no):
- if (location or (self.purpose == 'Issue' and self.source_location)):
- frappe.db.set_value('Serial No', d, 'location', location)
-
- if employee or self.docstatus==2 or self.purpose == 'Issue':
- frappe.db.set_value('Serial No', d, 'employee', employee)
+ frappe.db.set_value('Asset', d.asset, 'location', current_location)
+ frappe.db.set_value('Asset', d.asset, 'custodian', current_employee)
diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
index 4d85337445..c3755a3fb9 100644
--- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe
import unittest
+import erpnext
from erpnext.stock.doctype.item.test_item import make_item
from frappe.utils import now, nowdate, get_last_day, add_days
from erpnext.assets.doctype.asset.test_asset import create_asset_data
@@ -16,7 +17,6 @@ class TestAssetMovement(unittest.TestCase):
def setUp(self):
create_asset_data()
make_location()
- make_serialized_item()
def test_movement(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
@@ -38,68 +38,72 @@ class TestAssetMovement(unittest.TestCase):
if asset.docstatus == 0:
asset.submit()
+
+ # check asset movement is created
if not frappe.db.exists("Location", "Test Location 2"):
frappe.get_doc({
'doctype': 'Location',
'location_name': 'Test Location 2'
}).insert()
- movement1 = create_asset_movement(asset= asset.name, purpose = 'Transfer',
- company=asset.company, source_location="Test Location", target_location="Test Location 2")
+ movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
+ assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
+ reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
- movement2 = create_asset_movement(asset= asset.name, purpose = 'Transfer',
- company=asset.company, source_location = "Test Location 2", target_location="Test Location")
+ movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company,
+ assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}],
+ reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
- movement2.cancel()
- self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
-
- def test_movement_for_serialized_asset(self):
- asset_item = "Test Serialized Asset Item"
- pr = make_purchase_receipt(item_code=asset_item, rate = 1000, qty=3, location = "Mumbai")
- asset_name = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name')
-
+ employee = make_employee("testassetmovemp@example.com", company="_Test Company")
+ movement3 = create_asset_movement(purpose = 'Issue', company = asset.company,
+ assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}],
+ reference_doctype = 'Purchase Receipt', reference_name = pr.name)
+
+ # after issuing asset should belong to an employee not at a location
+ self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
+ self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
+
+ def test_last_movement_cancellation(self):
+ pr = make_purchase_receipt(item_code="Macbook Pro",
+ qty=1, rate=100000.0, location="Test Location")
+
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
- month_end_date = get_last_day(nowdate())
- asset.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
-
asset.calculate_depreciation = 1
+ asset.available_for_use_date = '2020-06-06'
+ asset.purchase_date = '2020-06-06'
asset.append("finance_books", {
- "expected_value_after_useful_life": 200,
+ "expected_value_after_useful_life": 10000,
+ "next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
- "depreciation_start_date": month_end_date
+ "depreciation_start_date": "2020-06-06"
})
- asset.submit()
- serial_nos = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'serial_no')
+ if asset.docstatus == 0:
+ asset.submit()
+
+ if not frappe.db.exists("Location", "Test Location 2"):
+ frappe.get_doc({
+ 'doctype': 'Location',
+ 'location_name': 'Test Location 2'
+ }).insert()
+
+ movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name })
+ self.assertRaises(frappe.ValidationError, movement.cancel)
- mov1 = create_asset_movement(asset=asset_name, purpose = 'Transfer',
- company=asset.company, source_location = "Mumbai", target_location="Pune", serial_no=serial_nos)
- self.assertEqual(mov1.target_location, "Pune")
+ movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
+ assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
+ reference_doctype = 'Purchase Receipt', reference_name = pr.name)
+ self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
- serial_no = frappe.db.get_value('Serial No', {'asset': asset_name}, 'name')
-
- employee = make_employee("testassetemp@example.com")
- create_asset_movement(asset=asset_name, purpose = 'Transfer',
- company=asset.company, serial_no=serial_no, to_employee=employee)
-
- self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), employee)
-
- create_asset_movement(asset=asset_name, purpose = 'Transfer', company=asset.company,
- serial_no=serial_no, from_employee=employee, to_employee="_T-Employee-00001")
-
- self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Pune")
-
- mov4 = create_asset_movement(asset=asset_name, purpose = 'Transfer',
- company=asset.company, source_location = "Pune", target_location="Nagpur", serial_no=serial_nos)
- self.assertEqual(mov4.target_location, "Nagpur")
- self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Nagpur")
- self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), "_T-Employee-00001")
+ movement1.cancel()
+ self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
def create_asset_movement(**args):
args = frappe._dict(args)
@@ -109,22 +113,14 @@ def create_asset_movement(**args):
movement = frappe.new_doc("Asset Movement")
movement.update({
- "asset": args.asset,
+ "assets": args.assets,
"transaction_date": args.transaction_date,
- "target_location": args.target_location,
"company": args.company,
'purpose': args.purpose or 'Receipt',
- 'serial_no': args.serial_no,
- 'quantity': len(get_serial_nos(args.serial_no)) if args.serial_no else 1,
- 'from_employee': "_T-Employee-00001" or args.from_employee,
- 'to_employee': args.to_employee
+ 'reference_doctype': args.reference_doctype,
+ 'reference_name': args.reference_name
})
- if args.source_location:
- movement.update({
- 'source_location': args.source_location
- })
-
movement.insert()
movement.submit()
@@ -137,33 +133,3 @@ def make_location():
'doctype': 'Location',
'location_name': location
}).insert(ignore_permissions = True)
-
-def make_serialized_item():
- asset_item = "Test Serialized Asset Item"
-
- if not frappe.db.exists('Item', asset_item):
- asset_category = frappe.get_all('Asset Category')
-
- if asset_category:
- asset_category = asset_category[0].name
-
- if not asset_category:
- doc = frappe.get_doc({
- 'doctype': 'Asset Category',
- 'asset_category_name': 'Test Asset Category',
- 'depreciation_method': 'Straight Line',
- 'total_number_of_depreciations': 12,
- 'frequency_of_depreciation': 1,
- 'accounts': [{
- 'company_name': '_Test Company',
- 'fixed_asset_account': '_Test Fixed Asset - _TC',
- 'accumulated_depreciation_account': 'Depreciation - _TC',
- 'depreciation_expense_account': 'Depreciation - _TC'
- }]
- }).insert()
-
- asset_category = doc.name
-
- make_item(asset_item, {'is_stock_item':0,
- 'stock_uom': 'Box', 'is_fixed_asset': 1, 'has_serial_no': 1,
- 'asset_category': asset_category, 'serial_no_series': 'ABC.###'})
diff --git a/erpnext/assets/doctype/asset_settings/__init__.py b/erpnext/assets/doctype/asset_movement_item/__init__.py
similarity index 100%
rename from erpnext/assets/doctype/asset_settings/__init__.py
rename to erpnext/assets/doctype/asset_movement_item/__init__.py
diff --git a/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json
new file mode 100644
index 0000000000..994c3c0989
--- /dev/null
+++ b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json
@@ -0,0 +1,86 @@
+{
+ "creation": "2019-10-07 18:49:00.737806",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "asset",
+ "source_location",
+ "from_employee",
+ "column_break_2",
+ "asset_name",
+ "target_location",
+ "to_employee"
+ ],
+ "fields": [
+ {
+ "fieldname": "asset",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Asset",
+ "options": "Asset",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "asset.asset_name",
+ "fieldname": "asset_name",
+ "fieldtype": "Data",
+ "label": "Asset Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "source_location",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Source Location",
+ "options": "Location"
+ },
+ {
+ "fieldname": "target_location",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Target Location",
+ "options": "Location"
+ },
+ {
+ "fieldname": "from_employee",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "From Employee",
+ "options": "Employee"
+ },
+ {
+ "fieldname": "to_employee",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "To Employee",
+ "options": "Employee"
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Company",
+ "options": "Company",
+ "read_only": 1
+ }
+ ],
+ "istable": 1,
+ "modified": "2019-10-09 15:59:08.265141",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Movement Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.py b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.py
similarity index 59%
rename from erpnext/assets/doctype/asset_settings/asset_settings.py
rename to erpnext/assets/doctype/asset_movement_item/asset_movement_item.py
index e303ebd23f..4c6aaab58a 100644
--- a/erpnext/assets/doctype/asset_settings/asset_settings.py
+++ b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
+# import frappe
from frappe.model.document import Document
-class AssetSettings(Document):
+class AssetMovementItem(Document):
pass
diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.js b/erpnext/assets/doctype/asset_settings/asset_settings.js
deleted file mode 100644
index 3b421486c3..0000000000
--- a/erpnext/assets/doctype/asset_settings/asset_settings.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Asset Settings', {
-});
diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.json b/erpnext/assets/doctype/asset_settings/asset_settings.json
deleted file mode 100644
index edc5ce169c..0000000000
--- a/erpnext/assets/doctype/asset_settings/asset_settings.json
+++ /dev/null
@@ -1,148 +0,0 @@
-{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-01-03 10:30:32.983381",
- "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": "depreciation_options",
- "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,
- "label": "Depreciation Options",
- "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": "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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-05-26 18:31:19.930563",
- "modified_by": "Administrator",
- "module": "Assets",
- "name": "Asset Settings",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_settings/test_asset_settings.js b/erpnext/assets/doctype/asset_settings/test_asset_settings.js
deleted file mode 100644
index eac2c928f3..0000000000
--- a/erpnext/assets/doctype/asset_settings/test_asset_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset Settings
- () => frappe.tests.make('Asset Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset_settings/test_asset_settings.py b/erpnext/assets/doctype/asset_settings/test_asset_settings.py
deleted file mode 100644
index 75f146a27e..0000000000
--- a/erpnext/assets/doctype/asset_settings/test_asset_settings.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-import unittest
-
-class TestAssetSettings(unittest.TestCase):
- pass
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json
index a25b4ce82e..3236e726de 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json
@@ -60,7 +60,8 @@
{
"fieldname": "date",
"fieldtype": "Date",
- "label": "Date"
+ "label": "Date",
+ "reqd": 1
},
{
"fieldname": "current_asset_value",
@@ -110,7 +111,7 @@
}
],
"is_submittable": 1,
- "modified": "2019-05-26 09:46:23.613412",
+ "modified": "2019-11-22 14:09:25.800375",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Value Adjustment",
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 56425a0dcb..155597e856 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -5,12 +5,13 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import flt, getdate, cint, date_diff
+from frappe.utils import flt, getdate, cint, date_diff, formatdate
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from frappe.model.document import Document
class AssetValueAdjustment(Document):
def validate(self):
+ self.validate_date()
self.set_difference_amount()
self.set_current_asset_value()
@@ -23,6 +24,12 @@ class AssetValueAdjustment(Document):
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
self.reschedule_depreciations(self.current_asset_value)
+
+ def validate_date(self):
+ asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
+ if getdate(self.date) < getdate(asset_purchase_date):
+ frappe.throw(_("Asset Value Adjustment cannot be posted before Asset's purchase date {0}.")
+ .format(formatdate(asset_purchase_date)), title="Incorrect Date")
def set_difference_amount(self):
self.difference_amount = flt(self.current_asset_value - self.new_asset_value)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 845ff747d6..f62df20ae1 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -313,7 +313,7 @@ def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=
last_purchase_details = get_last_purchase_details(item_code, name)
if last_purchase_details:
- last_purchase_rate = (last_purchase_details['base_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate
+ last_purchase_rate = (last_purchase_details['base_net_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate
return last_purchase_rate
else:
item_last_purchase_rate = frappe.get_cached_value("Item", item_code, "last_purchase_rate")
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 66ad97ac09..c409c1f46e 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -43,6 +43,7 @@
"base_amount",
"pricing_rules",
"is_free_item",
+ "is_fixed_asset",
"section_break_29",
"net_rate",
"net_amount",
@@ -699,11 +700,19 @@
"fieldtype": "Data",
"label": "Manufacturer Part Number",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fetch_from": "item_code.is_fixed_asset",
+ "fieldname": "is_fixed_asset",
+ "fieldtype": "Check",
+ "label": "Is Fixed Asset",
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-09-17 22:32:34.703923",
+ "modified": "2019-11-07 17:19:12.090355",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 9ad06f9b7e..2f0cfa64fc 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -134,7 +134,7 @@ frappe.ui.form.on("Request for Quotation",{
if (args.search_type === "Tag" && args.tag) {
return frappe.call({
type: "GET",
- method: "frappe.desk.tags.get_tagged_docs",
+ method: "frappe.desk.doctype.tag.tag.get_tagged_docs",
args: {
"doctype": "Supplier",
"tag": args.tag
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index a10ce46e33..95db33b0f8 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -344,13 +344,9 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc =
@frappe.whitelist()
def get_supplier_tag():
- data = frappe.db.sql("select _user_tags from `tabSupplier`")
-
- tags = []
- for tag in data:
- tags += filter(bool, tag[0].split(","))
-
- tags = list(set(tags))
-
- return tags
+ if not frappe.cache().hget("Supplier", "Tags"):
+ filters = {"document_type": "Supplier"}
+ tags = list(set([tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag]))
+ frappe.cache().hset("Supplier", "Tags", tags)
+ return frappe.cache().hget("Supplier", "Tags")
diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py
index 8c0a1e56f7..b5598f8d0b 100644
--- a/erpnext/buying/utils.py
+++ b/erpnext/buying/utils.py
@@ -24,12 +24,12 @@ def update_last_purchase_rate(doc, is_submit):
last_purchase_rate = None
if last_purchase_details and \
(last_purchase_details.purchase_date > this_purchase_date):
- last_purchase_rate = last_purchase_details['base_rate']
+ last_purchase_rate = last_purchase_details['base_net_rate']
elif is_submit == 1:
# even if this transaction is the latest one, it should be submitted
# for it to be considered for latest purchase rate
if flt(d.conversion_factor):
- last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor)
+ last_purchase_rate = flt(d.base_net_rate) / flt(d.conversion_factor)
# Check if item code is present
# Conversion factor should not be mandatory for non itemized items
elif d.item_code:
diff --git a/erpnext/change_log/v12/v12_2_0.md b/erpnext/change_log/v12/v12_2_0.md
new file mode 100644
index 0000000000..0ec0eeca3d
--- /dev/null
+++ b/erpnext/change_log/v12/v12_2_0.md
@@ -0,0 +1,14 @@
+# Version 12.2.0 Release Notes
+
+### Accounting
+
+1. Fixed Asset
+ - "Enable CWIP" options moved to Asset Category from Asset Settings
+ - Removed Asset link from Purchase Receipt Item table
+ - Enhanced Asset master
+ - Asset Movement now handles movement of multiple assets
+ - Introduced monthly depreciation
+2. GL Entries for Landed Cost Voucher now posted directly against individual Charges account
+3. Optimization of BOM Update Tool
+4. Syncing of Stock and Account balance is enforced, in case of perpetual inventory
+5. Rendered email template in Email Campaign
diff --git a/erpnext/config/assets.py b/erpnext/config/assets.py
index 3c9452f5a4..4cf7cf0806 100644
--- a/erpnext/config/assets.py
+++ b/erpnext/config/assets.py
@@ -21,10 +21,6 @@ def get_data():
"name": "Asset Category",
"onboard": 1,
},
- {
- "type": "doctype",
- "name": "Asset Settings",
- },
{
"type": "doctype",
"name": "Asset Movement",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 320a618f68..a912ef00d1 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -718,48 +718,6 @@ class AccountsController(TransactionBase):
# at quotation / sales order level and we shouldn't stop someone
# from creating a sales invoice if sales order is already created
- def validate_fixed_asset(self):
- for d in self.get("items"):
- if d.is_fixed_asset:
- # if d.qty > 1:
- # frappe.throw(_("Row #{0}: Qty must be 1, as item is a fixed asset. Please use separate row for multiple qty.").format(d.idx))
-
- if d.meta.get_field("asset") and d.asset:
- asset = frappe.get_doc("Asset", d.asset)
-
- if asset.company != self.company:
- frappe.throw(_("Row #{0}: Asset {1} does not belong to company {2}")
- .format(d.idx, d.asset, self.company))
-
- elif asset.item_code != d.item_code:
- frappe.throw(_("Row #{0}: Asset {1} does not linked to Item {2}")
- .format(d.idx, d.asset, d.item_code))
-
- # elif asset.docstatus != 1:
- # frappe.throw(_("Row #{0}: Asset {1} must be submitted").format(d.idx, d.asset))
-
- elif self.doctype == "Purchase Invoice":
- # if asset.status != "Submitted":
- # frappe.throw(_("Row #{0}: Asset {1} is already {2}")
- # .format(d.idx, d.asset, asset.status))
- if getdate(asset.purchase_date) != getdate(self.posting_date):
- frappe.throw(
- _("Row #{0}: Posting Date must be same as purchase date {1} of asset {2}").format(d.idx,
- asset.purchase_date,
- d.asset))
- elif asset.is_existing_asset:
- frappe.throw(
- _("Row #{0}: Purchase Invoice cannot be made against an existing asset {1}").format(
- d.idx, d.asset))
-
- elif self.docstatus == "Sales Invoice" and self.docstatus == 1:
- if self.update_stock:
- frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
-
- elif asset.status in ("Scrapped", "Cancelled", "Sold"):
- frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}")
- .format(d.idx, d.asset, asset.status))
-
def delink_advance_entries(self, linked_doc_name):
total_allocated_amount = 0
for adv in self.advances:
@@ -1172,6 +1130,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
data = json.loads(trans_items)
+ sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
for d in data:
@@ -1192,8 +1151,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
frappe.throw(_("Cannot set quantity less than received quantity"))
child_item.qty = flt(d.get("qty"))
+ precision = child_item.precision("rate") or 2
- if flt(child_item.billed_amt) > (flt(d.get("rate")) * flt(d.get("qty"))):
+ if flt(child_item.billed_amt, precision) > flt(flt(d.get("rate")) * flt(d.get("qty")), precision):
frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
.format(child_item.idx, child_item.item_code))
else:
@@ -1204,18 +1164,22 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
# if rate is greater than price_list_rate, set margin
# or set discount
child_item.discount_percentage = 0
- child_item.margin_type = "Amount"
- child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate,
- child_item.precision("margin_rate_or_amount"))
- child_item.rate_with_margin = child_item.rate
+
+ if parent_doctype in sales_doctypes:
+ child_item.margin_type = "Amount"
+ child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate,
+ child_item.precision("margin_rate_or_amount"))
+ child_item.rate_with_margin = child_item.rate
else:
child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
child_item.precision("discount_percentage"))
child_item.discount_amount = flt(
child_item.price_list_rate) - flt(child_item.rate)
- child_item.margin_type = ""
- child_item.margin_rate_or_amount = 0
- child_item.rate_with_margin = 0
+
+ if parent_doctype in sales_doctypes:
+ child_item.margin_type = ""
+ child_item.margin_rate_or_amount = 0
+ child_item.rate_with_margin = 0
child_item.flags.ignore_validate_update_after_submit = True
if new_child_flag:
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 0dde898005..d12643af82 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -101,7 +101,7 @@ class BuyingController(StockController):
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
def get_asset_items(self):
- if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']:
+ if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
return []
return [d.item_code for d in self.items if d.is_fixed_asset]
@@ -150,25 +150,26 @@ class BuyingController(StockController):
TODO: rename item_tax_amount to valuation_tax_amount
"""
- stock_items = self.get_stock_items() + self.get_asset_items()
+ stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
- stock_items_qty, stock_items_amount = 0, 0
- last_stock_item_idx = 1
+ stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
+ last_item_idx = 1
for d in self.get(parentfield):
- if d.item_code and d.item_code in stock_items:
- stock_items_qty += flt(d.qty)
- stock_items_amount += flt(d.base_net_amount)
- last_stock_item_idx = d.idx
+ if d.item_code and d.item_code in stock_and_asset_items:
+ stock_and_asset_items_qty += flt(d.qty)
+ stock_and_asset_items_amount += flt(d.base_net_amount)
+ last_item_idx = d.idx
total_valuation_amount = sum([flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]])
valuation_amount_adjustment = total_valuation_amount
for i, item in enumerate(self.get(parentfield)):
- if item.item_code and item.qty and item.item_code in stock_items:
- item_proportion = flt(item.base_net_amount) / stock_items_amount if stock_items_amount \
- else flt(item.qty) / stock_items_qty
- if i == (last_stock_item_idx - 1):
+ if item.item_code and item.qty and item.item_code in stock_and_asset_items:
+ item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
+ else flt(item.qty) / stock_and_asset_items_qty
+
+ if i == (last_item_idx - 1):
item.item_tax_amount = flt(valuation_amount_adjustment,
self.precision("item_tax_amount", item))
else:
@@ -572,43 +573,33 @@ class BuyingController(StockController):
asset_items = self.get_asset_items()
if asset_items:
- self.make_serial_nos_for_asset(asset_items)
+ self.auto_make_assets(asset_items)
- def make_serial_nos_for_asset(self, asset_items):
+ def auto_make_assets(self, asset_items):
items_data = get_asset_item_details(asset_items)
+ messages = []
for d in self.items:
if d.is_fixed_asset:
item_data = items_data.get(d.item_code)
- if not d.asset:
- asset = self.make_asset(d)
- d.db_set('asset', asset)
- if item_data.get('has_serial_no'):
- # If item has serial no
- if item_data.get('serial_no_series') and not d.serial_no:
- serial_nos = get_auto_serial_nos(item_data.get('serial_no_series'), d.qty)
- elif d.serial_no:
- serial_nos = d.serial_no
- elif not d.serial_no:
- frappe.throw(_("Serial no is mandatory for the item {0}").format(d.item_code))
+ if item_data.get('auto_create_assets'):
+ # If asset has to be auto created
+ # Check for asset naming series
+ if item_data.get('asset_naming_series'):
+ for qty in range(cint(d.qty)):
+ self.make_asset(d)
+ is_plural = 's' if cint(d.qty) != 1 else ''
+ messages.append(_('{0} Asset{2} Created for {1}').format(cint(d.qty), d.item_code, is_plural))
+ else:
+ frappe.throw(_("Row {1}: Asset Naming Series is mandatory for the auto creation for item {0}")
+ .format(d.item_code, d.idx))
+ else:
+ messages.append(_("Assets not created for {0}. You will have to create asset manually.")
+ .format(d.item_code))
- auto_make_serial_nos({
- 'serial_no': serial_nos,
- 'item_code': d.item_code,
- 'via_stock_ledger': False,
- 'company': self.company,
- 'supplier': self.supplier,
- 'actual_qty': d.qty,
- 'purchase_document_type': self.doctype,
- 'purchase_document_no': self.name,
- 'asset': d.asset,
- 'location': d.asset_location
- })
- d.db_set('serial_no', serial_nos)
-
- if d.asset:
- self.make_asset_movement(d)
+ for message in messages:
+ frappe.msgprint(message, title="Success")
def make_asset(self, row):
if not row.asset_location:
@@ -617,7 +608,7 @@ class BuyingController(StockController):
item_data = frappe.db.get_value('Item',
row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1)
- purchase_amount = flt(row.base_net_amount + row.item_tax_amount)
+ purchase_amount = flt(row.base_rate + row.item_tax_amount)
asset = frappe.get_doc({
'doctype': 'Asset',
'item_code': row.item_code,
@@ -640,57 +631,49 @@ class BuyingController(StockController):
asset.set_missing_values()
asset.insert()
- asset_link = frappe.utils.get_link_to_form('Asset', asset.name)
- frappe.msgprint(_("Asset {0} created").format(asset_link))
- return asset.name
-
- def make_asset_movement(self, row):
- asset_movement = frappe.get_doc({
- 'doctype': 'Asset Movement',
- 'asset': row.asset,
- 'target_location': row.asset_location,
- 'purpose': 'Receipt',
- 'serial_no': row.serial_no,
- 'quantity': len(get_serial_nos(row.serial_no)),
- 'company': self.company,
- 'transaction_date': self.posting_date,
- 'reference_doctype': self.doctype,
- 'reference_name': self.name
- }).insert()
-
- return asset_movement.name
-
def update_fixed_asset(self, field, delete_asset = False):
for d in self.get("items"):
- if d.is_fixed_asset and d.asset:
- asset = frappe.get_doc("Asset", d.asset)
+ if d.is_fixed_asset:
+ is_auto_create_enabled = frappe.db.get_value('Item', d.item_code, 'auto_create_assets')
+ assets = frappe.db.get_all('Asset', filters={ field : self.name, 'item_code' : d.item_code })
- if delete_asset and asset.docstatus == 0:
- frappe.delete_doc("Asset", asset.name)
- d.db_set('asset', None)
- continue
+ for asset in assets:
+ asset = frappe.get_doc('Asset', asset.name)
+ if delete_asset and is_auto_create_enabled:
+ # need to delete movements to delete assets otherwise throws link exists error
+ movements = frappe.db.sql(
+ """SELECT asm.name
+ FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
+ WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1)
+ for movement in movements:
+ frappe.delete_doc('Asset Movement', movement.name, force=1)
+ frappe.delete_doc("Asset", asset.name, force=1)
+ continue
- if self.docstatus in [0, 1] and not asset.get(field):
- asset.set(field, self.name)
- asset.purchase_date = self.posting_date
- asset.supplier = self.supplier
- elif self.docstatus == 2:
- asset.set(field, None)
- asset.supplier = None
+ if self.docstatus in [0, 1] and not asset.get(field):
+ asset.set(field, self.name)
+ asset.purchase_date = self.posting_date
+ asset.supplier = self.supplier
+ elif self.docstatus == 2:
+ if asset.docstatus == 0:
+ asset.set(field, None)
+ asset.supplier = None
+ if asset.docstatus == 1 and delete_asset:
+ frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\
+ Please cancel the it to continue.').format(asset.name))
- asset.flags.ignore_validate_update_after_submit = True
- asset.flags.ignore_mandatory = True
- if asset.docstatus == 0:
- asset.flags.ignore_validate = True
+ asset.flags.ignore_validate_update_after_submit = True
+ asset.flags.ignore_mandatory = True
+ if asset.docstatus == 0:
+ asset.flags.ignore_validate = True
- asset.save()
+ asset.save()
def delete_linked_asset(self):
if self.doctype == 'Purchase Invoice' and not self.get('update_stock'):
return
frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name)
- frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name)
def validate_schedule_date(self):
if not self.get("items"):
@@ -764,7 +747,7 @@ def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchas
def get_asset_item_details(asset_items):
asset_items_data = {}
- for d in frappe.get_all('Item', fields = ["name", "has_serial_no", "serial_no_series"],
+ for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
filters = {'name': ('in', asset_items)}):
asset_items_data.setdefault(d.name, d)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 2f6b59f0fb..7b4a4c92ad 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -152,6 +152,24 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
conditions = []
+ #Get searchfields from meta and use in Item Link field query
+ meta = frappe.get_meta("Item", cached=True)
+ searchfields = meta.get_search_fields()
+
+ if "description" in searchfields:
+ searchfields.remove("description")
+
+ columns = ''
+ extra_searchfields = [field for field in searchfields
+ if not field in ["name", "item_group", "description"]]
+
+ if extra_searchfields:
+ columns = ", " + ", ".join(extra_searchfields)
+
+ searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"]
+ if not field in searchfields]
+ searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
+
description_cond = ''
if frappe.db.count('Item', cache=True) < 50000:
# scan description only if items are less than 50000
@@ -162,17 +180,14 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
tabItem.item_group,
if(length(tabItem.description) > 40, \
- concat(substr(tabItem.description, 1, 40), "..."), description) as decription
+ concat(substr(tabItem.description, 1, 40), "..."), description) as description
+ {columns}
from tabItem
where tabItem.docstatus < 2
and tabItem.has_variants=0
and tabItem.disabled=0
and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00')
- and (tabItem.`{key}` LIKE %(txt)s
- 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)
+ and ({scond} or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
{description_cond})
{fcond} {mcond}
order by
@@ -182,6 +197,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
name, item_name
limit %(start)s, %(page_len)s """.format(
key=searchfield,
+ columns=columns,
+ scond=searchfields,
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype).replace('%', '%%'),
description_cond = description_cond),
@@ -463,3 +480,29 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
as_list=1
)
return item_manufacturers
+
+@frappe.whitelist()
+def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
+ query = """
+ select pr.name
+ from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pritem
+ where pr.docstatus = 1 and pritem.parent = pr.name
+ and pr.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
+
+ if filters and filters.get('item_code'):
+ query += " and pritem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
+
+ return frappe.db.sql(query, filters)
+
+@frappe.whitelist()
+def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
+ query = """
+ select pi.name
+ from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` piitem
+ where pi.docstatus = 1 and piitem.parent = pi.name
+ and pi.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
+
+ if filters and filters.get('item_code'):
+ query += " and piitem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
+
+ return frappe.db.sql(query, filters)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 859529204b..81fdbbefc3 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -72,7 +72,7 @@ def validate_returned_items(doc):
items_returned = False
for d in doc.get("items"):
- if d.item_code and (flt(d.qty) < 0 or d.get('received_qty') < 0):
+ if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0):
if d.item_code not in valid_items:
frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}")
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 9d1389c977..2b2c27b052 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -49,7 +49,8 @@ status_map = {
["Submitted", "eval:self.docstatus==1"],
["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"],
["Return", "eval:self.is_return==1 and self.docstatus==1"],
- ["Debit Note Issued", "eval:self.outstanding_amount < 0 and self.docstatus==1"],
+ ["Debit Note Issued",
+ "eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"],
["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"],
["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
["Cancelled", "eval:self.docstatus==2"],
@@ -118,7 +119,6 @@ class StatusUpdater(Document):
if self.doctype in status_map:
_status = self.status
-
if status and update:
self.db_set("status", status)
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 2d87a98f20..542073ebd7 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -207,41 +207,6 @@ class StockController(AccountsController):
reference_doctype=self.doctype,
reference_name=self.name)).insert().name
- 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, self.company)
-
- cost_center = self.get_company_default("cost_center")
- stock_adjustment_account = self.get_company_default("stock_adjustment_account")
-
- gl_entries = []
- for account, diff in acc_diff.items():
- if diff:
- gl_entries.append([
- # stock in hand account
- voucher_obj.get_gl_dict({
- "account": account,
- "against": stock_adjustment_account,
- "debit": diff,
- "remarks": "Adjustment Accounting Entry for Stock",
- }),
-
- # account against stock in hand
- voucher_obj.get_gl_dict({
- "account": stock_adjustment_account,
- "against": account,
- "credit": diff,
- "cost_center": cost_center or None,
- "remarks": "Adjustment Accounting Entry for Stock",
- }),
- ])
-
- if gl_entries:
- from erpnext.accounts.general_ledger import make_gl_entries
- make_gl_entries(gl_entries)
-
def check_expense_account(self, item):
if not item.get("expense_account"):
frappe.throw(_("Expense or Difference account is mandatory for Item {0} as it impacts overall stock value").format(item.item_code))
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.json b/erpnext/crm/doctype/email_campaign/email_campaign.json
index 3259136275..736a9d6173 100644
--- a/erpnext/crm/doctype/email_campaign/email_campaign.json
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.json
@@ -52,7 +52,8 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Email Campaign For ",
- "options": "\nLead\nContact"
+ "options": "\nLead\nContact",
+ "reqd": 1
},
{
"fieldname": "recipient",
@@ -69,7 +70,7 @@
"options": "User"
}
],
- "modified": "2019-07-12 13:47:37.261213",
+ "modified": "2019-11-11 17:18:47.342839",
"modified_by": "Administrator",
"module": "CRM",
"name": "Email Campaign",
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py
index 98e4927beb..3050d05a7c 100644
--- a/erpnext/crm/doctype/email_campaign/email_campaign.py
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.py
@@ -73,13 +73,13 @@ def send_mail(entry, email_campaign):
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email')
-
+ context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)}
# send mail and link communication to document
comm = make(
doctype = "Email Campaign",
name = email_campaign.name,
subject = email_template.get("subject"),
- content = email_template.get("response"),
+ content = frappe.render_template(email_template.get("response"), context),
sender = sender,
recipients = recipient,
communication_medium = "Email",
diff --git a/erpnext/education/doctype/education_settings/education_settings.json b/erpnext/education/doctype/education_settings/education_settings.json
index 32b5fb8198..967a030fd2 100644
--- a/erpnext/education/doctype/education_settings/education_settings.json
+++ b/erpnext/education/doctype/education_settings/education_settings.json
@@ -11,6 +11,7 @@
"validate_batch",
"validate_course",
"academic_term_reqd",
+ "user_creation_skip",
"section_break_7",
"instructor_created_by",
"web_academy_settings_section",
@@ -91,6 +92,13 @@
"fieldname": "enable_lms",
"fieldtype": "Check",
"label": "Enable LMS"
+ },
+ {
+ "default": "0",
+ "description": "By default, a new User is created for every new Student. If enabled, no new User will be created when a new Student is created.",
+ "fieldname": "user_creation_skip",
+ "fieldtype": "Check",
+ "label": "Skip User creation for new Student"
}
],
"issingle": 1,
@@ -133,4 +141,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/education/doctype/student/student.js b/erpnext/education/doctype/student/student.js
index 2c933e28b7..b6e741c4da 100644
--- a/erpnext/education/doctype/student/student.js
+++ b/erpnext/education/doctype/student/student.js
@@ -27,3 +27,16 @@ frappe.ui.form.on('Student', {
}
}
});
+
+frappe.ui.form.on('Student Guardian', {
+ guardians_add: function(frm){
+ frm.fields_dict['guardians'].grid.get_field('guardian').get_query = function(doc){
+ var guardian_list = [];
+ if(!doc.__islocal) guardian_list.push(doc.guardian);
+ $.each(doc.guardians, function(idx, val){
+ if (val.guardian) guardian_list.push(val.guardian);
+ });
+ return { filters: [['Guardian', 'name', 'not in', guardian_list]] };
+ };
+ }
+});
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 705c6e4e98..9af5e22913 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -40,7 +40,8 @@ class Student(Document):
frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant))
def after_insert(self):
- self.create_student_user()
+ if not frappe.get_single('Education Settings').user_creation_skip:
+ self.create_student_user()
def create_student_user(self):
"""Create a website user for student creation if not already exists"""
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 0b6ea8cc7c..28c2ab9e54 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -1,10 +1,8 @@
from __future__ import unicode_literals
import frappe, base64, hashlib, hmac, json
-import datetime
from frappe import _
-
def verify_request():
woocommerce_settings = frappe.get_doc("Woocommerce Settings")
sig = base64.b64encode(
@@ -30,191 +28,149 @@ def order(*args, **kwargs):
frappe.log_error(error_message, "WooCommerce Error")
raise
-
def _order(*args, **kwargs):
woocommerce_settings = frappe.get_doc("Woocommerce Settings")
if frappe.flags.woocomm_test_order_data:
- fd = frappe.flags.woocomm_test_order_data
+ order = frappe.flags.woocomm_test_order_data
event = "created"
elif frappe.request and frappe.request.data:
verify_request()
- fd = json.loads(frappe.request.data)
+ try:
+ order = json.loads(frappe.request.data)
+ except ValueError:
+ #woocommerce returns 'webhook_id=value' for the first request which is not JSON
+ order = frappe.request.data
event = frappe.get_request_header("X-Wc-Webhook-Event")
else:
return "success"
if event == "created":
- raw_billing_data = fd.get("billing")
- customer_woo_com_email = raw_billing_data.get("email")
-
- if frappe.get_value("Customer",{"woocommerce_email": customer_woo_com_email}):
- # Edit
- link_customer_and_address(raw_billing_data,1)
- else:
- # Create
- link_customer_and_address(raw_billing_data,0)
-
-
- items_list = fd.get("line_items")
- for item in items_list:
-
- item_woo_com_id = item.get("product_id")
-
- if frappe.get_value("Item",{"woocommerce_id": item_woo_com_id}):
- #Edit
- link_item(item,1)
- else:
- link_item(item,0)
-
-
+ raw_billing_data = order.get("billing")
customer_name = raw_billing_data.get("first_name") + " " + raw_billing_data.get("last_name")
+ link_customer_and_address(raw_billing_data, customer_name)
+ link_items(order.get("line_items"), woocommerce_settings)
+ create_sales_order(order, woocommerce_settings, customer_name)
- new_sales_order = frappe.new_doc("Sales Order")
- new_sales_order.customer = customer_name
-
- created_date = fd.get("date_created").split("T")
- new_sales_order.transaction_date = created_date[0]
-
- new_sales_order.po_no = fd.get("id")
- new_sales_order.woocommerce_id = fd.get("id")
- new_sales_order.naming_series = woocommerce_settings.sales_order_series or "SO-WOO-"
-
- placed_order_date = created_date[0]
- raw_date = datetime.datetime.strptime(placed_order_date, "%Y-%m-%d")
- raw_delivery_date = frappe.utils.add_to_date(raw_date,days = 7)
- order_delivery_date_str = raw_delivery_date.strftime('%Y-%m-%d')
- order_delivery_date = str(order_delivery_date_str)
-
- new_sales_order.delivery_date = order_delivery_date
- default_set_company = frappe.get_doc("Global Defaults")
- company = raw_billing_data.get("company") or default_set_company.default_company
- found_company = frappe.get_doc("Company",{"name":company})
- company_abbr = found_company.abbr
-
- new_sales_order.company = company
-
- for item in items_list:
- woocomm_item_id = item.get("product_id")
- found_item = frappe.get_doc("Item",{"woocommerce_id": woocomm_item_id})
-
- ordered_items_tax = item.get("total_tax")
-
- new_sales_order.append("items",{
- "item_code": found_item.item_code,
- "item_name": found_item.item_name,
- "description": found_item.item_name,
- "delivery_date":order_delivery_date,
- "uom": woocommerce_settings.uom or _("Nos"),
- "qty": item.get("quantity"),
- "rate": item.get("price"),
- "warehouse": woocommerce_settings.warehouse or "Stores" + " - " + company_abbr
- })
-
- add_tax_details(new_sales_order,ordered_items_tax,"Ordered Item tax",0)
-
- # shipping_details = fd.get("shipping_lines") # used for detailed order
- shipping_total = fd.get("shipping_total")
- shipping_tax = fd.get("shipping_tax")
-
- add_tax_details(new_sales_order,shipping_tax,"Shipping Tax",1)
- add_tax_details(new_sales_order,shipping_total,"Shipping Total",1)
-
- new_sales_order.submit()
-
- frappe.db.commit()
-
-def link_customer_and_address(raw_billing_data,customer_status):
-
- if customer_status == 0:
- # create
+def link_customer_and_address(raw_billing_data, customer_name):
+ customer_woo_com_email = raw_billing_data.get("email")
+ customer_exists = frappe.get_value("Customer", {"woocommerce_email": customer_woo_com_email})
+ if not customer_exists:
+ # Create Customer
customer = frappe.new_doc("Customer")
- address = frappe.new_doc("Address")
-
- if customer_status == 1:
- # Edit
- customer_woo_com_email = raw_billing_data.get("email")
- customer = frappe.get_doc("Customer",{"woocommerce_email": customer_woo_com_email})
+ else:
+ # Edit Customer
+ customer = frappe.get_doc("Customer", {"woocommerce_email": customer_woo_com_email})
old_name = customer.customer_name
- full_name = str(raw_billing_data.get("first_name"))+ " "+str(raw_billing_data.get("last_name"))
- customer.customer_name = full_name
- customer.woocommerce_email = str(raw_billing_data.get("email"))
- customer.save()
- frappe.db.commit()
+ customer.customer_name = customer_name
+ customer.woocommerce_email = customer_woo_com_email
+ customer.flags.ignore_mandatory = True
+ customer.save()
- if customer_status == 1:
- frappe.rename_doc("Customer", old_name, full_name)
- address = frappe.get_doc("Address",{"woocommerce_email":customer_woo_com_email})
- customer = frappe.get_doc("Customer",{"woocommerce_email": customer_woo_com_email})
+ if customer_exists:
+ frappe.rename_doc("Customer", old_name, customer_name)
+ address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email})
+ else:
+ address = frappe.new_doc("Address")
address.address_line1 = raw_billing_data.get("address_1", "Not Provided")
address.address_line2 = raw_billing_data.get("address_2", "Not Provided")
address.city = raw_billing_data.get("city", "Not Provided")
- address.woocommerce_email = str(raw_billing_data.get("email"))
- address.address_type = "Shipping"
- address.country = frappe.get_value("Country", filters={"code":raw_billing_data.get("country", "IN").lower()})
- address.state = raw_billing_data.get("state")
- address.pincode = str(raw_billing_data.get("postcode"))
- address.phone = str(raw_billing_data.get("phone"))
- address.email_id = str(raw_billing_data.get("email"))
-
+ address.woocommerce_email = customer_woo_com_email
+ address.address_type = "Billing"
+ address.country = frappe.get_value("Country", {"code": raw_billing_data.get("country", "IN").lower()})
+ address.state = raw_billing_data.get("state")
+ address.pincode = raw_billing_data.get("postcode")
+ address.phone = raw_billing_data.get("phone")
+ address.email_id = customer_woo_com_email
address.append("links", {
"link_doctype": "Customer",
"link_name": customer.customer_name
})
+ address.flags.ignore_mandatory = True
+ address = address.save()
- address.save()
- frappe.db.commit()
-
- if customer_status == 1:
-
- address = frappe.get_doc("Address",{"woocommerce_email":customer_woo_com_email})
+ if customer_exists:
old_address_title = address.name
- new_address_title = customer.customer_name+"-billing"
+ new_address_title = customer.customer_name + "-billing"
address.address_title = customer.customer_name
address.save()
- frappe.rename_doc("Address",old_address_title,new_address_title)
+ frappe.rename_doc("Address", old_address_title, new_address_title)
- frappe.db.commit()
-
-def link_item(item_data,item_status):
- woocommerce_settings = frappe.get_doc("Woocommerce Settings")
-
- if item_status == 0:
- #Create Item
- item = frappe.new_doc("Item")
-
- if item_status == 1:
- #Edit Item
+def link_items(items_list, woocommerce_settings):
+ for item_data in items_list:
item_woo_com_id = item_data.get("product_id")
- item = frappe.get_doc("Item",{"woocommerce_id": item_woo_com_id})
- item.item_name = str(item_data.get("name"))
- item.item_code = "woocommerce - " + str(item_data.get("product_id"))
- item.woocommerce_id = str(item_data.get("product_id"))
- item.item_group = _("WooCommerce Products")
- item.stock_uom = woocommerce_settings.uom or _("Nos")
- item.save()
+ if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}):
+ #Edit Item
+ item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id})
+ else:
+ #Create Item
+ item = frappe.new_doc("Item")
+
+ item.item_name = item_data.get("name")
+ item.item_code = _("woocommerce - {0}").format(item_data.get("product_id"))
+ item.woocommerce_id = item_data.get("product_id")
+ item.item_group = _("WooCommerce Products")
+ item.stock_uom = woocommerce_settings.uom or _("Nos")
+ item.flags.ignore_mandatory = True
+ item.save()
+
+def create_sales_order(order, woocommerce_settings, customer_name):
+ new_sales_order = frappe.new_doc("Sales Order")
+ new_sales_order.customer = customer_name
+
+ new_sales_order.po_no = new_sales_order.woocommerce_id = order.get("id")
+ new_sales_order.naming_series = woocommerce_settings.sales_order_series or "SO-WOO-"
+
+ created_date = order.get("date_created").split("T")
+ new_sales_order.transaction_date = created_date[0]
+ delivery_after = woocommerce_settings.delivery_after_days or 7
+ new_sales_order.delivery_date = frappe.utils.add_days(created_date[0], delivery_after)
+
+ new_sales_order.company = woocommerce_settings.company
+
+ set_items_in_sales_order(new_sales_order, woocommerce_settings, order)
+ new_sales_order.flags.ignore_mandatory = True
+ new_sales_order.insert()
+ new_sales_order.submit()
+
frappe.db.commit()
-def add_tax_details(sales_order,price,desc,status):
+def set_items_in_sales_order(new_sales_order, woocommerce_settings, order):
+ company_abbr = frappe.db.get_value('Company', woocommerce_settings.company, 'abbr')
- woocommerce_settings = frappe.get_doc("Woocommerce Settings")
+ for item in order.get("line_items"):
+ woocomm_item_id = item.get("product_id")
+ found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
- if status == 0:
- # Product taxes
- account_head_type = woocommerce_settings.tax_account
+ ordered_items_tax = item.get("total_tax")
- if status == 1:
- # Shipping taxes
- account_head_type = woocommerce_settings.f_n_f_account
+ new_sales_order.append("items",{
+ "item_code": found_item.item_code,
+ "item_name": found_item.item_name,
+ "description": found_item.item_name,
+ "delivery_date": new_sales_order.delivery_date,
+ "uom": woocommerce_settings.uom or _("Nos"),
+ "qty": item.get("quantity"),
+ "rate": item.get("price"),
+ "warehouse": woocommerce_settings.warehouse or _("Stores - {0}").format(company_abbr)
+ })
- sales_order.append("taxes",{
- "charge_type":"Actual",
- "account_head": account_head_type,
- "tax_amount": price,
- "description": desc
- })
+ add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
+
+ # shipping_details = order.get("shipping_lines") # used for detailed order
+
+ add_tax_details(new_sales_order, order.get("shipping_tax"), "Shipping Tax", woocommerce_settings.f_n_f_account)
+ add_tax_details(new_sales_order, order.get("shipping_total"), "Shipping Total", woocommerce_settings.f_n_f_account)
+
+def add_tax_details(sales_order, price, desc, tax_account_head):
+ sales_order.append("taxes", {
+ "charge_type":"Actual",
+ "account_head": tax_account_head,
+ "tax_amount": price,
+ "description": desc
+ })
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py
index 7d3f572978..a2b6af99b2 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py
@@ -39,7 +39,7 @@ def get_message(exception):
if hasattr(exception, 'message'):
message = exception.message
elif hasattr(exception, '__str__'):
- message = e.__str__()
+ message = exception.__str__()
else:
message = "Something went wrong while syncing"
return message
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index a4332b199e..64c3b2d273 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -43,14 +43,14 @@ class ShopifySettings(Document):
d.raise_for_status()
self.update_webhook_table(method, d.json())
except Exception as e:
- make_shopify_log(status="Warning", message=e, exception=False)
+ make_shopify_log(status="Warning", exception=e, rollback=True)
def unregister_webhooks(self):
session = get_request_session()
deleted_webhooks = []
for d in self.webhooks:
- url = get_shopify_url('admin/api/2019-04/webhooks.json'.format(d.webhook_id), self)
+ url = get_shopify_url('admin/api/2019-04/webhooks/{0}.json'.format(d.webhook_id), self)
try:
res = session.delete(url, headers=get_header(self))
res.raise_for_status()
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.json b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.json
index dd3c24dce5..956ae09cbd 100644
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.json
+++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.json
@@ -1,694 +1,175 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
"creation": "2018-02-12 15:10:05.495713",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "enable_sync",
+ "sb_00",
+ "woocommerce_server_url",
+ "secret",
+ "cb_00",
+ "api_consumer_key",
+ "api_consumer_secret",
+ "sb_accounting_details",
+ "tax_account",
+ "column_break_10",
+ "f_n_f_account",
+ "defaults_section",
+ "creation_user",
+ "warehouse",
+ "sales_order_series",
+ "column_break_14",
+ "company",
+ "delivery_after_days",
+ "uom",
+ "endpoints",
+ "endpoint"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
+ "default": "0",
"fieldname": "enable_sync",
"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": "Enable Sync",
- "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
+ "label": "Enable Sync"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "sb_00",
- "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,
- "label": "",
- "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
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "woocommerce_server_url",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Woocommerce Server URL",
- "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
+ "label": "Woocommerce Server URL"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "secret",
"fieldtype": "Code",
- "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": "Secret",
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "cb_00",
- "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
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "api_consumer_key",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
- "label": "API consumer key",
- "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
+ "label": "API consumer key"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "api_consumer_secret",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
- "label": "API consumer secret",
- "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
+ "label": "API consumer secret"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "sb_accounting_details",
"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,
- "label": "Accounting Details",
- "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
+ "label": "Accounting Details"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "tax_account",
"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": "Tax Account",
- "length": 0,
- "no_copy": 0,
"options": "Account",
- "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
+ "reqd": 1
},
{
- "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_10",
- "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
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "f_n_f_account",
"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": "Freight and Forwarding Account",
- "length": 0,
- "no_copy": 0,
"options": "Account",
- "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
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "defaults_section",
"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,
- "label": "Defaults",
- "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
+ "label": "Defaults"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"description": "The user that will be used to create Customers, Items and Sales Orders. This user should have the relevant permissions.",
- "fetch_if_empty": 0,
"fieldname": "creation_user",
"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": "Creation User",
- "length": 0,
- "no_copy": 0,
"options": "User",
- "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
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "This warehouse will be used to create Sale Orders. The fallback warehouse is \"Stores\".",
- "fetch_if_empty": 0,
+ "description": "This warehouse will be used to create Sales Orders. The fallback warehouse is \"Stores\".",
"fieldname": "warehouse",
"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": "Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "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
+ "options": "Warehouse"
},
{
- "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_14",
- "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
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"description": "The fallback series is \"SO-WOO-\".",
- "fetch_if_empty": 0,
"fieldname": "sales_order_series",
"fieldtype": "Select",
- "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": "Sales Order Series",
- "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
+ "label": "Sales Order Series"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"description": "This is the default UOM used for items and Sales orders. The fallback UOM is \"Nos\".",
- "fetch_if_empty": 0,
"fieldname": "uom",
"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": "UOM",
- "length": 0,
- "no_copy": 0,
- "options": "UOM",
- "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
+ "options": "UOM"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "endpoints",
"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,
- "label": "Endpoints",
- "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
+ "label": "Endpoints"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "endpoint",
"fieldtype": "Code",
- "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": "Endpoint",
- "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
+ "read_only": 1
+ },
+ {
+ "description": "This company will be used to create Sales Orders.",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "description": "This is the default offset (days) for the Delivery Date in Sales Orders. The fallback offset is 7 days from the order placement date.",
+ "fieldname": "delivery_after_days",
+ "fieldtype": "Int",
+ "label": "Delivery After (Days)"
}
],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "in_create": 0,
- "is_submittable": 0,
"issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "menu_index": 0,
- "modified": "2019-04-08 17:04:16.720696",
+ "modified": "2019-11-04 00:45:21.232096",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Woocommerce Settings",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
- "delete": 0,
"email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
- "report": 0,
"role": "System Manager",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
}
],
"quick_entry": 1,
- "read_only": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
index 055684d445..bd072f40a1 100644
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
+++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
@@ -8,6 +8,7 @@ from frappe import _
from frappe.utils.nestedset import get_root_of
from frappe.model.document import Document
from six.moves.urllib.parse import urlparse
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
class WoocommerceSettings(Document):
def validate(self):
@@ -17,75 +18,21 @@ class WoocommerceSettings(Document):
def create_delete_custom_fields(self):
if self.enable_sync:
+ custom_fields = {}
# create
- create_custom_field_id_and_check_status = False
- create_custom_field_email_check = False
- names = ["Customer-woocommerce_id","Sales Order-woocommerce_id","Item-woocommerce_id","Address-woocommerce_id"]
- names_check_box = ["Customer-woocommerce_check","Sales Order-woocommerce_check","Item-woocommerce_check","Address-woocommerce_check"]
- email_names = ["Customer-woocommerce_email","Address-woocommerce_email"]
+ for doctype in ["Customer", "Sales Order", "Item", "Address"]:
+ df = dict(fieldname='woocommerce_id', label='Woocommerce ID', fieldtype='Data', read_only=1, print_hide=1)
+ create_custom_field(doctype, df)
- for i in zip(names,names_check_box):
-
- if not frappe.get_value("Custom Field",{"name":i[0]}) or not frappe.get_value("Custom Field",{"name":i[1]}):
- create_custom_field_id_and_check_status = True
- break
-
-
- if create_custom_field_id_and_check_status:
- names = ["Customer","Sales Order","Item","Address"]
- for name in names:
- custom = frappe.new_doc("Custom Field")
- custom.dt = name
- custom.label = "woocommerce_id"
- custom.read_only = 1
- custom.save()
-
- custom = frappe.new_doc("Custom Field")
- custom.dt = name
- custom.label = "woocommerce_check"
- custom.fieldtype = "Check"
- custom.read_only = 1
- custom.save()
-
- for i in email_names:
-
- if not frappe.get_value("Custom Field",{"name":i}):
- create_custom_field_email_check = True
- break;
-
- if create_custom_field_email_check:
- names = ["Customer","Address"]
- for name in names:
- custom = frappe.new_doc("Custom Field")
- custom.dt = name
- custom.label = "woocommerce_email"
- custom.read_only = 1
- custom.save()
-
- if not frappe.get_value("Item Group",{"name": _("WooCommerce Products")}):
+ for doctype in ["Customer", "Address"]:
+ df = dict(fieldname='woocommerce_email', label='Woocommerce Email', fieldtype='Data', read_only=1, print_hide=1)
+ create_custom_field(doctype, df)
+
+ 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 = get_root_of("Item Group")
- item_group.save()
-
-
- elif not self.enable_sync:
- # delete
- names = ["Customer-woocommerce_id","Sales Order-woocommerce_id","Item-woocommerce_id","Address-woocommerce_id"]
- names_check_box = ["Customer-woocommerce_check","Sales Order-woocommerce_check","Item-woocommerce_check","Address-woocommerce_check"]
- email_names = ["Customer-woocommerce_email","Address-woocommerce_email"]
- for name in names:
- frappe.delete_doc("Custom Field",name)
-
- for name in names_check_box:
- frappe.delete_doc("Custom Field",name)
-
- for name in email_names:
- frappe.delete_doc("Custom Field",name)
-
- frappe.delete_doc("Item Group", _("WooCommerce Products"))
-
- frappe.db.commit()
+ item_group.insert()
def validate_settings(self):
if self.enable_sync:
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 5c61874f50..9e74bfd290 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -235,17 +235,16 @@ doc_events = {
("Sales Taxes and Charges Template", 'Price List'): {
"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
},
-
"Website Settings": {
"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
},
"Sales Invoice": {
- "on_submit": ["erpnext.regional.france.utils.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"],
+ "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"],
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"on_trash": "erpnext.regional.check_deletion_permission"
},
"Payment Entry": {
- "on_submit": ["erpnext.regional.france.utils.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.make_status_as_paid"],
+ "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.make_status_as_paid"],
"on_trash": "erpnext.regional.check_deletion_permission"
},
'Address': {
@@ -283,7 +282,6 @@ scheduler_events = {
],
"daily": [
"erpnext.stock.reorder_item.reorder_item",
- "erpnext.setup.doctype.email_digest.email_digest.send",
"erpnext.support.doctype.issue.issue.auto_close_tickets",
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
"erpnext.controllers.accounts_controller.update_invoice_status",
@@ -306,6 +304,7 @@ scheduler_events = {
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
],
"daily_long": [
+ "erpnext.setup.doctype.email_digest.email_digest.send",
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
"erpnext.hr.utils.generate_leave_encashment"
diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py
index 9f2f2013a7..df0f75a18c 100644
--- a/erpnext/hr/doctype/department_approver/department_approver.py
+++ b/erpnext/hr/doctype/department_approver/department_approver.py
@@ -19,14 +19,19 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
approvers = []
department_details = {}
department_list = []
- employee_department = filters.get("department") or frappe.get_value("Employee", filters.get("employee"), "department")
+ employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True)
+
+ employee_department = filters.get("department") or employee.department
if employee_department:
department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True)
if department_details:
department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s
and rgt >= %s
and disabled=0
- order by lft desc""", (department_details.lft, department_details.rgt), as_list = True)
+ order by lft desc""", (department_details.lft, department_details.rgt), as_list=True)
+
+ if filters.get("doctype") == "Leave Application" and employee.leave_approver:
+ approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']))
if filters.get("doctype") == "Leave Application":
parentfield = "leave_approvers"
@@ -41,4 +46,4 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
and approver.parentfield = %s
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
- return approvers
\ No newline at end of file
+ return set(tuple(approver) for approver in approvers)
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 3fc330e2d2..703ec06f83 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -167,10 +167,11 @@ class Employee(NestedSet):
def validate_status(self):
if self.status == 'Left':
reports_to = frappe.db.get_all('Employee',
- filters={'reports_to': self.name}
+ filters={'reports_to': self.name, 'status': "Active"},
+ fields=['name','employee_name']
)
if reports_to:
- link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name) for employee in reports_to]
+ link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to]
throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee: ")
+ ', '.join(link_to_employees), EmployeeLeftValidationError)
if not self.relieving_date:
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index 162b697ac8..11ad83ba37 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -21,7 +21,7 @@ def get_data():
},
{
'label': _('Expense'),
- 'items': ['Expense Claim', 'Travel Request']
+ 'items': ['Expense Claim', 'Travel Request', 'Employee Advance']
},
{
'label': _('Benefit'),
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 5a63beb283..d3410de2eb 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -45,7 +45,7 @@ class TestEmployee(unittest.TestCase):
employee1_doc.status = 'Left'
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
-def make_employee(user):
+def make_employee(user, company=None):
if not frappe.db.get_value("User", user):
frappe.get_doc({
"doctype": "User",
@@ -55,12 +55,12 @@ def make_employee(user):
"roles": [{"doctype": "Has Role", "role": "Employee"}]
}).insert()
- if not frappe.db.get_value("Employee", {"user_id": user}):
+ if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() }):
employee = frappe.get_doc({
"doctype": "Employee",
"naming_series": "EMP-",
"first_name": user,
- "company": erpnext.get_default_company(),
+ "company": company or erpnext.get_default_company(),
"user_id": user,
"date_of_birth": "1990-05-08",
"date_of_joining": "2013-01-01",
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index 4e2778f48d..5c2f490171 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -1,435 +1,436 @@
{
- "allow_import": 1,
- "autoname": "naming_series:",
- "creation": "2013-01-10 16:34:14",
- "doctype": "DocType",
- "document_type": "Setup",
- "engine": "InnoDB",
- "field_order": [
- "naming_series",
- "employee",
- "employee_name",
- "department",
- "column_break_5",
- "expense_approver",
- "approval_status",
- "is_paid",
- "expense_details",
- "expenses",
- "sb1",
- "taxes",
- "transactions_section",
- "total_sanctioned_amount",
- "total_taxes_and_charges",
- "total_advance_amount",
- "column_break_17",
- "grand_total",
- "total_claimed_amount",
- "total_amount_reimbursed",
- "section_break_16",
- "posting_date",
- "vehicle_log",
- "task",
- "cb1",
- "remark",
- "title",
- "email_id",
- "accounting_details",
- "company",
- "mode_of_payment",
- "clearance_date",
- "column_break_24",
- "payable_account",
- "accounting_dimensions_section",
- "project",
- "dimension_col_break",
- "cost_center",
- "more_details",
- "status",
- "amended_from",
- "advance_payments",
- "advances"
- ],
- "fields": [
- {
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "label": "Series",
- "no_copy": 1,
- "options": "HR-EXP-.YYYY.-",
- "print_hide": 1,
- "reqd": 1,
- "set_only_once": 1
- },
- {
- "fieldname": "employee",
- "fieldtype": "Link",
- "in_global_search": 1,
- "label": "From Employee",
- "oldfieldname": "employee",
- "oldfieldtype": "Link",
- "options": "Employee",
- "reqd": 1,
- "search_index": 1
- },
- {
- "fetch_from": "employee.employee_name",
- "fieldname": "employee_name",
- "fieldtype": "Data",
- "in_global_search": 1,
- "label": "Employee Name",
- "oldfieldname": "employee_name",
- "oldfieldtype": "Data",
- "read_only": 1,
- "width": "150px"
- },
- {
- "fetch_from": "employee.department",
- "fieldname": "department",
- "fieldtype": "Link",
- "label": "Department",
- "options": "Department",
- "read_only": 1
- },
- {
- "fieldname": "column_break_5",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "expense_approver",
- "fieldtype": "Link",
- "label": "Expense Approver",
- "options": "User"
- },
- {
- "default": "Draft",
- "fieldname": "approval_status",
- "fieldtype": "Select",
- "label": "Approval Status",
- "no_copy": 1,
- "options": "Draft\nApproved\nRejected",
- "search_index": 1
- },
- {
- "fieldname": "total_claimed_amount",
- "fieldtype": "Currency",
- "in_list_view": 1,
- "label": "Total Claimed Amount",
- "no_copy": 1,
- "oldfieldname": "total_claimed_amount",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
- "read_only": 1,
- "width": "160px"
- },
- {
- "fieldname": "total_sanctioned_amount",
- "fieldtype": "Currency",
- "label": "Total Sanctioned Amount",
- "no_copy": 1,
- "oldfieldname": "total_sanctioned_amount",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
- "read_only": 1,
- "width": "160px"
- },
- {
- "default": "0",
- "depends_on": "eval:(doc.docstatus==0 || doc.is_paid)",
- "fieldname": "is_paid",
- "fieldtype": "Check",
- "label": "Is Paid"
- },
- {
- "fieldname": "expense_details",
- "fieldtype": "Section Break",
- "oldfieldtype": "Section Break"
- },
- {
- "fieldname": "expenses",
- "fieldtype": "Table",
- "label": "Expenses",
- "oldfieldname": "expense_voucher_details",
- "oldfieldtype": "Table",
- "options": "Expense Claim Detail",
- "reqd": 1
- },
- {
- "fieldname": "sb1",
- "fieldtype": "Section Break",
- "options": "Simple"
- },
- {
- "default": "Today",
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "label": "Posting Date",
- "oldfieldname": "posting_date",
- "oldfieldtype": "Date",
- "reqd": 1
- },
- {
- "fieldname": "vehicle_log",
- "fieldtype": "Link",
- "label": "Vehicle Log",
- "options": "Vehicle Log",
- "read_only": 1
- },
- {
- "fieldname": "project",
- "fieldtype": "Link",
- "label": "Project",
- "options": "Project"
- },
- {
- "fieldname": "task",
- "fieldtype": "Link",
- "label": "Task",
- "options": "Task",
- "remember_last_selected_value": 1
- },
- {
- "fieldname": "cb1",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "total_amount_reimbursed",
- "fieldtype": "Currency",
- "in_list_view": 1,
- "label": "Total Amount Reimbursed",
- "no_copy": 1,
- "options": "Company:company:default_currency",
- "read_only": 1
- },
- {
- "fieldname": "remark",
- "fieldtype": "Small Text",
- "label": "Remark",
- "no_copy": 1,
- "oldfieldname": "remark",
- "oldfieldtype": "Small Text"
- },
- {
- "allow_on_submit": 1,
- "default": "{employee_name}",
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Title",
- "no_copy": 1
- },
- {
- "fieldname": "email_id",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Employees Email Id",
- "oldfieldname": "email_id",
- "oldfieldtype": "Data",
- "print_hide": 1
- },
- {
- "fieldname": "accounting_details",
- "fieldtype": "Section Break",
- "label": "Accounting Details"
- },
- {
- "fieldname": "company",
- "fieldtype": "Link",
- "label": "Company",
- "oldfieldname": "company",
- "oldfieldtype": "Link",
- "options": "Company",
- "remember_last_selected_value": 1,
- "reqd": 1
- },
- {
- "depends_on": "is_paid",
- "fieldname": "mode_of_payment",
- "fieldtype": "Link",
- "label": "Mode of Payment",
- "options": "Mode of Payment"
- },
- {
- "fieldname": "clearance_date",
- "fieldtype": "Date",
- "label": "Clearance Date"
- },
- {
- "fieldname": "column_break_24",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "payable_account",
- "fieldtype": "Link",
- "label": "Payable Account",
- "options": "Account"
- },
- {
- "fieldname": "cost_center",
- "fieldtype": "Link",
- "label": "Cost Center",
- "options": "Cost Center"
- },
- {
- "collapsible": 1,
- "fieldname": "more_details",
- "fieldtype": "Section Break",
- "label": "More Details"
- },
- {
- "default": "Draft",
- "fieldname": "status",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Status",
- "no_copy": 1,
- "options": "Draft\nPaid\nUnpaid\nRejected\nSubmitted\nCancelled",
- "print_hide": 1,
- "read_only": 1
- },
- {
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "ignore_user_permissions": 1,
- "label": "Amended From",
- "no_copy": 1,
- "oldfieldname": "amended_from",
- "oldfieldtype": "Data",
- "options": "Expense Claim",
- "print_hide": 1,
- "read_only": 1,
- "report_hide": 1,
- "width": "160px"
- },
- {
- "fieldname": "advance_payments",
- "fieldtype": "Section Break",
- "label": "Advance Payments"
- },
- {
- "fieldname": "advances",
- "fieldtype": "Table",
- "label": "Advances",
- "options": "Expense Claim Advance"
- },
- {
- "fieldname": "total_advance_amount",
- "fieldtype": "Currency",
- "label": "Total Advance Amount",
- "options": "Company:company:default_currency",
- "read_only": 1
- },
- {
- "fieldname": "accounting_dimensions_section",
- "fieldtype": "Section Break",
- "label": "Accounting Dimensions"
- },
- {
- "fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "taxes",
- "fieldtype": "Table",
- "label": "Expense Taxes and Charges",
- "options": "Expense Taxes and Charges"
- },
- {
- "fieldname": "section_break_16",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "transactions_section",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "grand_total",
- "fieldtype": "Currency",
- "in_list_view": 1,
- "label": "Grand Total",
- "options": "Company:company:default_currency",
- "read_only": 1
- },
- {
- "fieldname": "column_break_17",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "total_taxes_and_charges",
- "fieldtype": "Currency",
- "label": "Total Taxes and Charges",
- "options": "Company:company:default_currency",
- "read_only": 1
- }
- ],
- "icon": "fa fa-money",
- "idx": 1,
- "is_submittable": 1,
- "modified": "2019-06-26 18:05:52.530462",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Expense Claim",
- "name_case": "Title Case",
- "owner": "harshada@webnotestech.com",
- "permissions": [
- {
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "HR Manager",
- "share": 1,
- "submit": 1,
- "write": 1
- },
- {
- "create": 1,
- "email": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Employee",
- "share": 1,
- "write": 1
- },
- {
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Expense Approver",
- "share": 1,
- "submit": 1,
- "write": 1
- },
- {
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "HR User",
- "share": 1,
- "submit": 1,
- "write": 1
- }
- ],
- "search_fields": "employee,employee_name",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "timeline_field": "employee",
- "title_field": "title"
- }
\ No newline at end of file
+ "allow_import": 1,
+ "autoname": "naming_series:",
+ "creation": "2013-01-10 16:34:14",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "employee",
+ "employee_name",
+ "department",
+ "column_break_5",
+ "expense_approver",
+ "approval_status",
+ "is_paid",
+ "expense_details",
+ "expenses",
+ "sb1",
+ "taxes",
+ "transactions_section",
+ "total_sanctioned_amount",
+ "total_taxes_and_charges",
+ "total_advance_amount",
+ "column_break_17",
+ "grand_total",
+ "total_claimed_amount",
+ "total_amount_reimbursed",
+ "section_break_16",
+ "posting_date",
+ "vehicle_log",
+ "task",
+ "cb1",
+ "remark",
+ "title",
+ "email_id",
+ "accounting_details",
+ "company",
+ "mode_of_payment",
+ "clearance_date",
+ "column_break_24",
+ "payable_account",
+ "accounting_dimensions_section",
+ "project",
+ "dimension_col_break",
+ "cost_center",
+ "more_details",
+ "status",
+ "amended_from",
+ "advance_payments",
+ "advances"
+ ],
+ "fields": [
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "no_copy": 1,
+ "options": "HR-EXP-.YYYY.-",
+ "print_hide": 1,
+ "reqd": 1,
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "label": "From Employee",
+ "oldfieldname": "employee",
+ "oldfieldtype": "Link",
+ "options": "Employee",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "Employee Name",
+ "oldfieldname": "employee_name",
+ "oldfieldtype": "Data",
+ "read_only": 1,
+ "width": "150px"
+ },
+ {
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "expense_approver",
+ "fieldtype": "Link",
+ "label": "Expense Approver",
+ "options": "User"
+ },
+ {
+ "default": "Draft",
+ "fieldname": "approval_status",
+ "fieldtype": "Select",
+ "label": "Approval Status",
+ "no_copy": 1,
+ "options": "Draft\nApproved\nRejected",
+ "search_index": 1
+ },
+ {
+ "fieldname": "total_claimed_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Total Claimed Amount",
+ "no_copy": 1,
+ "oldfieldname": "total_claimed_amount",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "read_only": 1,
+ "width": "160px"
+ },
+ {
+ "fieldname": "total_sanctioned_amount",
+ "fieldtype": "Currency",
+ "label": "Total Sanctioned Amount",
+ "no_copy": 1,
+ "oldfieldname": "total_sanctioned_amount",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "read_only": 1,
+ "width": "160px"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:(doc.docstatus==0 || doc.is_paid)",
+ "fieldname": "is_paid",
+ "fieldtype": "Check",
+ "label": "Is Paid"
+ },
+ {
+ "fieldname": "expense_details",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break"
+ },
+ {
+ "fieldname": "expenses",
+ "fieldtype": "Table",
+ "label": "Expenses",
+ "oldfieldname": "expense_voucher_details",
+ "oldfieldtype": "Table",
+ "options": "Expense Claim Detail",
+ "reqd": 1
+ },
+ {
+ "fieldname": "sb1",
+ "fieldtype": "Section Break",
+ "options": "Simple"
+ },
+ {
+ "default": "Today",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Posting Date",
+ "oldfieldname": "posting_date",
+ "oldfieldtype": "Date",
+ "reqd": 1
+ },
+ {
+ "fieldname": "vehicle_log",
+ "fieldtype": "Link",
+ "label": "Vehicle Log",
+ "options": "Vehicle Log",
+ "read_only": 1
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
+ {
+ "fieldname": "task",
+ "fieldtype": "Link",
+ "label": "Task",
+ "options": "Task",
+ "remember_last_selected_value": 1
+ },
+ {
+ "fieldname": "cb1",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "total_amount_reimbursed",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Total Amount Reimbursed",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "remark",
+ "fieldtype": "Small Text",
+ "label": "Remark",
+ "no_copy": 1,
+ "oldfieldname": "remark",
+ "oldfieldtype": "Small Text"
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "{employee_name}",
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Title",
+ "no_copy": 1
+ },
+ {
+ "fieldname": "email_id",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Employees Email Id",
+ "oldfieldname": "email_id",
+ "oldfieldtype": "Data",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "accounting_details",
+ "fieldtype": "Section Break",
+ "label": "Accounting Details"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Link",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
+ {
+ "depends_on": "is_paid",
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "label": "Mode of Payment",
+ "options": "Mode of Payment"
+ },
+ {
+ "fieldname": "clearance_date",
+ "fieldtype": "Date",
+ "label": "Clearance Date"
+ },
+ {
+ "fieldname": "column_break_24",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "payable_account",
+ "fieldtype": "Link",
+ "label": "Payable Account",
+ "options": "Account",
+ "reqd": 1
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "more_details",
+ "fieldtype": "Section Break",
+ "label": "More Details"
+ },
+ {
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Draft\nPaid\nUnpaid\nRejected\nSubmitted\nCancelled",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "Amended From",
+ "no_copy": 1,
+ "oldfieldname": "amended_from",
+ "oldfieldtype": "Data",
+ "options": "Expense Claim",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1,
+ "width": "160px"
+ },
+ {
+ "fieldname": "advance_payments",
+ "fieldtype": "Section Break",
+ "label": "Advance Payments"
+ },
+ {
+ "fieldname": "advances",
+ "fieldtype": "Table",
+ "label": "Advances",
+ "options": "Expense Claim Advance"
+ },
+ {
+ "fieldname": "total_advance_amount",
+ "fieldtype": "Currency",
+ "label": "Total Advance Amount",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Expense Taxes and Charges",
+ "options": "Expense Taxes and Charges"
+ },
+ {
+ "fieldname": "section_break_16",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "transactions_section",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "grand_total",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Grand Total",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_17",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "total_taxes_and_charges",
+ "fieldtype": "Currency",
+ "label": "Total Taxes and Charges",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ }
+ ],
+ "icon": "fa fa-money",
+ "idx": 1,
+ "is_submittable": 1,
+ "modified": "2019-11-08 14:13:08.964547",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Expense Claim",
+ "name_case": "Title Case",
+ "owner": "harshada@webnotestech.com",
+ "permissions": [
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Employee",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Expense Approver",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "search_fields": "employee,employee_name",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "timeline_field": "employee",
+ "title_field": "title"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index caeb2dd946..59391505fa 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -140,10 +140,11 @@ class ExpenseClaim(AccountsController):
"against": ",".join([d.default_account for d in self.expenses]),
"party_type": "Employee",
"party": self.employee,
- "against_voucher_type": self.doctype,
- "against_voucher": self.name
+ "against_voucher_type": "Employee Advance",
+ "against_voucher": data.employee_advance
})
)
+
self.add_tax_gl_entries(gl_entry)
if self.is_paid and self.grand_total:
@@ -192,9 +193,6 @@ class ExpenseClaim(AccountsController):
if not self.cost_center:
frappe.throw(_("Cost center is required to book an expense claim"))
- if not self.payable_account:
- frappe.throw(_("Please set default payable account for the company {0}").format(getlink("Company",self.company)))
-
if self.is_paid:
if not self.mode_of_payment:
frappe.throw(_("Mode of payment is required to make a payment").format(self.employee))
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_list.js b/erpnext/hr/doctype/expense_claim/expense_claim_list.js
index 0e25e66687..6195ad414a 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim_list.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim_list.js
@@ -2,11 +2,11 @@ frappe.listview_settings['Expense Claim'] = {
add_fields: ["total_claimed_amount", "docstatus"],
get_indicator: function(doc) {
if(doc.status == "Paid") {
- return [__("Paid"), "green", "status,=,'Paid'"];
+ return [__("Paid"), "green", "status,=,Paid"];
}else if(doc.status == "Unpaid") {
- return [__("Unpaid"), "orange"];
+ return [__("Unpaid"), "orange", "status,=,Unpaid"];
} else if(doc.status == "Rejected") {
- return [__("Rejected"), "grey"];
+ return [__("Rejected"), "grey", "status,=,Rejected"];
}
}
};
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 97de40ffee..0e6630541c 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -55,11 +55,11 @@ class LeaveApplication(Document):
self.reload()
def on_cancel(self):
+ self.create_leave_ledger_entry(submit=False)
self.status = "Cancelled"
# notify leave applier about cancellation
self.notify_employee()
self.cancel_attendance()
- self.create_leave_ledger_entry(submit=False)
def validate_applicable_after(self):
if self.leave_type:
@@ -125,7 +125,7 @@ class LeaveApplication(Document):
status = "Half Day" if date == self.half_day_date else "On Leave"
attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
- attenance_date = date, docstatus = ('!=', 2)))
+ attendance_date = date, docstatus = ('!=', 2)))
if attendance_name:
# update existing attendance, change absent to on leave
@@ -351,6 +351,9 @@ class LeaveApplication(Document):
pass
def create_leave_ledger_entry(self, submit=True):
+ if self.status != 'Approved':
+ return
+
expiry_date = get_allocation_expiry(self.employee, self.leave_type,
self.to_date, self.from_date)
@@ -503,14 +506,17 @@ def get_leave_allocation_records(employee, date, leave_type=None):
def get_pending_leaves_for_period(employee, leave_type, from_date, to_date):
''' Returns leaves that are pending approval '''
- return frappe.db.get_value("Leave Application",
+ leaves = frappe.get_all("Leave Application",
filters={
"employee": employee,
"leave_type": leave_type,
- "from_date": ("<=", from_date),
- "to_date": (">=", to_date),
"status": "Open"
- }, fieldname=['SUM(total_leave_days)']) or flt(0)
+ },
+ or_filters={
+ "from_date": ["between", (from_date, to_date)],
+ "to_date": ["between", (from_date, to_date)]
+ }, fields=['SUM(total_leave_days) as leaves'])[0]
+ return leaves['leaves'] if leaves['leaves'] else 0.0
def get_remaining_leaves(allocation, leaves_taken, date, expiry):
''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry '''
diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
index 8075b7b5c5..c1d6a6665b 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
+++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
@@ -5,6 +5,12 @@ from frappe import _
def get_data():
return {
+ 'fieldname': 'leave_application',
+ 'transactions': [
+ {
+ 'items': ['Attendance']
+ }
+ ],
'reports': [
{
'label': _('Reports'),
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index ad141a5748..38ae808f27 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -72,7 +72,7 @@ class TestLeaveApplication(unittest.TestCase):
application.to_date = "2013-01-05"
return application
- def test_attendance_creation(self):
+ def test_overwrite_attendance(self):
'''check attendance is automatically created on leave approval'''
make_allocation_record()
application = self.get_application(_test_records[0])
@@ -82,7 +82,8 @@ class TestLeaveApplication(unittest.TestCase):
application.insert()
application.submit()
- attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'], dict(leave_application = application.name))
+ attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'],
+ dict(attendance_date=('between', ['2018-01-01', '2018-01-03']), docstatus=("!=", 2)))
# attendance created for all 3 days
self.assertEqual(len(attendance), 3)
@@ -95,20 +96,6 @@ class TestLeaveApplication(unittest.TestCase):
for d in ('2018-01-01', '2018-01-02', '2018-01-03'):
self.assertTrue(getdate(d) in dates)
- def test_overwrite_attendance(self):
- # employee marked as absent
- doc = frappe.new_doc("Attendance")
- doc.employee = '_T-Employee-00001'
- doc.attendance_date = '2018-01-01'
- doc.company = '_Test Company'
- doc.status = 'Absent'
- doc.flags.ignore_validate = True
- doc.insert(ignore_permissions=True)
- doc.submit()
-
- # now check if the status has been updated
- self.test_attendance_creation()
-
def test_block_list(self):
self._clear_roles()
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 27a51c30e7..46be4fe287 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe, erpnext
import datetime, math
-from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, getdate
+from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words
from frappe.model.naming import make_autoname
from frappe import msgprint, _
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js
index d56320a073..dd34ef2ae2 100755
--- a/erpnext/hr/doctype/salary_structure/salary_structure.js
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.js
@@ -46,10 +46,12 @@ frappe.ui.form.on('Salary Structure', {
frm.trigger("toggle_fields");
frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false);
frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false);
-
- frm.add_custom_button(__("Preview Salary Slip"), function() {
- frm.trigger('preview_salary_slip');
- });
+
+ if(frm.doc.docstatus === 1) {
+ frm.add_custom_button(__("Preview Salary Slip"), function() {
+ frm.trigger('preview_salary_slip');
+ });
+ }
if(frm.doc.docstatus==1) {
frm.add_custom_button(__("Assign Salary Structure"), function() {
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py
index f7d712d3f1..0e1a74f370 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.py
@@ -169,5 +169,10 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print =
@frappe.whitelist()
def get_employees(salary_structure):
employees = frappe.get_list('Salary Structure Assignment',
- filters={'salary_structure': salary_structure}, fields=['employee'])
+ filters={'salary_structure': salary_structure, 'docstatus': 1}, fields=['employee'])
+
+ if not employees:
+ frappe.throw(_("There's no Employee with Salary Structure: {0}. \
+ Assign {1} to an Employee to preview Salary Slip").format(salary_structure, salary_structure))
+
return list(set([d.employee for d in employees]))
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index e6afbcc220..595bcaa8d4 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -7,6 +7,7 @@ import frappe
from frappe.model.document import Document
from frappe import _
from frappe.utils import getdate, nowdate, cint, flt
+from frappe.utils.nestedset import get_descendants_of
class SubsidiaryCompanyError(frappe.ValidationError): pass
class ParentCompanyError(frappe.ValidationError): pass
@@ -131,7 +132,8 @@ def get_designation_counts(designation, company):
return False
employee_counts = {}
- company_set = get_company_set(company)
+ company_set = get_descendants_of('Company', company)
+ company_set.append(company)
employee_counts["employee_count"] = frappe.db.get_value("Employee",
filters={
@@ -167,14 +169,4 @@ def get_active_staffing_plan_details(company, designation, from_date=getdate(now
designation, from_date, to_date)
# Only a single staffing plan can be active for a designation on given date
- return staffing_plan if staffing_plan else None
-
-def get_company_set(company):
- return frappe.db.sql_list("""
- SELECT
- name
- FROM `tabCompany`
- WHERE
- parent_company=%(company)s
- OR name=%(company)s
- """, (dict(company=company)))
\ No newline at end of file
+ return staffing_plan if staffing_plan else None
\ No newline at end of file
diff --git a/erpnext/hr/report/department_analytics/department_analytics.js b/erpnext/hr/report/department_analytics/department_analytics.js
index a0b6fc7641..29fedcd735 100644
--- a/erpnext/hr/report/department_analytics/department_analytics.js
+++ b/erpnext/hr/report/department_analytics/department_analytics.js
@@ -2,4 +2,14 @@
// For license information, please see license.txt
frappe.query_reports["Department Analytics"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ ]
};
\ No newline at end of file
diff --git a/erpnext/hr/report/department_analytics/department_analytics.py b/erpnext/hr/report/department_analytics/department_analytics.py
index c4a9030c59..b28eac43f8 100644
--- a/erpnext/hr/report/department_analytics/department_analytics.py
+++ b/erpnext/hr/report/department_analytics/department_analytics.py
@@ -7,6 +7,10 @@ from frappe import _
def execute(filters=None):
if not filters: filters = {}
+
+ if not filters["company"]:
+ frappe.throw(_('{0} is mandatory').format(_('Company')))
+
columns = get_columns()
employees = get_employees(filters)
departments_result = get_department(filters)
@@ -28,6 +32,9 @@ def get_conditions(filters):
conditions = ""
if filters.get("department"): conditions += " and department = '%s'" % \
filters["department"].replace("'", "\\'")
+
+ if filters.get("company"): conditions += " and company = '%s'" % \
+ filters["company"].replace("'", "\\'")
return conditions
def get_employees(filters):
@@ -37,7 +44,7 @@ def get_employees(filters):
gender, company from `tabEmployee` where status = 'Active' %s""" % conditions, as_list=1)
def get_department(filters):
- return frappe.db.sql("""select name from `tabDepartment`""" , as_list=1)
+ return frappe.db.sql("""select name from `tabDepartment` where company = %s""", (filters["company"]), as_list=1)
def get_chart_data(departments,employees):
if not departments:
diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
index 15a5da00f8..777de02238 100644
--- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
+++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
@@ -75,7 +75,7 @@ def get_data(filters):
leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver)
- if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \
+ if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \
or ("HR Manager" in frappe.get_roles(user)):
row = frappe._dict({
'employee': employee.name,
@@ -111,10 +111,10 @@ def get_conditions(filters):
def get_department_leave_approver_map(department=None):
conditions=''
if department:
- conditions='and department_name = %(department)s or parent_department = %(department)s'%{'department': department}
+ conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
# get current department and all its child
- department_list = frappe.db.sql_list(''' SELECT name FROM `tabDepartment` WHERE disabled=0 {0}'''.format(conditions)) #nosec
+ department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
# retrieve approvers list from current department and from its subsequent child departments
approver_list = frappe.get_all('Department Approver', filters={
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
index ce95db3bf5..e940b6050c 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
@@ -20,6 +20,12 @@ frappe.ui.form.on('Maintenance Schedule', {
frm.set_value({transaction_date: frappe.datetime.get_today()});
}
},
+ refresh: function(frm) {
+ setTimeout(() => {
+ frm.toggle_display('generate_schedule', !(frm.is_new()));
+ frm.toggle_display('schedule', !(frm.is_new()));
+ },10);
+ },
customer: function(frm) {
erpnext.utils.get_party_details(frm)
},
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index 3a64e1aa67..94d85f77ef 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -150,7 +150,7 @@ class MaintenanceSchedule(TransactionBase):
elif not d.no_of_visits:
throw(_("Please mention no of visits required"))
elif not d.sales_person:
- throw(_("Please select Incharge Person's name"))
+ throw(_("Please select a Sales Person for item: {0}".format(d.item_name)))
if getdate(d.start_date) >= getdate(d.end_date):
throw(_("Start date should be less than end date for Item {0}").format(d.item_code))
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index c15b52ea38..db79d7feda 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -292,7 +292,8 @@ class BOM(WebsiteGenerator):
return valuation_rate
def manage_default_bom(self):
- """ Uncheck others if current one is selected as default,
+ """ Uncheck others if current one is selected as default or
+ check the current one as default if it the only bom for the selected item,
update default bom in item master
"""
if self.is_default and self.is_active:
@@ -301,6 +302,9 @@ class BOM(WebsiteGenerator):
item = frappe.get_doc("Item", self.item)
if item.default_bom != self.name:
frappe.db.set_value('Item', self.item, 'default_bom', self.name)
+ elif not frappe.db.exists(dict(doctype='BOM', docstatus=1, item=self.item, is_default=1)) \
+ and self.is_active:
+ frappe.db.set(self, "is_default", 1)
else:
frappe.db.set(self, "is_default", 0)
item = frappe.get_doc("Item", self.item)
@@ -416,8 +420,12 @@ class BOM(WebsiteGenerator):
def traverse_tree(self, bom_list=None):
def _get_children(bom_no):
- return frappe.db.sql_list("""select bom_no from `tabBOM Item`
- where parent = %s and ifnull(bom_no, '') != '' and parenttype='BOM'""", bom_no)
+ children = frappe.cache().hget('bom_children', bom_no)
+ if children is None:
+ children = frappe.db.sql_list("""SELECT `bom_no` FROM `tabBOM Item`
+ WHERE `parent`=%s AND `bom_no`!='' AND `parenttype`='BOM'""", bom_no)
+ frappe.cache().hset('bom_children', bom_no, children)
+ return children
count = 0
if not bom_list:
@@ -530,12 +538,24 @@ class BOM(WebsiteGenerator):
def get_child_exploded_items(self, bom_no, stock_qty):
""" Add all items from Flat BOM of child BOM"""
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
- child_fb_items = frappe.db.sql("""select bom_item.item_code, bom_item.item_name,
- bom_item.description, bom_item.source_warehouse, bom_item.operation,
- bom_item.stock_uom, bom_item.stock_qty, bom_item.rate, bom_item.include_item_in_manufacturing,
- bom_item.stock_qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit
- from `tabBOM Explosion Item` bom_item, tabBOM bom
- where bom_item.parent = bom.name and bom.name = %s and bom.docstatus = 1""", bom_no, as_dict = 1)
+ child_fb_items = frappe.db.sql("""
+ SELECT
+ bom_item.item_code,
+ bom_item.item_name,
+ bom_item.description,
+ bom_item.source_warehouse,
+ bom_item.operation,
+ bom_item.stock_uom,
+ bom_item.stock_qty,
+ bom_item.rate,
+ bom_item.include_item_in_manufacturing,
+ bom_item.stock_qty / ifnull(bom.quantity, 1) AS qty_consumed_per_unit
+ FROM `tabBOM Explosion Item` bom_item, tabBOM bom
+ WHERE
+ bom_item.parent = bom.name
+ AND bom.name = %s
+ AND bom.docstatus = 1
+ """, bom_no, as_dict = 1)
for d in child_fb_items:
self.add_to_cur_exploded_items(frappe._dict({
@@ -756,6 +776,8 @@ def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost
bom = frappe.get_doc('BOM', work_order.bom_no)
table = 'exploded_items' if work_order.get('use_multi_level_bom') else 'items'
+ expenses_included_in_valuation = frappe.get_cached_value("Company", work_order.company,
+ "expenses_included_in_valuation")
items = {}
for d in bom.get(table):
@@ -766,6 +788,7 @@ def add_additional_cost(stock_entry, work_order):
for name in non_stock_items:
stock_entry.append('additional_costs', {
+ 'expense_account': expenses_included_in_valuation,
'description': name[0],
'amount': items.get(name[0])
})
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index 87b8f67e53..2ca4d16a07 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -14,23 +14,23 @@ class BOMUpdateTool(Document):
def replace_bom(self):
self.validate_bom()
self.update_new_bom()
+ frappe.cache().delete_key('bom_children')
bom_list = self.get_parent_boms(self.new_bom)
updated_bom = []
for bom in bom_list:
try:
- bom_obj = frappe.get_doc("BOM", bom)
- bom_obj.load_doc_before_save()
- updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
+ bom_obj = frappe.get_cached_doc('BOM', bom)
+ # this is only used for versioning and we do not want
+ # to make separate db calls by using load_doc_before_save
+ # which proves to be expensive while doing bulk replace
+ bom_obj._doc_before_save = bom_obj.as_dict()
bom_obj.calculate_cost()
bom_obj.update_parent_cost()
bom_obj.db_update()
- if (getattr(bom_obj.meta, 'track_changes', False) and not bom_obj.flags.ignore_version):
+ if bom_obj.meta.get('track_changes') and not bom_obj.flags.ignore_version:
bom_obj.save_version()
-
- frappe.db.commit()
except Exception:
- frappe.db.rollback()
frappe.log_error(frappe.get_traceback())
def validate_bom(self):
@@ -42,22 +42,22 @@ class BOMUpdateTool(Document):
frappe.throw(_("The selected BOMs are not for the same item"))
def update_new_bom(self):
- new_bom_unitcost = frappe.db.sql("""select total_cost/quantity
- from `tabBOM` where name = %s""", self.new_bom)
+ new_bom_unitcost = frappe.db.sql("""SELECT `total_cost`/`quantity`
+ FROM `tabBOM` WHERE name = %s""", self.new_bom)
new_bom_unitcost = flt(new_bom_unitcost[0][0]) if new_bom_unitcost else 0
frappe.db.sql("""update `tabBOM Item` set bom_no=%s,
rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""",
(self.new_bom, new_bom_unitcost, new_bom_unitcost, self.current_bom))
- def get_parent_boms(self, bom, bom_list=None):
- if not bom_list:
- bom_list = []
-
- data = frappe.db.sql(""" select distinct parent from `tabBOM Item`
- where bom_no = %s and docstatus < 2 and parenttype='BOM'""", bom)
+ def get_parent_boms(self, bom, bom_list=[]):
+ data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item`
+ WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom)
for d in data:
+ if self.new_bom == d[0]:
+ frappe.throw(_("BOM recursion: {0} cannot be child of {1}").format(bom, self.new_bom))
+
bom_list.append(d[0])
self.get_parent_boms(d[0], bom_list)
diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
index 39d59f006b..f27197d09f 100644
--- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
+++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
@@ -1,443 +1,135 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-12-01 12:12:55.048691",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "creation": "2017-12-01 12:12:55.048691",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_code",
+ "item_name",
+ "warehouse",
+ "material_request_type",
+ "column_break_4",
+ "quantity",
+ "uom",
+ "projected_qty",
+ "actual_qty",
+ "item_details",
+ "description",
+ "min_order_qty",
+ "section_break_8",
+ "sales_order",
+ "requested_qty"
+ ],
"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": "item_code",
- "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": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "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
- },
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "options": "Item",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "item_name",
- "fieldtype": "Data",
- "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 Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "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
- },
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "label": "Item Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "warehouse",
- "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": 1,
- "label": "Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "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
- },
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Warehouse",
+ "options": "Warehouse",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "material_request_type",
- "fieldtype": "Select",
- "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": "Material Request Type",
- "length": 0,
- "no_copy": 0,
- "options": "\nPurchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided",
- "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
- },
+ "fieldname": "material_request_type",
+ "fieldtype": "Select",
+ "label": "Material Request Type",
+ "options": "\nPurchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided"
+ },
{
- "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_4",
- "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
- },
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "quantity",
- "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": "Required Quantity",
- "length": 0,
- "no_copy": 1,
- "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
- },
+ "fieldname": "quantity",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Required Quantity",
+ "no_copy": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "projected_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": "Projected Qty",
- "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
- },
+ "fieldname": "projected_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Projected Qty",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "collapsible_depends_on": "",
- "columns": 0,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "actual_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": "Actual Qty",
- "length": 0,
- "no_copy": 1,
- "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
- },
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Actual Qty",
+ "no_copy": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "min_order_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": "Minimum Order Quantity",
- "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
- },
+ "fieldname": "min_order_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Minimum Order Quantity",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "section_break_8",
- "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,
- "label": "Reference",
- "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
- },
+ "collapsible": 1,
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Reference"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "sales_order",
- "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": "Sales Order",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Order",
- "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
- },
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "label": "Sales Order",
+ "options": "Sales Order",
+ "read_only": 1
+ },
{
- "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": "requested_qty",
- "fieldtype": "Float",
- "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": "Requested Qty",
- "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
+ "fieldname": "requested_qty",
+ "fieldtype": "Float",
+ "label": "Requested Qty",
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "item_details",
+ "fieldtype": "Section Break",
+ "label": "Item Description"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description"
+ },
+ {
+ "fieldname": "uom",
+ "fieldtype": "Link",
+ "label": "UOM",
+ "options": "UOM",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-04-08 18:15:26.849602",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Material Request Plan Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "modified": "2019-11-08 15:15:43.979360",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Material Request Plan Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 2aeea5827d..3b24d0fa0f 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -3,6 +3,11 @@
frappe.ui.form.on('Production Plan', {
setup: function(frm) {
+ frm.custom_make_buttons = {
+ 'Work Order': 'Work Order',
+ 'Material Request': 'Material Request',
+ };
+
frm.fields_dict['po_items'].grid.get_field('warehouse').get_query = function(doc) {
return {
filters: {
@@ -103,7 +108,7 @@ frappe.ui.form.on('Production Plan', {
${__('Reserved Qty for Production: Raw materials quantity to make manufacturing items.')}
- + ${__("Notes")}:
|