Exemple #1
0
def update_shipping_rate(address, awc_session):

	awc = awc_session.get("cart")

	shipping_rate_api = frappe.get_hooks("shipping_rate_api")[0]
	address_link = frappe.get_value("AWC Settings", "AWC Settings", "shipping_address")
	from_address = frappe.get_doc("Address", address_link)

	package_items=[]

	for item in awc["items"]:
		if not item.get("options", {}).get("subgroup"):
			package_item = {
				"item_code": item.get("sku"),
				"qty": item.get("qty")
			}
			package_items.append(package_item)

	try:
		rates = frappe.call(shipping_rate_api["module"], from_address=from_address, to_address=address, items=package_items)
		if rates:
			# cache quoted rates to reference later on checkout
			awc_session["shipping_address"] = address
			awc_session["shipping_rates"] = { rate.get("name"): rate for rate in rates }
			awc_session["shipping_rates_list"] = rates
		else:
			rates = []

	except Exception as ex:
		log(traceback.format_exc())
		return []

	return rates
Exemple #2
0
	def create_request(self, data):
		# simulates a transaction by just submitting a quotation
		# if there is enough credit available there should be no issues

		self.process_data = frappe._dict(data)

		self.billing_info = self.process_data.get("billing_info")
		self.shipping_info = self.process_data.get("shipping_info")

		redirect_url = ""
		redirect_to = data.get("notes", {}).get("redirect_to") or None
		redirect_message = data.get("notes", {}).get("redirect_message") or None
		status = "Completed"

		if not self.process_data.get("unittest"):
			self.integration_request = create_request_log(self.process_data, "Host", self.service_name)
			self.integration_request.status = status
			self.integration_request.save()

		custom_redirect_to = None
		try:
			if not self.process_data.get("unittest"):
				ref_doc = frappe.get_doc(
					self.process_data.reference_doctype,
					self.process_data.reference_docname)

				ref_doc.flags["skip_payment_request"] = 1

				custom_redirect_to = ref_doc.run_method("on_payment_authorized", status)
		except Exception as ex:
			log(frappe.get_traceback())
			raise ex

		if custom_redirect_to:
			redirect_to = custom_redirect_to

		redirect_url = "/integrations/credit_success"
		redirect_message = "Continue Shopping"
		success = True

		params = []
		if redirect_to:
			params.append(urllib.urlencode({"redirect_to": redirect_to}))
		if redirect_message:
			params.append(urllib.urlencode({"redirect_message": redirect_message}))

		if len(params) > 0:
			redirect_url += "?" + "&".join(params)

		self.process_data = {}
		self.billing_info = {}
		self.shipping_info = {}

		return {
			"redirect_to": redirect_url,
			"error": redirect_message if status == "Failed" else None,
			"status": status
		}
Exemple #3
0
def get_shipping_rate(address):

	try:
		address = json.loads(address)
	except Exception as ex:
		log(ex)
		return []

	awc_session = get_awc_session()
	result = update_shipping_rate(address, awc_session)

	set_awc_session(awc_session)
	return result
Exemple #4
0
def update_shipping_rate(address, awc_session, is_pickup=False):
	validation_hooks = frappe.get_hooks("awc_address_validation") or []
	quotation = get_user_quotation(awc_session).get('doc')

	if quotation:
		for fn in validation_hooks:
			frappe.call(fn, doc=quotation, address=address)

	if address:
		address_link = frappe.get_value("AWC Settings", "AWC Settings", "shipping_address")
		from_address = frappe.get_doc("Address", address_link)

	package_items = []
	awc = awc_session.get("cart")

	if not is_pickup:
		for item in awc["items"]:
			if not item.get("options", {}).get("subgroup"):
				package_item = {
					"item_code": item.get("sku"),
					"qty": item.get("qty")
				}
				package_items.append(package_item)

	shipping_rate_api = frappe.get_hooks("shipping_rate_api")[0]
	try:
		if address and not is_pickup:
			rates = frappe.call(shipping_rate_api["module"], from_address=from_address, to_address=address, items=package_items)
		else:
			rates = []

		if rates or is_pickup:
			rates.append({u'fee': 0, u'name': u'PICK UP', u'label': u'FLORIDA HQ PICK UP'})
		else:
			rates = []

	except Exception as ex:
		log(traceback.format_exc())
		rates = []

	if address:
		awc_session["last_shipping_address"] = address
		awc_session["shipping_address"] = address
	elif "shipping_address" in awc_session:
		del awc_session["shipping_address"]

	awc_session["shipping_rates"] = { rate.get("name"): rate for rate in rates }
	awc_session["shipping_rates_list"] = rates

	return rates
Exemple #5
0
def call_hook(hook_name, **kwargs):
	hooks = frappe.get_hooks(hook_name) or []
	for hook in hooks:
		# don't allow hooks to break processing
		try:
			frappe.call(hook, **kwargs)
		except Exception:
			# Hook inception, pass exception to hook listening for exception reporting(sentry)
			error_hooks = frappe.get_hooks("error_capture_log") or []
			if len(error_hooks) > 0:
				for error_hook in error_hooks:
					frappe.call(error_hook, async=True)
			else:
				log("Error calling hook method: {}->{}".format(hook_name, hook))
				log(frappe.get_traceback())
