Developers

Import Costs from CSV

Import cost data, create missing categories and tax rates, and link costs to projects with Python.

This example imports costs from a CSV file. It retrieves existing cost categories, tax rates, and projects, creates missing categories or tax rates, and then sends each cost to the IC Project API.

Prerequisites

  • Python 3
  • The requests package
  • An IC Project instance slug and authorization token
  • A CSV file containing the cost data

Example CSV

sample-costs.csv
name,description,priceNet,priceGross,date,isBilled,isPosted,category,taxRate,taxRateValue,project
Sample Cost 1,Description for cost 1,100.00,123.00,2024-09-15,true,false,Category A,Standard Tax,10,Project X
Sample Cost 2,Description for cost 2,200.00,246.00,2024-09-16,false,true,Category B,Reduced Tax,5,

Python script

Replace your-instance-slug and your-api-key before running the script.

import-costs-from-csv.py
import csv
import requests
from datetime import datetime

# Function to fetch existing cost categories from the API
def get_existing_cost_categories(api_url, headers):
    response = requests.get(f"{api_url}/finance/cost-categories", headers=headers)
    if response.status_code == 200:
        return {category['name']: category for category in response.json()}
    else:
        print(f"Error fetching cost categories: {response.status_code}")
        return {}

# Function to fetch existing tax rates from the API
def get_existing_tax_rates(api_url, headers):
    response = requests.get(f"{api_url}/finance/tax-rates", headers=headers)
    if response.status_code == 200:
        return {tax_rate['name']: tax_rate for tax_rate in response.json()}
    else:
        print(f"Error fetching tax rates: {response.status_code}")
        return {}

# Function to fetch existing projects from the API
def get_existing_projects(api_url, headers):
    response = requests.get(f"{api_url}/project/projects", headers=headers)
    if response.status_code == 200:
        return {project['name']: project for project in response.json()}
    else:
        print(f"Error fetching projects: {response.status_code}")
        return {}

# Function to create a new cost category
def create_cost_category(name, api_url, headers):
    data = {
        "name": name,
    }
    response = requests.post(f"{api_url}/finance/cost-categories", headers=headers, json=data)
    if response.status_code == 201:
        return response.json()
    else:
        print(f"Error creating cost category {name}: {response.status_code}, {response.text}")
        return None

# Function to create a new tax rate
def create_tax_rate(name, value, api_url, headers):
    data = {
        "name": name,
        "value": value,
        "isDefault": False
    }
    response = requests.post(f"{api_url}/finance/tax-rates", headers=headers, json=data)
    if response.status_code == 201:
        return response.json()
    else:
        print(f"Error creating tax rate {name}: {response.status_code}, {response.text}")
        return None

# Function to convert CSV data to JSON format for costs
def csv_to_costs(file_path, api_url, headers):
    # Fetch existing cost categories, tax rates, and projects
    existing_categories = get_existing_cost_categories(api_url, headers)
    existing_tax_rates = get_existing_tax_rates(api_url, headers)
    existing_projects = get_existing_projects(api_url, headers)

    costs = []

    # Open the CSV file
    with open(file_path, mode='r', encoding='utf-8') as file:
        csv_reader = csv.DictReader(file)

        # Iterate through rows in the CSV
        for row in csv_reader:
            # Check if cost category already exists; create new one if not
            category_name = row['category']
            if category_name not in existing_categories:
                new_category = create_cost_category(category_name, api_url, headers)
                if new_category:
                    existing_categories[category_name] = new_category
            cost_category = existing_categories.get(category_name, {})

            # Check if tax rate already exists; create new one if not
            tax_rate_name = row['taxRate']
            tax_rate_value = float(row['taxRateValue'])
            if tax_rate_name not in existing_tax_rates:
                new_tax_rate = create_tax_rate(tax_rate_name, tax_rate_value, api_url, headers)
                if new_tax_rate:
                    existing_tax_rates[tax_rate_name] = new_tax_rate
            tax_rate = existing_tax_rates.get(tax_rate_name, {})

            # Check if project exists
            project_name = row.get('project', '')
            project_id = existing_projects.get(project_name, {}).get('id') if project_name else None

            cost = {
                "name": row['name'],  # Cost name
                "description": row['description'],  # Cost description
                "priceNet": float(row['priceNet']),  # Net price
                "priceGross": float(row['priceGross']),  # Gross price
                "date": row['date'],  # Date
                "isBilled": row['isBilled'].lower() == 'true',  # Is billed
                "isPosted": row['isPosted'].lower() == 'true',  # Is posted
                "createdAt": datetime.now().isoformat(),  # Created at
                "updatedAt": datetime.now().isoformat(),  # Updated at
                "costCategory": cost_category.get('id'),  # Cost category
                "taxRate": tax_rate.get('id'),  # Tax rate
                "financeProject": project_id  # Project ID, if exists
            }

            costs.append(cost)

    return costs

# Function to send cost data to the API
def send_costs_to_api(costs, api_url, headers):
    # Iterate through each cost and send data to the API
    for cost in costs:
        response = requests.post(f"{api_url}/finance/costs", headers=headers, json=cost)
        if response.status_code == 201:
            print(f"Success: Cost {cost['name']} was sent.")
        else:
            print(f"Error: Failed to send cost {cost['name']}. Response code: {response.status_code}, error: {response.text}")

# Example usage
csv_file = 'sample-costs.csv'  # Path to the CSV file
instance_slug = 'your-instance-slug'  # Your instance slug
api_url = f'https://app.icproject.com/api/instance/{instance_slug}'  # API endpoint
api_key = 'your-api-key'  # Your API key

# Headers for authorization and content type
headers = {
    'X-Auth-Token': api_key,  # Authorization token
    'Content-Type': 'application/json',  # Content type for the request
    'Accept': 'application/json',  # Expected response type
}

# Convert CSV data
costs_data = csv_to_costs(csv_file, api_url, headers)

# Send data to the API
send_costs_to_api(costs_data, api_url, headers)

Run the script with:

python import-costs-from-csv.py

View the original source on GitHub

On this page