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/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 75107b0b6d..3bb3df8dbd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -357,7 +357,7 @@ class PurchaseInvoice(BuyingController): return if not gl_entries: gl_entries = self.get_gl_entries() - + if gl_entries: update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" @@ -507,7 +507,7 @@ class PurchaseInvoice(BuyingController): 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) - + if not item.is_fixed_asset: amount = flt(item.base_net_amount, item.precision("base_net_amount")) else: @@ -520,7 +520,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "project": item.project }, 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") @@ -542,9 +542,9 @@ class PurchaseInvoice(BuyingController): "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', + assets = frappe.db.get_all('Asset', filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } ) for asset in assets: @@ -636,7 +636,7 @@ class PurchaseInvoice(BuyingController): if asset_eiiav_currency == self.company_currency else 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: @@ -658,9 +658,9 @@ class PurchaseInvoice(BuyingController): "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', + assets = frappe.db.get_all('Asset', filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } ) for asset in assets: diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 7a48fcc94c..2ba319d05e 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -185,7 +185,7 @@ def validate_account_for_perpetual_inventory(gl_map): raise_exception=StockValueAndAccountBalanceOutOfSync, title=_('Values Out Of Sync'), primary_action={ - 'label': 'Make Journal Entry', + 'label': _('Make Journal Entry'), 'client_action': 'erpnext.route_to_adjustment_jv', 'args': journal_entry_args }) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 382a89b310..94697be02f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -630,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 @@ -639,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(""" @@ -665,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/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 546f374094..40f1e1efc9 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -33,7 +33,7 @@ class Asset(AccountsController): 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() @@ -43,7 +43,7 @@ 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' @@ -51,8 +51,8 @@ class Asset(AccountsController): 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)) @@ -125,15 +125,17 @@ class Asset(AccountsController): frappe.throw(_("Available-for-use Date should be after purchase date")) def cancel_auto_gen_movement(self): - reference_docname = self.purchase_invoice or self.purchase_receipt - movement = frappe.db.get_all('Asset Movement', filters={ 'reference_name': reference_docname, 'docstatus': 1 }) - if len(movement) > 1: + 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', movement[0].get('name')) + 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 @@ -202,7 +204,7 @@ class Asset(AccountsController): if has_pro_rata and n==0: 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) @@ -260,7 +262,7 @@ class Asset(AccountsController): else: date = add_months(monthly_schedule_date, r) amount = depreciation_amount / month_range - + self.append("schedules", { "schedule_date": date, "depreciation_amount": amount, @@ -650,31 +652,18 @@ def make_journal_entry(asset_name): 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.purpose = purpose - prev_reference_docname = '' - + asset_movement.quantity = len(assets) for asset in assets: asset = frappe.get_doc('Asset', asset.get('name')) - # get PR/PI linked with asset - reference_docname = asset.get('purchase_receipt') if asset.get('purchase_receipt') \ - else asset.get('purchase_invoice') - # checks if all the assets are linked with a single PR/PI - if prev_reference_docname == '': - prev_reference_docname = reference_docname - elif prev_reference_docname != reference_docname: - frappe.throw(_('Assets selected should belong to same reference document.')) - asset_movement.company = asset.get('company') - asset_movement.reference_doctype = 'Purchase Receipt' if asset.get('purchase_receipt') else 'Purchase Invoice' - asset_movement.reference_name = prev_reference_docname asset_movement.append("assets", { 'asset': asset.get('name'), 'source_location': asset.get('location'), diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js index 89977e2952..06d8879091 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.js +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -31,6 +31,13 @@ frappe.ui.form.on('Asset Movement', { name: ["in", ["Purchase Receipt", "Purchase Invoice"]] } }; + }), + frm.set_query("asset", "assets", () => { + return { + filters: { + status: ["not in", ["Draft"]] + } + } }) }, @@ -76,50 +83,6 @@ frappe.ui.form.on('Asset Movement', { }); }); frm.refresh_field('assets'); - }, - - reference_name: function(frm) { - if (frm.doc.reference_name && frm.doc.reference_doctype) { - const reference_doctype = frm.doc.reference_doctype === 'Purchase Invoice' ? 'purchase_invoice' : 'purchase_receipt'; - // On selection of reference name, - // sets query to display assets linked to that reference doc - frm.set_query('asset', 'assets', function() { - return { - filters: { - [reference_doctype] : frm.doc.reference_name - } - }; - }); - - // fetches linked asset & adds to the assets table - frappe.db.get_list('Asset', { - fields: ['name', 'location', 'custodian'], - filters: { - [reference_doctype] : frm.doc.reference_name - } - }).then((docs) => { - if (docs.length == 0) { - frappe.msgprint(frappe._(`Please select ${frm.doc.reference_doctype} which has assets.`)); - frm.doc.reference_name = ''; - frm.refresh_field('reference_name'); - return; - } - frm.doc.assets = []; - docs.forEach(doc => { - frm.add_child('assets', { - asset: doc.name, - source_location: doc.location, - from_employee: doc.custodian - }); - frm.refresh_field('assets'); - }) - }).catch((err) => { - console.log(err); // eslint-disable-line - }); - } else { - // if reference is deleted then remove query - frm.set_query('asset', 'assets', () => ({ filters: {} })); - } } }); diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index e62d684411..3472ab5d7d 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -9,12 +9,12 @@ "purpose", "column_break_4", "transaction_date", + "section_break_10", + "assets", "reference", "reference_doctype", "column_break_9", "reference_name", - "section_break_10", - "assets", "amended_from" ], "fields": [ @@ -47,6 +47,7 @@ "fieldtype": "Column Break" }, { + "collapsible": 1, "fieldname": "reference", "fieldtype": "Section Break", "label": "Reference" @@ -54,18 +55,16 @@ { "fieldname": "reference_doctype", "fieldtype": "Link", - "label": "Reference Document", + "label": "Reference Document Type", "no_copy": 1, - "options": "DocType", - "reqd": 1 + "options": "DocType" }, { "fieldname": "reference_name", "fieldtype": "Dynamic Link", "label": "Reference Document Name", "no_copy": 1, - "options": "reference_doctype", - "reqd": 1 + "options": "reference_doctype" }, { "fieldname": "amended_from", @@ -93,7 +92,7 @@ } ], "is_submittable": 1, - "modified": "2019-11-21 14:35:51.880332", + "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 714845dfac..4e1822b2ce 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -22,7 +22,7 @@ class AssetMovement(Document): if company != self.company: frappe.throw(_("Asset {0} does not belong to company {1}").format(d.asset, self.company)) - if not(d.source_location or d.target_location or d.from_employee or d.to_employee): + 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): 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/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 3392850e96..d12643af82 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -641,7 +641,10 @@ class BuyingController(StockController): 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.get_all('Asset Movement', filters={ 'reference_name': self.name }) + 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) @@ -652,8 +655,12 @@ class BuyingController(StockController): asset.purchase_date = self.posting_date asset.supplier = self.supplier elif self.docstatus == 2: - asset.set(field, None) - asset.supplier = None + 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 diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index f0036277c8..59391505fa 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -140,32 +140,6 @@ 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 - }) - ) - - gl_entry.append( - self.get_gl_dict({ - "account": data.advance_account, - "debit": data.allocated_amount, - "debit_in_account_currency": data.allocated_amount, - "against": self.payable_account, - "party_type": "Employee", - "party": self.employee, - "against_voucher_type": self.doctype, - "against_voucher": self.name - }) - ) - - gl_entry.append( - self.get_gl_dict({ - "account": self.payable_account, - "credit": data.allocated_amount, - "credit_in_account_currency": data.allocated_amount, - "against": data.advance_account, - "party_type": "Employee", - "party": self.employee, "against_voucher_type": "Employee Advance", "against_voucher": data.employee_advance }) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 54fce8d6db..7083d694f8 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -7,7 +7,7 @@ import json import frappe from frappe import _, throw -from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate +from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today from frappe.utils.nestedset import NestedSet from frappe.desk.form.assign_to import close_all_assignments, clear from frappe.utils import date_diff @@ -212,8 +212,11 @@ def set_multiple_status(names, status): task.save() def set_tasks_as_overdue(): - tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Completed']]}) + tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Closed']]}) for task in tasks: + if frappe.db.get_value("Task", task.name, "status") in 'Pending Review': + if getdate(frappe.db.get_value("Task", task.name, "review_date")) < getdate(today()): + continue frappe.get_doc("Task", task.name).update_status() @frappe.whitelist()