Initial commit
This commit is contained in:
310
Reference Data/python_project/scripts/import_services.py
Normal file
310
Reference Data/python_project/scripts/import_services.py
Normal file
@@ -0,0 +1,310 @@
|
||||
import csv
|
||||
from proteus import config, Model
|
||||
from decimal import Decimal
|
||||
|
||||
# Configuration
|
||||
DATABASE_NAME = 'tradon'
|
||||
CSV_FILE_PATH = r'C:\Users\SylvainDUVERNAY\Open Squared\Production - Documents\TRADON Implementation\ITSA\Reference Data\Loaders\Services.csv' # UPDATE THIS PATH!
|
||||
|
||||
# Product configuration
|
||||
PRODUCT_TYPE = 'service' # Service type products
|
||||
DEFAULT_CATEGORY = 'Services' # Default category name if not found
|
||||
DEFAULT_UOM = 'Mt' # Default UOM if not found
|
||||
|
||||
def connect_to_tryton():
|
||||
"""Establish connection to Tryton database"""
|
||||
print(f"Connecting to Tryton database: {DATABASE_NAME}")
|
||||
try:
|
||||
#config.set_trytond(DATABASE_NAME)
|
||||
config.set_xmlrpc('https://admin:dsproject@itsa.open-squared.tech/tradon/')
|
||||
|
||||
print("✓ Connected successfully!\n")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ Connection failed: {e}")
|
||||
return False
|
||||
|
||||
def get_or_create_category(category_name):
|
||||
"""Find or create a product category"""
|
||||
Category = Model.get('product.category')
|
||||
|
||||
# Try to find existing category
|
||||
categories = Category.find([('name', '=', category_name)])
|
||||
|
||||
if categories:
|
||||
print(f" Found existing category: {category_name}")
|
||||
return categories[0]
|
||||
else:
|
||||
# Create new category
|
||||
new_category = Category()
|
||||
new_category.name = category_name
|
||||
new_category.save()
|
||||
print(f" ✓ Created new category: {category_name}")
|
||||
return new_category
|
||||
|
||||
def get_uom(uom_name):
|
||||
"""Find Unit of Measure by name"""
|
||||
Uom = Model.get('product.uom')
|
||||
|
||||
# Try exact match first
|
||||
uoms = Uom.find([('name', '=', uom_name)])
|
||||
|
||||
if uoms:
|
||||
return uoms[0]
|
||||
|
||||
# Try case-insensitive search
|
||||
all_uoms = Uom.find([])
|
||||
for uom in all_uoms:
|
||||
if uom.name.lower() == uom_name.lower():
|
||||
return uom
|
||||
|
||||
# If not found, return Unit (default)
|
||||
print(f" ⚠ Warning: UOM '{uom_name}' not found, using 'Unit'")
|
||||
default_uoms = Uom.find([('name', '=', 'Unit')])
|
||||
if default_uoms:
|
||||
return default_uoms[0]
|
||||
|
||||
# If even Unit is not found, get the first available
|
||||
all_uoms = Uom.find([])
|
||||
if all_uoms:
|
||||
print(f" ⚠ Using first available UOM: {all_uoms[0].name}")
|
||||
return all_uoms[0]
|
||||
|
||||
raise ValueError("No UOM found in database!")
|
||||
|
||||
def check_product_exists(code):
|
||||
"""Check if product with given code already exists"""
|
||||
Product = Model.get('product.product')
|
||||
products = Product.find([('code', '=', code)])
|
||||
return products[0] if products else None
|
||||
|
||||
def create_service_product(row, category, uom):
|
||||
"""Create a new service product"""
|
||||
Product = Model.get('product.product')
|
||||
Template = Model.get('product.template')
|
||||
|
||||
# Create template first
|
||||
template = Template()
|
||||
template.name = row['name']
|
||||
template.type = PRODUCT_TYPE
|
||||
template.list_price = Decimal(row['sale_price']) if row['sale_price'] else Decimal('0.00')
|
||||
template.cost_price = Decimal(row['cost_price']) if row['cost_price'] else Decimal('0.00')
|
||||
template.default_uom = uom
|
||||
template.category = category
|
||||
template.salable = True
|
||||
template.purchasable = False # Services typically not purchased
|
||||
|
||||
if row.get('description'):
|
||||
template.description = row['description']
|
||||
|
||||
template.save()
|
||||
|
||||
# Create product variant
|
||||
product = Product()
|
||||
product.template = template
|
||||
product.code = row['code']
|
||||
product.save()
|
||||
|
||||
return product
|
||||
|
||||
def import_services(csv_file):
|
||||
"""Import services from CSV file"""
|
||||
Product = Model.get('product.product')
|
||||
|
||||
imported_count = 0
|
||||
skipped_count = 0
|
||||
error_count = 0
|
||||
errors = []
|
||||
|
||||
print(f"{'='*70}")
|
||||
print(f"Importing service products from: {csv_file}")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
try:
|
||||
# Open with utf-8-sig to handle BOM
|
||||
with open(csv_file, 'r', encoding='utf-8-sig') as file:
|
||||
reader = csv.DictReader(file)
|
||||
|
||||
# Debug: Show detected columns
|
||||
print(f"Detected columns: {reader.fieldnames}\n")
|
||||
|
||||
for row_num, row in enumerate(reader, start=2):
|
||||
try:
|
||||
# Clean up values
|
||||
code = row.get('code', '').strip()
|
||||
name = row.get('name', '').strip()
|
||||
category_name = row.get('category', DEFAULT_CATEGORY).strip() or DEFAULT_CATEGORY
|
||||
uom_name = row.get('uom', DEFAULT_UOM).strip() or DEFAULT_UOM
|
||||
sale_price = row.get('sale_price', '0.00').strip()
|
||||
cost_price = row.get('cost_price', '0.00').strip()
|
||||
description = row.get('description', '').strip()
|
||||
|
||||
# Skip empty rows
|
||||
if not code and not name:
|
||||
continue
|
||||
|
||||
# Validate required fields
|
||||
if not code or not name:
|
||||
errors.append(f"Row {row_num}: Missing code or name")
|
||||
error_count += 1
|
||||
print(f"✗ Row {row_num}: Missing required fields")
|
||||
continue
|
||||
|
||||
print(f"Processing Row {row_num}: {code} - {name}")
|
||||
|
||||
# Check if product already exists
|
||||
existing_product = check_product_exists(code)
|
||||
|
||||
if existing_product:
|
||||
print(f" ⚠ Product code '{code}' already exists: {existing_product.template.name}")
|
||||
print(f" Skipping...")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Get or create category
|
||||
category = get_or_create_category(category_name)
|
||||
|
||||
# Get UOM
|
||||
uom = get_uom(uom_name)
|
||||
print(f" Using UOM: {uom.name}")
|
||||
|
||||
# Create the product
|
||||
row_data = {
|
||||
'code': code,
|
||||
'name': name,
|
||||
'sale_price': sale_price,
|
||||
'cost_price': cost_price,
|
||||
'description': description
|
||||
}
|
||||
|
||||
product = create_service_product(row_data, category, uom)
|
||||
|
||||
print(f" ✓ Created service product: {name}")
|
||||
print(f" Code: {code}")
|
||||
print(f" Category: {category.name}")
|
||||
print(f" Sale Price: {sale_price}")
|
||||
print(f" Cost Price: {cost_price}")
|
||||
if description:
|
||||
print(f" Description: {description[:50]}...")
|
||||
print()
|
||||
|
||||
imported_count += 1
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Row {row_num} - {code} ({name}): {str(e)}"
|
||||
errors.append(error_msg)
|
||||
error_count += 1
|
||||
print(f"✗ Error on row {row_num}: {e}\n")
|
||||
|
||||
# Summary
|
||||
print(f"{'='*70}")
|
||||
print("IMPORT SUMMARY")
|
||||
print(f"{'='*70}")
|
||||
print(f"Successfully imported: {imported_count} service products")
|
||||
print(f"Skipped (already exist): {skipped_count} products")
|
||||
print(f"Errors: {error_count}")
|
||||
|
||||
if errors:
|
||||
print(f"\nError details:")
|
||||
for error in errors:
|
||||
print(f" - {error}")
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"✗ Error: CSV file not found at {csv_file}")
|
||||
print(f"Please update CSV_FILE_PATH in the script with the correct path.")
|
||||
except Exception as e:
|
||||
print(f"✗ Fatal error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def verify_import():
|
||||
"""Verify imported service products"""
|
||||
Product = Model.get('product.product')
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print("VERIFICATION - Service Products")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
# Find all service type products
|
||||
products = Product.find([('template.type', '=', 'service')])
|
||||
|
||||
if products:
|
||||
print(f"Found {len(products)} service products:\n")
|
||||
print(f"{'Code':<12} {'Name':<35} {'Category':<20} {'Sale Price':<12}")
|
||||
print("-" * 80)
|
||||
|
||||
for product in products:
|
||||
code = product.code or 'N/A'
|
||||
name = product.template.name[:34] if product.template.name else 'N/A'
|
||||
category = product.template.category.name if product.template.category else 'N/A'
|
||||
sale_price = f"{product.template.list_price:.2f}" if product.template.list_price else '0.00'
|
||||
|
||||
print(f"{code:<12} {name:<35} {category:<20} {sale_price:<12}")
|
||||
else:
|
||||
print("No service products found")
|
||||
|
||||
print()
|
||||
|
||||
def list_available_uoms():
|
||||
"""List all available UOMs in the database"""
|
||||
print(f"\n{'='*70}")
|
||||
print("AVAILABLE UNITS OF MEASURE")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
Uom = Model.get('product.uom')
|
||||
uoms = Uom.find([])
|
||||
|
||||
if uoms:
|
||||
print(f"Found {len(uoms)} UOMs:\n")
|
||||
for uom in uoms:
|
||||
print(f" - {uom.name} (Symbol: {uom.symbol if hasattr(uom, 'symbol') else 'N/A'})")
|
||||
else:
|
||||
print("No UOMs found")
|
||||
|
||||
print()
|
||||
|
||||
def list_available_categories():
|
||||
"""List all available product categories"""
|
||||
print(f"\n{'='*70}")
|
||||
print("AVAILABLE PRODUCT CATEGORIES")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
Category = Model.get('product.category')
|
||||
categories = Category.find([])
|
||||
|
||||
if categories:
|
||||
print(f"Found {len(categories)} categories:\n")
|
||||
for cat in categories:
|
||||
print(f" - {cat.name}")
|
||||
else:
|
||||
print("No categories found")
|
||||
|
||||
print()
|
||||
|
||||
def main():
|
||||
print("="*70)
|
||||
print("TRYTON SERVICE PRODUCT IMPORT SCRIPT (using Proteus)")
|
||||
print("="*70)
|
||||
print()
|
||||
|
||||
# Connect to Tryton
|
||||
if not connect_to_tryton():
|
||||
return 1
|
||||
|
||||
# Optional: List available UOMs and categories
|
||||
# Uncomment these if you want to see what's available in your database
|
||||
# list_available_uoms()
|
||||
# list_available_categories()
|
||||
|
||||
# Import service products
|
||||
import_services(CSV_FILE_PATH)
|
||||
|
||||
# Verify import
|
||||
verify_import()
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
Reference in New Issue
Block a user