Merge branch 'develop' into subcontracting

This commit is contained in:
Sagar Sharma 2022-05-21 10:02:47 +05:30 committed by GitHub
commit 07dc5f180d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 779 additions and 7 deletions

View File

@ -94,7 +94,7 @@ class JournalEntry(AccountsController):
unlink_ref_doc_from_payment_entries(self) unlink_ref_doc_from_payment_entries(self)
unlink_ref_doc_from_salary_slip(self.name) unlink_ref_doc_from_salary_slip(self.name)
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.make_gl_entries(1) self.make_gl_entries(1)
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()

View File

@ -95,7 +95,7 @@ class PaymentEntry(AccountsController):
self.set_status() self.set_status()
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.update_expense_claim() self.update_expense_claim()
self.update_outstanding_amounts() self.update_outstanding_amounts()

View File

@ -0,0 +1,8 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Payment Ledger Entry', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,180 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:PLE-{YY}-{MM}-{######}",
"creation": "2022-05-09 19:35:03.334361",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"posting_date",
"company",
"account_type",
"account",
"party_type",
"party",
"due_date",
"cost_center",
"finance_book",
"voucher_type",
"voucher_no",
"against_voucher_type",
"against_voucher_no",
"amount",
"account_currency",
"amount_in_account_currency",
"delinked"
],
"fields": [
{
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date"
},
{
"fieldname": "account_type",
"fieldtype": "Select",
"label": "Account Type",
"options": "Receivable\nPayable"
},
{
"fieldname": "account",
"fieldtype": "Link",
"label": "Account",
"options": "Account"
},
{
"fieldname": "party_type",
"fieldtype": "Link",
"label": "Party Type",
"options": "DocType"
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"label": "Party",
"options": "party_type"
},
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Voucher Type",
"options": "DocType"
},
{
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Voucher No",
"options": "voucher_type"
},
{
"fieldname": "against_voucher_type",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Against Voucher Type",
"options": "DocType"
},
{
"fieldname": "against_voucher_no",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Against Voucher No",
"options": "against_voucher_type"
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "Company:company:default_currency"
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
},
{
"fieldname": "amount_in_account_currency",
"fieldtype": "Currency",
"label": "Amount in Account Currency",
"options": "account_currency"
},
{
"default": "0",
"fieldname": "delinked",
"fieldtype": "Check",
"in_list_view": 1,
"label": "DeLinked"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "due_date",
"fieldtype": "Date",
"label": "Due Date"
},
{
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-05-19 18:04:44.609115",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Auditor",
"share": 1
}
],
"search_fields": "voucher_no, against_voucher_no",
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,22 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
class PaymentLedgerEntry(Document):
def validate_account(self):
valid_account = frappe.db.get_list(
"Account",
"name",
filters={"name": self.account, "account_type": self.account_type, "company": self.company},
ignore_permissions=True,
)
if not valid_account:
frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type))
def validate(self):
self.validate_account()

View File