Exemple #6
0
def save_and_commit_quotation(quotation, is_dirty, awc_session, commit=False, save_session=True):
	result=(False, None)

	if quotation and is_dirty:
		try:
			update_cart_settings(quotation, awc_session)
			quotation.flags.ignore_permissions = True
			quotation.save()
			collect_totals(quotation, None, awc_session)

			result = (True, None)
		except Exception as ex:
			log(traceback.format_exc())
			result = (False, ex)
	else:
		collect_totals(None, None, awc_session)

	if save_session:
		set_awc_session(awc_session)

	if commit:
		frappe.db.commit()

	return result
    def on_payment_authorized(self, payment_status):
        try:
            quotation = frappe.get_doc("Quotation", self.order_id)

            # check if we have a billing address linked
            if self.get('billing_address'):
                quotation.customer_address = self.billing_address
            else:
                # else create one from transaction data
                quotation.customer_address = create_address(
                    parent_dt="Customer",
                    parent=quotation.customer,
                    address_1=self.get("billing_address_1"),
                    address_2=self.get("billing_address_2"),
                    city=self.get("billing_city"),
                    state=self.get("billing_state"),
                    pincode=self.get("billing_pincode"),
                    country=self.get("billing_country"),
                    email=self.get("payer_email"),
                    address_type="Billing",
                    phone=self.get("billing_phone"),
                    title=self.get("billing_title"),
                    return_name=1,
                    flags={"ignore_permissions": 1})

            # check if we have a shipping address linked
            quotation.shipping_address_name = self.shipping_address

            # assign formatted address text
            if not quotation.shipping_address_name:
                quotation.shipping_address_name = frappe.get_value(
                    "AWC Settings", "AWC Settings", "shipping_address")

            quotation.address_display = get_address_display(
                frappe.get_doc("Address",
                               quotation.customer_address).as_dict())
            quotation.shipping_address = get_address_display(
                frappe.get_doc("Address",
                               quotation.shipping_address_name).as_dict())

            quotation.flags.ignore_permissions = 1
            quotation.save()

            # create sales order
            so = convert_quotation_to_sales_order(quotation)

            if self.flags.get("skip_payment_request", False):
                so.submit()
                # NOTE: TEST THIS!!!! WHY IS THERE A SAVE AFTER SUBMIT???????
                #so.save()

                self.reference_doctype = "Sales Order"
                self.reference_docname = so.name
            else:
                # then immediately create payment request
                # no emails should be sent as this is intended for immediate fullfilment

                req_type = frappe.local.response.get("type", None)
                req_location = frappe.local.response.get("location", None)

                preq = payment_request.make_payment_request(dt="Sales Order",
                                                            dn=so.name,
                                                            submit_doc=1,
                                                            return_doc=1,
                                                            mute_email=1)

                #############################################################
                # DIRTY FIX: payment request codebase redirects
                # shopping cart payment requests. Here we are undoing that.
                if req_type:
                    frappe.local.response["type"] = req_type
                elif frappe.local.response.get("type"):
                    frappe.local.response.pop("type")

                if req_location:
                    frappe.local.response["location"] = req_location
                elif frappe.local.response.get("location"):
                    frappe.local.response.pop("location")
                #############################################################

                preq.flags.ignore_permissions = 1
                # preq.insert()

                log(preq)

                if preq.docstatus != 1:
                    preq.submit()

                # update transaction record to track payment request record
                self.reference_doctype = "Payment Request"
                self.reference_docname = preq.name

            self.order_id = so.name
            self.flags.ignore_permissions = 1
            self.save()

            if not self.flags.get("skip_payment_request", False):
                # finally let payment request run its own code to finalize transaction
                # invoice, payment entry docs should be created here
                result = preq.run_method("on_payment_authorized",
                                         payment_status)
            else:
                result = None

            # update shipping method in Sales Order
            #if self.get("shipping_method"):
            #	frappe.db.set_value("Sales Order", self.order_id, "fedex_shipping_method", self.get("shipping_method"))

            if self.get("gateway_service"):
                has_universals = False
                for item in frappe.get_doc("Sales Order", self.order_id).items:
                    if frappe.db.get_value("Item", item.item_code,
                                           "item_group") == "Universal":
                        has_universals = True
                if self.get("gateway_service") == "credit_gateway":
                    frappe.db.set_value("Sales Order", self.order_id,
                                        "payment_method", "Bill Me")
                elif self.get("gateway_service") == "paypal":
                    frappe.db.set_value("Sales Order", self.order_id,
                                        "payment_method", "PayPal")
                    frappe.db.set_value("Sales Order", self.order_id,
                                        "authorize_production", False)
                    frappe.db.set_value("Sales Order", self.order_id,
                                        "authorize_delivery", False)
                else:
                    if self.get("gateway_service") == "authorizenet":
                        frappe.db.set_value("Sales Order", self.order_id,
                                            "payment_method", "Card")
                    else:
                        frappe.db.set_value("Sales Order", self.order_id,
                                            "payment_method",
                                            self.get("gateway_service"))

                    if not has_universals:
                        frappe.db.set_value("Sales Order", self.order_id,
                                            "authorize_production", True)
                        frappe.db.set_value("Sales Order", self.order_id,
                                            "authorize_delivery", True)

            # override redirection to orders page
            if result:
                result = '/iems#filter=custom'

            # this is here to remove duplication warning messages.
            # TODO: Consider bring this to erpnext team to remove warning
            if frappe.local.message_log:
                for msg in frappe.local.message_log:
                    self.log_action(msg, "Info")
                frappe.local.message_log = []

            # don't kill processing if saving cleaning session address info breaks
            try:
                # clears awc session data
                awc.clear_awc_session()
            except Exception as awc_ex:
                log(frappe.get_traceback())
                self.log_action(frappe.get_traceback(), "Error")
                pass

            return result
        except Exception as ex:
            log(frappe.get_traceback())
            self.log_action(frappe.get_traceback(), "Error")
            raise ex
