diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 1a574edfe86..8a3f9341182 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -358,7 +358,7 @@ def update_outstanding_amt( if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: ref_doc = frappe.get_doc(against_voucher_type, against_voucher) - # Didn't use db_set for optimisation purpose + # Didn't use db_set for optimization purpose ref_doc.outstanding_amount = bal frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 98f3420d87e..1d596c1bfbb 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -36,6 +36,15 @@ frappe.ui.form.on('POS Closing Entry', { }); set_html_data(frm); + + if (frm.doc.docstatus == 1) { + if (!frm.doc.posting_date) { + frm.set_value("posting_date", frappe.datetime.nowdate()); + } + if (!frm.doc.posting_time) { + frm.set_value("posting_time", frappe.datetime.now_time()); + } + } }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json index d6e35c6a50d..9d15e6cf357 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -11,6 +11,7 @@ "period_end_date", "column_break_3", "posting_date", + "posting_time", "pos_opening_entry", "status", "section_break_5", @@ -51,7 +52,6 @@ "fieldtype": "Datetime", "in_list_view": 1, "label": "Period End Date", - "read_only": 1, "reqd": 1 }, { @@ -219,6 +219,13 @@ "fieldtype": "Small Text", "label": "Error", "read_only": 1 + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "no_copy": 1, + "reqd": 1 } ], "is_submittable": 1, @@ -228,10 +235,11 @@ "link_fieldname": "pos_closing_entry" } ], - "modified": "2021-10-20 16:19:25.340565", + "modified": "2022-08-01 11:37:14.991228", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -278,5 +286,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 49aab0d0bbf..655c4ec0035 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -15,6 +15,9 @@ from erpnext.controllers.status_updater import StatusUpdater class POSClosingEntry(StatusUpdater): def validate(self): + self.posting_date = self.posting_date or frappe.utils.nowdate() + self.posting_time = self.posting_time or frappe.utils.nowtime() + if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json index d7620870780..a0594556474 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json @@ -6,6 +6,7 @@ "engine": "InnoDB", "field_order": [ "posting_date", + "posting_time", "merge_invoices_based_on", "column_break_3", "pos_closing_entry", @@ -105,12 +106,19 @@ "label": "Customer Group", "mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'", "options": "Customer Group" + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "no_copy": 1, + "reqd": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-09-14 11:17:19.001142", + "modified": "2022-08-01 11:36:42.456429", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Merge Log", @@ -173,5 +181,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 5003a1d6a81..f6002163f0f 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -9,7 +9,7 @@ from frappe import _ from frappe.core.page.background_jobs.background_jobs import get_info from frappe.model.document import Document from frappe.model.mapper import map_child_doc, map_doc -from frappe.utils import cint, flt, getdate, nowdate +from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime from frappe.utils.background_jobs import enqueue from frappe.utils.scheduler import is_scheduler_inactive @@ -79,6 +79,7 @@ class POSInvoiceMergeLog(Document): if sales: sales_invoice = self.process_merging_into_sales_invoice(sales) + self.flags.ignore_validate_update_after_submit = True self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note) @@ -99,6 +100,7 @@ class POSInvoiceMergeLog(Document): sales_invoice.is_consolidated = 1 sales_invoice.set_posting_time = 1 sales_invoice.posting_date = getdate(self.posting_date) + sales_invoice.posting_time = get_time(self.posting_time) sales_invoice.save() sales_invoice.submit() @@ -115,6 +117,7 @@ class POSInvoiceMergeLog(Document): credit_note.is_consolidated = 1 credit_note.set_posting_time = 1 credit_note.posting_date = getdate(self.posting_date) + credit_note.posting_time = get_time(self.posting_time) # TODO: return could be against multiple sales invoice which could also have been consolidated? # credit_note.return_against = self.consolidated_invoice credit_note.save() @@ -402,6 +405,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): merge_log.posting_date = ( getdate(closing_entry.get("posting_date")) if closing_entry else nowdate() ) + merge_log.posting_time = ( + get_time(closing_entry.get("posting_time")) if closing_entry else nowtime() + ) merge_log.customer = customer merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 0c2ec5ffa31..aed8cb56d1b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -539,7 +539,7 @@ frappe.ui.form.on("Purchase Invoice", { }, add_custom_buttons: function(frm) { - if (frm.doc.per_received < 100) { + if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) { frm.add_custom_button(__('Purchase Receipt'), () => { frm.events.make_purchase_receipt(frm); }, __('Create')); diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 96ee05d2304..75508be8d09 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -567,7 +567,6 @@ class PurchaseInvoice(BuyingController): self.make_supplier_gl_entry(gl_entries) self.make_item_gl_entries(gl_entries) - self.make_discount_gl_entries(gl_entries) if self.check_asset_cwip_enabled(): self.get_asset_gl_entry(gl_entries) @@ -794,7 +793,7 @@ class PurchaseInvoice(BuyingController): ) if not item.is_fixed_asset: - dummy, amount = self.get_amount_and_base_amount(item, self.enable_discount_accounting) + dummy, amount = self.get_amount_and_base_amount(item, None) else: amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) @@ -1110,7 +1109,7 @@ class PurchaseInvoice(BuyingController): valuation_tax = {} for tax in self.get("taxes"): - amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting) + amount, base_amount = self.get_tax_amounts(tax, None) if tax.category in ("Total", "Valuation and Total") and flt(base_amount): account_currency = get_account_currency(tax.account_head) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 1fdadea8bed..18b0636beca 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -304,59 +304,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) - def test_purchase_invoice_with_discount_accounting_enabled(self): - enable_discount_accounting() - - discount_account = create_account( - account_name="Discount Account", - parent_account="Indirect Expenses - _TC", - company="_Test Company", - ) - pi = make_purchase_invoice(discount_account=discount_account, rate=45) - - expected_gle = [ - ["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()], - ["Creditors - _TC", 0.0, 225.0, nowdate()], - ["Discount Account - _TC", 0.0, 25.0, nowdate()], - ] - - check_gl_entries(self, pi.name, expected_gle, nowdate()) - enable_discount_accounting(enable=0) - - def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self): - enable_discount_accounting() - additional_discount_account = create_account( - account_name="Discount Account", - parent_account="Indirect Expenses - _TC", - company="_Test Company", - ) - - pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC") - pi.apply_discount_on = "Grand Total" - pi.additional_discount_account = additional_discount_account - pi.additional_discount_percentage = 10 - pi.disable_rounded_total = 1 - pi.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account VAT - _TC", - "cost_center": "Main - _TC", - "description": "Test", - "rate": 10, - }, - ) - pi.submit() - - expected_gle = [ - ["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()], - ["_Test Account VAT - _TC", 25.0, 0.0, nowdate()], - ["Creditors - _TC", 0.0, 247.5, nowdate()], - ["Discount Account - _TC", 0.0, 27.5, nowdate()], - ] - - check_gl_entries(self, pi.name, expected_gle, nowdate()) - def test_purchase_invoice_change_naming_series(self): pi = frappe.copy_doc(test_records[1]) pi.insert() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 8f7038df30e..c765e4f07ff 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -480,9 +480,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte is_cash_or_non_trade_discount() { this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount); + this.frm.set_df_property("additional_discount_account", "reqd", this.frm.doc.is_cash_or_non_trade_discount); + if (!this.frm.doc.is_cash_or_non_trade_discount) { this.frm.set_value("additional_discount_account", ""); } + + this.calculate_taxes_and_totals(); } }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 29f3970d0dc..1badb809341 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1065,22 +1065,6 @@ class SalesInvoice(SellingController): ) ) - if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"): - gl_entries.append( - self.get_gl_dict( - { - "account": self.additional_discount_account, - "against": self.debit_to, - "debit": self.base_discount_amount, - "debit_in_account_currency": self.discount_amount, - "cost_center": self.cost_center, - "project": self.project, - }, - self.currency, - item=self, - ) - ) - def make_tax_gl_entries(self, gl_entries): for tax in self.get("taxes"): amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 5a095fbb7ea..7bb564b066a 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -279,7 +279,6 @@ "label": "Discount (%) on Price List Rate with Margin", "oldfieldname": "adj_rate", "oldfieldtype": "Float", - "precision": "2", "print_hide": 1 }, { @@ -842,7 +841,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-02-24 14:41:36.392560", + "modified": "2022-08-26 12:06:31.205417", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4eee74b2cbd..527c9389d8a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1105,17 +1105,17 @@ class AccountsController(TransactionBase): frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting") ) + if self.doctype == "Purchase Invoice": + dr_or_cr = "credit" + rev_dr_cr = "debit" + supplier_or_customer = self.supplier + + else: + dr_or_cr = "debit" + rev_dr_cr = "credit" + supplier_or_customer = self.customer + if enable_discount_accounting: - if self.doctype == "Purchase Invoice": - dr_or_cr = "credit" - rev_dr_cr = "debit" - supplier_or_customer = self.supplier - - else: - dr_or_cr = "debit" - rev_dr_cr = "credit" - supplier_or_customer = self.customer - for item in self.get("items"): if item.get("discount_amount") and item.get("discount_account"): discount_amount = item.discount_amount * item.qty @@ -1169,18 +1169,22 @@ class AccountsController(TransactionBase): ) ) - if self.get("discount_amount") and self.get("additional_discount_account"): - gl_entries.append( - self.get_gl_dict( - { - "account": self.additional_discount_account, - "against": supplier_or_customer, - dr_or_cr: self.discount_amount, - "cost_center": self.cost_center, - }, - item=self, - ) + if ( + (enable_discount_accounting or self.get("is_cash_or_non_trade_discount")) + and self.get("additional_discount_account") + and self.get("discount_amount") + ): + gl_entries.append( + self.get_gl_dict( + { + "account": self.additional_discount_account, + "against": supplier_or_customer, + dr_or_cr: self.discount_amount, + "cost_center": self.cost_center, + }, + item=self, ) + ) def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 589c75783fe..548ef03c2fd 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -37,6 +37,12 @@ class calculate_taxes_and_totals(object): self.set_discount_amount() self.apply_discount_amount() + # Update grand total as per cash and non trade discount + if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): + self.doc.grand_total -= self.doc.discount_amount + self.doc.base_grand_total -= self.doc.base_discount_amount + self.set_rounded_total() + self.calculate_shipping_charges() if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]: @@ -500,9 +506,6 @@ class calculate_taxes_and_totals(object): else: self.doc.grand_total = flt(self.doc.net_total) - if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): - self.doc.grand_total -= self.doc.discount_amount - if self.doc.get("taxes"): self.doc.total_taxes_and_charges = flt( self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), @@ -597,16 +600,16 @@ class calculate_taxes_and_totals(object): if not self.doc.apply_discount_on: frappe.throw(_("Please select Apply Discount On")) + self.doc.base_discount_amount = flt( + self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount") + ) + if self.doc.apply_discount_on == "Grand Total" and self.doc.get( "is_cash_or_non_trade_discount" ): self.discount_amount_applied = True return - self.doc.base_discount_amount = flt( - self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount") - ) - total_for_discount_amount = self.get_total_for_discount_amount() taxes = self.doc.get("taxes") net_total = 0 diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index ee00e6719c0..dbadbc48916 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -52,7 +52,7 @@ frappe.ui.form.on("Leave Application", { make_dashboard: function(frm) { var leave_details; let lwps; - if (frm.doc.employee && frm.doc.from_date) { + if (frm.doc.employee) { frappe.call({ method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details", async: false, diff --git a/erpnext/hr/employee_property_update.js b/erpnext/hr/employee_property_update.js index 60d06b41f07..468b80d8521 100644 --- a/erpnext/hr/employee_property_update.js +++ b/erpnext/hr/employee_property_update.js @@ -8,11 +8,11 @@ frappe.ui.form.on(cur_frm.doctype, { }; }); }, - onload: function(frm){ - if(frm.doc.__islocal){ - if(frm.doctype == "Employee Promotion"){ + onload: function(frm) { + if (frm.doc.__islocal && !frm.doc.amended_from) { + if (frm.doctype == "Employee Promotion") { frm.doc.promotion_details = []; - }else if (frm.doctype == "Employee Transfer") { + } else if (frm.doctype == "Employee Transfer") { frm.doc.transfer_details = []; } } @@ -106,12 +106,12 @@ var render_dynamic_field = function(d, fieldtype, options, fieldname) { var add_to_details = function(frm, d, table) { let data = d.data; - if(data.fieldname){ - if(validate_duplicate(frm, table, data.fieldname)){ + if (data.fieldname) { + if (validate_duplicate(frm, table, data.fieldname)) { frappe.show_alert({message:__("Property already added"), indicator:'orange'}); return false; } - if(data.current == data.new){ + if (data.current == data.new) { frappe.show_alert({message:__("Nothing to change"), indicator:'orange'}); d.get_primary_btn().attr('disabled', false); return false; @@ -123,12 +123,14 @@ var add_to_details = function(frm, d, table) { new: data.new }); frm.refresh_field(table); + frm.fields_dict[table].grid.wrapper.find(".grid-add-row").hide(); + d.fields_dict.field_html.$wrapper.html(""); d.set_value("property", ""); d.set_value('current', ""); frappe.show_alert({message:__("Added to details"),indicator:'green'}); d.data = {}; - }else { + } else { frappe.show_alert({message:__("Value missing"),indicator:'red'}); } }; diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 2f9f4571f28..19d0d84a46f 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -734,6 +734,7 @@ def get_amounts(amounts, against_loan, posting_date): ) amounts["pending_accrual_entries"] = pending_accrual_entries amounts["unaccrued_interest"] = flt(unaccrued_interest, precision) + amounts["written_off_amount"] = flt(against_loan_doc.written_off_amount, precision) if final_due_date: amounts["due_date"] = final_due_date diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py index 81464a36c3d..25c72d91a7c 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py @@ -57,7 +57,7 @@ def process_loan_interest_accrual_for_demand_loans( def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=None, loan=None): - if not term_loan_accrual_pending(posting_date or nowdate()): + if not term_loan_accrual_pending(posting_date or nowdate(), loan=loan): return loan_process = frappe.new_doc("Process Loan Interest Accrual") @@ -71,9 +71,12 @@ def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=No return loan_process.name -def term_loan_accrual_pending(date): - pending_accrual = frappe.db.get_value( - "Repayment Schedule", {"payment_date": ("<=", date), "is_accrued": 0} - ) +def term_loan_accrual_pending(date, loan=None): + filters = {"payment_date": ("<=", date), "is_accrued": 0} + + if loan: + filters.update({"parent": loan}) + + pending_accrual = frappe.db.get_value("Repayment Schedule", filters) return pending_accrual diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json index b7b20d945d6..32e281ddf41 100644 --- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json +++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json @@ -64,7 +64,7 @@ "fieldname": "total_payment", "fieldtype": "Currency", "in_list_view": 1, - "label": "Total Payment", + "label": "Paid Amount", "options": "Company:company:default_currency" }, { @@ -87,7 +87,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-01-31 14:50:14.823213", + "modified": "2022-08-29 08:50:39.030296", "modified_by": "Administrator", "module": "Loan Management", "name": "Salary Slip Loan", @@ -96,6 +96,5 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", - "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 256f66071f3..2acafc848a1 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -395,7 +395,7 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No }, "Maintenance Schedule Item": { "doctype": "Maintenance Visit Purpose", - "condition": lambda doc: doc.item_name == item_name, + "condition": lambda doc: doc.item_name == item_name if item_name else True, "field_map": {"sales_person": "service_person"}, "postprocess": update_serial, }, diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json index 507d319b515..a79ccfc8ce4 100644 --- a/erpnext/manufacturing/doctype/bom/test_records.json +++ b/erpnext/manufacturing/doctype/bom/test_records.json @@ -162,5 +162,54 @@ "item": "_Test Variant Item", "quantity": 1.0, "with_operations": 1 + }, + { + "operations": [ + { + "operation": "_Test Operation 1", + "description": "_Test", + "workstation": "_Test Workstation 1", + "hour_rate": 100, + "time_in_mins": 60, + "operating_cost": 100 + } + ], + "items": [ + { + "amount": 5000.0, + "doctype": "BOM Item", + "item_code": "_Test Item", + "parentfield": "items", + "qty": 1.0, + "rate": 5000.0, + "uom": "_Test UOM", + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC", + "include_item_in_manufacturing": 1 + }, + { + "amount": 3000.0, + "bom_no": "BOM-_Test Item Home Desktop Manufactured-001", + "doctype": "BOM Item", + "item_code": "_Test Item Home Desktop Manufactured", + "parentfield": "items", + "qty": 3.0, + "rate": 1000.0, + "uom": "_Test UOM", + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC", + "include_item_in_manufacturing": 1 + } + ], + "docstatus": 1, + "doctype": "BOM", + "is_active": 1, + "is_default": 1, + "currency": "USD", + "conversion_rate": 60, + "company": "_Test Company", + "item": "_Test FG Item 3", + "quantity": 1.0, + "with_operations": 1 } ] diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 210c0ea6a72..6bd64594202 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -11,6 +11,7 @@ "col_break1", "workstation", "time_in_mins", + "fixed_time", "costing_section", "hour_rate", "base_hour_rate", @@ -80,6 +81,14 @@ "oldfieldtype": "Currency", "reqd": 1 }, + { + "default": "0", + "description": "Operation time does not depend on quantity to produce", + "fieldname": "fixed_time", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Fixed Time" + }, { "fieldname": "operating_cost", "fieldtype": "Currency", @@ -177,7 +186,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-04-08 01:18:33.547481", + "modified": "2022-08-22 01:18:33.547481", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", @@ -185,4 +194,4 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 1e629b2fbfa..cfba0fff15e 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -95,7 +95,7 @@ class TestWorkOrder(FrappeTestCase): def test_planned_operating_cost(self): wo_order = make_wo_order_test_record( - item="_Test FG Item 2", planned_start_date=now(), qty=1, do_not_save=True + item="_Test FG Item 3", planned_start_date=now(), qty=1, do_not_save=True ) wo_order.set_work_order_operations() cost = wo_order.planned_operating_cost @@ -1001,6 +1001,49 @@ class TestWorkOrder(FrappeTestCase): close_work_order(wo_order, "Closed") self.assertEqual(wo_order.get("status"), "Closed") + def test_fix_time_operations(self): + bom = frappe.get_doc( + { + "doctype": "BOM", + "item": "_Test FG Item 2", + "is_active": 1, + "is_default": 1, + "quantity": 1.0, + "with_operations": 1, + "operations": [ + { + "operation": "_Test Operation 1", + "description": "_Test", + "workstation": "_Test Workstation 1", + "time_in_mins": 60, + "operating_cost": 140, + "fixed_time": 1, + } + ], + "items": [ + { + "amount": 5000.0, + "doctype": "BOM Item", + "item_code": "_Test Item", + "parentfield": "items", + "qty": 1.0, + "rate": 5000.0, + }, + ], + } + ) + bom.save() + bom.submit() + + wo1 = make_wo_order_test_record( + item=bom.item, bom_no=bom.name, qty=1, skip_transfer=1, do_not_submit=1 + ) + wo2 = make_wo_order_test_record( + item=bom.item, bom_no=bom.name, qty=2, skip_transfer=1, do_not_submit=1 + ) + + self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins) + def test_partial_manufacture_entries(self): cancel_stock_entry = [] @@ -1015,7 +1058,6 @@ class TestWorkOrder(FrappeTestCase): ste1 = test_stock_entry.make_stock_entry( item_code="_Test Item", target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0 ) - ste2 = test_stock_entry.make_stock_entry( item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index dc553c15ad5..2802310250b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -653,20 +653,31 @@ class WorkOrder(Document): """Fetch operations from BOM and set in 'Work Order'""" def _get_operations(bom_no, qty=1): - return frappe.db.sql( - f"""select - operation, description, workstation, idx, - base_hour_rate as hour_rate, time_in_mins * {qty} as time_in_mins, - "Pending" as status, parent as bom, batch_size, sequence_id - from - `tabBOM Operation` - where - parent = %s order by idx - """, - bom_no, - as_dict=1, + data = frappe.get_all( + "BOM Operation", + filters={"parent": bom_no}, + fields=[ + "operation", + "description", + "workstation", + "idx", + "base_hour_rate as hour_rate", + "time_in_mins", + "parent as bom", + "batch_size", + "sequence_id", + "fixed_time", + ], + order_by="idx", ) + for d in data: + if not d.fixed_time: + d.time_in_mins = flt(d.time_in_mins) * flt(qty) + d.status = "Pending" + + return data + self.set("operations", []) if not self.bom_no or not frappe.get_cached_value("BOM", self.bom_no, "with_operations"): return @@ -692,7 +703,8 @@ class WorkOrder(Document): def calculate_time(self): for d in self.get("operations"): - d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size)) + if not d.fixed_time: + d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size)) self.calculate_operating_cost() diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py index f3ed5f9e2b9..35cea017e8c 100644 --- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py @@ -330,13 +330,13 @@ class TestPayrollEntry(FrappeTestCase): ) salary_slip = frappe.get_doc("Salary Slip", name) + for row in salary_slip.loans: if row.loan == loan.name: interest_amount = (280000 * 8.4) / (12 * 100) principal_amount = loan.monthly_repayment_amount - interest_amount self.assertEqual(row.interest_amount, interest_amount) self.assertEqual(row.principal_amount, principal_amount) - self.assertEqual(row.total_payment, interest_amount + principal_amount) if salary_slip.docstatus == 0: frappe.delete_doc("Salary Slip", name) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index ad00d6d323f..4214c0238c3 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -377,7 +377,6 @@ "fieldtype": "Column Break" }, { - "depends_on": "total_loan_repayment", "fieldname": "loan_repayment", "fieldtype": "Section Break", "label": "Loan Repayment" @@ -647,7 +646,7 @@ "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-10-08 11:48:47.098248", + "modified": "2022-08-29 08:59:50.819986", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index a5c892687c5..14a2a3ae398 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1379,12 +1379,29 @@ class SalarySlip(TransactionBase): for loan in self.get_loan_details(): amounts = calculate_amounts(loan.name, self.posting_date, "Regular Payment") - if amounts["interest_amount"] or amounts["payable_principal_amount"]: + if (amounts["interest_amount"] or amounts["payable_principal_amount"]) and ( + amounts["payable_principal_amount"] + amounts["interest_amount"] + > amounts["written_off_amount"] + ): + + if amounts["interest_amount"] > amounts["written_off_amount"]: + amounts["interest_amount"] -= amounts["written_off_amount"] + amounts["written_off_amount"] = 0 + else: + amounts["written_off_amount"] -= amounts["interest_amount"] + amounts["interest_amount"] = 0 + + if amounts["payable_principal_amount"] > amounts["written_off_amount"]: + amounts["payable_principal_amount"] -= amounts["written_off_amount"] + amounts["written_off_amount"] = 0 + else: + amounts["written_off_amount"] -= amounts["payable_principal_amount"] + amounts["payable_principal_amount"] = 0 + self.append( "loans", { "loan": loan.name, - "total_payment": amounts["interest_amount"] + amounts["payable_principal_amount"], "interest_amount": amounts["interest_amount"], "principal_amount": amounts["payable_principal_amount"], "loan_account": loan.loan_account, @@ -1395,7 +1412,7 @@ class SalarySlip(TransactionBase): for payment in self.get("loans"): amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment") total_amount = amounts["interest_amount"] + amounts["payable_principal_amount"] - if payment.total_payment > total_amount: + if flt(payment.total_payment) > total_amount: frappe.throw( _( """Row {0}: Paid amount {1} is greater than pending accrued amount {2} against loan {3}""" @@ -1407,10 +1424,10 @@ class SalarySlip(TransactionBase): ) ) - self.total_interest_amount += payment.interest_amount - self.total_principal_amount += payment.principal_amount + self.total_interest_amount += flt(payment.interest_amount) + self.total_principal_amount += flt(payment.principal_amount) - self.total_loan_repayment += payment.total_payment + self.total_loan_repayment += flt(payment.total_payment) def get_loan_details(self): loan_details = frappe.get_all( @@ -1436,7 +1453,7 @@ class SalarySlip(TransactionBase): def make_loan_repayment_entry(self): payroll_payable_account = get_payroll_payable_account(self.company, self.payroll_entry) for loan in self.loans: - if loan.total_payment: + if flt(loan.total_payment) > 0: repayment_entry = create_repayment_entry( loan.loan, self.employee, diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 987c1ac281e..e33f8cce4c4 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -670,9 +670,10 @@ class TestSalarySlip(FrappeTestCase): ss = make_employee_salary_slip( "test_loan_repayment_salary_slip@salary.com", "Monthly", "Test Loan Repayment Salary Structure" ) + + ss.loans[0].total_payment = 592 ss.submit() - self.assertEqual(ss.total_loan_repayment, 592) self.assertEqual( ss.net_pay, (flt(ss.gross_pay) - (flt(ss.total_deduction) + flt(ss.total_loan_repayment))) ) diff --git a/erpnext/projects/doctype/task_type/task_type.json b/erpnext/projects/doctype/task_type/task_type.json index 3254444a48e..b04264e9c74 100644 --- a/erpnext/projects/doctype/task_type/task_type.json +++ b/erpnext/projects/doctype/task_type/task_type.json @@ -1,127 +1,70 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, + "actions": [], "autoname": "Prompt", - "beta": 0, "creation": "2019-04-19 15:04:05.317138", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "weight", + "description" + ], "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": "weight", "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": "Weight", - "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": "Weight" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Small 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": "Description", - "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": "Description" } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-19 15:31:48.080164", + "links": [], + "modified": "2022-08-29 17:46:41.342979", "modified_by": "Administrator", "module": "Projects", "name": "Task Type", - "name_case": "", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { - "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": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1 } ], "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index fe23ff38126..c75e7b77f70 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -39,6 +39,12 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this._calculate_taxes_and_totals(); this.calculate_discount_amount(); + // # Update grand total as per cash and non trade discount + if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) { + this.frm.doc.grand_total -= this.frm.doc.discount_amount; + this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount; + } + await this.calculate_shipping_charges(); // Advance calculation applicable to Sales /Purchase Invoice @@ -611,6 +617,10 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.frm.doc.base_discount_amount = flt(this.frm.doc.discount_amount * this.frm.doc.conversion_rate, precision("base_discount_amount")); + if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) { + return; + } + var total_for_discount_amount = this.get_total_for_discount_amount(); var net_total = 0; // calculate item amount after Discount Amount diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index eacf480ef8f..e7dd211c0f4 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -524,7 +524,8 @@ erpnext.PointOfSale.ItemCart = class { const currency = this.events.get_frm().doc.currency; const taxes_html = taxes.map(t => { if (t.tax_amount_after_discount_amount == 0.0) return; - const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; + // if tax rate is 0, don't print it. + const description = /[0-9]+/.test(t.description) ? t.description : ((t.rate != 0) ? `${t.description} @ ${t.rate}%`: t.description); return `