Merge pull request #38320 from ruthra-kumar/version-14

chore: release v14
This commit is contained in:
ruthra kumar
2023-11-24 14:16:16 +05:30
committed by GitHub
28 changed files with 281 additions and 75 deletions

View File

@@ -51,7 +51,7 @@ frappe.ui.form.on("Journal Entry", {
}, __('Make')); }, __('Make'));
} }
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm); erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
}, },
before_save: function(frm) { before_save: function(frm) {
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) { if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {

View File

@@ -548,8 +548,16 @@
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 176, "idx": 176,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [
"modified": "2023-08-10 14:32:22.366895", {
"is_child_table": 1,
"link_doctype": "Bank Transaction Payments",
"link_fieldname": "payment_entry",
"parent_doctype": "Bank Transaction",
"table_fieldname": "payment_entries"
}
],
"modified": "2023-11-23 12:11:04.128015",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry", "name": "Journal Entry",

View File

@@ -203,7 +203,8 @@
"fieldtype": "Select", "fieldtype": "Select",
"label": "Reference Type", "label": "Reference Type",
"no_copy": 1, "no_copy": 1,
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry" "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry",
"search_index": 1
}, },
{ {
"fieldname": "reference_name", "fieldname": "reference_name",
@@ -211,7 +212,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Reference Name", "label": "Reference Name",
"no_copy": 1, "no_copy": 1,
"options": "reference_type" "options": "reference_type",
"search_index": 1
}, },
{ {
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])", "depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
@@ -278,13 +280,14 @@
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Reference Detail No", "label": "Reference Detail No",
"no_copy": 1 "no_copy": 1,
"search_index": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-06-16 14:11:13.507807", "modified": "2023-11-23 11:44:25.841187",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry Account", "name": "Journal Entry Account",

View File

@@ -7,7 +7,7 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
frappe.ui.form.on('Payment Entry', { frappe.ui.form.on('Payment Entry', {
onload: function(frm) { onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payments', 'Unreconcile Payment Entries']; frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries'];
if(frm.doc.__islocal) { if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null); if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
@@ -158,7 +158,7 @@ frappe.ui.form.on('Payment Entry', {
}, __('Actions')); }, __('Actions'));
} }
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm); erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
}, },
validate_company: (frm) => { validate_company: (frm) => {

View File

@@ -739,8 +739,16 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [
"modified": "2023-06-19 11:38:04.387219", {
"is_child_table": 1,
"link_doctype": "Bank Transaction Payments",
"link_fieldname": "payment_entry",
"parent_doctype": "Bank Transaction",
"table_fieldname": "payment_entries"
}
],
"modified": "2023-11-23 12:07:20.887885",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",
@@ -786,4 +794,4 @@
"states": [], "states": [],
"title_field": "title", "title_field": "title",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -107,7 +107,7 @@ class PaymentEntry(AccountsController):
"Repost Payment Ledger Items", "Repost Payment Ledger Items",
"Repost Accounting Ledger", "Repost Accounting Ledger",
"Repost Accounting Ledger Items", "Repost Accounting Ledger Items",
"Unreconcile Payments", "Unreconcile Payment",
"Unreconcile Payment Entries", "Unreconcile Payment Entries",
) )
super(PaymentEntry, self).on_cancel() super(PaymentEntry, self).on_cancel()

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import json
import unittest import unittest
import frappe import frappe
@@ -21,6 +22,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
create_sales_invoice, create_sales_invoice,
create_sales_invoice_against_cost_center, create_sales_invoice_against_cost_center,
) )
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.setup.doctype.employee.test_employee import make_employee from erpnext.setup.doctype.employee.test_employee import make_employee
@@ -1219,6 +1221,76 @@ class TestPaymentEntry(FrappeTestCase):
so.reload() so.reload()
self.assertEqual(so.advance_paid, so.rounded_total) self.assertEqual(so.advance_paid, so.rounded_total)
def test_partial_cancel_for_payment_entry(self):
si = create_sales_invoice()
pe = get_payment_entry(si.doctype, si.name)
pe.save()
pe.submit()
# Additional GL Entry
tax_amount = 10
reference_row = pe.references[0]
gl_args = {
"party_type": pe.party_type,
"party": pe.party,
"against_voucher_type": reference_row.reference_doctype,
"against_voucher": reference_row.reference_name,
"voucher_detail_no": reference_row.name,
}
gl_dicts = []
gl_dicts.extend(
[
pe.get_gl_dict(
{
"account": pe.paid_to,
"credit": tax_amount,
"credit_in_account_currency": tax_amount,
**gl_args,
}
),
pe.get_gl_dict(
{
"account": pe.paid_from,
"debit": tax_amount,
"debit_in_account_currency": tax_amount,
**gl_args,
}
),
]
)
make_gl_entries(gl_dicts)
# Assert PLEs Before
self.assertPLEntries(
pe,
[
{"amount": -100.0, "against_voucher_no": si.name},
{"amount": 10.0, "against_voucher_no": si.name},
],
)
# Partially cancel Payment Entry
make_reverse_gl_entries(gl_dicts, partial_cancel=True)
self.assertPLEntries(pe, [{"amount": -100.0, "against_voucher_no": si.name}])
def assertPLEntries(self, payment_doc, expected_pl_entries):
pl_entries = frappe.get_all(
"Payment Ledger Entry",
filters={
"voucher_type": payment_doc.doctype,
"voucher_no": payment_doc.name,
"delinked": 0,
},
fields=["amount", "against_voucher_no"],
)
out_str = json.dumps(sorted(pl_entries, key=json.dumps))
expected_out_str = json.dumps(sorted(expected_pl_entries, key=json.dumps))
self.assertEqual(out_str, expected_out_str)
def create_payment_entry(**args): def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry") payment_entry = frappe.new_doc("Payment Entry")

