Skip to content

Instantly share code, notes, and snippets.

@robjstanley
Created April 16, 2025 15:20
Show Gist options
  • Save robjstanley/7628330cd8c1555bf4b3eec78617e93d to your computer and use it in GitHub Desktop.
Save robjstanley/7628330cd8c1555bf4b3eec78617e93d to your computer and use it in GitHub Desktop.
Convert Visualsoft Order XML to Matrixify CSV
import xml.etree.ElementTree as ET
import csv
import os
import re
def natural_sort_key(s):
return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', s)]
orders_dir = "orders"
xml_files = sorted([os.path.join(orders_dir, file) for file in os.listdir(orders_dir) if file.endswith(".xml")], key=natural_sort_key)
headers = [
"Name", "Command", "Send Receipt", "Inventory Behaviour", "Number", "Phone", "Email", "Note",
"Tags", "Tags Command", "Cancelled At", "Cancel: Reason", "Cancel: Send Receipt", "Cancel: Refund",
"Processed At", "Closed At", "Currency", "Source", "Source Identifier", "Source URL", "Weight Total",
"Tax: Included", "Tax: Total", "Payment: Status", "Additional Details", "Customer: Email",
"Customer: Phone", "Customer: First Name", "Customer: Last Name", "Customer: Note", "Customer: State",
"Customer: Tags", "Billing: First Name", "Billing: Last Name", "Billing: Name", "Billing: Company",
"Billing: Phone", "Billing: Address 1", "Billing: Address 2", "Billing: Zip", "Billing: City",
"Billing: Province", "Billing: Province Code", "Billing: Country", "Billing: Country Code",
"Shipping: First Name", "Shipping: Last Name", "Shipping: Name", "Shipping: Company", "Shipping: Phone",
"Shipping: Address 1", "Shipping: Address 2", "Shipping: Zip", "Shipping: City", "Shipping: Province",
"Shipping: Province Code", "Shipping: Country", "Shipping: Country Code", "Line: Type", "Line: Command",
"Line: Product ID", "Line: Product Handle", "Line: Title", "Line: Name", "Line: Variant ID",
"Line: Variant Title", "Line: SKU", "Line: Quantity", "Line: Price", "Line: Discount", "Line: Grams",
"Line: Requires Shipping", "Line: Vendor", "Line: Properties", "Line: Gift Card", "Line: Force Gift Card",
"Line: Taxable", "Line: Tax 1 Title", "Line: Tax 1 Rate", "Line: Tax 1 Price", "Line: Fulfillment Service",
"Transaction: Kind", "Transaction: Processed At", "Transaction: Amount", "Transaction: Currency",
"Transaction: Status", "Transaction: Gateway", "Transaction: Force Gateway", "Transaction: Test",
"Transaction: Authorization", "Transaction: Parent ID", "Fulfillment: ID", "Fulfillment: Status",
"Fulfillment: Processed At", "Fulfillment: Tracking Company", "Fulfillment: Location",
"Fulfillment: Shipment Status", "Fulfillment: Tracking Number", "Fulfillment: Tracking URL",
"Fulfillment: Send Receipt"
]
max_orders = 10000 # Matrixify limit
order_count = 0
file_count = 1
writer = None
# Function to create a new CSV file and writer
def create_csv_writer():
global writer, file_count
csv_file = f"orders_{file_count}.csv"
file_count += 1
file = open(csv_file, mode='w', newline='', encoding='utf-8')
writer = csv.DictWriter(file, fieldnames=headers)
writer.writeheader()
return file
file = create_csv_writer()
# Iterate over all XML files in the orders directory
for xml_file in xml_files:
print(f"Processing {xml_file}...")
if xml_file.endswith(".xml"):
tree = ET.parse(os.path.join(xml_file))
root = tree.getroot()
# Iterate over each order in the XML file
for order in root.findall(".//web_order"):
payment_status = "paid"
if order.findtext(".//order_state") == "Order Refunded":
payment_status = "refunded"
global_row = {key: "" for key in headers}
global_row.update({
"Name": order.findtext(".//order_reference", ""),
"Command": "REPLACE",
"Send Receipt": "false",
"Inventory Behaviour": "bypass",
"Number": order.findtext(".//order_id", ""),
"Phone": "",
"Email": order.findtext(".//email_address", ""),
"Note": order.findtext(".//order/notes","") + "\n" + order.findtext(".//payment/notes", ""),
"Tags": "visualsoft_order,state-" + order.findtext(".//order/order_state","").replace(" ","_").lower() + "," + (f"parent_{ref}" if (ref := order.findtext(".//parent_order/order/order_reference")) else ""),
"Tags Command": "MERGE",
"Cancelled At": "",
"Cancel: Reason": "",
"Cancel: Send Receipt": "false",
"Cancel: Refund": "false",
"Processed At": order.findtext(".//order_date", ""),
"Closed At": "",
"Currency": order.findtext(".//order_currency", ""),
"Source": order.findtext(".//order_type", ""),
"Source Identifier": order.findtext(".//order_reference", ""),
"Source URL": "",
"Weight Total": "",
"Tax: Included": "true",
"Tax: Total": order.findtext(".//grand_total_vat", ""),
"Payment: Status": payment_status,
"Additional Details": "",
"Customer: Email": order.findtext(".//email_address", ""),
"Customer: Phone": "",
"Customer: First Name": order.findtext(".//billing_firstname", ""),
"Customer: Last Name": order.findtext(".//billing_lastname", ""),
"Customer: Note": "",
"Customer: State": "",
"Customer: Tags": "",
"Billing: First Name": order.findtext(".//billing_firstname", ""),
"Billing: Last Name": order.findtext(".//billing_lastname", ""),
"Billing: Name": order.findtext(".//billing_fullname", ""),
"Billing: Company": "",
"Billing: Phone": "",
"Billing: Address 1": order.findtext(".//billing_address1", ""),
"Billing: Address 2": order.findtext(".//billing_address2", ""),
"Billing: Zip": order.findtext(".//billing_postcode", ""),
"Billing: City": order.findtext(".//billing_town", ""),
"Billing: Province": "",
"Billing: Province Code": "",
"Billing: Country": order.findtext(".//billing_country_name", ""),
"Billing: Country Code": "",
"Shipping: First Name": order.findtext(".//delivery_firstname", ""),
"Shipping: Last Name": order.findtext(".//delivery_lastname", ""),
"Shipping: Name": order.findtext(".//delivery_fullname", ""),
"Shipping: Company": "",
"Shipping: Phone": "",
"Shipping: Address 1": order.findtext(".//delivery_address1", ""),
"Shipping: Address 2": order.findtext(".//delivery_address2", ""),
"Shipping: Zip": order.findtext(".//delivery_postcode", ""),
"Shipping: City": order.findtext(".//delivery_town", ""),
"Shipping: Province": "",
"Shipping: Province Code": "",
"Shipping: Country": order.findtext(".//delivery_country_name", ""),
"Shipping: Country Code": "",
})
for product in order.findall(".//products"):
row = global_row.copy()
row.update({
"Line: Type": "Line Item",
"Line: Command": "DEFAULT",
"Line: Product ID": "",
"Line: Product Handle": "",
"Line: Title": product.findtext(".//title", ""),
"Line: Name": product.findtext(".//title", "") + " - " + product.findtext(".//summary", ""),
"Line: Variant ID": "",
"Line: Variant Title": product.findtext(".//summary", ""),
"Line: SKU": product.findtext(".//model", ""),
"Line: Quantity": product.findtext(".//quantity", ""),
"Line: Price": product.findtext(".//price_inc", ""),
"Line: Discount": "",
"Line: Grams": product.findtext(".//weight", ""),
"Line: Requires Shipping": "",
"Line: Vendor": product.findtext(".//manufacturer_name", ""),
"Line: Properties": "",
"Line: Gift Card": "false",
"Line: Force Gift Card": "",
"Line: Taxable": "true",
"Line: Tax 1 Title": "",
"Line: Tax 1 Rate": "",
"Line: Tax 1 Price": "",
"Line: Fulfillment Service": "manual",
})
writer.writerow(row)
if float(order.findtext(".//order/discount_inc")) > 0:
row = global_row.copy()
row.update({
"Line: Type": "Discount",
"Line: Command": "DEFAULT",
"Line: Title": "fixed_amount",
"Line: Name": "Discount",
"Line: Price": float(order.findtext(".//order/product_total_inc")) - float(order.findtext(".//order/discount_inc")),
"Line: Discount": '-' + order.findtext(".//order/discount_inc"),
})
writer.writerow(row)
if float(order.findtext(".//order/shipping_total_inc")) > 0:
row = global_row.copy()
row.update({
"Line: Type": "Shipping Line",
"Line: Title": "Shipping " + order.findtext(".//courier_name", ""),
"Line: Price": order.findtext(".//order/shipping_total_inc"),
})
writer.writerow(row)
row = global_row.copy()
row.update({
"Line: Type": "Transaction",
"Line: Command": "DEFAULT",
"Transaction: Kind": "sale",
"Transaction: Processed At": "",
"Transaction: Amount": order.findtext(".//payment/payment_amount", ""),
"Transaction: Currency": order.findtext(".//order_currency", ""),
"Transaction: Status": "success",
"Transaction: Gateway": "manual (" + order.findtext(".//payment/payment_type", "") + ")",
"Transaction: Force Gateway": "",
"Transaction: Test": "No",
"Transaction: Authorization": order.findtext(".//payment/transaction_reference", ""),
"Transaction: Parent ID": "",
})
writer.writerow(row)
if payment_status == "refunded":
row = global_row.copy()
row.update({
"Line: Type": "Transaction",
"Line: Command": "DEFAULT",
"Transaction: Kind": "refund",
"Transaction: Processed At": "",
"Transaction: Amount": '-' + order.findtext(".//payment/payment_amount", ""),
"Transaction: Currency": order.findtext(".//order_currency", ""),
"Transaction: Status": "success",
"Transaction: Gateway": "manual",
"Transaction: Force Gateway": "",
"Transaction: Test": "No",
"Transaction: Authorization": "",
"Transaction: Parent ID": "",
})
writer.writerow(row)
if order.findtext(".//dispatch_date") != "0000-00-00 00:00:00":
row = global_row.copy()
row.update({
"Line: Type": "Fulfillment Line",
"Line: Command": "DEFAULT",
"Fulfillment: ID": "",
"Fulfillment: Status": "" if order.findtext(".//dispatch_date") == "0000-00-00 00:00:00" else "success",
"Fulfillment: Processed At": "",
"Fulfillment: Tracking Company": order.findtext(".//courier_name", ""),
"Fulfillment: Location": "",
"Fulfillment: Shipment Status": "" if order.findtext(".//dispatch_date") == "0000-00-00 00:00:00" else "delivered",
"Fulfillment: Tracking Number": "",
"Fulfillment: Tracking URL": "",
"Fulfillment: Send Receipt": "false",
})
writer.writerow(row)
order_count += 1
if order_count % max_orders == 0:
file.close()
file = create_csv_writer()
file.close()
print(f"Data has been combined into multiple CSV files successfully.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment