Commit all views
This commit is contained in:
223
Reference Data/python_project/scripts/import_price_index.py
Normal file
223
Reference Data/python_project/scripts/import_price_index.py
Normal file
@@ -0,0 +1,223 @@
|
||||
# Add parent directory to Python path so we can import helpers
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
parent_dir = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(parent_dir))
|
||||
|
||||
import csv
|
||||
from proteus import Model
|
||||
|
||||
from helpers.config import (
|
||||
PRICE_INDEXES_CSV,
|
||||
connect_to_tryton,
|
||||
)
|
||||
|
||||
|
||||
CSV_FILE_PATH = PRICE_INDEXES_CSV
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main import function
|
||||
# ---------------------------------------------------------------------------
|
||||
def import_price_indexes(csv_file):
|
||||
PriceIndex = Model.get('price.price')
|
||||
ProductMonth = Model.get('product.month')
|
||||
Currency = Model.get('currency.currency')
|
||||
PriceFixType = Model.get('price.fixtype')
|
||||
ProductUom = Model.get('product.uom')
|
||||
|
||||
print(f"{'='*70}")
|
||||
print(f"IMPORTING PRICE INDEXES FROM CSV")
|
||||
print(f"{'='*70}\n")
|
||||
print(f"Reading from: {csv_file}\n")
|
||||
|
||||
imported_count = 0
|
||||
skipped_count = 0
|
||||
error_count = 0
|
||||
ignored = [] # list of (row_num, price_index, reason)
|
||||
errors = [] # list of error strings
|
||||
|
||||
# Build lookup maps upfront to avoid repeated DB calls
|
||||
print("Loading existing price indexes...")
|
||||
all_indexes = PriceIndex.find([])
|
||||
index_map = {pi.price_index: pi for pi in all_indexes}
|
||||
print(f" Found {len(index_map)} price indexes in database\n")
|
||||
|
||||
print("Loading product months...")
|
||||
all_months = ProductMonth.find([])
|
||||
month_map = {pm.month_name: pm for pm in all_months}
|
||||
print(f" Found {len(month_map)} product months in database\n")
|
||||
|
||||
print("Loading reference data (currencies, price types, units)...")
|
||||
currency_map = {c.id: c for c in Currency.find([])}
|
||||
fixtype_map = {pft.id: pft for pft in PriceFixType.find([])}
|
||||
uom_map = {u.id: u for u in ProductUom.find([])}
|
||||
print()
|
||||
|
||||
row_num = 0
|
||||
|
||||
try:
|
||||
with open(csv_file, 'r', encoding='utf-8-sig') as file:
|
||||
reader = csv.DictReader(file)
|
||||
|
||||
for row in reader:
|
||||
row_num += 1
|
||||
index_name = row.get('price_index', '').strip()
|
||||
|
||||
try:
|
||||
print(f"{'='*70}")
|
||||
print(f"Row {row_num}: {index_name}")
|
||||
print(f"{'='*70}")
|
||||
|
||||
if not index_name:
|
||||
raise ValueError("price_index is empty")
|
||||
|
||||
# Already exists → skip silently
|
||||
if index_name in index_map:
|
||||
print(f" ⏭ Already exists in database — skipping\n")
|
||||
ignored.append((row_num, index_name, "Already exists in price_price"))
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Lookup pricing period in product_month
|
||||
period_label = row.get('pricing_period_label', '').strip()
|
||||
if not period_label:
|
||||
msg = "pricing_period_label is empty"
|
||||
print(f" ✗ {msg} — not imported\n")
|
||||
ignored.append((row_num, index_name, msg))
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
product_month = month_map.get(period_label)
|
||||
if not product_month:
|
||||
# Case-insensitive fallback
|
||||
for k, v in month_map.items():
|
||||
if k and k.upper() == period_label.upper():
|
||||
product_month = v
|
||||
break
|
||||
|
||||
if not product_month:
|
||||
msg = f"pricing_period_label '{period_label}' not found in product_month"
|
||||
print(f" ✗ {msg} — not imported\n")
|
||||
ignored.append((row_num, index_name, msg))
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Resolve FK: currency
|
||||
raw_currency = row.get('price_currency', '').strip()
|
||||
currency = None
|
||||
if raw_currency:
|
||||
try:
|
||||
currency = currency_map.get(int(raw_currency))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if not currency:
|
||||
raise ValueError(f"price_currency id '{raw_currency}' not found in currency_currency")
|
||||
|
||||
# Resolve FK: price type (optional)
|
||||
raw_type = row.get('price_type', '').strip()
|
||||
price_type = None
|
||||
if raw_type:
|
||||
try:
|
||||
price_type = fixtype_map.get(int(raw_type))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Resolve FK: unit of measure (optional)
|
||||
raw_unit = row.get('price_unit', '').strip()
|
||||
price_unit = None
|
||||
if raw_unit:
|
||||
try:
|
||||
price_unit = uom_map.get(int(raw_unit))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Scalar fields
|
||||
price_desc = row.get('price_desc', '').strip()
|
||||
price_curve_type = row.get('price_curve_type', '').strip()
|
||||
active_raw = row.get('active', 'TRUE').strip().upper()
|
||||
active = active_raw in ('TRUE', '1', 'YES')
|
||||
|
||||
# Create the record
|
||||
print(f" Creating price index...")
|
||||
record = PriceIndex()
|
||||
record.price_index = index_name
|
||||
record.price_desc = price_desc
|
||||
record.price_currency = currency
|
||||
record.price_curve_type = price_curve_type
|
||||
record.price_period = product_month
|
||||
record.active = active
|
||||
if price_type:
|
||||
record.price_type = price_type
|
||||
if price_unit:
|
||||
record.price_unit = price_unit
|
||||
record.save()
|
||||
|
||||
print(f" ✓ Created (ID: {record.id})")
|
||||
print(f" Index : {index_name}")
|
||||
print(f" Period : {period_label} (ID: {product_month.id})")
|
||||
print(f" Desc : {price_desc}")
|
||||
print()
|
||||
imported_count += 1
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Row {row_num} - {index_name or 'Unknown'}: {str(e)}"
|
||||
errors.append(error_msg)
|
||||
error_count += 1
|
||||
print(f" ✗ Error on row {row_num}: {e}\n")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"✗ Error: CSV file not found at {csv_file}")
|
||||
print(f"Please update PRICE_INDEXES_CSV in helpers/config.py with the correct path.")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"✗ Fatal error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*70}")
|
||||
print("IMPORT SUMMARY")
|
||||
print(f"{'='*70}")
|
||||
print(f"Total rows processed : {row_num}")
|
||||
print(f"Successfully imported : {imported_count}")
|
||||
print(f"Skipped / Not imported : {skipped_count}")
|
||||
print(f"Errors : {error_count}")
|
||||
|
||||
if ignored:
|
||||
print(f"\nIgnored / Not imported ({len(ignored)}):")
|
||||
for r, name, reason in ignored:
|
||||
print(f" - Row {r:>4} [{name}]: {reason}")
|
||||
|
||||
if errors:
|
||||
print(f"\nErrors ({len(errors)}):")
|
||||
for err in errors:
|
||||
print(f" - {err}")
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("TRYTON PRICE INDEX IMPORT SCRIPT")
|
||||
print("Using Proteus with XML-RPC Connection")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
if not connect_to_tryton():
|
||||
return 1
|
||||
|
||||
import_price_indexes(CSV_FILE_PATH)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
@@ -6,6 +6,7 @@ parent_dir = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(parent_dir))
|
||||
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from proteus import Model
|
||||
|
||||
@@ -97,7 +98,11 @@ def import_prices(csv_file):
|
||||
price_index = index_map[index_name]
|
||||
|
||||
# -- Parse fields -----------------------------------
|
||||
price_date = parse_date(row.get('price_date', ''))
|
||||
raw_date = row.get('price_date', '').strip()
|
||||
try:
|
||||
price_date = datetime.strptime(raw_date, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
price_date = parse_date(raw_date)
|
||||
high_price = parse_decimal(row.get('high_price', ''), 'high_price')
|
||||
low_price = parse_decimal(row.get('low_price', ''), 'low_price')
|
||||
open_price = parse_decimal(row.get('open_price', ''), 'open_price')
|
||||
|
||||
@@ -7,6 +7,7 @@ sys.path.insert(0, str(parent_dir))
|
||||
|
||||
import psycopg2
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from proteus import Model
|
||||
|
||||
@@ -34,7 +35,8 @@ from helpers.tryton_helpers import (
|
||||
get_party_invoice_address,
|
||||
find_purchase_contract_by_number,
|
||||
find_or_create_analytic_dimension_value,
|
||||
link_analytic_dimensions_to_purchase
|
||||
link_analytic_dimensions_to_purchase,
|
||||
ensure_party_has_category
|
||||
)
|
||||
|
||||
# Import migration mapping helper
|
||||
@@ -324,7 +326,11 @@ def import_purchases(csv_file):
|
||||
continue
|
||||
|
||||
# Parse other fields
|
||||
purchase_date = parse_date(row.get('purchase_date'))
|
||||
raw_purchase_date = row.get('purchase_date', '').strip()
|
||||
try:
|
||||
purchase_date = datetime.strptime(raw_purchase_date, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
purchase_date = parse_date(raw_purchase_date)
|
||||
party_name = row.get('party_name', '').strip()
|
||||
|
||||
# Find related records
|
||||
@@ -409,17 +415,21 @@ def import_purchases(csv_file):
|
||||
purchase.state = DEFAULT_STATE
|
||||
purchase.invoice_method = DEFAULT_INVOICE_METHOD
|
||||
|
||||
# Retrieve trader
|
||||
trader = find_party_by_name(trader_name)
|
||||
if not party:
|
||||
raise ValueError(f"Trader not found: {trader_name}")
|
||||
purchase.trader = trader
|
||||
# Retrieve trader (optional)
|
||||
if trader_name:
|
||||
trader = find_party_by_name(trader_name)
|
||||
if not trader:
|
||||
raise ValueError(f"Trader not found: {trader_name}")
|
||||
trader, _ = ensure_party_has_category(trader, 'TRADER')
|
||||
purchase.trader = Party(trader.id)
|
||||
|
||||
# Retrieve operator
|
||||
operator = find_party_by_name(operator_name)
|
||||
if not party:
|
||||
raise ValueError(f"Operator not found: {operator_name}")
|
||||
purchase.operator = operator
|
||||
# Retrieve operator (optional)
|
||||
if operator_name:
|
||||
operator = find_party_by_name(operator_name)
|
||||
if not operator:
|
||||
raise ValueError(f"Operator not found: {operator_name}")
|
||||
operator, _ = ensure_party_has_category(operator, 'OPERATOR')
|
||||
purchase.operator = Party(operator.id)
|
||||
|
||||
|
||||
# Save the purchase
|
||||
@@ -460,8 +470,17 @@ def import_purchases(csv_file):
|
||||
unit = find_uom_by_code(row.get('line_unit_code', ''))
|
||||
|
||||
# Parse shipping dates
|
||||
from_del = parse_date(row.get('line_from_del', ''))
|
||||
to_del = parse_date(row.get('line_to_del', ''))
|
||||
raw_from_del = row.get('line_from_del', '').strip()
|
||||
try:
|
||||
from_del = datetime.strptime(raw_from_del, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
from_del = parse_date(raw_from_del)
|
||||
|
||||
raw_to_del = row.get('line_to_del', '').strip()
|
||||
try:
|
||||
to_del = datetime.strptime(raw_to_del, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
to_del = parse_date(raw_to_del)
|
||||
|
||||
# Create line
|
||||
line = PurchaseLine()
|
||||
@@ -500,7 +519,11 @@ def import_purchases(csv_file):
|
||||
|
||||
# Create pricing estimate if applicable
|
||||
pricing_trigger = row.get('pricing_trigger', '').strip()
|
||||
pricing_estimated_date = parse_date(row.get('pricing_estimated_date', ''))
|
||||
raw_pricing_date = row.get('pricing_estimated_date', '').strip()
|
||||
try:
|
||||
pricing_estimated_date = datetime.strptime(raw_pricing_date, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
pricing_estimated_date = parse_date(raw_pricing_date)
|
||||
if pricing_trigger:
|
||||
pricing_data = {
|
||||
'trigger': pricing_trigger,
|
||||
|
||||
@@ -7,6 +7,7 @@ sys.path.insert(0, str(parent_dir))
|
||||
|
||||
import psycopg2
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from proteus import Model
|
||||
|
||||
@@ -323,7 +324,11 @@ def import_sales(csv_file):
|
||||
continue
|
||||
|
||||
# Parse other fields
|
||||
sale_date = parse_date(row.get('sale_date'))
|
||||
raw_sale_date = row.get('sale_date', '').strip()
|
||||
try:
|
||||
sale_date = datetime.strptime(raw_sale_date, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
sale_date = parse_date(raw_sale_date)
|
||||
party_name = row.get('party_name', '').strip()
|
||||
|
||||
# Find related records
|
||||
@@ -460,8 +465,17 @@ def import_sales(csv_file):
|
||||
unit = find_uom_by_code(row.get('line_unit_code', ''))
|
||||
|
||||
# Parse shipping dates
|
||||
from_del = parse_date(row.get('line_from_del', ''))
|
||||
to_del = parse_date(row.get('line_to_del', ''))
|
||||
raw_from_del = row.get('line_from_del', '').strip()
|
||||
try:
|
||||
from_del = datetime.strptime(raw_from_del, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
from_del = parse_date(raw_from_del)
|
||||
|
||||
raw_to_del = row.get('line_to_del', '').strip()
|
||||
try:
|
||||
to_del = datetime.strptime(raw_to_del, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
to_del = parse_date(raw_to_del)
|
||||
|
||||
# Create line
|
||||
line = SaleLine()
|
||||
@@ -499,7 +513,11 @@ def import_sales(csv_file):
|
||||
|
||||
# Create pricing estimate if applicable
|
||||
pricing_trigger = row.get('pricing_trigger', '').strip()
|
||||
pricing_estimated_date = parse_date(row.get('pricing_estimated_date', ''))
|
||||
raw_pricing_date = row.get('pricing_estimated_date', '').strip()
|
||||
try:
|
||||
pricing_estimated_date = datetime.strptime(raw_pricing_date, '%m/%d/%Y').date()
|
||||
except (ValueError, AttributeError):
|
||||
pricing_estimated_date = parse_date(raw_pricing_date)
|
||||
if pricing_trigger:
|
||||
pricing_data = {
|
||||
'trigger': pricing_trigger,
|
||||
|
||||
Reference in New Issue
Block a user