View File

@@ -181,7 +181,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
} }
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1); this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm); erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
} }
unblock_invoice() { unblock_invoice() {

View File

@@ -532,7 +532,11 @@ class PurchaseInvoice(BuyingController):
if self.update_stock == 1: if self.update_stock == 1:
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
self.update_project() if (
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
):
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.update_advance_tax_references() self.update_advance_tax_references()
@@ -1269,7 +1273,10 @@ class PurchaseInvoice(BuyingController):
if self.update_stock == 1: if self.update_stock == 1:
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
self.update_project() if (
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
):
self.update_project()
self.db_set("status", "Cancelled") self.db_set("status", "Cancelled")
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
@@ -1287,13 +1294,21 @@ class PurchaseInvoice(BuyingController):
self.update_advance_tax_references(cancel=1) self.update_advance_tax_references(cancel=1)
def update_project(self): def update_project(self):
project_list = [] projects = frappe._dict()
for d in self.items: for d in self.items:
if d.project and d.project not in project_list: if d.project:
project = frappe.get_doc("Project", d.project) if self.docstatus == 1:
project.update_purchase_costing() projects[d.project] = projects.get(d.project, 0) + d.base_net_amount
project.db_update() elif self.docstatus == 2:
project_list.append(d.project) projects[d.project] = projects.get(d.project, 0) - d.base_net_amount
pj = frappe.qb.DocType("Project")
for proj, value in projects.items():
res = (
frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
)
current_purchase_cost = res and res[0][0] or 0
frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)
def validate_supplier_invoice(self): def validate_supplier_invoice(self):
if self.bill_date: if self.bill_date:

View File

@@ -34,7 +34,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.onload(); super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payments", "Unreconcile Payment Entries"]; 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) { if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format // show debit_to in print format
@@ -178,7 +178,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
} }
} }
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm); erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
} }
make_maintenance_schedule() { make_maintenance_schedule() {

View File

@@ -1613,7 +1613,8 @@
"hide_seconds": 1, "hide_seconds": 1,
"label": "Inter Company Invoice Reference", "label": "Inter Company Invoice Reference",
"options": "Purchase Invoice", "options": "Purchase Invoice",
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "customer_group", "fieldname": "customer_group",
@@ -2164,7 +2165,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2023-11-20 11:51:43.555197", "modified": "2023-11-23 16:56:29.679499",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -408,7 +408,7 @@ class SalesInvoice(SellingController):
"Repost Payment Ledger Items", "Repost Payment Ledger Items",
"Repost Accounting Ledger", "Repost Accounting Ledger",
"Repost Accounting Ledger Items", "Repost Accounting Ledger Items",
"Unreconcile Payments", "Unreconcile Payment",
"Unreconcile Payment Entries", "Unreconcile Payment Entries",
"Payment Ledger Entry", "Payment Ledger Entry",
) )

