def update_events(self, doctype, doc, migration_id): try: event = self.gcalendar.events().get(calendarId=self.account.gcalendar_id, eventId=migration_id).execute() event = { 'summary': doc.summary, 'description': doc.description } if doc.event_type == "Cancel": event.update({"status": "cancelled"}) dates = self.return_dates(doc) event.update(dates) if doc.repeat_this_event != 0: recurrence = self.return_recurrence(doctype, doc) if recurrence: event.update({"recurrence": ["RRULE:" + str(recurrence)]}) try: updated_event = self.gcalendar.events().update(calendarId=self.account.gcalendar_id, eventId=migration_id, body=event).execute() return {self.name_field: updated_event["id"]} except Exception as e: frappe.log_error(e, "GCalendar Synchronization Error") except HttpError as err: if err.resp.status in [404]: self.insert_events(doctype, doc, migration_id) else: frappe.log_error(err.resp, "GCalendar Synchronization Error")
def get_events(self, remote_objectname, filters, page_length): page_token = None results = [] events = {"items": []} while True: try: events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=self.account.next_sync_token or None).execute() except HttpError as err: if err.resp.status in [410]: events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime('%Y-%m-%dT%H:%M:%SZ')).execute() else: frappe.log_error(err.resp, "GCalendar Events Fetch Error") for event in events['items']: event.update({'account': self.account.name}) event.update({'calendar_tz': events['timeZone']}) results.append(event) page_token = events.get('nextPageToken') if not page_token: if events.get('nextSyncToken'): frappe.db.set_value("GCalendar Account", self.connector.username, "next_sync_token", events.get('nextSyncToken')) break return list(results)
def confirm_payment(token): try: custom_redirect_to = None data, params, url = get_paypal_and_transaction_details(token) params.update({ "METHOD": "DoExpressCheckoutPayment", "PAYERID": data.get("payerid"), "TOKEN": token, "PAYMENTREQUEST_0_PAYMENTACTION": "SALE", "PAYMENTREQUEST_0_AMT": data.get("amount"), "PAYMENTREQUEST_0_CURRENCYCODE": data.get("currency").upper() }) response = make_post_request(url, data=params) if response.get("ACK")[0] == "Success": update_integration_request_status(token, { "transaction_id": response.get("PAYMENTINFO_0_TRANSACTIONID")[0], "correlation_id": response.get("CORRELATIONID")[0] }, "Completed") if data.get("reference_doctype") and data.get("reference_docname"): custom_redirect_to = frappe.get_doc(data.get("reference_doctype"), data.get("reference_docname")).run_method("on_payment_authorized", "Completed") frappe.db.commit() redirect_url = '/integrations/payment-success?doctype={0}&docname={1}'.format(data.get("reference_doctype"), data.get("reference_docname")) else: redirect_url = "/integrations/payment-failed" setup_redirect(data, redirect_url, custom_redirect_to) except Exception: frappe.log_error(frappe.get_traceback())
def sync(): try: gcalendar_settings = frappe.get_doc('GCalendar Settings') if gcalendar_settings.enable == 1: gcalendar_settings.sync() except Exception: frappe.log_error(frappe.get_traceback())
def finalize_request(self): redirect_to = self.data.get('redirect_to') or None redirect_message = self.data.get('redirect_message') or None status = self.integration_request.status if self.flags.status_changed_to == "Completed": if self.data.reference_doctype and self.data.reference_docname: custom_redirect_to = None try: custom_redirect_to = frappe.get_doc(self.data.reference_doctype, self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) except Exception: frappe.log_error(frappe.get_traceback()) if custom_redirect_to: redirect_to = custom_redirect_to redirect_url = 'payment-success' if self.redirect_url: redirect_url = self.redirect_url redirect_to = None else: redirect_url = 'payment-failed' if redirect_to: redirect_url += '?' + urlencode({'redirect_to': redirect_to}) if redirect_message: redirect_url += '&' + urlencode({'redirect_message': redirect_message}) return { "redirect_to": redirect_url, "status": status }
def get_orders(after_date): try: orders = get_orders_instance() statuses = ["PartiallyShipped", "Unshipped", "Shipped", "Canceled"] mws_settings = frappe.get_doc("Amazon MWS Settings") market_place_list = return_as_list(mws_settings.market_place_id) orders_response = call_mws_method(orders.list_orders, marketplaceids=market_place_list, fulfillment_channels=["MFN", "AFN"], lastupdatedafter=after_date, orderstatus=statuses, max_results='50') while True: orders_list = [] if "Order" in orders_response.parsed.Orders: orders_list = return_as_list(orders_response.parsed.Orders.Order) if len(orders_list) == 0: break for order in orders_list: create_sales_order(order, after_date) if not "NextToken" in orders_response.parsed: break next_token = orders_response.parsed.NextToken orders_response = call_mws_method(orders.list_orders_by_next_token, next_token) except Exception as e: frappe.log_error(title="get_orders", message=e)
def capture_payment(is_sandbox=False, sanbox_response=None): """ Verifies the purchase as complete by the merchant. After capture, the amount is transferred to the merchant within T+3 days where T is the day on which payment is captured. Note: Attempting to capture a payment whose status is not authorized will produce an error. """ controller = frappe.get_doc("Razorpay Settings") for doc in frappe.get_all("Integration Request", filters={"status": "Authorized", "integration_request_service": "Razorpay"}, fields=["name", "data"]): try: if is_sandbox: resp = sanbox_response else: data = json.loads(doc.data) settings = controller.get_settings(data) resp = make_post_request("https://api.razorpay.com/v1/payments/{0}/capture".format(data.get("razorpay_payment_id")), auth=(settings.api_key, settings.api_secret), data={"amount": data.get("amount")}) if resp.get("status") == "captured": frappe.db.set_value("Integration Request", doc.name, "status", "Completed") except Exception: doc = frappe.get_doc("Integration Request", doc.name) doc.status = "Failed" doc.error = frappe.get_traceback() frappe.log_error(doc.error, '{0} Failed'.format(doc.name))
def run_background(prepared_report): instance = frappe.get_doc("Prepared Report", prepared_report) report = frappe.get_doc("Report", instance.ref_report_doctype) try: report.custom_columns = [] if report.report_type == 'Custom Report': custom_report_doc = report reference_report = custom_report_doc.reference_report report = frappe.get_doc("Report", reference_report) report.custom_columns = custom_report_doc.json result = generate_report_result(report, filters=instance.filters, user=instance.owner) create_json_gz_file(result['result'], 'Prepared Report', instance.name) instance.status = "Completed" instance.columns = json.dumps(result["columns"]) instance.report_end_time = frappe.utils.now() instance.save() except Exception: frappe.log_error(frappe.get_traceback()) instance = frappe.get_doc("Prepared Report", prepared_report) instance.status = "Error" instance.error_message = frappe.get_traceback() instance.save() frappe.publish_realtime( 'report_generated', {"report_name": instance.report_name, "name": instance.name}, user=frappe.session.user )
def publish_selected_items(items_to_publish): items_to_publish = json.loads(items_to_publish) if not len(items_to_publish): frappe.throw('No items to publish') for item in items_to_publish: item_code = item.get('item_code') frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) frappe.get_doc({ 'doctype': 'Hub Tracked Item', 'item_code': item_code, 'hub_category': item.get('hub_category'), 'image_list': item.get('image_list') }).insert(ignore_if_duplicate=True) items = map_fields(items_to_publish) try: item_sync_preprocess(len(items)) convert_relative_image_urls_to_absolute(items) # TODO: Publish Progress connection = get_hub_connection() connection.insert_many(items) item_sync_postprocess() except Exception as e: frappe.log_error(message=e, title='Hub Sync Error')
def send_slack_message(webhook_url, message, reference_doctype, reference_name): slack_url = frappe.db.get_value("Slack Webhook URL", webhook_url, "webhook_url") doc_url = get_url_to_form(reference_doctype, reference_name) attachments = [ { "fallback": _("See the document at {0}").format(doc_url), "actions": [ { "type": "button", "text": _("Go to the document"), "url": doc_url, "style": "primary" } ] } ] data = {"text": message, "attachments": attachments} r = requests.post(slack_url, data=json.dumps(data)) if r.ok == True: return 'success' elif r.ok == False: frappe.log_error(r.error, _('Slack Webhook Error')) return 'error'
def get_express_checkout_details(token): try: doc = frappe.get_doc("PayPal Settings") doc.setup_sandbox_env(token) params, url = doc.get_paypal_params_and_url() params.update({ "METHOD": "GetExpressCheckoutDetails", "TOKEN": token }) response = make_post_request(url, data=params) if response.get("ACK")[0] != "Success": frappe.respond_as_web_page(_("Something went wrong"), _("Looks like something went wrong during the transaction. Since we haven't confirmed the payment, Paypal will automatically refund you this amount. If it doesn't, please send us an email and mention the Correlation ID: {0}.").format(response.get("CORRELATIONID", [None])[0]), indicator_color='red', http_status_code=frappe.ValidationError.http_status_code) return update_integration_request_status(token, { "payerid": response.get("PAYERID")[0], "payer_email": response.get("EMAIL")[0] }, "Authorized") frappe.local.response["type"] = "redirect" frappe.local.response["location"] = get_url( \ "/api/method/frappe.integrations.doctype.paypal_settings.paypal_settings.confirm_payment?token={0}".format(token)) except Exception: frappe.log_error(frappe.get_traceback())
def setup_addon(self, settings, **kwargs): """ Addon template: { "item": { "name": row.upgrade_type, "amount": row.amount, "currency": currency, "description": "add-on description" }, "quantity": 1 (The total amount is calculated as item.amount * quantity) } """ url = "https://api.razorpay.com/v1/subscriptions/{0}/addons".format(kwargs.get('subscription_id')) try: if not frappe.conf.converted_rupee_to_paisa: convert_rupee_to_paisa(**kwargs) for addon in kwargs.get("addons"): resp = make_post_request( url, auth=(settings.api_key, settings.api_secret), data=json.dumps(addon), headers={ "content-type": "application/json" } ) if not resp.get('id'): frappe.log_error(str(resp), 'Razorpay Failed while creating subscription') except: frappe.log_error(frappe.get_traceback()) # failed pass
def create_bank_entries(columns, data, bank_account): header_map = get_header_mapping(columns, bank_account) success = 0 errors = 0 for d in json.loads(data): if all(item is None for item in d) is True: continue fields = {} for key, value in iteritems(header_map): fields.update({key: d[int(value)-1]}) try: bank_transaction = frappe.get_doc({ "doctype": "Bank Transaction" }) bank_transaction.update(fields) bank_transaction.date = getdate(parse_date(bank_transaction.date)) bank_transaction.bank_account = bank_account bank_transaction.insert() bank_transaction.submit() success += 1 except Exception: frappe.log_error(frappe.get_traceback()) errors += 1 return {"success": success, "errors": errors}
def check_remote_calendar(self): def _create_calendar(): timezone = frappe.db.get_value("System Settings", None, "time_zone") calendar = { 'summary': self.account.calendar_name, 'timeZone': timezone } try: created_calendar = self.gcalendar.calendars().insert(body=calendar).execute() frappe.db.set_value("GCalendar Account", self.account.name, "gcalendar_id", created_calendar["id"]) except Exception: frappe.log_error(frappe.get_traceback()) try: if self.account.gcalendar_id is not None: try: self.gcalendar.calendars().get(calendarId=self.account.gcalendar_id).execute() except Exception: frappe.log_error(frappe.get_traceback()) else: _create_calendar() except HttpError as err: if err.resp.status in [403, 500, 503]: time.sleep(5) elif err.resp.status in [404]: _create_calendar() else: raise
def upload_file_to_dropbox(filename, folder, dropbox_client): """upload files with chunk of 15 mb to reduce session append calls""" if not os.path.exists(filename): return create_folder_if_not_exists(folder, dropbox_client) chunk_size = 15 * 1024 * 1024 file_size = os.path.getsize(encode(filename)) mode = (dropbox.files.WriteMode.overwrite) f = open(encode(filename), 'rb') path = "{0}/{1}".format(folder, os.path.basename(filename)) try: if file_size <= chunk_size: dropbox_client.files_upload(f.read(), path, mode) else: upload_session_start_result = dropbox_client.files_upload_session_start(f.read(chunk_size)) cursor = dropbox.files.UploadSessionCursor(session_id=upload_session_start_result.session_id, offset=f.tell()) commit = dropbox.files.CommitInfo(path=path, mode=mode) while f.tell() < file_size: if ((file_size - f.tell()) <= chunk_size): dropbox_client.files_upload_session_finish(f.read(chunk_size), cursor, commit) else: dropbox_client.files_upload_session_append(f.read(chunk_size), cursor.session_id,cursor.offset) cursor.offset = f.tell() except dropbox.exceptions.ApiError as e: if isinstance(e.error, dropbox.files.UploadError): error = "File Path: {path}\n".format(path=path) error += frappe.get_traceback() frappe.log_error(error) else: raise
def create_charge_on_braintree(self): self.configure_braintree() redirect_to = self.data.get('redirect_to') or None redirect_message = self.data.get('redirect_message') or None result = braintree.Transaction.sale({ "amount": self.data.amount, "payment_method_nonce": self.data.payload_nonce, "options": { "submit_for_settlement": True } }) if result.is_success: self.integration_request.db_set('status', 'Completed', update_modified=False) self.flags.status_changed_to = "Completed" self.integration_request.db_set('output', result.transaction.status, update_modified=False) elif result.transaction: self.integration_request.db_set('status', 'Failed', update_modified=False) error_log = frappe.log_error("code: " + str(result.transaction.processor_response_code) + " | text: " + str(result.transaction.processor_response_text), "Braintree Payment Error") self.integration_request.db_set('error', error_log.error, update_modified=False) else: self.integration_request.db_set('status', 'Failed', update_modified=False) for error in result.errors.deep_errors: error_log = frappe.log_error("code: " + str(error.code) + " | message: " + str(error.message), "Braintree Payment Error") self.integration_request.db_set('error', error_log.error, update_modified=False) if self.flags.status_changed_to == "Completed": status = 'Completed' if self.data.reference_doctype and self.data.reference_docname: custom_redirect_to = None try: custom_redirect_to = frappe.get_doc(self.data.reference_doctype, self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) braintree_success_page = frappe.get_hooks('braintree_success_page') if braintree_success_page: custom_redirect_to = frappe.get_attr(braintree_success_page[-1])(self.data) except Exception: frappe.log_error(frappe.get_traceback()) if custom_redirect_to: redirect_to = custom_redirect_to redirect_url = 'payment-success' else: status = 'Error' redirect_url = 'payment-failed' if redirect_to: redirect_url += '?' + urlencode({'redirect_to': redirect_to}) if redirect_message: redirect_url += '&' + urlencode({'redirect_message': redirect_message}) return { "redirect_to": redirect_url, "status": status }
def get_email_queue(recipients, sender, subject, **kwargs): '''Make Email Queue object''' e = frappe.new_doc('Email Queue') e.priority = kwargs.get('send_priority') attachments = kwargs.get('attachments') if attachments: # store attachments with fid or print format details, to be attached on-demand later _attachments = [] for att in attachments: if att.get('fid'): _attachments.append(att) elif att.get("print_format_attachment") == 1: _attachments.append(att) e.attachments = json.dumps(_attachments) try: mail = get_email(recipients, sender=sender, subject=subject, formatted=kwargs.get('formatted'), text_content=kwargs.get('text_content'), attachments=kwargs.get('attachments'), reply_to=kwargs.get('reply_to'), cc=kwargs.get('cc'), bcc=kwargs.get('bcc'), email_account=kwargs.get('email_account'), expose_recipients=kwargs.get('expose_recipients'), inline_images=kwargs.get('inline_images'), header=kwargs.get('header')) mail.set_message_id(kwargs.get('message_id'),kwargs.get('is_notification')) if kwargs.get('read_receipt'): mail.msg_root["Disposition-Notification-To"] = sender if kwargs.get('in_reply_to'): mail.set_in_reply_to(kwargs.get('in_reply_to')) e.message_id = mail.msg_root["Message-Id"].strip(" <>") e.message = cstr(mail.as_string()) e.sender = mail.sender except frappe.InvalidEmailAddressError: # bad Email Address - don't add to queue frappe.log_error('Invalid Email ID Sender: {0}, Recipients: {1}'.format(mail.sender, ', '.join(mail.recipients)), 'Email Not Sent') e.set_recipients(recipients + kwargs.get('cc', []) + kwargs.get('bcc', [])) e.reference_doctype = kwargs.get('reference_doctype') e.reference_name = kwargs.get('reference_name') e.add_unsubscribe_link = kwargs.get("add_unsubscribe_link") e.unsubscribe_method = kwargs.get('unsubscribe_method') e.unsubscribe_params = kwargs.get('unsubscribe_params') e.expose_recipients = kwargs.get('expose_recipients') e.communication = kwargs.get('communication') e.send_after = kwargs.get('send_after') e.show_as_cc = ",".join(kwargs.get('cc', [])) e.show_as_bcc = ",".join(kwargs.get('bcc', [])) e.insert(ignore_permissions=True) return e
def get_access_token(self, public_token): if public_token is None: frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error")) response = self.client.Item.public_token.exchange(public_token) access_token = response['access_token'] return access_token
def create_recurring_profile(token, payerid): try: custom_redirect_to = None updating = False data, params, url = get_paypal_and_transaction_details(token) addons = data.get("addons") subscription_details = data.get("subscription_details") if data.get('subscription_id') and addons: updating = True manage_recurring_payment_profile_status(data['subscription_id'], 'Cancel', params, url) params.update({ "METHOD": "CreateRecurringPaymentsProfile", "PAYERID": payerid, "TOKEN": token, "DESC": data.get("description"), "BILLINGPERIOD": subscription_details.get("billing_period"), "BILLINGFREQUENCY": subscription_details.get("billing_frequency"), "AMT": data.get("amount") if data.get("subscription_amount") == data.get("amount") else data.get("subscription_amount"), "CURRENCYCODE": data.get("currency").upper(), "INITAMT": data.get("upfront_amount") }) status_changed_to = 'Completed' if data.get("starting_immediately") or updating else 'Verified' starts_at = get_datetime(subscription_details.get("start_date")) or frappe.utils.now_datetime() starts_at = starts_at.replace(tzinfo=pytz.timezone(frappe.utils.get_time_zone())).astimezone(pytz.utc) #"PROFILESTARTDATE": datetime.utcfromtimestamp(get_timestamp(starts_at)).isoformat() params.update({ "PROFILESTARTDATE": starts_at.isoformat() }) response = make_post_request(url, data=params) if response.get("ACK")[0] == "Success": update_integration_request_status(token, { "profile_id": response.get("PROFILEID")[0], }, "Completed") if data.get("reference_doctype") and data.get("reference_docname"): data['subscription_id'] = response.get("PROFILEID")[0] frappe.flags.data = data custom_redirect_to = frappe.get_doc(data.get("reference_doctype"), data.get("reference_docname")).run_method("on_payment_authorized", status_changed_to) frappe.db.commit() redirect_url = '/integrations/payment-success?doctype={0}&docname={1}'.format(data.get("reference_doctype"), data.get("reference_docname")) else: redirect_url = "/integrations/payment-failed" setup_redirect(data, redirect_url, custom_redirect_to) except Exception: frappe.log_error(frappe.get_traceback())
def cancel_subscription(self, subscription_id): settings = self.get_settings({}) try: resp = make_post_request("https://api.razorpay.com/v1/subscriptions/{0}/cancel" .format(subscription_id), auth=(settings.api_key, settings.api_secret)) except Exception: frappe.log_error(frappe.get_traceback())
def confirm_payment(token): try: redirect = True status_changed_to, redirect_to = None, None doc = frappe.get_doc("PayPal Settings") doc.setup_sandbox_env(token) integration_request = frappe.get_doc("Integration Request", token) data = json.loads(integration_request.data) redirect_to = data.get('redirect_to') or None redirect_message = data.get('redirect_message') or None params, url = doc.get_paypal_params_and_url() params.update({ "METHOD": "DoExpressCheckoutPayment", "PAYERID": data.get("payerid"), "TOKEN": token, "PAYMENTREQUEST_0_PAYMENTACTION": "SALE", "PAYMENTREQUEST_0_AMT": data.get("amount"), "PAYMENTREQUEST_0_CURRENCYCODE": data.get("currency").upper() }) response = make_post_request(url, data=params) if response.get("ACK")[0] == "Success": update_integration_request_status(token, { "transaction_id": response.get("PAYMENTINFO_0_TRANSACTIONID")[0], "correlation_id": response.get("CORRELATIONID")[0] }, "Completed") if data.get("reference_doctype") and data.get("reference_docname"): custom_redirect_to = frappe.get_doc(data.get("reference_doctype"), data.get("reference_docname")).run_method("on_payment_authorized", "Completed") frappe.db.commit() if custom_redirect_to: redirect_to = custom_redirect_to redirect_url = '/integrations/payment-success' else: redirect_url = "/integrations/payment-failed" if redirect_to: redirect_url += '?' + urlencode({'redirect_to': redirect_to}) if redirect_message: redirect_url += '&' + urlencode({'redirect_message': redirect_message}) # this is done so that functions called via hooks can update flags.redirect_to if redirect: frappe.local.response["type"] = "redirect" frappe.local.response["location"] = get_url(redirect_url) except Exception: frappe.log_error(frappe.get_traceback())
def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=None): if not (from_currency and to_currency): # manqala 19/09/2016: Should this be an empty return or should it throw and exception? return if from_currency == to_currency: return 1 if not transaction_date: transaction_date = nowdate() currency_settings = frappe.get_doc("Accounts Settings").as_dict() allow_stale_rates = currency_settings.get("allow_stale") filters = [ ["date", "<=", get_datetime_str(transaction_date)], ["from_currency", "=", from_currency], ["to_currency", "=", to_currency] ] if args == "for_buying": filters.append(["for_buying", "=", "1"]) elif args == "for_selling": filters.append(["for_selling", "=", "1"]) if not allow_stale_rates: stale_days = currency_settings.get("stale_days") checkpoint_date = add_days(transaction_date, -stale_days) filters.append(["date", ">", get_datetime_str(checkpoint_date)]) # cksgb 19/09/2016: get last entry in Currency Exchange with from_currency and to_currency. entries = frappe.get_all( "Currency Exchange", fields=["exchange_rate"], filters=filters, order_by="date desc", limit=1) if entries: return flt(entries[0].exchange_rate) try: cache = frappe.cache() key = "currency_exchange_rate:{0}:{1}".format(from_currency, to_currency) value = cache.get(key) if not value: import requests api_url = "https://frankfurter.app/{0}".format(transaction_date) response = requests.get(api_url, params={ "base": from_currency, "symbols": to_currency }) # expire in 6 hours response.raise_for_status() value = response.json()["rates"][to_currency] cache.setex(key, value, 6 * 60 * 60) return flt(value) except: frappe.log_error(title="Get Exchange Rate") frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually").format(from_currency, to_currency, transaction_date)) return 0.0
def update(self, doctype, doc, migration_id): if doctype == 'Events': from frappe.desk.doctype.event.event import has_permission d = frappe.get_doc("Event", doc["name"]) if has_permission(d, self.account.name): if doc["start_datetime"] >= datetime.now() and migration_id is not None: try: doctype = "Event" return self.update_events(doctype, doc, migration_id) except Exception: frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")
def setup_app_type(): try: shopify_settings = frappe.get_doc("Shopify Settings") shopify_settings.app_type = 'Private' shopify_settings.update_price_in_erpnext_price_list = 0 if getattr(shopify_settings, 'push_prices_to_shopify', None) else 1 shopify_settings.flags.ignore_mandatory = True shopify_settings.ignore_permissions = True shopify_settings.save() except Exception: frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0) frappe.log_error(frappe.get_traceback())
def _create_calendar(): timezone = frappe.db.get_value("System Settings", None, "time_zone") calendar = { 'summary': self.account.calendar_name, 'timeZone': timezone } try: created_calendar = self.gcalendar.calendars().insert(body=calendar).execute() frappe.db.set_value("GCalendar Account", self.account.name, "gcalendar_id", created_calendar["id"]) except Exception: frappe.log_error(frappe.get_traceback())
def create_request(self, data): self.data = frappe._dict(data) try: self.integration_request = create_request_log(self.data, "Host", "Stripe") return self.create_charge_on_stripe() except Exception: frappe.log_error(frappe.get_traceback()) return{ "redirect_to": frappe.redirect_to_message(_('Server Error'), _("Seems issue with server's razorpay config. Don't worry, in case of failure amount will get refunded to your account.")), "status": 401 }
def save_invoice(e, si_doc, name, name_list): try: if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): si_doc.docstatus = 0 si_doc.flags.ignore_mandatory = True si_doc.due_date = si_doc.posting_date si_doc.insert() name_list.append(name) except Exception: frappe.log_error(frappe.get_traceback()) return name_list
def insert(self, doctype, doc): if doctype == 'Events': from frappe.desk.doctype.event.event import has_permission d = frappe.get_doc("Event", doc["name"]) if has_permission(d, self.account.name): if doc["start_datetime"] >= datetime.now(): try: doctype = "Event" e = self.insert_events(doctype, doc) return e except Exception: frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")
def get_redirect_url(): url = "{0}/api/method/dropbox_erpnext_broker.www.setup_dropbox.get_authotize_url".format(frappe.conf.dropbox_broker_site) try: response = make_post_request(url, data={"site": get_url()}) if response.get("message"): return response["message"] except Exception as e: frappe.log_error() frappe.throw( _("Something went wrong while generating dropbox access token. Please check error log for more details.") )
def reset_enabled_scheduler_events(login_manager): if login_manager.info.user_type == "System User": try: frappe.db.set_global('enabled_scheduler_events', None) except pymysql.InternalError as e: if e.args[0]==ER.LOCK_WAIT_TIMEOUT: frappe.log_error(frappe.get_traceback(), "Error in reset_enabled_scheduler_events") else: raise else: is_dormant = frappe.conf.get('dormant') if is_dormant: update_site_config('dormant', 'None')
def create_request(self, data): self.data = frappe._dict(data) try: self.integration_request = frappe.get_doc("Integration Request", self.data.token) self.integration_request.update_status(self.data, "Queued") return self.authorize_payment() except Exception: frappe.log_error(frappe.get_traceback()) return { "redirect_to": frappe.redirect_to_message( _("Server Error"), _("Seems issue with server's razorpay config. Don't worry, in case of failure amount will get refunded to your account." ), ), "status": 401, }
def read_camt053(content, account): #read_camt_transactions_re(content) soup = BeautifulSoup(content, 'lxml') # general information try: #iban = doc['Document']['BkToCstmrStmt']['Stmt']['Acct']['Id']['IBAN'] iban = soup.document.bktocstmrstmt.stmt.acct.id.iban.get_text() except: # node not found, probably wrong format iban = "n/a" frappe.log_error( "Unable to read structure. Please make sure that you have selected the correct format.", "BankWizard read_camt053") # transactions #new_payment_entries = read_camt_transactions(doc['Document']['BkToCstmrStmt']['Stmt']['Ntry'], bank, account, auto_submit) entries = soup.find_all('ntry') transactions = read_camt_transactions(entries, account) return {'transactions': transactions}
def get_transactions(self, start_date, end_date, account_id=None): self.auth() kwargs = dict( access_token=self.access_token, start_date=start_date, end_date=end_date ) if account_id: kwargs.update(dict(account_ids=[account_id])) try: response = self.client.Transactions.get(**kwargs) transactions = response["transactions"] while len(transactions) < response["total_transactions"]: response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions)) transactions.extend(response["transactions"]) return transactions except ItemError as e: raise e except Exception: frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
def execute(host, api_token, payload, verify_ssl=True, method="GET"): try: response = requests.request( method=method, url=host, json=payload, auth=HTTPBasicAuth("MailChimpConnector", api_token), verify=verify_ssl) status=response.status_code text=response.text if status not in [200, 404]: frappe.log_error("Unexpected MailChimp response: http {method} {host} response {status} with message {text} on payload {payload}".format( status=status,text=text, payload=payload, method=method, host=host)) if status == 404: return None return text except Exception as e: #frappe.log_error("Execution of http request failed. Please check host and API token.") frappe.throw("Execution of http request failed. Please check host and API token. ({0})".format(e))
def create_documents(self): try: new_doc = self.make_new_document() if self.notify_by_email and self.recipients: self.send_notification(new_doc) except Exception: error_log = frappe.log_error(frappe.get_traceback(), _("Auto Repeat Document Creation Failure")) self.disable_auto_repeat() if self.reference_document and not frappe.flags.in_test: self.notify_error_to_user(error_log)
def get_translation_dict_from_file(path, lang, app, throw=False): """load translation dict from given path""" translation_map = {} if os.path.exists(path): csv_content = read_csv_file(path) for item in csv_content: if len(item) == 3 and item[2]: key = item[0] + ":" + item[2] translation_map[key] = strip(item[1]) elif len(item) in [2, 3]: translation_map[item[0]] = strip(item[1]) elif item: msg = "Bad translation in '{app}' for language '{lang}': {values}".format( app=app, lang=lang, values=cstr(item)) frappe.log_error(message=msg, title="Error in translation file") if throw: frappe.throw(msg, title="Error in translation file") return translation_map
def get_api_key(request): account = None if request.query_string: parsed_qs = parse_qs(frappe.safe_decode(request.query_string)) account = parsed_qs.get("account") if isinstance(account, list): account = account[0] if account: return account, frappe.db.get_value("GoCardless Settings", account, "webhooks_secret") else: gocardless_accounts = frappe.get_all("GoCardless Settings") if len(gocardless_accounts) > 1: frappe.log_error(_("Please define your GoCardless account in the webhook URL's query string"),\ _("GoCardless webhook error")) else: return gocardless_accounts[0].get("name"), frappe.db.get_value( "GoCardless Settings", gocardless_accounts[0].get("name"), "webhooks_secret")
def upload_file_to_ftp(filename, folder, ftp_client): """upload files with chunk of 15 mb to reduce session append calls""" if not os.path.exists(filename): return with open(encode(filename), 'rb') as f: path = "{0}/{1}".format(folder, os.path.basename(filename)) try: create_folder_if_not_exists(ftp_client, folder) pwd = ftp_client.pwd() ftp_client.cwd(folder) ftp_client.storbinary('STOR %s' % os.path.basename(filename), f) ftp_client.cwd(pwd) except Exception: error = "File Path: {path}\n".format(path=path) error += frappe.get_traceback() frappe.log_error(error) print (error)
def insert_events(self, doctype, doc): event = {'summary': doc.summary, 'description': doc.description} dates = self.return_dates(doc) event.update(dates) if doc.gcalendar_sync_id: event.update({"id": doc.gcalendar_sync_id}) if doc.repeat_this_event != 0: recurrence = self.return_recurrence(doctype, doc) if not not recurrence: event.update({"recurrence": ["RRULE:" + str(recurrence)]}) try: remote_event = self.gcalendar.events().insert( calendarId=self.account.gcalendar_id, body=event).execute() return {self.name_field: remote_event["id"]} except Exception: frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")
def mark_email_as_seen(name=None): try: if name and frappe.db.exists("Communication", name) and not frappe.db.get_value("Communication", name, "read_by_recipient"): frappe.db.set_value("Communication", name, "read_by_recipient", 1) frappe.db.set_value("Communication", name, "delivery_status", "Read") frappe.db.set_value("Communication", name, "read_by_recipient_on", get_datetime()) frappe.db.commit() except Exception: frappe.log_error(frappe.get_traceback()) finally: # Return image as response under all circumstances from PIL import Image import io im = Image.new('RGBA', (1, 1)) im.putdata([(255,255,255,0)]) buffered_obj = io.BytesIO() im.save(buffered_obj, format="PNG") frappe.response["type"] = 'binary' frappe.response["filename"] = "imaginary_pixel.png" frappe.response["filecontent"] = buffered_obj.getvalue()
def call_mws_method(mws_method, *args, **kwargs): mws_settings = frappe.get_doc("Amazon MWS Settings") max_retries = mws_settings.max_retry_limit for x in xrange(0, max_retries): try: response = mws_method(*args, **kwargs) return response except Exception as e: delay = math.pow(4, x) * 125 frappe.log_error(message=e, title=str(mws_method)) time.sleep(delay) continue mws_settings.enable_synch = 0 mws_settings.save() frappe.throw( _("Sync has been temporarily disabled because maximum retries have been exceeded" ))
def create_stripe_subscription(gateway_controller, data): stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) stripe_settings.data = frappe._dict(data) stripe.api_key = stripe_settings.get_password(fieldname="secret_key", raise_exception=False) stripe.default_http_client = stripe.http_client.RequestsClient() try: stripe_settings.integration_request = create_request_log(stripe_settings.data, "Host", "Stripe") stripe_settings.payment_plans = frappe.get_doc("Payment Request", stripe_settings.data.reference_docname).subscription_plans return create_subscription_on_stripe(stripe_settings) except Exception: frappe.log_error(frappe.get_traceback()) return{ "redirect_to": frappe.redirect_to_message( _('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.") ), "status": 401 }
def send_daily(): '''Check reports to be sent daily''' current_day = calendar.day_name[now_datetime().weekday()] enabled_reports = frappe.get_all('Auto Email Report', filters={'enabled': 1, 'frequency': ('in', ('Daily', 'Weekdays', 'Weekly'))}) for report in enabled_reports: auto_email_report = frappe.get_doc('Auto Email Report', report.name) # if not correct weekday, skip if auto_email_report.frequency == "Weekdays": if current_day in ("Saturday", "Sunday"): continue elif auto_email_report.frequency == 'Weekly': if auto_email_report.day_of_week != current_day: continue try: auto_email_report.send() except Exception as e: frappe.log_error(e, _('Failed to send {0} Auto Email Report').format(auto_email_report.name))
def create_mandate(data): data = frappe._dict(data) if not frappe.db.exists("Sepa Mandate", data.get('mandate')): try: reference_doc = frappe.db.get_value(data.get('reference_doctype'), data.get('reference_docname'),\ ["reference_doctype", "reference_name", "payment_gateway"], as_dict=1) origin_transaction = frappe.db.get_value(reference_doc.reference_doctype, reference_doc.reference_name,\ ["customer"], as_dict=1) frappe.get_doc({ "doctype": "Sepa Mandate", "mandate": data.get('mandate'), "customer": origin_transaction.get("customer"), "registered_on_gocardless": 1 }).insert(ignore_permissions=True) add_gocardless_customer_id(reference_doc, data.get('customer')) except Exception as e: frappe.log_error(e, "Sepa Mandate Registration Error")
def parse_item_stock(response): # create soup container soup = BeautifulSoup(response, 'lxml') # find all item transactions items = soup.find_all('wn1:productstockdata') item_stock = {} for item in items: barcode = item.find('wn1:itemno').getText() #sql_query = """SELECT `name` # FROM `tabItem` # WHERE `barcode` = "{0}" AND `disabled` = 0;""".format(barcode.replace("`", "'")) #print(sql_query) #item_match = frappe.db.sql(sql_query, as_dict=True) item_match = frappe.get_all("Item", filters={'barcode': barcode}, fields=['name']) if len(item_match) == 0: frappe.log_error("Item not found for barcode {0}".format(barcode), "MS Direct item stock issue") else: qty = float(item.find('wn1:calculatetquantity').getText()) item_stock[item_match[0]['name']] = qty return item_stock
def start_import(invoices): errors = 0 names = [] for idx, d in enumerate(invoices): try: publish(idx, len(invoices), d.doctype) doc = frappe.get_doc(d) doc.insert() doc.submit() frappe.db.commit() names.append(doc.name) except Exception: errors += 1 frappe.db.rollback() message = "\n".join(["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()]) frappe.log_error(title="Error while creating Opening Invoice", message=message) frappe.db.commit() if errors: frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details") .format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured")) return names
def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10): search = filters.get('search') if remote_objectname == 'Contact': try: return self.get_contacts(search, start, page_length) except Exception as e: frappe.log_error(e, 'Mautic Contact Get Error') if remote_objectname == 'Company': try: return self.get_companies(search, start, page_length) except Exception as e: frappe.log_error(e, 'Mautic Company Get Error') if remote_objectname == 'Segment': try: return self.get_segments(search, start, page_length) except Exception as e: frappe.log_error(e, 'Mautic Segment Get Error')
def letter_to_pdf(html, title, letterhead=None, attach=False, doctype=None, docname=None): html = get_formatted_letter(title, html, letterhead) pdf = get_pdf(html) if attach: try: private_files = frappe.get_site_path('private', 'files') fname = os.path.join( private_files, "{0}-{1}.pdf".format(title, frappe.generate_hash(length=6))) with open(fname, "wb") as f: f.write(pdf) new_file = frappe.get_doc({ "doctype": "File", "file_name": title, "attached_to_doctype": doctype, "attached_to_name": docname, "is_private": 1, "file_url": "/private/files/" + fname.split('/private/files/')[1] }) new_file.insert() except Exception: frappe.log_error("Letter error", frappe.get_traceback()) frappe.local.response.filename = "{0}.pdf".format( title.replace(" ", "-").replace("/", "-")) frappe.local.response.filecontent = pdf frappe.local.response.type = "pdf"
def send(self, doc): '''Build recipients and send SMS Notification''' context = get_context(doc) context = {"doc": doc, "alert": self, "comments": None} if doc.get("_comments"): context["comments"] = json.loads(doc.get("_comments")) if self.is_standard: self.load_standard_properties(context) try: if self.channel == 'SMS': self.send_sms(doc, context) except: frappe.log_error(title='Failed to send notification', message=frappe.get_traceback()) if self.set_property_after_alert: allow_update = True if doc.docstatus == 1 and not doc.meta.get_field(self.set_property_after_alert).allow_on_submit: allow_update = False try: if allow_update and not doc.flags.in_notification_update: fieldname = self.set_property_after_alert value = self.property_value if doc.meta.get_field(fieldname).fieldtype in frappe.model.numeric_fieldtypes: value = frappe.utils.cint(value) doc.set(fieldname, value) doc.flags.updater_reference = { 'doctype': self.doctype, 'docname': self.name, 'label': _('via SMS Notification') } doc.flags.in_notification_update = True doc.save(ignore_permissions=True) doc.flags.in_notification_update = False except Exception: frappe.log_error(title='Document update failed', message=frappe.get_traceback())
def run_background(prepared_report): instance = frappe.get_doc("Prepared Report", prepared_report) report = frappe.get_doc("Report", instance.ref_report_doctype) try: report.custom_columns = [] if report.report_type == "Custom Report": custom_report_doc = report reference_report = custom_report_doc.reference_report report = frappe.get_doc("Report", reference_report) report.custom_columns = custom_report_doc.json result = generate_report_result( report=report, filters=instance.filters, user=instance.owner ) create_json_gz_file(result["result"], "Prepared Report", instance.name) instance.status = "Completed" instance.columns = json.dumps(result["columns"]) instance.report_end_time = frappe.utils.now() instance.save(ignore_permissions=True) except Exception: frappe.log_error(frappe.get_traceback()) instance = frappe.get_doc("Prepared Report", prepared_report) instance.status = "Error" instance.error_message = frappe.get_traceback() instance.save(ignore_permissions=True) frappe.publish_realtime( "report_generated", { "report_name": instance.report_name, "name": instance.name }, user=frappe.session.user )
def get_bills(number_plate): headers = { 'x-transfer-key': 'e9f3e572-db87-4eff-9ed6-66922f1f7f24', } url = ( "https://termis.tarura.go.tz:6003/termis-parking-service/api/v1/parkingDetails/debts/plateNumber/" + number_plate ) try: response = requests.get(url=url, headers=headers, timeout=5) if response.status_code == 200: return frappe._dict(json.loads(response.text)) else: res = None try: res = json.loads(response.text) except: res = response.text frappe.log_error(res) return except Timeout: frappe.log_error(_("Timeout error for plate {0}").format(number_plate)) except Exception as e: frappe.log_error(e)
def get_contacts(email_strings): email_addrs = [] for email_string in email_strings: if email_string: for email in email_string.split(","): parsed_email = parseaddr(email)[1] if parsed_email: email_addrs.append(parsed_email) contacts = [] for email in email_addrs: email = get_email_without_link(email) contact_name = get_contact_name(email) if not contact_name and email and cint(frappe.db.get_single_value("System Settings", \ "create_contacts_from_incoming_emails", True)): email_parts = email.split("@") first_name = frappe.unscrub(email_parts[0]) try: contact_name = '{0}-{1}'.format( first_name, email_parts[1]) if first_name == 'Contact' else first_name contact = frappe.get_doc({ "doctype": "Contact", "first_name": contact_name, "name": contact_name }) contact.add_email(email_id=email, is_primary=True) contact.insert(ignore_permissions=True) contact_name = contact.name except Exception: traceback = frappe.get_traceback() frappe.log_error(traceback) if contact_name: contacts.append(contact_name) return contacts
def check_mandate(data, reference_doctype, reference_docname): data = json.loads(data) client = gocardless_initialization(reference_docname) payer = frappe.get_doc("Customer", data["payer_name"]) if payer.customer_type == "Individual" and payer.customer_primary_contact is not None: primary_contact = frappe.get_doc("Contact", payer.customer_primary_contact) prefilled_customer = { "company_name": payer.name, "given_name": primary_contact.first_name, "family_name": primary_contact.last_name, } if primary_contact.email_id is not None: prefilled_customer.update({"email": primary_contact.email_id}) else: prefilled_customer.update({"email": frappe.session.user}) else: prefilled_customer = { "company_name": payer.name, "email": frappe.session.user } success_url = get_url("./integrations/gocardless_confirmation?reference_doctype=" + reference_doctype + "&reference_docname=" + reference_docname) try: redirect_flow = client.redirect_flows.create(params={ "description": _("Pay {0} {1}".format(data['amount'], data['currency'])), "session_token": frappe.session.user, "success_redirect_url": success_url, "prefilled_customer": prefilled_customer }) return {"redirect_to": redirect_flow.redirect_url} except Exception as e: frappe.log_error(e, "GoCardless Payment Error") return {"redirect_to": '/integrations/payment-failed'}
def evaluate_alert(doc, alert, event): from jinja2 import TemplateError try: if isinstance(alert, basestring): alert = frappe.get_doc("Email Alert", alert) context = get_context(doc) if alert.condition: if not frappe.safe_eval(alert.condition, None, context): return if event == "Value Change" and not doc.is_new(): try: db_value = frappe.db.get_value(doc.doctype, doc.name, alert.value_changed) except frappe.DatabaseOperationalError as e: if e.args[0] == 1054: alert.db_set('enabled', 0) frappe.log_error( 'Email Alert {0} has been disabled due to missing field' .format(alert.name)) return db_value = parse_val(db_value) if (doc.get(alert.value_changed) == db_value) or \ (not db_value and not doc.get(alert.value_changed)): return # value not changed if event != "Value Change" and not doc.is_new(): # reload the doc for the latest values & comments, # except for validate type event. doc = frappe.get_doc(doc.doctype, doc.name) alert.send(doc) except TemplateError: frappe.throw( _("Error while evaluating Email Alert {0}. Please fix your template." ).format(alert))
def link_existing_conversations(doc, state): """ Called from hooks on creation of Contact or Lead to link all the existing conversations. """ if doc.doctype != 'Contact': return try: numbers = [d.phone for d in doc.phone_nos] for number in numbers: number = strip_number(number) if not number: continue logs = frappe.db.sql_list(""" SELECT cl.name FROM `tabCall Log` cl LEFT JOIN `tabDynamic Link` dl ON cl.name = dl.parent WHERE (cl.`from` like %(phone_number)s or cl.`to` like %(phone_number)s) GROUP BY cl.name HAVING SUM( CASE WHEN dl.link_doctype = %(doctype)s AND dl.link_name = %(docname)s THEN 1 ELSE 0 END )=0 """, dict( phone_number='%{}'.format(number), docname=doc.name, doctype=doc.doctype ) ) for log in logs: call_log = frappe.get_doc('Call Log', log) call_log.add_link(link_type=doc.doctype, link_name=doc.name) call_log.save() frappe.db.commit() except Exception: frappe.log_error(title=_('Error during caller information update'))
def setup_subscription(self, settings, **kwargs): start_date = get_timestamp(kwargs.get('subscription_details').get("start_date")) \ if kwargs.get('subscription_details').get("start_date") else None subscription_details = { "plan_id": kwargs.get('subscription_details').get("plan_id"), "total_count": kwargs.get('subscription_details').get("billing_frequency"), "customer_notify": kwargs.get('subscription_details').get("customer_notify") } if start_date: subscription_details['start_at'] = cint(start_date) if kwargs.get('addons'): convert_rupee_to_paisa(**kwargs) subscription_details.update({ "addons": kwargs.get('addons') }) try: resp = make_post_request( "https://api.midtrans.com/v1/subscriptions", auth=(settings.client_key, settings.server_key), data=json.dumps(subscription_details), headers={ "content-type": "application/json" } ) if resp.get('status') == 'created': kwargs['subscription_id'] = resp.get('id') frappe.flags.status = 'created' return kwargs else: frappe.log_error(str(resp), 'midtrans Failed while creating subscription') except: frappe.log_error(frappe.get_traceback()) # failed pass
def upload_file_to_dropbox(filename, folder, dropbox_client): """upload files with chunk of 15 mb to reduce session append calls""" if not os.path.exists(filename): return create_folder_if_not_exists(folder, dropbox_client) file_size = os.path.getsize(encode(filename)) chunk_size = get_chunk_site(file_size) mode = (dropbox.files.WriteMode.overwrite) f = open(encode(filename), 'rb') path = "{0}/{1}".format(folder, os.path.basename(filename)) try: if file_size <= chunk_size: dropbox_client.files_upload(f.read(), path, mode) else: upload_session_start_result = dropbox_client.files_upload_session_start( f.read(chunk_size)) cursor = dropbox.files.UploadSessionCursor( session_id=upload_session_start_result.session_id, offset=f.tell()) commit = dropbox.files.CommitInfo(path=path, mode=mode) while f.tell() < file_size: if ((file_size - f.tell()) <= chunk_size): dropbox_client.files_upload_session_finish( f.read(chunk_size), cursor, commit) else: dropbox_client.files_upload_session_append( f.read(chunk_size), cursor.session_id, cursor.offset) cursor.offset = f.tell() except dropbox.exceptions.ApiError as e: if isinstance(e.error, dropbox.files.UploadError): error = "File Path: {path}\n".format(path=path) error += frappe.get_traceback() frappe.log_error(error) else: raise
def make_request(method, url, auth=None, headers=None, data=None): auth = auth or "" data = data or {} headers = headers or {} try: s = get_request_session() frappe.flags.integration_request = s.request(method, url, data=data, auth=auth, headers=headers) frappe.flags.integration_request.raise_for_status() if frappe.flags.integration_request.headers.get( "content-type") == "text/plain; charset=utf-8": return parse_qs(frappe.flags.integration_request.text) return frappe.flags.integration_request.json() except Exception as exc: frappe.log_error() raise exc
def process_request_data(data): try: verify_signature(data) except Exception as e: log = frappe.log_error(e, "Membership Webhook Verification Error") notify_failure(log) return {"status": "Failed", "reason": e} if isinstance(data, six.string_types): data = json.loads(data) data = frappe._dict(data) return data
def create_customer(user_details, member=None): customer = frappe.new_doc("Customer") customer.customer_name = user_details.fullname customer.customer_type = "Individual" customer.flags.ignore_mandatory = True customer.insert(ignore_permissions=True) try: contact = frappe.new_doc("Contact") contact.first_name = user_details.fullname if user_details.mobile: contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) if user_details.email: contact.add_email(user_details.email, is_primary=1) contact.insert(ignore_permissions=True) contact.append("links", { "link_doctype": "Customer", "link_name": customer.name }) if member: contact.append("links", { "link_doctype": "Member", "link_name": member }) contact.save(ignore_permissions=True) except frappe.DuplicateEntryError: return customer.name except Exception as e: frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) pass return customer.name