Last active
December 20, 2015 07:29
-
-
Save projectgus/6093951 to your computer and use it in GitHub Desktop.
At CCHS hackerspace we make a lot of group parts orders to save shipping costs. This script can split up a a DigiKey order's total by participant (assuming each person's name is listed in the Customer Reference field for the order.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Digikey order splitter script for group buys. For Python 3.x. Public Domain. | |
Supports cases where people are paying in a different currency to the Digikey currency, | |
and also pro rata splitting of shipping costs. | |
If you use this then please sanity check the results before sending them | |
Usage: | |
* Go to Digikey "My Digikey" -> "Web Order History" -> find the order (must be Submitted) | |
* Select "Download Order" -> "CSV w/ Header" | |
* Run script as: | |
digikey_ordersplit.py <order CSV file> [optional total amount] | |
Names of people in the order must have been used to fill the "Customer | |
Reference" field. Only the first word (up to a space) is used for the | |
name, which allows you to also add item-specific comments | |
(designators, etc.) in the field if necessary. | |
The optional argument "total amount" is for if people are paying in a | |
different currency to the Digikey order currency. All amounts are | |
adjusted pro rata to meet the new total. | |
If the order includes a shipping charge then this is split up pro rata | |
between the order participants. | |
Uses floating point math everywhere because lazy. | |
TODO: Support the situation where multiple people order the same thing. | |
""" | |
import sys, csv, re | |
def main(): | |
if len(sys.argv) < 2 or len(sys.argv)> 3: | |
print("Usage: digikey_ordersplit <order CSV file w/ header> [optional total amount]") | |
return | |
with open(sys.argv[1], 'r') as orderfile: | |
reader = csv.reader(orderfile) | |
# Skip the general order details to the "Index" subheader above the specifics | |
for row in reader: | |
if len(row) > 0 and row[0] == "Index": | |
break | |
participants = split_order(reader) | |
shipping = find_shipping(reader) | |
apply_pro_rata_shipping(participants, shipping) | |
if len(sys.argv) == 3: | |
scale_order(participants, float(sys.argv[2])) | |
for name in participants: | |
print("%s:" % name) | |
for item in participants[name]: | |
print(" {:2} {:<25} {:<40} - {:>5.2f}".format(*item)) | |
print("Total Share: {:>66.2f}\n".format(person_total(participants, name))) | |
print("Order total (check against invoice): {:.2f}".format(order_total(participants))) | |
def split_order(csv): | |
""" Split an order into a list of order items for each participant | |
Items are tuples (qty, code, item, total price) | |
""" | |
participants = {} | |
for row in csv: | |
if row[0] == "": | |
return participants # have reached subtotal row | |
name = row[4].title().split(" ")[0] | |
if not name in participants: | |
participants[name] = [] | |
qty = int(row[1]) | |
price = parse_price(row[8]) | |
participants[name].append((qty,row[2],row[3],price)) | |
return participants # should never get here but might if the CSV is the non-header kind | |
def find_shipping(csv): | |
""" Find the Shipping line and return the shipping total """ | |
for row in csv: | |
if len(row) > 7 and row[6] == "Shipping": | |
return parse_price(row[7]) | |
return 0.0 | |
def apply_pro_rata_shipping(participants, shipping): | |
""" Apply a pro rata shipping charge to each participant, add it to their | |
list of items""" | |
if shipping == 0: | |
return | |
subtotal = order_total(participants) | |
shipping = shipping | |
for name in participants: | |
share = person_total(participants, name) / subtotal * shipping | |
participants[name].append((1,"","Share of Shipping",share)) | |
def scale_order(participants, actual_total): | |
""" Scale everything in the order so the order total reaches actual_total """ | |
old_total = order_total(participants) | |
for name in participants: | |
items = participants[name] | |
participants[name] = [(qty,part,desc,cost/old_total*actual_total) | |
for (qty,part,desc,cost) in items] | |
def person_total(participants, name): | |
""" Return the total for a given person's share of the order """ | |
return sum([p[3] for p in participants[name]]) | |
def order_total(participants): | |
""" Return the total for the order as given across all participants """ | |
return sum([person_total(participants, name) for name in participants]) | |
def parse_price(price): | |
""" Given a price of the form AU$0.40 (or similar), return a float """ | |
price = re.search(r"[\d\.]+$", price).group(0) | |
return float(price) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment