Пример #1
0
def create_sales_invoice(shop_name: str, shopify_order: "Order",
                         sales_order: "SalesOrder"):
    """
	Helper function to create a Sales Invoice document for a Shopify order.

	Args:
		shop_name (str): The name of the Shopify configuration for the store.
		shopify_order (Order): The Shopify order data.
		sales_order (SalesOrder): The reference Sales Order document for the Shopify order.

	Returns:
		SalesInvoice: The created or existing Sales Invoice document, if any, otherwise None.
	"""

    shopify_settings: "ShopifySettings" = frappe.get_doc(
        "Shopify Settings", shop_name)
    if not cint(shopify_settings.sync_sales_invoice):
        return

    existing_invoice = get_shopify_document(shop_name=shop_name,
                                            doctype="Sales Invoice",
                                            order=shopify_order)
    if existing_invoice:
        existing_invoice: "SalesInvoice"
        frappe.db.set_value(
            "Sales Invoice", existing_invoice.name, {
                "shopify_settings":
                shopify_settings.name,
                "shopify_order_id":
                shopify_order.id,
                "shopify_order_number":
                shopify_order.attributes.get("order_number")
            })
        return existing_invoice

    if sales_order.docstatus == 1 and not sales_order.per_billed:
        sales_invoice: "SalesInvoice" = make_sales_invoice(
            sales_order.name, ignore_permissions=True)
        sales_invoice.update({
            "shopify_settings":
            shopify_settings.name,
            "shopify_order_id":
            shopify_order.id,
            "shopify_order_number":
            shopify_order.attributes.get("order_number"),
            "set_posting_time":
            True,
            "posting_date":
            getdate(shopify_order.attributes.get("created_at")),
            "naming_series":
            shopify_settings.sales_invoice_series or "SI-Shopify-"
        })

        for item in sales_invoice.items:
            item.cost_center = shopify_settings.cost_center

        sales_invoice.flags.ignore_mandatory = True
        sales_invoice.insert(ignore_mandatory=True)
        frappe.db.commit()
        return sales_invoice
	def test_order(self):
		shopify_settings = frappe.get_doc("Shopify Settings", "Test Shopify")

		# create customer
		with open(os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
			customer = Customer()
			customer_data = json.loads(shopify_customer.read())
			formatted_customer_data = prepare_customer_format(customer_data)
			customer.attributes.update(formatted_customer_data)
			create_customer(shopify_settings.name, customer)

		# create item
		with open(os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
			item = Product()
			product_data = json.loads(shopify_item.read())
			formatted_product_data = prepare_product_format(product_data)
			item.attributes.update(formatted_product_data)
			make_item(shopify_settings, item)

		# create order, invoice and delivery
		with open(os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
			order = Order()
			order_data = json.loads(shopify_order.read())
			formatted_order_data = prepare_order_format(order_data)
			order.attributes.update(formatted_order_data)

			sales_order = create_sales_order(shopify_settings.name, order)
			create_shopify_invoice(shopify_settings.name, order, sales_order)
			create_shopify_delivery(shopify_settings.name, order, sales_order)

		# verify sales order IDs
		sales_order = get_shopify_document(shopify_settings.name, "Sales Order", order_id=order.id)
		self.assertEqual(cstr(order.id), sales_order.shopify_order_id)

		# verify customer IDs
		shopify_order_customer_id = cstr(order.customer.id)
		sales_order_customer_id = frappe.db.get_value("Customer", sales_order.customer, "shopify_customer_id")
		self.assertEqual(shopify_order_customer_id, sales_order_customer_id)

		# verify sales invoice totals
		sales_invoice = get_shopify_document(shopify_settings.name, "Sales Invoice", order_id=sales_order.shopify_order_id)
		self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)

		# verify delivery notes created for all fulfillments
		delivery_note = get_shopify_document(shopify_settings.name, "Delivery Note", order_id=sales_order.shopify_order_id)
		self.assertEqual(len(delivery_note.items), len(order.fulfillments))
Пример #3
0
def create_missing_orders(shopify_settings: "ShopifySettings", shopify_order_ids: List[str]):
	"""
	Create missing Sales Orders, Sales Invoices and Delivery Notes, if enabled in Shopify Settings.

	Args:
		shopify_settings (ShopifySettings): The Shopify configuration for the store.
		shopify_order_ids (list of str): The Shopify order IDs to create documents against.
	"""

	for shopify_order_id in shopify_order_ids:
		sales_order = get_shopify_document(shop_name=shopify_settings.name,
			doctype="Sales Order", order_id=shopify_order_id)
		sales_invoice = get_shopify_document(shop_name=shopify_settings.name,
			doctype="Sales Invoice", order_id=shopify_order_id)
		delivery_note = get_shopify_document(shop_name=shopify_settings.name,
			doctype="Delivery Note", order_id=shopify_order_id)

		if all([sales_order, sales_invoice, delivery_note]):
			continue

		orders = shopify_settings.get_orders(shopify_order_id)
		if not orders:
			continue

		order: "Order" = orders[0]

		# create an order, invoice and delivery, if missing
		if not sales_order:
			sales_order = create_shopify_order(shopify_settings.name, order)

		if sales_order:
			sales_order: "SalesOrder"
			if not sales_invoice:
				create_shopify_invoice(shopify_settings.name, order, sales_order)
			if not delivery_note or sales_order.per_delivered < 100:
				# multiple deliveries can be made against a single order
				create_shopify_delivery(shopify_settings.name, order, sales_order)
Пример #4
0
def prepare_sales_invoice(shop_name: str, order: "Order", log_id: str = str()):
    """
	Webhook endpoint to process invoices for Shopify orders.

	Args:
		shop_name (str): The name of the Shopify configuration for the store.
		order (Order): The Shopify order data.
		log_id (str, optional): The ID of an existing Shopify Log.
			Defaults to an empty string.
	"""

    from shopify_integration.orders import create_shopify_documents

    frappe.set_user("Administrator")
    frappe.flags.log_id = log_id

    try:
        sales_order = get_shopify_document(shop_name=shop_name,
                                           doctype="Sales Order",
                                           order=order)
        if not sales_order:
            create_shopify_documents(shop_name, order, log_id)
            sales_order = get_shopify_document(shop_name=shop_name,
                                               doctype="Sales Order",
                                               order=order)

        if sales_order:
            sales_order: "SalesOrder"
            create_sales_invoice(shop_name, order, sales_order)
            make_shopify_log(status="Success", response_data=order.to_dict())
        else:
            make_shopify_log(status="Skipped", response_data=order.to_dict())
    except Exception as e:
        make_shopify_log(status="Error",
                         response_data=order.to_dict(),
                         exception=e,
                         rollback=True)
Пример #5
0
def cancel_shopify_order(shop_name: str, order: "Order", log_id: str = str()):
    """
	Cancel all sales documents if a Shopify order is cancelled.

	Args:
		shop_name (str): The name of the Shopify configuration for the store.
		order (Order): The Shopify order data.
		log_id (str, optional): The ID of an existing Shopify Log.
			Defaults to an empty string.
	"""

    frappe.set_user("Administrator")
    frappe.flags.log_id = log_id

    doctypes = ["Delivery Note", "Sales Invoice", "Sales Order"]
    for doctype in doctypes:
        doc = get_shopify_document(shop_name=shop_name,
                                   doctype=doctype,
                                   order=order)
        if not doc:
            continue

        # recursively cancel all Shopify documents
        if doc.docstatus == 1:
            try:
                # ignore document links to Shopify Payout while cancelling
                doc.flags.ignore_links = True
                doc.cancel()
            except Exception as e:
                make_shopify_log(status="Error",
                                 response_data=order.to_dict(),
                                 exception=e,
                                 rollback=True)

        # update the financial status in all linked Shopify Payouts
        payout_transactions = frappe.get_all(
            "Shopify Payout Transaction",
            filters={
                frappe.scrub(doctype):
                doc.name,
                "source_order_financial_status":
                ["!=", order.attributes.get("financial_status")]
            })

        for transaction in payout_transactions:
            frappe.db.set_value(
                "Shopify Payout Transaction", transaction.name,
                "source_order_financial_status",
                frappe.unscrub(order.attributes.get("financial_status")))
Пример #6
0
def create_shopify_delivery(shop_name: str,
                            shopify_order: "Order",
                            sales_order: "SalesOrder" = None,
                            log_id: str = str(),
                            rollback: bool = False):
    """
	Create Delivery Note documents for each Shopify delivery.

	Args:
		shop_name (str): The name of the Shopify configuration for the store.
		shopify_order (Order): The Shopify order data.
		sales_order (SalesOrder, optional): The reference Sales Order document for the
			Shopify order. Defaults to None.
		log_id (str, optional): The ID of an existing Shopify Log. Defaults to an empty string.
		rollback (bool, optional): If an error occurs while processing the order, all
			transactions will be rolled back, if this field is `True`. Defaults to False.

	Returns:
		list: The list of created Delivery Note documents, if any, otherwise an empty list.
	"""

    if not shopify_order.attributes.get("fulfillments"):
        return []
    if not sales_order:
        sales_order = get_shopify_document(shop_name=shop_name,
                                           doctype="Sales Order",
                                           order=shopify_order)
    if not sales_order or sales_order.docstatus != 1:
        return []

    frappe.flags.log_id = log_id
    try:
        delivery_notes = create_delivery_notes(shop_name, shopify_order,
                                               sales_order)
    except Exception as e:
        make_shopify_log(status="Error",
                         response_data=shopify_order.to_dict(),
                         exception=e,
                         rollback=rollback)
        return []
    else:
        make_shopify_log(status="Success",
                         response_data=shopify_order.to_dict())
        return delivery_notes
Пример #7
0
def create_shopify_order(shop_name: str,
                         shopify_order: "Order",
                         log_id: str = str()):
    """
	Create a Sales Order document for a Shopify order.

	Args:
		shop_name (str): The name of the Shopify configuration for the store.
		shopify_order (Order): The Shopify order data.
		log_id (str, optional): The ID of an existing Shopify Log. Defaults
			to an empty string.

	Returns:
		SalesOrder: The created Sales Order document, if any, otherwise None.
	"""

    from shopify_integration.customers import validate_customer
    from shopify_integration.products import validate_item

    frappe.flags.log_id = log_id

    existing_so = get_shopify_document(shop_name=shop_name,
                                       doctype="Sales Order",
                                       order=shopify_order)
    if existing_so:
        existing_so: "SalesOrder"
        make_shopify_log(status="Skipped",
                         response_data=shopify_order.to_dict())
        return existing_so

    try:
        validate_customer(shop_name, shopify_order)
        validate_item(shop_name, shopify_order)
        sales_order = create_sales_order(shop_name, shopify_order)
    except Exception as e:
        make_shopify_log(status="Error",
                         response_data=shopify_order.to_dict(),
                         exception=e)
    else:
        make_shopify_log(status="Success",
                         response_data=shopify_order.to_dict())
        return sales_order
Пример #8
0
def create_shopify_payout(shopify_settings: "ShopifySettings", payout: "Payouts"):
	"""
	Create a Shopify Payout document from Shopify's Payout information.

	Args:
		shopify_settings (ShopifySettings): The Shopify configuration for the store.
		payout (Payouts): The Payout payload from Shopify.

	Returns:
		ShopifyPayout: The created Shopify Payout document.
	"""

	payout_doc: "ShopifyPayout" = frappe.new_doc("Shopify Payout")
	payout_doc.update({
		"shop_name": shopify_settings.name,
		"company": shopify_settings.company,
		"payout_id": payout.id,
		"payout_date": getdate(payout.date),
		"status": frappe.unscrub(payout.status),
		"amount": flt(payout.amount),
		"currency": payout.currency,
		**payout.summary.to_dict()  # unpack the payout amounts and fees from the summary
	})

	try:
		payout_transactions: List["Transactions"] = shopify_settings.get_payout_transactions(payout_id=payout.id)
	except Exception as e:
		payout_doc.save(ignore_permissions=True)
		make_shopify_log(status="Payout Transactions Error", response_data=payout.to_dict(), exception=e)
		return payout_doc

	payout_doc.set("transactions", [])
	for transaction in payout_transactions:
		shopify_order_id = transaction.source_order_id

		order_financial_status = sales_order = sales_invoice = delivery_note = None
		if shopify_order_id:
			orders = shopify_settings.get_orders(shopify_order_id)
			if not orders:
				continue
			order = orders[0]
			order_financial_status = frappe.unscrub(order.financial_status)

			sales_order = get_shopify_document(shop_name=shopify_settings.name,
				doctype="Sales Order", order_id=shopify_order_id)
			sales_invoice = get_shopify_document(shop_name=shopify_settings.name,
				doctype="Sales Invoice", order_id=shopify_order_id)
			delivery_note = get_shopify_document(shop_name=shopify_settings.name,
				doctype="Delivery Note", order_id=shopify_order_id)

		total_amount = -flt(transaction.amount) if transaction.type == "payout" else flt(transaction.amount)
		net_amount = -flt(transaction.net) if transaction.type == "payout" else flt(transaction.net)

		payout_doc.append("transactions", {
			"transaction_id": transaction.id,
			"transaction_type": frappe.unscrub(transaction.type),
			"processed_at": getdate(transaction.processed_at),
			"total_amount": total_amount,
			"fee": flt(transaction.fee),
			"net_amount": net_amount,
			"currency": transaction.currency,
			"sales_order": sales_order.name if sales_order else None,
			"sales_invoice": sales_invoice.name if sales_invoice else None,
			"delivery_note": delivery_note.name if delivery_note else None,
			"source_id": transaction.source_id,
			"source_type": frappe.unscrub(transaction.source_type),
			"source_order_financial_status": order_financial_status,
			"source_order_id": shopify_order_id,
			"source_order_transaction_id": transaction.source_order_transaction_id,
		})

	payout_doc.save(ignore_permissions=True)
	frappe.db.commit()
	return payout_doc