@ -0,0 +1,408 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.item.test_item import create_item
class TestPaymentLedgerEntry(FrappeTestCase):
def setUp(self):
self.ple = qb.DocType("Payment Ledger Entry")
self.create_company()
self.create_item()
self.create_customer()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def create_company(self):
company_name = "_Test Payment Ledger"
company = None
if frappe.db.exists("Company", company_name):
company = frappe.get_doc("Company", company_name)
else:
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": company_name,
"country": "India",
"default_currency": "INR",
"create_chart_of_accounts_based_on": "Standard Template",
"chart_of_accounts": "Standard",
}
)
company = company.save()
self.company = company.name
self.cost_center = company.cost_center
self.warehouse = "All Warehouses - _PL"
self.income_account = "Sales - _PL"
self.expense_account = "Cost of Goods Sold - _PL"
self.debit_to = "Debtors - _PL"
self.creditors = "Creditors - _PL"
# create bank account
if frappe.db.exists("Account", "HDFC - _PL"):
self.bank = "HDFC - _PL"
else:
bank_acc = frappe.get_doc(
{
"doctype": "Account",
"account_name": "HDFC",
"parent_account": "Bank Accounts - _PL",
"company": self.company,
}
)
bank_acc.save()
self.bank = bank_acc.name
def create_item(self):
item_name = "_Test PL Item"
item = create_item(
item_code=item_name, is_stock_item=0, company=self.company, warehouse=self.warehouse
)
self.item = item if isinstance(item, str) else item.item_code
def create_customer(self):
name = "_Test PL Customer"
if frappe.db.exists("Customer", name):
self.customer = name
else:
customer = frappe.new_doc("Customer")
customer.customer_name = name
customer.type = "Individual"
customer.save()
self.customer = customer.name
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
sinv = create_sales_invoice(
qty=qty,
rate=rate,
company=self.company,
customer=self.customer,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return sinv
def create_payment_entry(self, amount=100, posting_date=nowdate()):
"""
Helper function to populate default values in payment entry
"""
payment = create_payment_entry(
company=self.company,
payment_type="Receive",
party_type="Customer",
party=self.customer,
paid_from=self.debit_to,
paid_to=self.bank,
paid_amount=amount,
)
payment.posting_date = posting_date
return payment
def clear_old_entries(self):
doctype_list = [
"GL Entry",
"Payment Ledger Entry",
"Sales Invoice",
"Purchase Invoice",
"Payment Entry",
"Journal Entry",
]
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def create_journal_entry(
self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None
):
je = frappe.new_doc("Journal Entry")
je.posting_date = posting_date or nowdate()
je.company = self.company
je.user_remark = "test"
if not cost_center:
cost_center = self.cost_center
je.set(
"accounts",
[
{
"account": acc1,
"cost_center": cost_center,
"debit_in_account_currency": amount if amount > 0 else 0,
"credit_in_account_currency": abs(amount) if amount < 0 else 0,
},
{
"account": acc2,
"cost_center": cost_center,
"credit_in_account_currency": amount if amount > 0 else 0,
"debit_in_account_currency": abs(amount) if amount < 0 else 0,
},
],
)
return je
def test_payment_against_invoice(self):
transaction_date = nowdate()
amount = 100
ple = self.ple
# full payment using PE
si1 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
pe1 = get_payment_entry(si1.doctype, si1.name).save().submit()
pl_entries = (
qb.from_(ple)
.select(
ple.voucher_type,
ple.voucher_no,
ple.against_voucher_type,
ple.against_voucher_no,
ple.amount,
ple.delinked,
)
.where((ple.against_voucher_type == si1.doctype) & (ple.against_voucher_no == si1.name))
.orderby(ple.creation)
.run(as_dict=True)
)
expected_values = [
{
"voucher_type": si1.doctype,
"voucher_no": si1.name,
"against_voucher_type": si1.doctype,
"against_voucher_no": si1.name,
"amount": amount,
"delinked": 0,
},
{
"voucher_type": pe1.doctype,
"voucher_no": pe1.name,
"against_voucher_type": si1.doctype,
"against_voucher_no": si1.name,
"amount": -amount,
"delinked": 0,
},
]
self.assertEqual(pl_entries[0], expected_values[0])
self.assertEqual(pl_entries[1], expected_values[1])
def test_partial_payment_against_invoice(self):
ple = self.ple
transaction_date = nowdate()
amount = 100
# partial payment of invoice using PE
si2 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
pe2 = get_payment_entry(si2.doctype, si2.name)
pe2.get("references")[0].allocated_amount = 50
pe2.get("references")[0].outstanding_amount = 50
pe2 = pe2.save().submit()
pl_entries = (
qb.from_(ple)
.select(
ple.voucher_type,
ple.voucher_no,
ple.against_voucher_type,
ple.against_voucher_no,
ple.amount,
ple.delinked,
)
.where((ple.against_voucher_type == si2.doctype) & (ple.against_voucher_no == si2.name))
.orderby(ple.creation)
.run(as_dict=True)
)
expected_values = [
{
"voucher_type": si2.doctype,
"voucher_no": si2.name,
"against_voucher_type": si2.doctype,
"against_voucher_no": si2.name,
"amount": amount,
"delinked": 0,
},
{
"voucher_type": pe2.doctype,
"voucher_no": pe2.name,
"against_voucher_type": si2.doctype,
"against_voucher_no": si2.name,
"amount": -50,
"delinked": 0,
},
]
self.assertEqual(pl_entries[0], expected_values[0])
self.assertEqual(pl_entries[1], expected_values[1])
def test_cr_note_against_invoice(self):
ple = self.ple
transaction_date = nowdate()
amount = 100
# reconcile against return invoice
si3 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
cr_note1 = self.create_sales_invoice(
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
)
cr_note1.is_return = 1
cr_note1.return_against = si3.name
cr_note1 = cr_note1.save().submit()
pl_entries = (
qb.from_(ple)
.select(
ple.voucher_type,
ple.voucher_no,
ple.against_voucher_type,
ple.against_voucher_no,
ple.amount,
ple.delinked,
)
.where((ple.against_voucher_type == si3.doctype) & (ple.against_voucher_no == si3.name))
.orderby(ple.creation)
.run(as_dict=True)
)
expected_values = [
{
"voucher_type": si3.doctype,
"voucher_no": si3.name,
"against_voucher_type": si3.doctype,
"against_voucher_no": si3.name,
"amount": amount,
"delinked": 0,
},
{
"voucher_type": cr_note1.doctype,
"voucher_no": cr_note1.name,
"against_voucher_type": si3.doctype,
"against_voucher_no": si3.name,
"amount": -amount,
"delinked": 0,
},
]
self.assertEqual(pl_entries[0], expected_values[0])
self.assertEqual(pl_entries[1], expected_values[1])
def test_je_against_inv_and_note(self):
ple = self.ple
transaction_date = nowdate()
amount = 100
# reconcile against return invoice using JE
si4 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
cr_note2 = self.create_sales_invoice(
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
)
cr_note2.is_return = 1
cr_note2 = cr_note2.save().submit()
je1 = self.create_journal_entry(
self.debit_to, self.debit_to, amount, posting_date=transaction_date
)
je1.get("accounts")[0].party_type = je1.get("accounts")[1].party_type = "Customer"
je1.get("accounts")[0].party = je1.get("accounts")[1].party = self.customer
je1.get("accounts")[0].reference_type = cr_note2.doctype
je1.get("accounts")[0].reference_name = cr_note2.name
je1.get("accounts")[1].reference_type = si4.doctype
je1.get("accounts")[1].reference_name = si4.name
je1 = je1.save().submit()
pl_entries_for_invoice = (
qb.from_(ple)
.select(
ple.voucher_type,
ple.voucher_no,
ple.against_voucher_type,
ple.against_voucher_no,
ple.amount,
ple.delinked,
)
.where((ple.against_voucher_type == si4.doctype) & (ple.against_voucher_no == si4.name))
.orderby(ple.creation)
.run(as_dict=True)
)
expected_values = [
{
"voucher_type": si4.doctype,
"voucher_no": si4.name,
"against_voucher_type": si4.doctype,
"against_voucher_no": si4.name,
"amount": amount,
"delinked": 0,
},
{
"voucher_type": je1.doctype,
"voucher_no": je1.name,
"against_voucher_type": si4.doctype,
"against_voucher_no": si4.name,
"amount": -amount,
"delinked": 0,
},
]
self.assertEqual(pl_entries_for_invoice[0], expected_values[0])
self.assertEqual(pl_entries_for_invoice[1], expected_values[1])
pl_entries_for_crnote = (
qb.from_(ple)
.select(
ple.voucher_type,
ple.voucher_no,
ple.against_voucher_type,
ple.against_voucher_no,
ple.amount,
ple.delinked,
)
.where(
(ple.against_voucher_type == cr_note2.doctype) & (ple.against_voucher_no == cr_note2.name)
)
.orderby(ple.creation)
.run(as_dict=True)
)
expected_values = [
{
"voucher_type": cr_note2.doctype,
"voucher_no": cr_note2.name,
"against_voucher_type": cr_note2.doctype,
"against_voucher_no": cr_note2.name,
"amount": -amount,
"delinked": 0,
},
{
"voucher_type": je1.doctype,
"voucher_no": je1.name,
"against_voucher_type": cr_note2.doctype,
"against_voucher_no": cr_note2.name,
"amount": amount,
"delinked": 0,
},
]
self.assertEqual(pl_entries_for_crnote[0], expected_values[0])
self.assertEqual(pl_entries_for_crnote[1], expected_values[1])

View File

@ -96,6 +96,7 @@ class POSInvoice(SalesInvoice):
) )
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = "Payment Ledger Entry"
# run on cancel method of selling controller # run on cancel method of selling controller
super(SalesInvoice, self).on_cancel() super(SalesInvoice, self).on_cancel()
if not self.is_return and self.loyalty_program: if not self.is_return and self.loyalty_program:

View File

@ -1416,7 +1416,12 @@ class PurchaseInvoice(BuyingController):
frappe.db.set(self, "status", "Cancelled") frappe.db.set(self, "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)
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Payment Ledger Entry",
)
self.update_advance_tax_references(cancel=1) self.update_advance_tax_references(cancel=1)
def update_project(self): def update_project(self):

View File

@ -396,7 +396,12 @@ class SalesInvoice(SellingController):
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)
self.unlink_sales_invoice_from_timesheets() self.unlink_sales_invoice_from_timesheets()
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Payment Ledger Entry",
)
def update_status_updater_args(self): def update_status_updater_args(self):
if cint(self.update_stock): if cint(self.update_stock):

View File

@ -14,6 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions, get_accounting_dimensions,
) )
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.utils import create_payment_ledger_entry
class ClosedAccountingPeriod(frappe.ValidationError): class ClosedAccountingPeriod(frappe.ValidationError):
@ -34,6 +35,7 @@ def make_gl_entries(
validate_disabled_accounts(gl_map) validate_disabled_accounts(gl_map)
gl_map = process_gl_map(gl_map, merge_entries) gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1: if gl_map and len(gl_map) > 1:
create_payment_ledger_entry(gl_map)
save_entries(gl_map, adv_adj, update_outstanding, from_repost) save_entries(gl_map, adv_adj, update_outstanding, from_repost)
# Post GL Map proccess there may no be any GL Entries # Post GL Map proccess there may no be any GL Entries
elif gl_map: elif gl_map:
@ -479,6 +481,7 @@ def make_reverse_gl_entries(
).run(as_dict=1) ).run(as_dict=1)
if gl_entries: if gl_entries:
create_payment_ledger_entry(gl_entries, cancel=1)
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)
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])