Exemple #8
0
    def on_payment_authorized(self, payment_status):
        try:

            # dumb loop to catch out of sync quotation.
            # loop at least 3 times for timestamp error
            tries = 3
            while tries > 0:
                try:
                    quotation = self.update_quotation()
                    tries = 0  # success, exit loop
                except Exception as ex:
                    tries = tries - 1
                    if tries <= 0:
                        raise ex
                    else:
                        msg = """
						--------------------------------------------------------------
						Caught error while trying to save quotation in awc transaction
						Attempting to save again...
						{}
						- RESPONSE --------------
						{}""".format(ex, frappe.local.respone)
                        log(msg, trace=1)

            call_hook("awc_transaction_on_payment_authorized",
                      transaction=self,
                      payment_status=payment_status)

            # create sales order
            so = convert_quotation_to_sales_order(quotation)

            if self.flags.get("skip_payment_request", False):
                so.submit()

                self.reference_doctype = "Sales Order"
                self.reference_docname = so.name
            else:
                # then immediately create payment request
                # no emails should be sent as this is intended for immediate fullfilment

                req_type = frappe.local.response.get("type", None)
                req_location = frappe.local.response.get("location", None)

                preq = payment_request.make_payment_request(dt="Sales Order",
                                                            dn=so.name,
                                                            submit_doc=1,
                                                            return_doc=1,
                                                            mute_email=1)

                #############################################################
                # DIRTY FIX: payment request codebase redirects
                # shopping cart payment requests. Here we are undoing that.
                if req_type:
                    frappe.local.response["type"] = req_type
                elif frappe.local.response.get("type"):
                    frappe.local.response.pop("type")

                if req_location:
                    frappe.local.response["location"] = req_location
                elif frappe.local.response.get("location"):
                    frappe.local.response.pop("location")
                #############################################################

                preq.flags.ignore_permissions = 1

                if preq.docstatus != 1:
                    preq.submit()

                # update transaction record to track payment request record
                self.reference_doctype = "Payment Request"
                self.reference_docname = preq.name

            self.order_id = so.name
            self.flags.ignore_permissions = 1
            self.save()

            if not self.flags.get("skip_payment_request", False):
                # finally let payment request run its own code to finalize transaction
                # invoice, payment entry docs should be created here
                result = preq.run_method("on_payment_authorized",
                                         payment_status)
            else:
                result = None

            # override redirection to orders page
            if result:
                result = '/iems#filter=custom'

            # let other apps do work after order is generated
            call_hook("awc_transaction_after_on_sales_order_created",
                      transaction=self,
                      order=so,
                      pr_result=result)

            # remove redirect alert on cart generate so's
            if frappe.local.message_log:
                frappe.local.message_log = []

            # don't kill processing if saving cleaning session address info breaks
            try:
                # clears awc session data
                awc.clear_awc_session()
            except Exception as awc_ex:
                log(frappe.get_traceback())
                pass

            return result
        except Exception as ex:
            log(frappe.get_traceback())
            raise ex
	def process_payment(self):
		# used for feedback about which payment was used
		authorizenet_data = {}
		# the current logged in contact
		contact = get_contact()
		# get authorizenet user if available
		authnet_user = get_authorizenet_user()
		# the cc data available
		data = self.process_data

		# get auth keys
		settings = self.get_settings()
		# fetch redirect info
		redirect_to = data.get("notes", {}).get("redirect_to") or None
		redirect_message = data.get("notes", {}).get("redirect_message") or None

		# uses dummy request doc for unittests as we are only testing processing
		if not data.get("unittest"):
			if data.get("name"):
				request = frappe.get_doc("AuthorizeNet Request", data.get("name"))
			else:
				# Create request from scratch when embeding form on the fly
				#
				# This allows payment processing without having to pre-create
				# a request first.
				#
				# This path expects all the payment request information to be
				# available!!
				#
				# keys expected: ('amount', 'currency', 'order_id', 'title', \
				#                 'description', 'payer_email', 'payer_name', \
				#                 'reference_docname', 'reference_doctype')
				request = self.build_authorizenet_request(**{ \
					key: data[key] for key in \
						('amount', 'currency', 'order_id', 'title', \
						 'description', 'payer_email', 'payer_name', \
						 'reference_docname', 'reference_doctype') })

				data["name"] = request.get("name")
		else:
			request = frappe.get_doc({"doctype": "AuthorizeNet Request"})

		request.flags.ignore_permissions = 1

		# set the max log level as per settings
		request.max_log_level(self.log_level)

		try:

			if self.card_info:
				# ensure card fields exist
				required_card_fields = ['name_on_card', 'card_number', 'exp_month', 'exp_year', 'card_code']
				for f in required_card_fields:
					if not self.card_info.get(f):
						request.status = "Error"
						return {
							request,
							None,
							"Missing field: %s" % f,
							{}
						}

			# prepare authorize api
			authorize.Configuration.configure(
				authorize.Environment.TEST if self.use_sandbox else authorize.Environment.PRODUCTION,
				settings.api_login_id,
				settings.api_transaction_key
			)

			# cache billing fields as per authorize api requirements
			billing = authnet_address(self.billing_info)
			if self.shipping_info:
				shipping = authnet_address(self.shipping_info)
			else:
				shipping = None

			# attempt to find valid email address
			email = self.process_data.get("payer_email")
			
			if email:
				email = email.split(',')[0]

			if "@" not in email and contact:
				email = contact.get("email_id")

				if "@" not in email:
					if contact and contact.user:
						email = frappe.get_value("User", contact.user, "email_id")

						if "@" not in email:
							log("AUTHNET FAILURE! Bad email: {0}".format(email))
							log(pretty_json(self.process_data))
							raise ValueError("There are no valid emails associated with this customer")

			# build transaction data
			transaction_data = {
				"order": {
					"invoice_number": data["order_id"]
				},
				"amount": flt(self.process_data.get("amount")),
				"email": email,
				"description": self.card_info.get("name_on_card"),
				"customer_type": "individual"
			}

			# track ip for tranasction records
			if frappe.local.request_ip:
				transaction_data.update({
					"extra_options": {
						"customer_ip": frappe.local.request_ip
					}
				})

			# get authorizenet profile informatio for stored payments
			authorizenet_profile = self.process_data.get("authorizenet_profile");

			# use card
			# see: https://vcatalano.github.io/py-authorize/transaction.html
			if self.card_info != None:
				# exp formating for sale/auth api
				expiration_date = "{0}/{1}".format(
					self.card_info.get("exp_month"),
					self.card_info.get("exp_year"))

				transaction_data.update({
					"credit_card": {
						"card_number": self.card_info.get("card_number"),
						"expiration_date": expiration_date,
						"card_code": self.card_info.get("card_code")
					}
				})
			elif authorizenet_profile:

				# if the customer_id isn't provided, then fetch from authnetuser
				if not authorizenet_profile.get("customer_id"):
					authorizenet_profile["customer_id"] = authnet_user.get("authorizenet_id")

				# or stored payment
				transaction_data.update({
					"customer_id": authorizenet_profile.get("customer_id"),
					"payment_id": authorizenet_profile.get("payment_id")
				})

				# track transaction payment profile ids to return later
				authorizenet_data.update({
					"customer_id": authorizenet_profile.get("customer_id"),
					"payment_id": authorizenet_profile.get("payment_id")
				})
			else:
				raise "Missing Credit Card Information"

			name_parts = self.card_info["name_on_card"].split(' ')
			first_name = name_parts[0]
			last_name = " ".join(name_parts[1:])

			# add billing information if available
			if len(billing.keys()):
				transaction_data["billing"] = billing
				transaction_data["billing"]["first_name"] = first_name
				transaction_data["billing"]["last_name"] = last_name

			if shipping and len(shipping.keys()):
				transaction_data["shipping"] = billing
				transaction_data["shipping"]["first_name"] = first_name
				transaction_data["shipping"]["last_name"] = last_name

			# include line items if available
			if self.process_data.get("line_items"):
				transaction_data["line_items"] = self.process_data.get("line_items")

			request.log_action("Requesting Transaction: %s" % \
				json.dumps(transaction_data), "Debug")

			# performt transaction finally
			result = authorize.Transaction.sale(transaction_data)
			request.log_action(json.dumps(result), "Debug")

			# if all went well, record transaction id
			request.transaction_id = result.transaction_response.trans_id
			request.status = "Captured"
			request.flags.ignore_permissions = 1

		except AuthorizeInvalidError as iex:
			# log validation errors
			request.log_action(frappe.get_traceback(), "Error")
			request.status = "Error"
			error_msg = ""
			errors = []

			if iex.children and len(iex.children) > 0:
				for field_error in iex.children:
					print(field_error.asdict())
					for field_name, error in field_error.asdict().iteritems():
						errors.append(error)

			error_msg = "\n".join(errors)

			request.error_msg = error_msg

		except AuthorizeResponseError as ex:
			# log authorizenet server response errors
			result = ex.full_response
			request.log_action(json.dumps(result), "Debug")
			request.log_action(str(ex), "Error")
			request.status = "Error"
			request.error_msg = ex.text

			redirect_message = str(ex)
			if result and hasattr(result, 'transaction_response'):
				# if there is extra transaction data, log it
				errors = result.transaction_response.errors
				request.log_action("\n".join([err.error_text for err in errors]), "Error")
				request.log_action(frappe.get_traceback(), "Error")

				request.transaction_id = result.transaction_response.trans_id
				redirect_message = "Success"

			pass

		except Exception as ex:
			# any other errors
			request.log_action(frappe.get_traceback(), "Error")
			request.status = "Error"
			request.error_msg = "[UNEXPECTED ERROR]: {0}".format(ex)
			pass


		# now check if we should store payment information on success
		if request.status in ("Captured", "Authorized") and \
			self.card_info and \
			self.card_info.get("store_payment") and \
			contact:

			try:

				# create customer if authnet_user doesn't exist
				if not authnet_user:
					request.log_action("Creating AUTHNET customer", "Info")

					customer_result = authorize.Customer.from_transaction(request.transaction_id)

					request.log_action("Success", "Debug")

					authnet_user = frappe.get_doc({
						"doctype": "AuthorizeNet Users",
						"authorizenet_id": customer_result.customer_id,
						"contact": contact.name
					})

				card_store_info = {
					"card_number": self.card_info.get("card_number"),
					"expiration_month": self.card_info.get("exp_month"),
					"expiration_year": self.card_info.get("exp_year"),
					"card_code": self.card_info.get("card_code"),
					"billing": self.billing_info
				}

				request.log_action("Storing Payment Information With AUTHNET", "Info")
				request.log_action(json.dumps(card_store_info), "Debug")

				try:
					card_result = authorize.CreditCard.create(
						authnet_user.get("authorizenet_id"), card_store_info)
				except AuthorizeResponseError as ex:
					card_result = ex.full_response
					request.log_action(json.dumps(card_result), "Debug")
					request.log_action(str(ex), "Error")

					try:
						# duplicate payment profile
						if card_result["messages"][0]["message"]["code"] == "E00039":
							request.log_action("Duplicate payment profile, ignore", "Error")
						else:
							raise ex
					except:
						raise ex


				request.log_action("Success: %s" % card_result.payment_id, "Debug")

				address_short = "{0}, {1} {2}".format(
					billing.get("city"),
					billing.get("state"),
					billing.get("pincode"))

				card_label = "{0}{1}".format(
					get_card_accronym(self.card_info.get("card_number")), self.card_info.get("card_number")[-4:])

				authnet_user.flags.ignore_permissions = 1
				authnet_user.append("stored_payments", {
					"doctype": "AuthorizeNet Stored Payment",
					"short_text": "%s %s" % (card_label,
					address_short),
					"long_text": "{0}\n{1}\n{2}, {3} {4}\n{5}".format(
						card_label,
						billing.get("address", ""),
						billing.get("city", ""),
						billing.get("state", ""),
						billing.get("pincode", ""),
						frappe.get_value("Country",  filters={"name": self.billing_info.get("country")}, fieldname="country_name")
					),
					"address_1": self.billing_info.get("address_1"),
					"address_2": self.billing_info.get("address_2"),
					"expires": "{0}-{1}-01".format(
						self.card_info.get("exp_year"),
						self.card_info.get("exp_month")),
					"city": self.billing_info.get("city"),
					"state": self.billing_info.get("state"),
					"postal_code": self.billing_info.get("pincode"),
					"country": frappe.get_value("Country", self.billing_info.get("country"), fieldname="code"),
					"payment_type": "Card",
					"authorizenet_payment_id": card_result.payment_id
				})

				authorizenet_data.update({
					"customer_id": authnet_user.get("authorizenet_id"),
					"payment_id": card_result.payment_id
				})


				if not data.get("unittest"):
					authnet_user.save()

				request.log_action("Stored in DB", "Debug")
			except Exception as exx:
				# any other errors
				request.log_action(frappe.get_traceback(), "Error")
				raise exx

		return request, redirect_to, redirect_message, authorizenet_data