View File

@@ -10,7 +10,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase): class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
def setUp(self): def setUp(self):
self.create_company() self.create_company()
self.create_customer() self.create_customer()
@@ -73,7 +73,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
unreconcile = frappe.get_doc( unreconcile = frappe.get_doc(
{ {
"doctype": "Unreconcile Payments", "doctype": "Unreconcile Payment",
"company": self.company, "company": self.company,
"voucher_type": pe.doctype, "voucher_type": pe.doctype,
"voucher_no": pe.name, "voucher_no": pe.name,
@@ -138,7 +138,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
unreconcile = frappe.get_doc( unreconcile = frappe.get_doc(
{ {
"doctype": "Unreconcile Payments", "doctype": "Unreconcile Payment",
"company": self.company, "company": self.company,
"voucher_type": pe2.doctype, "voucher_type": pe2.doctype,
"voucher_no": pe2.name, "voucher_no": pe2.name,
@@ -196,7 +196,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
unreconcile = frappe.get_doc( unreconcile = frappe.get_doc(
{ {
"doctype": "Unreconcile Payments", "doctype": "Unreconcile Payment",
"company": self.company, "company": self.company,
"voucher_type": pe.doctype, "voucher_type": pe.doctype,
"voucher_no": pe.name, "voucher_no": pe.name,
@@ -281,7 +281,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
unreconcile = frappe.get_doc( unreconcile = frappe.get_doc(
{ {
"doctype": "Unreconcile Payments", "doctype": "Unreconcile Payment",
"company": self.company, "company": self.company,
"voucher_type": pe2.doctype, "voucher_type": pe2.doctype,
"voucher_no": pe2.name, "voucher_no": pe2.name,

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Unreconcile Payments", { frappe.ui.form.on("Unreconcile Payment", {
refresh(frm) { refresh(frm) {
frm.set_query("voucher_type", function() { frm.set_query("voucher_type", function() {
return { return {

View File

@@ -21,7 +21,7 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Amended From", "label": "Amended From",
"no_copy": 1, "no_copy": 1,
"options": "Unreconcile Payments", "options": "Unreconcile Payment",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@@ -61,7 +61,7 @@
"modified": "2023-08-28 17:42:50.261377", "modified": "2023-08-28 17:42:50.261377",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Unreconcile Payments", "name": "Unreconcile Payment",
"naming_rule": "Expression", "naming_rule": "Expression",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
@@ -90,4 +90,4 @@
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -15,7 +15,7 @@ from erpnext.accounts.utils import (
) )
class UnreconcilePayments(Document): class UnreconcilePayment(Document):
def validate(self): def validate(self):
self.supported_types = ["Payment Entry", "Journal Entry"] self.supported_types = ["Payment Entry", "Journal Entry"]
if not self.voucher_type in self.supported_types: if not self.voucher_type in self.supported_types:
@@ -142,7 +142,7 @@ def create_unreconcile_doc_for_selection(selections=None):
selections = frappe.json.loads(selections) selections = frappe.json.loads(selections)
# assuming each row is a unique voucher # assuming each row is a unique voucher
for row in selections: for row in selections:
unrecon = frappe.new_doc("Unreconcile Payments") unrecon = frappe.new_doc("Unreconcile Payment")
unrecon.company = row.get("company") unrecon.company = row.get("company")
unrecon.voucher_type = row.get("voucher_type") unrecon.voucher_type = row.get("voucher_type")
unrecon.voucher_no = row.get("voucher_no") unrecon.voucher_no = row.get("voucher_no")

View File

@@ -556,7 +556,12 @@ def get_round_off_account_and_cost_center(
def make_reverse_gl_entries( def make_reverse_gl_entries(
gl_entries=None, voucher_type=None, voucher_no=None, adv_adj=False, update_outstanding="Yes" gl_entries=None,
voucher_type=None,
voucher_no=None,
adv_adj=False,
update_outstanding="Yes",
partial_cancel=False,
): ):
""" """
Get original gl entries of the voucher Get original gl entries of the voucher
@@ -576,14 +581,19 @@ def make_reverse_gl_entries(
if gl_entries: if gl_entries:
create_payment_ledger_entry( create_payment_ledger_entry(
gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding gl_entries,
cancel=1,
adv_adj=adv_adj,
update_outstanding=update_outstanding,
partial_cancel=partial_cancel,
) )
validate_accounting_period(gl_entries) validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], adv_adj) check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries) is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"]) validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) if not partial_cancel:
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
for entry in gl_entries: for entry in gl_entries:
new_gle = copy.deepcopy(entry) new_gle = copy.deepcopy(entry)

View File

@@ -1617,6 +1617,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
due_date=gle.due_date, due_date=gle.due_date,
voucher_type=gle.voucher_type, voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no, voucher_no=gle.voucher_no,
voucher_detail_no=gle.voucher_detail_no,
against_voucher_type=gle.against_voucher_type against_voucher_type=gle.against_voucher_type
if gle.against_voucher_type if gle.against_voucher_type
else gle.voucher_type, else gle.voucher_type,
@@ -1638,7 +1639,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
def create_payment_ledger_entry( def create_payment_ledger_entry(
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0 gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
): ):
if gl_entries: if gl_entries:
ple_map = get_payment_ledger_entries(gl_entries, cancel=cancel) ple_map = get_payment_ledger_entries(gl_entries, cancel=cancel)
@@ -1648,7 +1649,7 @@ def create_payment_ledger_entry(
ple = frappe.get_doc(entry) ple = frappe.get_doc(entry)
if cancel: if cancel:
delink_original_entry(ple) delink_original_entry(ple, partial_cancel=partial_cancel)
ple.flags.ignore_permissions = 1 ple.flags.ignore_permissions = 1
ple.flags.adv_adj = adv_adj ple.flags.adv_adj = adv_adj
@@ -1695,7 +1696,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
ref_doc.set_status(update=True) ref_doc.set_status(update=True)
def delink_original_entry(pl_entry): def delink_original_entry(pl_entry, partial_cancel=False):
if pl_entry: if pl_entry:
ple = qb.DocType("Payment Ledger Entry") ple = qb.DocType("Payment Ledger Entry")
query = ( query = (
@@ -1715,6 +1716,10 @@ def delink_original_entry(pl_entry):
& (ple.against_voucher_no == pl_entry.against_voucher_no) & (ple.against_voucher_no == pl_entry.against_voucher_no)
) )
) )
if partial_cancel:
query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no)
query.run() query.run()
@@ -1791,6 +1796,28 @@ class QueryPaymentLedger(object):
Table("outstanding").amount_in_account_currency >= self.max_outstanding Table("outstanding").amount_in_account_currency >= self.max_outstanding
) )
if self.limit and self.get_invoices:
outstanding_vouchers = (
qb.from_(ple)
.select(
ple.against_voucher_no.as_("voucher_no"),
Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
)
.where(ple.delinked == 0)
.where(Criterion.all(filter_on_against_voucher_no))
.where(Criterion.all(self.common_filter))
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
.orderby(ple.posting_date, ple.voucher_no)
.having(qb.Field("amount_in_account_currency") > 0)
.limit(self.limit)
.run()
)
if outstanding_vouchers:
filter_on_voucher_no.append(ple.voucher_no.isin([x[0] for x in outstanding_vouchers]))
filter_on_against_voucher_no.append(
ple.against_voucher_no.isin([x[0] for x in outstanding_vouchers])
)
# build query for voucher amount # build query for voucher amount
query_voucher_amount = ( query_voucher_amount = (
qb.from_(ple) qb.from_(ple)

View File

@@ -464,6 +464,9 @@ def restore_asset(asset_name):
def depreciate_asset(asset, date): def depreciate_asset(asset, date):
if not asset.calculate_depreciation:
return
asset.flags.ignore_validate_update_after_submit = True asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_disposal=date) asset.prepare_depreciation_data(date_of_disposal=date)
asset.save() asset.save()
@@ -472,6 +475,9 @@ def depreciate_asset(asset, date):
def reset_depreciation_schedule(asset, date): def reset_depreciation_schedule(asset, date):
if not asset.calculate_depreciation:
return
asset.flags.ignore_validate_update_after_submit = True asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset # recreate original depreciation schedule of the asset

View File

@@ -17,6 +17,7 @@
"po_required", "po_required",
"pr_required", "pr_required",
"blanket_order_allowance", "blanket_order_allowance",
"project_update_frequency",
"column_break_12", "column_break_12",
"maintain_same_rate", "maintain_same_rate",
"set_landed_cost_based_on_purchase_invoice_rate", "set_landed_cost_based_on_purchase_invoice_rate",
@@ -172,6 +173,14 @@
"fieldname": "blanket_order_allowance", "fieldname": "blanket_order_allowance",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Blanket Order Allowance (%)" "label": "Blanket Order Allowance (%)"
},
{
"default": "Each Transaction",
"description": "How often should Project be updated of Total Purchase Cost ?",
"fieldname": "project_update_frequency",
"fieldtype": "Select",
"label": "Update frequency of Project",
"options": "Each Transaction\nManual"
} }
], ],
"icon": "fa fa-cog", "icon": "fa fa-cog",
@@ -179,7 +188,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2023-10-25 14:03:32.520418", "modified": "2023-11-24 10:55:51.287327",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying Settings", "name": "Buying Settings",

View File

@@ -143,16 +143,17 @@ class Supplier(TransactionBase):
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters): def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
supplier = filters.get("supplier") supplier = filters.get("supplier")
return frappe.db.sql( contact = frappe.qb.DocType("Contact")
""" dynamic_link = frappe.qb.DocType("Dynamic Link")
SELECT
`tabContact`.name from `tabContact`, return (
`tabDynamic Link` frappe.qb.from_(contact)
WHERE .join(dynamic_link)
`tabContact`.name = `tabDynamic Link`.parent .on(contact.name == dynamic_link.parent)
and `tabDynamic Link`.link_name = %(supplier)s .select(contact.name, contact.email_id)
and `tabDynamic Link`.link_doctype = 'Supplier' .where(
and `tabContact`.name like %(txt)s (dynamic_link.link_name == supplier)
""", & (dynamic_link.link_doctype == "Supplier")
{"supplier": supplier, "txt": "%%%s%%" % txt}, & (contact.name.like("%{0}%".format(txt)))
) )
).run(as_dict=False)

View File

@@ -238,7 +238,7 @@ class AccountsController(TransactionBase):
references_map.setdefault(x.parent, []).append(x.name) references_map.setdefault(x.parent, []).append(x.name)
for doc, rows in references_map.items(): for doc, rows in references_map.items():
unreconcile_doc = frappe.get_doc("Unreconcile Payments", doc) unreconcile_doc = frappe.get_doc("Unreconcile Payment", doc)
for row in rows: for row in rows:
unreconcile_doc.remove(unreconcile_doc.get("allocations", {"name": row})[0]) unreconcile_doc.remove(unreconcile_doc.get("allocations", {"name": row})[0])
@@ -247,9 +247,9 @@ class AccountsController(TransactionBase):
unreconcile_doc.save(ignore_permissions=True) unreconcile_doc.save(ignore_permissions=True)
# delete docs upon parent doc deletion # delete docs upon parent doc deletion
unreconcile_docs = frappe.db.get_all("Unreconcile Payments", filters={"voucher_no": self.name}) unreconcile_docs = frappe.db.get_all("Unreconcile Payment", filters={"voucher_no": self.name})
for x in unreconcile_docs: for x in unreconcile_docs:
_doc = frappe.get_doc("Unreconcile Payments", x.name) _doc = frappe.get_doc("Unreconcile Payment", x.name)
if _doc.docstatus == 1: if _doc.docstatus == 1:
_doc.cancel() _doc.cancel()
_doc.delete() _doc.delete()

View File

@@ -351,5 +351,6 @@ erpnext.patches.v14_0.rename_depreciation_amount_based_on_num_days_in_month_to_d
erpnext.patches.v14_0.add_default_for_repost_settings erpnext.patches.v14_0.add_default_for_repost_settings
erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
erpnext.patches.v14_0.update_zero_asset_quantity_field erpnext.patches.v14_0.update_zero_asset_quantity_field
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
# below migration patch should always run last # below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.patches.v14_0.migrate_gl_to_payment_ledger

View File

@@ -21,6 +21,9 @@ def execute():
params = set({x.casefold(): x for x in params}.values()) params = set({x.casefold(): x for x in params}.values())
for parameter in params: for parameter in params:
if frappe.db.exists("Quality Inspection Parameter", parameter):
continue
frappe.get_doc( frappe.get_doc(
{"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter} {"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
).insert(ignore_permissions=True) ).insert(ignore_permissions=True)

View File

@@ -68,6 +68,10 @@ frappe.ui.form.on("Project", {
frm.events.create_duplicate(frm); frm.events.create_duplicate(frm);
}, __("Actions")); }, __("Actions"));
frm.add_custom_button(__('Update Total Purchase Cost'), () => {
frm.events.update_total_purchase_cost(frm);
}, __("Actions"));
frm.trigger("set_project_status_button"); frm.trigger("set_project_status_button");
@@ -92,6 +96,22 @@ frappe.ui.form.on("Project", {
}, },
update_total_purchase_cost: function(frm) {
frappe.call({
method: "erpnext.projects.doctype.project.project.recalculate_project_total_purchase_cost",
args: {project: frm.doc.name},
freeze: true,
freeze_message: __('Recalculating Purchase Cost against this Project...'),
callback: function(r) {
if (r && !r.exc) {
frappe.msgprint(__('Total Purchase Cost has been updated'));
frm.refresh();
}
}
});
},
set_project_status_button: function(frm) { set_project_status_button: function(frm) {
frm.add_custom_button(__('Set Project Status'), () => { frm.add_custom_button(__('Set Project Status'), () => {
let d = new frappe.ui.Dialog({ let d = new frappe.ui.Dialog({

View File

@@ -4,9 +4,10 @@
import frappe import frappe
from email_reply_parser import EmailReplyParser from email_reply_parser import EmailReplyParser
from frappe import _ from frappe import _, qb
from frappe.desk.reportview import get_match_cond from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
from erpnext import get_default_company from erpnext import get_default_company
@@ -246,12 +247,7 @@ class Project(Document):
self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) * 100 self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) * 100
def update_purchase_costing(self): def update_purchase_costing(self):
total_purchase_cost = frappe.db.sql( total_purchase_cost = calculate_total_purchase_cost(self.name)
"""select sum(base_net_amount)
from `tabPurchase Invoice Item` where project = %s and docstatus=1""",
self.name,
)
self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0 self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0
def update_sales_amount(self): def update_sales_amount(self):
@@ -671,3 +667,29 @@ def get_holiday_list(company=None):
def get_users_email(doc): def get_users_email(doc):
return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")] return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")]
def calculate_total_purchase_cost(project: str | None = None):
if project:
pitem = qb.DocType("Purchase Invoice Item")
frappe.qb.DocType("Purchase Invoice Item")
total_purchase_cost = (
qb.from_(pitem)
.select(Sum(pitem.base_net_amount))
.where((pitem.project == project) & (pitem.docstatus == 1))
.run(as_list=True)
)
return total_purchase_cost
return None
@frappe.whitelist()
def recalculate_project_total_purchase_cost(project: str | None = None):
if project:
total_purchase_cost = calculate_total_purchase_cost(project)
frappe.db.set_value(
"Project",
project,
"total_purchase_cost",
(total_purchase_cost and total_purchase_cost[0][0] or 0),
)

View File

@@ -1,6 +1,6 @@
frappe.provide('erpnext.accounts'); frappe.provide('erpnext.accounts');
erpnext.accounts.unreconcile_payments = { erpnext.accounts.unreconcile_payment = {
add_unreconcile_btn(frm) { add_unreconcile_btn(frm) {
if (frm.doc.docstatus == 1) { if (frm.doc.docstatus == 1) {
if(((frm.doc.doctype == "Journal Entry") && (frm.doc.voucher_type != "Journal Entry")) if(((frm.doc.doctype == "Journal Entry") && (frm.doc.voucher_type != "Journal Entry"))
@@ -10,7 +10,7 @@ erpnext.accounts.unreconcile_payments = {
} }
frappe.call({ frappe.call({
"method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.doc_has_references", "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.doc_has_references",
"args": { "args": {
"doctype": frm.doc.doctype, "doctype": frm.doc.doctype,
"docname": frm.doc.name "docname": frm.doc.name
@@ -18,7 +18,7 @@ erpnext.accounts.unreconcile_payments = {
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
frm.add_custom_button(__("UnReconcile"), function() { frm.add_custom_button(__("UnReconcile"), function() {
erpnext.accounts.unreconcile_payments.build_unreconcile_dialog(frm); erpnext.accounts.unreconcile_payment.build_unreconcile_dialog(frm);
}, __('Actions')); }, __('Actions'));
} }
} }
@@ -74,7 +74,7 @@ erpnext.accounts.unreconcile_payments = {
// get linked payments // get linked payments
frappe.call({ frappe.call({
"method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.get_linked_payments_for_doc", "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.get_linked_payments_for_doc",
"args": { "args": {
"company": frm.doc.company, "company": frm.doc.company,
"doctype": frm.doc.doctype, "doctype": frm.doc.doctype,
@@ -96,8 +96,8 @@ erpnext.accounts.unreconcile_payments = {
let selected_allocations = values.allocations.filter(x=>x.__checked); let selected_allocations = values.allocations.filter(x=>x.__checked);
if (selected_allocations.length > 0) { if (selected_allocations.length > 0) {
let selection_map = erpnext.accounts.unreconcile_payments.build_selection_map(frm, selected_allocations); let selection_map = erpnext.accounts.unreconcile_payment.build_selection_map(frm, selected_allocations);
erpnext.accounts.unreconcile_payments.create_unreconcile_docs(selection_map); erpnext.accounts.unreconcile_payment.create_unreconcile_docs(selection_map);
d.hide(); d.hide();
} else { } else {
@@ -115,7 +115,7 @@ erpnext.accounts.unreconcile_payments = {
create_unreconcile_docs(selection_map) { create_unreconcile_docs(selection_map) {
frappe.call({ frappe.call({
"method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.create_unreconcile_doc_for_selection", "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.create_unreconcile_doc_for_selection",
"args": { "args": {
"selections": selection_map "selections": selection_map
}, },