View File

@ -7,7 +7,7 @@ from typing import List, Tuple
import frappe import frappe
import frappe.defaults import frappe.defaults
from frappe import _, throw from frappe import _, qb, throw
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
@ -15,6 +15,7 @@ import erpnext
# imported to enable erpnext.accounts.utils.get_account_currency # imported to enable erpnext.accounts.utils.get_account_currency
from erpnext.accounts.doctype.account.account import get_account_currency # noqa from erpnext.accounts.doctype.account.account import get_account_currency # noqa
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.stock import get_warehouse_account_map from erpnext.stock import get_warehouse_account_map
from erpnext.stock.utils import get_stock_value_on from erpnext.stock.utils import get_stock_value_on
@ -1345,3 +1346,102 @@ def check_and_delete_linked_reports(report):
if icons: if icons:
for icon in icons: for icon in icons:
frappe.delete_doc("Desktop Icon", icon) frappe.delete_doc("Desktop Icon", icon)
def create_payment_ledger_entry(gl_entries, cancel=0):
if gl_entries:
ple = None
# companies
account = qb.DocType("Account")
companies = list(set([x.company for x in gl_entries]))
# receivable/payable account
accounts_with_types = (
qb.from_(account)
.select(account.name, account.account_type)
.where(
(account.account_type.isin(["Receivable", "Payable"]) & (account.company.isin(companies)))
)
.run(as_dict=True)
)
receivable_or_payable_accounts = [y.name for y in accounts_with_types]
def get_account_type(account):
for entry in accounts_with_types:
if entry.name == account:
return entry.account_type
dr_or_cr = 0
account_type = None
for gle in gl_entries:
if gle.account in receivable_or_payable_accounts:
account_type = get_account_type(gle.account)
if account_type == "Receivable":
dr_or_cr = gle.debit - gle.credit
dr_or_cr_account_currency = gle.debit_in_account_currency - gle.credit_in_account_currency
elif account_type == "Payable":
dr_or_cr = gle.credit - gle.debit
dr_or_cr_account_currency = gle.credit_in_account_currency - gle.debit_in_account_currency
if cancel:
dr_or_cr *= -1
dr_or_cr_account_currency *= -1
ple = frappe.get_doc(
{
"doctype": "Payment Ledger Entry",
"posting_date": gle.posting_date,
"company": gle.company,
"account_type": account_type,
"account": gle.account,
"party_type": gle.party_type,
"party": gle.party,
"cost_center": gle.cost_center,
"finance_book": gle.finance_book,
"due_date": gle.due_date,
"voucher_type": gle.voucher_type,
"voucher_no": gle.voucher_no,
"against_voucher_type": gle.against_voucher_type
if gle.against_voucher_type
else gle.voucher_type,
"against_voucher_no": gle.against_voucher if gle.against_voucher else gle.voucher_no,
"currency": gle.currency,
"amount": dr_or_cr,
"amount_in_account_currency": dr_or_cr_account_currency,
"delinked": True if cancel else False,
}
)
dimensions_and_defaults = get_dimensions()
if dimensions_and_defaults:
for dimension in dimensions_and_defaults[0]:
ple.set(dimension.fieldname, gle.get(dimension.fieldname))
if cancel:
delink_original_entry(ple)
ple.flags.ignore_permissions = 1
ple.submit()
def delink_original_entry(pl_entry):
if pl_entry:
ple = qb.DocType("Payment Ledger Entry")
query = (
qb.update(ple)
.set(ple.delinked, True)
.set(ple.modified, now())
.set(ple.modified_by, frappe.session.user)
.where(
(ple.company == pl_entry.company)
& (ple.account_type == pl_entry.account_type)
& (ple.account == pl_entry.account)
& (ple.party_type == pl_entry.party_type)
& (ple.party == pl_entry.party)
& (ple.voucher_type == pl_entry.voucher_type)
& (ple.voucher_no == pl_entry.voucher_no)
& (ple.against_voucher_type == pl_entry.against_voucher_type)
& (ple.against_voucher_no == pl_entry.against_voucher_no)
)
)
query.run()