Exemple #10
0
def sync_awc_and_quotation(awc_session, quotation, quotation_is_dirty=False, save_quotation=False):
	# convert quotation to awc object
	# and merge items in the case where items are added before logging in.

	# steps:
	# 1) loop over all awc items and update quotation items matching names/ids
	# 2) remove invalid awc items who's skus do not match any products(awc items)
	# 3) loop over remaining unmatched quotation items and create awc items
	# 4) remove invalid quotation items who's skus do not match any products(awc items)
	awc = awc_session.get("cart")

	# fixes issue where new quotation items require a parent to be inserted
	# and we require a quotation item name to reference in awc
	if quotation and quotation.name == None:
		save_and_commit_quotation(quotation, True, awc_session, commit=False, save_session=False)

	if not awc:
		# abnormal, there should be a cart instance on the session
		log(awc_session, trace=1)

	awc_is_dirty = False
	awc_items_to_remove = []
	awc_items_matched = []
	# step 1
	# iterate over all awc items and update quotation to match values
	awc_items = awc.get("items", [])
	for awc_idx in range(0, len(awc_items)):
		awc_item = awc_items[awc_idx]
		product = get_product_by_sku(awc_item.get("sku"), quotation=quotation)

		if awc_item.get("id"):
			idx = find_index(quotation.get("items", []), lambda itm: itm.get("name") == awc_item.get("id"))
			if idx > -1:
				item = quotation.items[idx]
				# make sure product exists
				if product.get("success"):
					product = product.get("data")
					if item.qty != awc_item.get("qty"):
						item.qty = awc_item.get("qty")
						quotation_is_dirty = True

					if item.item_code != awc_item.get("sku"):
						item.item_code = awc_item.get("sku")
						quotation_is_dirty = True

					if item.awc_group != awc_item.get('options', {}).get('group'):
						item.awc_group = awc_item.get('options', {}).get('group')
						quotation_is_dirty = True

					if item.awc_subgroup != awc_item.get('options', {}).get('subgroup'):
						item.awc_subgroup = awc_item.get('options', {}).get('subgroup')
						quotation_is_dirty = True

					if item.awc_group_label != awc_item.get('options', {}).get('label'):
						item.awc_group_label = awc_item.get('options', {}).get('label')
						quotation_is_dirty = True

					if item.description != awc_item.get("options", {}).get("description", item.description):
						item.description = awc_item.get("options", {}).get("description")
						quotation_is_dirty = True

					if not item.image and awc_item.get("options", {}).get("image") and item.image != awc_item.get("options", {}).get("image"):
						item.image = awc_item["options"]["image"]
						quotation_is_dirty = True

					item.warehouse = product.get("warehouse")
					update_quotation_item_awc_fields(item, awc_item)

					if awc_item.get("options", {}).get("custom", {}).get("rate", None) != None:
						item.set("ignore_pricing_rule", 1)
						set_quotation_item_rate(item, awc_item["options"]["custom"]["rate"], product)
					else:
						set_quotation_item_rate(item, product.get("price"), product)
						item.set("ignore_pricing_rule", 0)

					awc_items_matched.append(awc_item.get("id"))
				else:
					# sku is invalid. Flag item to be removed from awc session
					awc_items_to_remove.append(awc_item)

			elif awc_item.get('id')[0:4] == "QUOD":
				# remove orphaned items
				awc_items_to_remove.append(awc_item)
			else:
				if product.get("success"):
					product = product.get("data")

					# no quotation item matched, so lets create one
					item_data = {
						"doctype": "Quotation Item",
						"item_code": awc_item.get("sku"),
						"item_name": product.get("name"),
						"description": awc_item.get("options", {}).get("description", product.get("name")),
						"qty": cint(awc_item.get("qty")),
						"warehouse": product.get("warehouse")
					}

					if awc_item.get("options", {}).get("image"):
						item_data["image"] = awc_item["options"]["image"]

					update_quotation_item_awc_fields(item_data, awc_item)

					new_quotation_item = quotation.append("items", item_data)

					if awc_item.get("options", {}).get("custom", {}).get("rate", None) != None:
						new_quotation_item.set("ignore_pricing_rule", 1)
						set_quotation_item_rate(new_quotation_item, awc_item["options"]["custom"]["rate"], product)
					else:
						new_quotation_item.set("ignore_pricing_rule", 0)
						set_quotation_item_rate(new_quotation_item, product.get("price"), product)

					awc_item["unit"] = new_quotation_item.rate
					awc_item["total"] = new_quotation_item.amount

					# BUGFIX: makes sure this item gets a name during login
					new_quotation_item.parent = quotation.name
					new_quotation_item.parenttype = "Quotation"
					new_quotation_item.parentfield = "items"
					new_quotation_item.save()

					awc_item["id"] = new_quotation_item.name
					# make sure we won't add this new item again on step 2
					update_quotation_item_awc_fields(new_quotation_item, awc_item)
					awc_items_matched.append(awc_item.get("id"))

					quotation_is_dirty = True 	# update quotation records
					awc_is_dirty = True			# flag awc session for storage
				else:
					awc_items_to_remove.append(awc_item)
		else:
			# drop awc items if they have invalid ids
			awc_items_to_remove.append(awc_item)

	# step 2
	# remove invalid awc items
	for awc_item in awc_items_to_remove:
		idx = awc["items"].index(awc_item)
		del awc["items"][idx]
		awc_is_dirty = True

	quotation_item_to_remove = []

	# step 3
	# now create awc items for quotation items not matched with existing awc session
	for item in [qitem for qitem in quotation.get("items", []) \
		if qitem.name not in awc_items_matched]:

		product = get_product_by_sku(item.get("item_code"), quotation=quotation)
		if product.get("success"):
			product = product.get("data")
			awc_item = {
				"id": item.name,
				"sku": item.item_code,
				"qty": cint(item.qty),
				"warehouse": product.get("warehouse"),
				"unit": item.rate,
				"total": item.amount,
				"image": item.image,
				"base_price": product.get("base_price"),
				"options": {
					"description": item.description
				}
			}

			if item.awc_group:
				awc_item["options"].update({
					"group": item.awc_group,
					"subgroup": item.awc_subgroup,
					"label": item.awc_group_label,
					"image": item.image
				})

				if awc_item["base_price"] != awc_item["unit"]:
					awc_item["options"]["custom"] = {
						"rate": item.rate
					}

			awc["items"].append(awc_item)
			awc_is_dirty = True
		else:
			quotation_item_to_remove.append(item)

	# step 4
	# remove invalid quotation items
	for item in quotation_item_to_remove:
		idx = quotation.items.index(item)
		del quotation.items[idx]
		quotation_is_dirty = True

	call_awc_sync_hook(awc_session, quotation)

	if quotation_is_dirty:
		update_cart_settings(quotation, awc_session)
		quotation.flags.ignore_permissions = True
		if save_quotation:
			try:
				quotation.save()
				frappe.db.commit()
			except Exception as ex:
				log(traceback.format_exc())

	collect_totals(quotation, awc, awc_session)

	if awc_is_dirty:
		set_awc_session(awc_session)

	return quotation_is_dirty
