2022-05-16 09:03:28 +00:00
|
|
|
import frappe
|
|
|
|
from frappe import qb
|
2023-03-09 14:12:28 +00:00
|
|
|
from frappe.query_builder import CustomFunction
|
2022-05-24 06:24:30 +00:00
|
|
|
from frappe.query_builder.custom import ConstantColumn
|
2023-01-20 10:02:16 +00:00
|
|
|
from frappe.query_builder.functions import Count, IfNull
|
|
|
|
from frappe.utils import flt
|
2022-05-16 09:03:28 +00:00
|
|
|
|
|
|
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|
|
|
get_dimensions,
|
|
|
|
make_dimension_in_accounting_doctypes,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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"])
|
|
|
|
|
|
|
|
|
2023-03-09 14:12:28 +00:00
|
|
|
def generate_name_and_calculate_amount(gl_entries, start, receivable_accounts):
|
2023-01-20 10:02:16 +00:00
|
|
|
for index, entry in enumerate(gl_entries, 0):
|
|
|
|
entry.name = start + index
|
2023-03-09 14:12:28 +00:00
|
|
|
if entry.account in receivable_accounts:
|
|
|
|
entry.account_type = "Receivable"
|
|
|
|
entry.amount = entry.debit - entry.credit
|
|
|
|
entry.amount_in_account_currency = (
|
|
|
|
entry.debit_in_account_currency - entry.credit_in_account_currency
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
entry.account_type = "Payable"
|
|
|
|
entry.amount = entry.credit - entry.debit
|
|
|
|
entry.amount_in_account_currency = (
|
|
|
|
entry.credit_in_account_currency - entry.debit_in_account_currency
|
|
|
|
)
|
2022-05-24 06:24:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_columns():
|
|
|
|
columns = [
|
|
|
|
"name",
|
|
|
|
"creation",
|
|
|
|
"modified",
|
|
|
|
"modified_by",
|
|
|
|
"owner",
|
|
|
|
"docstatus",
|
|
|
|
"posting_date",
|
|
|
|
"account_type",
|
|
|
|
"account",
|
|
|
|
"party_type",
|
|
|
|
"party",
|
|
|
|
"voucher_type",
|
|
|
|
"voucher_no",
|
|
|
|
"against_voucher_type",
|
|
|
|
"against_voucher_no",
|
|
|
|
"amount",
|
|
|
|
"amount_in_account_currency",
|
|
|
|
"account_currency",
|
|
|
|
"company",
|
|
|
|
"cost_center",
|
|
|
|
"due_date",
|
|
|
|
"finance_book",
|
|
|
|
]
|
|
|
|
|
2023-03-10 10:19:53 +00:00
|
|
|
if frappe.db.has_column("Payment Ledger Entry", "remarks"):
|
|
|
|
columns.append("remarks")
|
|
|
|
|
2022-05-24 06:24:30 +00:00
|
|
|
dimensions_and_defaults = get_dimensions()
|
|
|
|
if dimensions_and_defaults:
|
|
|
|
for dimension in dimensions_and_defaults[0]:
|
|
|
|
columns.append(dimension.fieldname)
|
|
|
|
|
|
|
|
return columns
|
|
|
|
|
|
|
|
|
|
|
|
def build_insert_query():
|
|
|
|
ple = qb.DocType("Payment Ledger Entry")
|
|
|
|
columns = get_columns()
|
|
|
|
insert_query = qb.into(ple)
|
|
|
|
|
|
|
|
# build 'insert' columns in query
|
|
|
|
insert_query = insert_query.columns(tuple(columns))
|
|
|
|
|
|
|
|
return insert_query
|
|
|
|
|
|
|
|
|
|
|
|
def insert_chunk_into_payment_ledger(insert_query, gl_entries):
|
|
|
|
if gl_entries:
|
|
|
|
columns = get_columns()
|
|
|
|
|
|
|
|
# build tuple of data with same column order
|
|
|
|
for entry in gl_entries:
|
|
|
|
data = ()
|
|
|
|
for column in columns:
|
|
|
|
data += (entry[column],)
|
|
|
|
insert_query = insert_query.insert(data)
|
|
|
|
insert_query.run()
|
|
|
|
|
|
|
|
|
2022-05-16 09:03:28 +00:00
|
|
|
def execute():
|
2023-01-20 10:02:16 +00:00
|
|
|
"""
|
|
|
|
Description:
|
|
|
|
Migrate records from `tabGL Entry` to `tabPayment Ledger Entry`.
|
|
|
|
Patch is non-resumable. if patch failed or is terminatted abnormally, clear 'tabPayment Ledger Entry' table manually before re-running. Re-running is safe only during V13->V14 update.
|
|
|
|
|
|
|
|
Note: Post successful migration to V14, re-running is NOT-SAFE and SHOULD NOT be attempted.
|
|
|
|
"""
|
|
|
|
|
2022-05-24 06:24:30 +00:00
|
|
|
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
|
|
|
|
# create accounting dimension fields in Payment Ledger
|
|
|
|
create_accounting_dimension_fields()
|
|
|
|
|
|
|
|
gl = qb.DocType("GL Entry")
|
|
|
|
account = qb.DocType("Account")
|
2022-06-21 14:20:50 +00:00
|
|
|
ifelse = CustomFunction("IF", ["condition", "then", "else"])
|
2022-05-24 06:24:30 +00:00
|
|
|
|
2023-01-20 10:02:16 +00:00
|
|
|
# Get Records Count
|
2023-03-09 14:12:28 +00:00
|
|
|
relavant_accounts = (
|
2023-01-20 10:02:16 +00:00
|
|
|
qb.from_(account)
|
2023-03-09 14:12:28 +00:00
|
|
|
.select(account.name, account.account_type)
|
2023-01-20 10:02:16 +00:00
|
|
|
.where((account.account_type == "Receivable") | (account.account_type == "Payable"))
|
|
|
|
.orderby(account.name)
|
2023-03-09 14:12:28 +00:00
|
|
|
.run(as_dict=True)
|
2023-01-20 10:02:16 +00:00
|
|
|
)
|
2023-03-09 14:12:28 +00:00
|
|
|
|
|
|
|
receivable_accounts = [x.name for x in relavant_accounts if x.account_type == "Receivable"]
|
|
|
|
accounts = [x.name for x in relavant_accounts]
|
|
|
|
|
2023-01-20 10:02:16 +00:00
|
|
|
un_processed = (
|
2022-05-16 09:03:28 +00:00
|
|
|
qb.from_(gl)
|
2023-01-20 10:02:16 +00:00
|
|
|
.select(Count(gl.name))
|
|
|
|
.where((gl.is_cancelled == 0) & (gl.account.isin(accounts)))
|
|
|
|
.run()
|
|
|
|
)[0][0]
|
|
|
|
|
|
|
|
if un_processed:
|
|
|
|
print(f"Migrating {un_processed} GL Entries to Payment Ledger")
|
|
|
|
|
|
|
|
processed = 0
|
|
|
|
last_update_percent = 0
|
|
|
|
batch_size = 5000
|
|
|
|
last_name = None
|
|
|
|
|
|
|
|
while True:
|
|
|
|
if last_name:
|
2023-03-09 14:12:28 +00:00
|
|
|
where_clause = gl.name.gt(last_name) & gl.account.isin(accounts) & gl.is_cancelled == 0
|
2023-01-20 10:02:16 +00:00
|
|
|
else:
|
2023-03-09 14:12:28 +00:00
|
|
|
where_clause = gl.account.isin(accounts) & gl.is_cancelled == 0
|
2023-01-20 10:02:16 +00:00
|
|
|
|
|
|
|
gl_entries = (
|
|
|
|
qb.from_(gl)
|
|
|
|
.select(
|
|
|
|
gl.star,
|
|
|
|
ConstantColumn(1).as_("docstatus"),
|
|
|
|
IfNull(
|
|
|
|
ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
|
|
|
|
).as_("against_voucher_type"),
|
|
|
|
IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
|
|
|
|
"against_voucher_no"
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.where(where_clause)
|
|
|
|
.orderby(gl.name)
|
|
|
|
.limit(batch_size)
|
|
|
|
.run(as_dict=True)
|
2022-05-24 06:24:30 +00:00
|
|
|
)
|
|
|
|
|
2023-01-20 10:02:16 +00:00
|
|
|
if gl_entries:
|
|
|
|
last_name = gl_entries[-1].name
|
|
|
|
|
2023-03-09 14:12:28 +00:00
|
|
|
# add primary key(name) and calculate based on debit and credit
|
|
|
|
generate_name_and_calculate_amount(gl_entries, processed, receivable_accounts)
|
2023-01-20 10:02:16 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
insert_query = build_insert_query()
|
|
|
|
insert_chunk_into_payment_ledger(insert_query, gl_entries)
|
|
|
|
frappe.db.commit()
|
|
|
|
|
|
|
|
processed += len(gl_entries)
|
|
|
|
|
|
|
|
# Progress message
|
|
|
|
percent = flt((processed / un_processed) * 100, 2)
|
|
|
|
if percent - last_update_percent > 1:
|
|
|
|
print(f"{percent}% ({processed}) records processed")
|
|
|
|
last_update_percent = percent
|
|
|
|
|
|
|
|
except Exception as err:
|
|
|
|
print("Migration Failed. Clear `tabPayment Ledger Entry` table before re-running")
|
|
|
|
raise err
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
print(f"{processed} records have been sucessfully migrated")
|