import frappe from frappe import qb from frappe.query_builder import CustomFunction from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Count, IfNull from frappe.utils import flt 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"]) def generate_name_and_calculate_amount(gl_entries, start, receivable_accounts): for index, entry in enumerate(gl_entries, 0): entry.name = start + index 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 ) 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", ] if frappe.db.has_column("Payment Ledger Entry", "remarks"): columns.append("remarks") 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() def execute(): """ 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. """ 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") ifelse = CustomFunction("IF", ["condition", "then", "else"]) # Get Records Count relavant_accounts = ( qb.from_(account) .select(account.name, account.account_type) .where((account.account_type == "Receivable") | (account.account_type == "Payable")) .orderby(account.name) .run(as_dict=True) ) receivable_accounts = [x.name for x in relavant_accounts if x.account_type == "Receivable"] accounts = [x.name for x in relavant_accounts] un_processed = ( qb.from_(gl) .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: where_clause = gl.name.gt(last_name) & gl.account.isin(accounts) & gl.is_cancelled == 0 else: where_clause = gl.account.isin(accounts) & gl.is_cancelled == 0 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) ) if gl_entries: last_name = gl_entries[-1].name # add primary key(name) and calculate based on debit and credit generate_name_and_calculate_amount(gl_entries, processed, receivable_accounts) 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")