Exemple #11
0
def fetch_products(tags="", terms="", order_by="order_weight", order_dir="asc", start=0, limit=None):
	"""Fetches a list of products filtered by tags"""

	awc_session = get_awc_session()

	cache_key = "fetch_products-{}-{}-{}-{}-{}-{}-{}".format(tags, terms, order_by, order_dir, start, limit, is_logged_in())
	cache_data = get_cache(cache_key, session=awc_session)
	if cache_data:
		return cache_data

	payload = {
		"success": False
	}
	try:
		price_list = None
		if is_logged_in():
			quotation = _get_cart_quotation()
			price_list = quotation.get("selling_price_list")

		tags = tags.split(',')  # split tag string into list
		# Convert order_by and order_dir values to acceptable values or defaults
		order_by_clean = dict(weight="order_weight").get(order_by if order_by else "", "order_weight")
		order_dir_clean = dict(asc="asc", desc="desc").get(order_dir if order_dir else "", "asc")

		# builds the WHERE part of the sql query to match tags by AND/OR binary matches
		# matches are grouped into groups of AND matches with extra groups being OR
		# example:
		# ( tagmatch AND tagmatch AND tagmatch ) OR ( tagmatch AND tagmatch AND tagmatch )
		tags_match = []
		tag_group = []
		for tag in tags:
			if tag:
				# anything prepended with a pipe is an OR match
				if tag[0] == '|':
					if len(tag_group) > 0:
						tags_match.append(tag_group)
						tag_group = []
					tag_group.append(' a.tags REGEXP "(^|,){}(,|$)" '.format(tag[1:]))
				else: # anything else is an AND match
					tag_group.append(' a.tags REGEXP "(^|,){}(,|$)" '.format(tag))

		# add any dangly groups to match list
		if len(tag_group) > 0:
			tags_match.append(tag_group)

		# build actual WHERE query part from groups
		if len(tags_match) > 0:
			tags_match = " OR ".join( \
				["({})".format(" AND ".join(group)) \
					for group in tags_match]
			)
			if tags_match:
				tags_match = "({})".format(tags_match)

		else:
			tags_match = ""

		sql_count = "SELECT count(*) \
			FROM `tabAWC Item` a\
			{};\
			".format("WHERE %s" % tags_match if tags_match else "")

		result_count = cint(frappe.db.sql(sql_count, as_list=1)[0][0])

		sql = """SELECT
			i.name,
			i.item_code,
			i.item_name,
			i.has_variants,
			i.standard_rate,
			i.net_weight,
			a.name as awc_item_name,
			a.product_route as awc_product_route,
			a.description_short as awc_description_short,
			a.description_long as awc_description_long,
			a.listing_widget as awc_listing_widget,
			a.product_widget as awc_product_widget,
			a.product_template as awc_product_template,
			a.product_thumbnail as awc_product_thumbnail,
			a.slider as awc_slider,
			a.tags as awc_tags
			FROM `tabAWC Item` a, `tabItem` i
			WHERE i.name = a.product_name
			AND a.catalog_visible = 1 and i.disabled != 1
			{}
			ORDER BY {} {}
			{}""".format(
				"AND %s" % tags_match if tags_match else "",
				order_by_clean,
				order_dir_clean,
				"LIMIT {}, {}".format(
					int(start),
					int(limit)) if limit != None else ""
				)

		result = frappe.db.sql(sql, as_dict=1)

		products = []
		for item in result:
			price_info = get_price(item.get("item_code"), price_list)
			price = price_info.get("rate")

			variants = frappe.get_all("Item", fields=["name", "item_code"], filters={"variant_of": item.get("name"), "disabled": 0})
			for vitem in variants:
				vprice = get_price(vitem.get("item_code"), price_list).get("rate")
				if vprice < price:
					price = vprice

			product = dict(
				sku=item.item_code,
				name=item.item_name,
				weight=item.get("net_weight", 0),
				custom=get_awc_item_custom_data(item.awc_item_name),
				productUrl="/p/%s" % item.awc_product_route,
				description=item.awc_description_short,
				imageUrl=item.awc_product_thumbnail,
				base_price=price_info.get("base_price_rate") ,
				price=price,
				listing_widget=item.awc_listing_widget,
				product_widget=item.awc_product_widget,
				product_template=item.awc_product_template,
				options=build_awc_options_from_varients(item),
				tags=item.awc_tags.split(',') if item.awc_tags else []
			)
			products.append(product)

		payload["success"] = True
		payload["total_records"] = result_count
		payload["data"] = products
	except Exception as ex:
		log("ERROR")
		payload["success"] = False
		payload["message"] = traceback.format_exc(ex)
		log(payload["message"])

	set_cache(cache_key, payload, session=awc_session)

	return payload