View File

@ -328,6 +328,7 @@ class PurchaseOrder(BuyingController):
update_linked_doc(self.doctype, self.name, self.inter_company_order_reference) update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = "Payment Ledger Entry"
super(PurchaseOrder, self).on_cancel() super(PurchaseOrder, self).on_cancel()
if self.is_against_so(): if self.is_against_so():

View File

@ -487,6 +487,7 @@ communication_doctypes = ["Customer", "Supplier"]
accounting_dimension_doctypes = [ accounting_dimension_doctypes = [
"GL Entry", "GL Entry",
"Payment Ledger Entry",
"Sales Invoice", "Sales Invoice",
"Purchase Invoice", "Purchase Invoice",
"Payment Entry", "Payment Entry",

View File

@ -105,7 +105,7 @@ class ExpenseClaim(AccountsController):
def on_cancel(self): def on_cancel(self):
self.update_task_and_project() self.update_task_and_project()
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
if self.payable_account: if self.payable_account:
self.make_gl_entries(cancel=True) self.make_gl_entries(cancel=True)

View File

@ -0,0 +1,38 @@
import frappe
from frappe import qb
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_dimensions,
make_dimension_in_accounting_doctypes,
)
from erpnext.accounts.utils import create_payment_ledger_entry
def create_accounting_dimension_fields():
dimensions_and_defaults = get_dimensions()
if dimensions_and_defaults:
for dimension in dimensions_and_defaults[0]:
make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
def execute():
# create accounting dimension fields in Payment Ledger
create_accounting_dimension_fields()
gl = qb.DocType("GL Entry")
accounts = frappe.db.get_list(
"Account", "name", filters={"account_type": ["in", ["Receivable", "Payable"]]}, as_list=True
)
gl_entries = []
if accounts:
# get all gl entries on receivable/payable accounts
gl_entries = (
qb.from_(gl)
.select("*")
.where(gl.account.isin(accounts))
.where(gl.is_cancelled == 0)
.run(as_dict=True)
)
if gl_entries:
# create payment ledger entries for the accounts receivable/payable
create_payment_ledger_entry(gl_entries, 0)

View File

@ -232,7 +232,7 @@ class SalesOrder(SellingController):
update_coupon_code_count(self.coupon_code, "used") update_coupon_code_count(self.coupon_code, "used")
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
super(SalesOrder, self).on_cancel() super(SalesOrder, self).on_cancel()
# Cannot cancel closed SO # Cannot cancel closed SO