# -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe and contributors # For license information, please see license.txt from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils import cint,cstr, today from frappe import _ import re import datetime from collections import OrderedDict def create_bank_remittance_txt(name): payment_order = frappe.get_cached_doc("Payment Order", name) no_of_records = len(payment_order.get("references")) total_amount = sum(entry.get("amount") for entry in payment_order.get("references")) product_code, client_code, company_email = frappe.db.get_value("Company", filters={'name' : payment_order.company}, fieldname=['product_code', 'client_code', 'email']) header, file_name = get_header_row(payment_order, client_code) batch = get_batch_row(payment_order, no_of_records, total_amount, product_code) detail = [] for ref_doc in payment_order.get("references"): detail += get_detail_row(ref_doc, payment_order, company_email) trailer = get_trailer_row(no_of_records, total_amount) detail_records = "\n".join(detail) return "\n".join([header, batch, detail_records, trailer]), file_name @frappe.whitelist() def generate_report(name): data, file_name = create_bank_remittance_txt(name) f = frappe.get_doc({ 'doctype': 'File', 'file_name': file_name, 'content': data, "attached_to_doctype": 'Payment Order', "attached_to_name": name, 'is_private': True }) f.save() return { 'file_url': f.file_url, 'file_name': file_name } def generate_file_name(name, company_account, date): ''' generate file name with format (account_code)_mmdd_(payment_order_no) ''' bank, acc_no = frappe.db.get_value("Bank Account", {"name": company_account}, ['bank', 'bank_account_no']) return bank[:1]+str(acc_no)[-4:]+'_'+date.strftime("%m%d")+sanitize_data(name, '')[4:]+'.txt' def get_header_row(doc, client_code): ''' Returns header row and generated file name ''' file_name = generate_file_name(doc.name, doc.company_bank_account, doc.posting_date) header = ["H"] header.append(validate_field_size(client_code, "Client Code", 20)) header += [''] * 3 header.append(validate_field_size(file_name, "File Name", 20)) return "~".join(header), file_name def get_batch_row(doc, no_of_records, total_amount, product_code): batch = ["B"] batch.append(validate_field_size(no_of_records, "No Of Records", 5)) batch.append(validate_amount(format(total_amount, '0.2f'), 17)) batch.append(sanitize_data(doc.name, '_')[:20]) batch.append(format_date(doc.posting_date)) batch.append(validate_field_size(product_code,"Product Code", 20)) return "~".join(batch) def get_detail_row(ref_doc, payment_entry, company_email): payment_date = format_date(payment_entry.posting_date) payment_entry = frappe.get_cached_doc('Payment Entry', ref_doc.payment_entry) supplier_bank_details = frappe.get_cached_doc('Bank Account', ref_doc.bank_account) company_bank_acc_no = frappe.db.get_value("Bank Account", {'name': payment_entry.bank_account}, ['bank_account_no']) addr_link = frappe.db.get_value('Dynamic Link', { 'link_doctype': 'Supplier', 'link_name': 'Sample Supplier', 'parenttype':'Address', 'parent': ('like', '%-Billing') }, 'parent') supplier_billing_address = frappe.get_cached_doc('Address', addr_link) email = ','.join(filter(None, [supplier_billing_address.email_id, company_email])) detail = OrderedDict( record_identifier='D', payment_ref_no=sanitize_data(ref_doc.payment_entry), payment_type=cstr(payment_entry.mode_of_payment)[:10], amount=str(validate_amount(format(ref_doc.amount, '.2f'),13)), payment_date=payment_date, instrument_date=payment_date, instrument_number='', dr_account_no_client=str(validate_field_size(company_bank_acc_no, "Company Bank Account", 20)), dr_description='', dr_ref_no='', cr_ref_no='', bank_code_indicator='M', beneficiary_code='', beneficiary_name=sanitize_data(validate_information(payment_entry, "party", 160), ' '), beneficiary_bank=sanitize_data(validate_information(supplier_bank_details, "bank", 10)), beneficiary_branch_code=cstr(validate_information(supplier_bank_details, "branch_code", 11)), beneficiary_acc_no=validate_information(supplier_bank_details, "bank_account_no", 20), location='', print_location='', beneficiary_address_1=validate_field_size(sanitize_data(cstr(supplier_billing_address.address_line1), ' '), " Beneficiary Address 1", 50), beneficiary_address_2=validate_field_size(sanitize_data(cstr(supplier_billing_address.address_line2), ' '), " Beneficiary Address 2", 50), beneficiary_address_3='', beneficiary_address_4='', beneficiary_address_5='', beneficiary_city=validate_field_size(cstr(supplier_billing_address.city), "Beneficiary City", 20), beneficiary_zipcode=validate_field_size(cstr(supplier_billing_address.pincode), "Pin Code", 6), beneficiary_state=validate_field_size(cstr(supplier_billing_address.state), "Beneficiary State", 20), beneficiary_email=cstr(email)[:255], beneficiary_mobile=validate_field_size(cstr(supplier_billing_address.phone), "Beneficiary Mobile", 10), payment_details_1='', payment_details_2='', payment_details_3='', payment_details_4='', delivery_mode='' ) detail_record = ["~".join(list(detail.values()))] detail_record += get_advice_rows(payment_entry) return detail_record def get_advice_rows(payment_entry): ''' Returns multiple advice rows for a single detail entry ''' payment_entry_date = payment_entry.posting_date.strftime("%b%y%d%m").upper() mode_of_payment = payment_entry.mode_of_payment advice_rows = [] for record in payment_entry.references: advice = ['E'] advice.append(cstr(mode_of_payment)) advice.append(cstr(record.total_amount)) advice.append('') advice.append(cstr(record.outstanding_amount)) advice.append(record.reference_name) advice.append(format_date(record.due_date)) advice.append(payment_entry_date) advice_rows.append("~".join(advice)) return advice_rows def get_trailer_row(no_of_records, total_amount): ''' Returns trailer row ''' trailer = ["T"] trailer.append(validate_field_size(no_of_records, "No of Records", 5)) trailer.append(validate_amount(format(total_amount, "0.2f"), 17)) return "~".join(trailer) def sanitize_data(val, replace_str=''): ''' Remove all the non-alphanumeric characters from string ''' pattern = re.compile('[\W_]+') return pattern.sub(replace_str, val) def format_date(val): ''' Convert a datetime object to DD/MM/YYYY format ''' return val.strftime("%d/%m/%Y") def validate_amount(val, max_int_size): ''' Validate amount to be within the allowed limits ''' int_size = len(str(val).split('.')[0]) if int_size > max_int_size: frappe.throw(_("Amount for a single transaction exceeds maximum allowed amount, create a separate payment order by splitting the transactions")) return val def validate_information(obj, attr, max_size): ''' Checks if the information is not set in the system and is within the size ''' if hasattr(obj, attr): return validate_field_size(getattr(obj, attr), frappe.unscrub(attr), max_size) else: frappe.throw(_("{0} is mandatory for generating remittance payments, set the field and try again".format(frappe.unscrub(attr)))) def validate_field_size(val, label, max_size): ''' check the size of the val ''' if len(cstr(val)) > max_size: frappe.throw(_("{0} field is limited to size {1}".format(label, max_size))) return cstr(val)