Exemple #12
0
def cart(data=None, action=None):
	if data and isinstance(data, basestring):
		try:
			data = json.loads(data)
		except ex:
			log("REMOTE ADDR: {0}".format(frappe.request.get("remote_addr", "NO REMOTE ADDRESS?")))
			log("URL: {0}".format(frappe.request.get("url", "NO URL DATA")))
			log("Action: {0}".format(action))
			log("Data: {0}".format(data))
			log(traceback.format_exc())
			data = None

	# make sure we can handle bulk actions
	if not isinstance(data, list):
		data = [data]

	customer = get_current_customer()
	quotation = None
	awc_session = get_awc_session()

	quotation_is_dirty = False
	if customer:
		cart_info = get_user_quotation(awc_session)
		quotation = cart_info.get('doc')

		if len(quotation.items) == 0:
			apply_cart_settings(quotation=quotation)

	awc = awc_session.get("cart")

	if not awc:
		awc = clear_awc_session()

	if quotation:
		quotation_is_dirty = sync_awc_and_quotation(awc_session, quotation)
	else:
		call_awc_sync_hook(awc_session, quotation)

	if not action:
		save_and_commit_quotation(quotation, quotation_is_dirty, awc_session, commit=True)
		return { "data": awc, "success": True}

	elif action == "calculate_shipping":
		rate_name = data[0].get("name")
		address = data[0].get("address")
		if address:
			address_name = address.get("shipping_address")
			if not address_name:
				new_address = frappe.new_doc("Address")
				new_address.update({
					"address_title": data[0].get("address").get("title"),
					"address_type": data[0].get("address").get("address_type", "Shipping"),
					"address_contact": data[0].get("address").get("address_contact", ""),
					"address_line1": data[0].get("address").get("address_1"),
					"address_line2": data[0].get("address").get("address_2"),
					"city": data[0].get("address").get("city"),
					"state": data[0].get("address").get("state"),
					"country": data[0].get("address").get("country"),
					"phone": data[0].get("address").get("phone"),
					"email_id": frappe.session.user,
					"pincode": data[0].get("address").get("pincode"),
					"links": [{"link_doctype" : "Customer", "link_name" : quotation.customer}]
				})
				new_address.flags.ignore_permissions= True
				new_address.save()
				address_name = new_address.name
				address["shipping_address"] = address_name

			if quotation:
				quotation.shipping_address_name = address_name
				awc_session["last_shipping_address"] = address_name

		# check and update use_customer_fedex_account field in quotation
		if quotation:
			quotation.use_customer_fedex_account = 1 if data[0].get(
				"address", {}).get("use_customer_fedex_account") else 0
			quotation.flags.ignore_permissions = True
			quotation.save()
			frappe.db.commit()

		result = calculate_shipping(rate_name, address, awc_session, quotation, save=True)

		return result

	elif action == "updateItem":

		# for now only qty field is updatable
		# key in awc fields and values are erpnext fields for quotation item
		valid_update_fields = {"qty": "qty"}
		remove_items = []
		removed_ids = []

		for item in data:

			awc_item = next((i for i in awc["items"] if i.get("id") == item.get("id")), None)

			if awc_item:
				quotation_item = None
				if quotation:
					quotation_item = next((q for q in quotation.get("items", []) if q.name == awc_item.get("id")), None)

				for awc_key, erp_key in valid_update_fields.iteritems():

					if awc_key in item:
						awc_item[awc_key] = item.get(awc_key)

						if awc_key == "qty":
							awc_item["total"] = awc_item["unit"] * awc_item["qty"]

						if quotation_item:
							quotation_item.set(erp_key, item.get(awc_key))

							#if erp_key == "qty":
								#quotation_item.amount = flt(quotation_item.rate) * quotation_item.qty

				if awc_item.get('options', {}).get('group'):
					# find all subgroup items and update qty accordingly
					for sub_item in [i for i in awc["items"] if i.get('options', {}).get('group') == awc_item.get('options', {}).get('group')]:
						sub_item["qty"] = awc_item["qty"]

						if quotation:
							sub_quotation_item = next((q for q in quotation.get("items", []) if q.name == sub_item.get("id")), None)
							if sub_quotation_item:
								sub_quotation_item.set("qty", sub_item.get("qty"))
								#sub_quotation_item.amount = flt(sub_quotation_item.rate) * sub_quotation_item.qty

						sub_item["total"] = sub_item["unit"] * sub_item["qty"]

				if awc_item.get("qty") == 0:
					remove_items.append(awc_item)

		# remove all 0 qty items
		if len(remove_items) > 0:
			success, removed_ids, awc_items = remove_from_cart(remove_items, awc["items"])
			if success:
				awc["items"] = awc_items

				if quotation:
					# remove item and related grouped items from quote
					quotation_items = [ itm for itm in quotation.get("items", []) \
						if itm.name not in removed_ids ]

					quotation.set("items", quotation_items)

		shipping_info = calculate_shipping(None, None, awc_session, quotation, save=0, force=True)

		if quotation:
			quotation_is_dirty=True

		save_and_commit_quotation(quotation, quotation_is_dirty, awc_session, commit=True)

		return session_response({
			"success": True,
			"removed": removed_ids,
			"shipping_rates": shipping_info.get("shipping_rates")
		}, awc_session, quotation)

	elif action == "addToCart":

		to_remove = []

		for item in data:
			# need basic data validation here
			if not item.get("sku"):
				return { "success": False, "message": "Invalid Data" }

			if not item.get("qty"):
				return { "success": False, "message": "Invalid Data" }

			product = get_product_by_sku(item.get("sku"), quotation=quotation).get("data")

			if item.get("replaces"):
				to_remove.append(item.get("replaces"))
				del item["replaces"]

			if item.get("options", {}).get("custom", {}).get("rate", None) != None:
				item["total"] = flt(item["options"]["custom"]["rate"]) * item.get("qty")
				item["unit"] = flt(item["options"]["custom"]["rate"])
			else:
				item["total"] = flt(product.get("price") * item.get("qty"))
				item["unit"] = flt(product.get("price"))

			if quotation:

				item_data = {
					"doctype": "Quotation Item",
					"item_code": item.get("sku"),
					"item_name": product.get("name"),
					"description": item.get("options", {}).get("description", product.get("name")),
					"qty": cint(item.get("qty")),
					"warehouse": product.get("warehouse")
				}

				update_quotation_item_awc_fields(item_data, item)

				quotation_item = quotation.append("items", item_data)

				# TODO: ( >_<) shitty way of setting rate due to rate reset
				#       Please fix when not utterly pissed off
				if item.get("options", {}).get("custom", {}).get("rate", None) != None:
					quotation_item.set("ignore_pricing_rule", 1)
					set_quotation_item_rate(quotation_item, item["options"]["custom"]["rate"], product)
					item_data['total'] = item["options"]["custom"]["rate"] * cint(item.get("qty"))
				else:
					quotation_item.set("ignore_pricing_rule", 0)
					set_quotation_item_rate(quotation_item, product.get("price"), product)
					item_data['total'] = product.get("price") * cint(item.get("qty"))

				quotation_item.save()

				item["old_id"] = item["id"]
				item["id"] = quotation_item.name

			awc["items"].append(item)

		removed_ids = []
		if len(to_remove) > 0:
			remove_success, removed_ids, awc_items = remove_from_cart([{ "id": x} for x in to_remove], awc["items"])
			if remove_success:
				awc["items"] = awc_items

		if quotation:
			if len(removed_ids) > 0:
				# remove item and related grouped items from quote
				quotation_items = [ itm for itm in quotation.get("items", []) \
					if itm.name not in removed_ids ]

				quotation.set("items", quotation_items)

			update_cart_settings(quotation, awc_session)
			quotation_is_dirty = True

		save_and_commit_quotation(quotation, quotation_is_dirty, awc_session, commit=True)

		return session_response({
			"success": True,
			"removed": removed_ids
		}, awc_session, quotation)

	elif action == "removeFromCart":

		success = False
		removed_ids = []

		success, removed_ids, awc_items = remove_from_cart(data, awc["items"])

		if success:

			awc["items"] = awc_items

			if quotation:
				# remove item and related grouped items from quote
				quotation_items = [ itm for itm in quotation.get("items", []) \
					if itm.name not in removed_ids ]

				quotation.set("items", quotation_items)
				quotation_is_dirty = True

			save_and_commit_quotation(quotation, quotation_is_dirty, awc_session, commit=True)

			return session_response({
				"success": True,
				"removed": removed_ids
			}, awc_session, quotation)

		return session_response({
			"success": False,
			"message": "Item not found."
		}, awc_session, quotation)

	elif action == "applyCoupon" and len(data) > 0:
		coupon = data[0]
		success = False
		msg = "Coupon not found"

		if quotation:
			# validate coupon
			msg = is_coupon_valid(coupon)
			is_valid = frappe.response.get("is_coupon_valid")

			if is_valid:
				discount, msg = calculate_coupon_discount(quotation.items, coupon)[0:2]
				if discount == 0:
					is_valid = False
					success = False
					msg = "Coupon is invalid for the current cart."
				else:
					quotation.coupon_code = coupon
					quotation_is_dirty = True
					success = True
		else:
			# must be logged in to use cupon
			success = False
			msg = "Please Login to Apply Coupon"

		if success:
			save_and_commit_quotation(quotation, quotation_is_dirty, awc_session, commit=True)
			return session_response({
				"success": True
			}, awc_session, quotation)

		save_and_commit_quotation(quotation, quotation_is_dirty, awc_session, commit=True)
		return session_response({
			"success": False,
			"message": _(msg)
		}, awc_session, quotation)


	elif action == "removeCoupon":
		if quotation:
			quotation.discount_amount = 0
			quotation.coupon_code = None
			quotation_is_dirty = True

		save_and_commit_quotation(quotation, quotation_is_dirty, awc_session, commit=True)

		return session_response({
			"success": True
		}, awc_session, quotation)

	else:
		return session_response({
			"success": False,
			"message": "Unknown Command."
		}, awc_session, quotation)
