192 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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")
 |