Exemple #13
0
    def on_payment_authorized(self, payment_status):
        try:

            # dumb loop to catch out of sync quotation.
            # loop at least 3 times for timestamp error
            tries = 3
            while tries > 0:
                try:
                    quotation = self.update_quotation()
                    tries = 0  # success, exit loop
                except Exception as ex:
                    tries = tries - 1
                    if tries <= 0:
                        raise ex
                    else:
                        msg = """
						--------------------------------------------------------------
						Caught error while trying to save quotation in awc transaction
						Attempting to save again...
						{}
						- RESPONSE --------------
						{}""".format(ex, frappe.local.respone)
                        log(msg, trace=1)

            # create sales order
            so = convert_quotation_to_sales_order(quotation)

            if self.flags.get("skip_payment_request", False):
                so.submit()

                self.reference_doctype = "Sales Order"
                self.reference_docname = so.name
            else:
                # then immediately create payment request
                # no emails should be sent as this is intended for immediate fullfilment

                req_type = frappe.local.response.get("type", None)
                req_location = frappe.local.response.get("location", None)

                preq = payment_request.make_payment_request(dt="Sales Order",
                                                            dn=so.name,
                                                            submit_doc=1,
                                                            return_doc=1,
                                                            mute_email=1)

                #############################################################
                # DIRTY FIX: payment request codebase redirects
                # shopping cart payment requests. Here we are undoing that.
                if req_type:
                    frappe.local.response["type"] = req_type
                elif frappe.local.response.get("type"):
                    frappe.local.response.pop("type")

                if req_location:
                    frappe.local.response["location"] = req_location
                elif frappe.local.response.get("location"):
                    frappe.local.response.pop("location")
                #############################################################

                preq.flags.ignore_permissions = 1

                if preq.docstatus != 1:
                    preq.submit()

                # update transaction record to track payment request record
                self.reference_doctype = "Payment Request"
                self.reference_docname = preq.name

            self.order_id = so.name
            self.flags.ignore_permissions = 1
            self.save()

            if not self.flags.get("skip_payment_request", False):
                # finally let payment request run its own code to finalize transaction
                # invoice, payment entry docs should be created here
                result = preq.run_method("on_payment_authorized",
                                         payment_status)
            else:
                result = None

            if self.get("gateway_service"):
                has_universals = False
                for item in frappe.get_doc("Sales Order", self.order_id).items:
                    if frappe.db.get_value("Item", item.item_code,
                                           "item_group") == "Universal":
                        has_universals = True
                if self.get("gateway_service") == "credit_gateway":
                    frappe.db.set_value("Sales Order", self.order_id,
                                        "payment_method", "Bill Me")
                elif self.get("gateway_service") == "paypal":
                    frappe.db.set_value("Sales Order", self.order_id,
                                        "payment_method", "PayPal")
                    frappe.db.set_value("Sales Order", self.order_id,
                                        "authorize_production", False)
                    frappe.db.set_value("Sales Order", self.order_id,
                                        "authorize_delivery", False)
                else:
                    if self.get("gateway_service") == "authorizenet":
                        frappe.db.set_value("Sales Order", self.order_id,
                                            "payment_method", "Card")
                    else:
                        frappe.db.set_value("Sales Order", self.order_id,
                                            "payment_method",
                                            self.get("gateway_service"))

                    if not has_universals:
                        frappe.db.set_value("Sales Order", self.order_id,
                                            "authorize_production", True)
                        frappe.db.set_value("Sales Order", self.order_id,
                                            "authorize_delivery", True)

            # override redirection to orders page
            if result:
                result = '/iems#filter=custom'

            # this is here to remove duplication warning messages.
            # TODO: Consider bring this to erpnext team to remove warning
            if frappe.local.message_log:
                for msg in frappe.local.message_log:
                    log(msg)
                frappe.local.message_log = []

            # don't kill processing if saving cleaning session address info breaks
            try:
                # clears awc session data
                awc.clear_awc_session()
            except Exception as awc_ex:
                log(frappe.get_traceback())
                pass

            return result
        except Exception as ex:
            log(frappe.get_traceback())
            raise ex