{
  "project": "WHIMS to Central partscan_inventory Synchronization",
  "description": "Sync stock data from WHIMS database (whims_dev) to Central platform (goparts.partscan_inventory) without Finale API",
  "version": "1.0",
  "created": "2025-10-06",

  "database_connections": {
    "source": {
      "database": "whims_dev",
      "description": "WHIMS warehouse management system",
      "credentials_location": "/home/whgoparts/public_html/whims-dev/.env"
    },
    "target": {
      "database": "goparts",
      "table": "partscan_inventory",
      "description": "Central platform inventory table",
      "credentials_location": "/home/opsgoparts/www/central/.env"
    },
    "root_credentials": {
      "username": "root",
      "password": ")V9IR<W:~4=HrqbL",
      "note": "Use for cross-database sync queries"
    }
  },

  "source_tables": {
    "products": {
      "description": "Product metadata from WHIMS",
      "key_fields": [
        "id (primary key)",
        "product_id (unique SKU)",
        "supplier_part_number",
        "partslink",
        "part_type",
        "supplier",
        "average_price",
        "status (active/inactive)"
      ]
    },
    "stocks": {
      "description": "Inventory quantities per location",
      "key_fields": [
        "id (primary key)",
        "product_id (FK to products.id)",
        "warehouse_id (FK to warehouses.id)",
        "location_id (FK to locations.id)",
        "sub_location",
        "quantity",
        "reserved_quantity",
        "is_selling",
        "condition (new/defective/etc)",
        "average_price"
      ]
    },
    "warehouses": {
      "description": "Warehouse definitions",
      "records": [
        {"id": 30, "name": "Go-Parts GA"},
        {"id": 31, "name": "SimplePrice"},
        {"id": 32, "name": "Today Delivery INC"},
        {"id": 33, "name": "RapidFulfillment"}
      ]
    },
    "locations": {
      "description": "Physical locations within warehouses",
      "key_fields": [
        "id (primary key)",
        "warehouse_id",
        "location",
        "description",
        "type (storage/staging/etc)"
      ]
    }
  },

  "target_table": {
    "name": "partscan_inventory",
    "database": "goparts",
    "description": "Central platform inventory tracking table",
    "key_fields": {
      "ID": "auto_increment primary key",
      "PartNumber": "Product SKU from WHIMS products.product_id",
      "Qty": "Stock quantity (0 if not is_selling)",
      "Listed": "1 if selling and not defective, 0 otherwise",
      "Warehouse": "GA or CA (mapped from warehouse name)",
      "Marketplace": "B2C, B2B, SimplePrice, Local, or RapidFulfillmentOntario",
      "source": "Set to 'whims' (vs 'FinaleInventory')",
      "supplier": "From products.supplier",
      "partslink": "From products.partslink",
      "is_active": "1 if product active and selling, 0 otherwise",
      "is_spn": "0 if has partslink, 1 if supplier part number only",
      "uniq_key": "whims_{product_id}_{warehouse_id}_{location_id}",
      "ps_sku_base": "Calculated by helper (optional)",
      "params": "JSON with full product + stock metadata"
    },
    "existing_sources": {
      "FinaleInventory": "26,606 records (existing Finale API sync)",
      "NULL": "11 records",
      "whims": "NEW - will be created by this sync"
    }
  },

  "warehouse_marketplace_mapping": {
    "Go-Parts GA": {
      "Warehouse": "GA",
      "default_Marketplace": "B2C"
    },
    "SimplePrice": {
      "Warehouse": "GA",
      "default_Marketplace": "SimplePrice"
    },
    "Today Delivery INC": {
      "Warehouse": "CA",
      "default_Marketplace": "B2C"
    },
    "RapidFulfillment": {
      "Warehouse": "CA",
      "default_Marketplace": "RapidFulfillmentOntario"
    }
  },

  "marketplace_logic": {
    "priority_order": "Location-based (highest) → Supplier-based (medium) → Warehouse default (lowest)",
    "rules": [
      {
        "name": "Warehouse Default",
        "priority": 1,
        "description": "Use default marketplace from warehouse mapping"
      },
      {
        "name": "Supplier Override",
        "priority": 2,
        "condition": "supplier IN ('DEPO', 'TYC', 'PBI') AND (Warehouse='GA' OR (Warehouse='CA' AND default_marketplace='B2C'))",
        "action": "Set Marketplace = 'B2B'"
      },
      {
        "name": "Location Override",
        "priority": 3,
        "condition": "sub_location matches local patterns",
        "action": "Set Marketplace = 'Local'",
        "local_location_patterns": [
          "1.4 D", "1.1 D", "1.2 D", "1.3 D",
          "3.3 G", "3.2 H",
          "6.4 A", "6.4 B", "6.4 C", "6.4 G",
          "2.3 C", "5.2 I", "2.2 D", "5.3 G", "5.1 I"
        ]
      },
      {
        "name": "Defective Handling",
        "priority": 4,
        "condition": "condition='defective' OR location contains 'DEFECT'",
        "action": "Set Qty=0, Listed=0"
      }
    ]
  },

  "data_extraction_query": {
    "sql": "SELECT p.id as product_pk, p.product_id, p.supplier_part_number, p.partslink, p.part_type, p.supplier, p.average_price, p.status, s.id as stock_pk, s.warehouse_id, s.location_id, s.sub_location, s.quantity, s.reserved_quantity, s.is_selling, s.condition, w.name as warehouse_name, w.state as warehouse_state, l.location as location_name, l.description as location_description, l.type as location_type FROM stocks s INNER JOIN products p ON s.product_id = p.id INNER JOIN warehouses w ON s.warehouse_id = w.id LEFT JOIN locations l ON s.location_id = l.id WHERE p.status = 'active' AND w.is_active = 1 ORDER BY p.product_id, w.id, l.id",
    "notes": [
      "Include all stocks (even qty=0) to properly sync deactivations",
      "Filter only active products and warehouses",
      "Each product/warehouse/location combination creates ONE partscan_inventory record"
    ]
  },

  "transformation_logic": {
    "step_1": {
      "name": "Determine Warehouse Code",
      "logic": "Use warehouse_marketplace_mapping to get Warehouse and default Marketplace"
    },
    "step_2": {
      "name": "Apply Supplier Override",
      "condition": "if supplier in ['DEPO', 'TYC', 'PBI'] and (Warehouse='GA' or (Warehouse='CA' and Marketplace='B2C'))",
      "action": "Set Marketplace = 'B2B'"
    },
    "step_3": {
      "name": "Apply Location Override",
      "condition": "if sub_location matches local_location_patterns",
      "action": "Set Marketplace = 'Local'"
    },
    "step_4": {
      "name": "Handle Defective Items",
      "condition": "if condition='defective' or location contains 'DEFECT'",
      "action": "Set quantity=0, listed=0, isDefective=true"
    },
    "step_5": {
      "name": "Build Unique Key",
      "format": "whims_{product_id}_{warehouse_id}_{location_id}"
    },
    "step_6": {
      "name": "Determine is_spn",
      "logic": "is_spn = (partslink is empty) ? 1 : 0"
    },
    "step_7": {
      "name": "Build params JSON",
      "structure": {
        "product_id": "from products.product_id",
        "supplier_part_number": "from products.supplier_part_number",
        "partslink": "from products.partslink",
        "part_type": "from products.part_type",
        "supplier": "from products.supplier",
        "warehouse": {
          "id": "warehouse_id",
          "name": "warehouse_name",
          "code": "mapped Warehouse (GA/CA)",
          "marketplace": "calculated Marketplace"
        },
        "location": {
          "id": "location_id",
          "name": "location_name",
          "sub_location": "sub_location",
          "description": "location_description"
        },
        "stock": {
          "quantity": "stocks.quantity",
          "reserved": "stocks.reserved_quantity",
          "is_selling": "stocks.is_selling",
          "condition": "stocks.condition"
        }
      }
    },
    "step_8": {
      "name": "Prepare partscan_inventory Record",
      "fields": {
        "PartNumber": "product_id",
        "Qty": "is_selling ? quantity : 0",
        "Listed": "(is_selling && !isDefective) ? 1 : 0",
        "Warehouse": "mapped Warehouse code",
        "Marketplace": "calculated Marketplace",
        "source": "whims",
        "supplier": "products.supplier",
        "partslink": "products.partslink",
        "is_active": "(status='active' && is_selling) ? 1 : 0",
        "is_spn": "calculated is_spn",
        "uniq_key": "calculated unique key",
        "params": "JSON string from step 7"
      }
    }
  },

  "sync_process": {
    "step_1": {
      "name": "Mark all WHIMS records inactive",
      "sql": "UPDATE partscan_inventory SET is_active = 0 WHERE source = 'whims'"
    },
    "step_2": {
      "name": "Process each stock record",
      "logic": [
        "Extract data from WHIMS using data_extraction_query",
        "Transform each record using transformation_logic",
        "Check if record exists: SELECT * FROM partscan_inventory WHERE source='whims' AND uniq_key=?",
        "If exists: UPDATE record",
        "If not exists: INSERT new record",
        "Track stats: total, created, updated"
      ]
    },
    "step_3": {
      "name": "Set qty=0 for inactive records",
      "sql": "UPDATE partscan_inventory SET Qty = 0, Listed = 0 WHERE source = 'whims' AND is_active = 0"
    },
    "step_4": {
      "name": "Update ps_sku_base column",
      "action": "Call PartscanInventoryHelper::updatePsSkuBaseColumn() if helper exists",
      "optional": true
    }
  },

  "file_structure": {
    "base_path": "/home/opsgoparts/www/central/app",
    "files_to_create": [
      {
        "path": "tasks/WhimsInventoryTask.php",
        "description": "CLI task entry point",
        "namespace": "App\\Tasks",
        "class": "WhimsInventoryTask",
        "extends": "\\Phalcon\\CLI\\Task",
        "methods": [
          "syncStocksAction(array $params = [])"
        ]
      },
      {
        "path": "models/WhimsInventoryImportModel.php",
        "description": "Core import logic",
        "namespace": "App\\Models",
        "class": "WhimsInventoryImportModel",
        "extends": "\\Phalcon\\Di\\Injectable",
        "methods": [
          "importProductsWithStocks()",
          "connectDatabases()",
          "extractWhimsStockData()",
          "transformStockRecord($stock)",
          "upsertPartscanInventory($record)",
          "deactivateAllWhimsRecords()",
          "setZeroQtyForInactive()",
          "updatePsSkuBase()"
        ]
      },
      {
        "path": "helpers/WhimsInventoryHelper.php",
        "description": "Database connections and utilities",
        "namespace": "App\\Helpers",
        "class": "WhimsInventoryHelper",
        "extends": "\\Phalcon\\Di\\Injectable",
        "methods": [
          "getWhimsDbConnection()",
          "getGopartsDbConnection()",
          "buildUniqKey($productId, $warehouseId, $locationId)",
          "getWarehouseMapping()",
          "getLocalLocationPatterns()",
          "isLocalLocation($subLocation)"
        ]
      }
    ]
  },

  "cli_usage": {
    "standard_sync": "php /home/opsgoparts/www/central/app/cli.php WhimsInventoryTask syncStocks",
    "debug_mode": "php /home/opsgoparts/www/central/app/cli.php WhimsInventoryTask syncStocks 1",
    "cron_schedule": "0 * * * * php /home/opsgoparts/www/central/app/cli.php WhimsInventoryTask syncStocks >> /home/opsgoparts/www/central/storage/logs/whims_sync.log 2>&1",
    "cron_description": "Run hourly to sync WHIMS inventory to Central"
  },

  "implementation_checklist": [
    "Create WhimsInventoryHelper.php with database connection methods",
    "Create WhimsInventoryImportModel.php with import logic",
    "Create WhimsInventoryTask.php CLI task",
    "Test database connections to both whims_dev and goparts",
    "Run initial sync with debug logging enabled",
    "Verify record counts: SELECT COUNT(*) FROM partscan_inventory WHERE source='whims'",
    "Compare with WHIMS: SELECT COUNT(*) FROM whims_dev.stocks WHERE products.status='active'",
    "Set up cron job for hourly sync",
    "Monitor logs for errors: /home/opsgoparts/www/central/storage/logs/whims_sync.log",
    "Validate marketplace assignments manually for sample records",
    "Test coexistence with FinaleInventory sync"
  ],

  "important_notes": [
    "Source value 'whims' keeps this sync separate from 'FinaleInventory' (26,606 existing records)",
    "Unique key format 'whims_{product_id}_{warehouse_id}_{location_id}' prevents conflicts",
    "Each WHIMS stock record (product + warehouse + location) = ONE partscan_inventory record",
    "Marketplace priority: Location-based (Local) > Supplier-based (B2B) > Warehouse default",
    "Expected sync time: 2-5 minutes for ~4,000 stock records",
    "Both WHIMS sync and Finale sync can coexist in same partscan_inventory table",
    "Use prepared statements for all database queries",
    "Batch inserts in groups of 500-1000 for performance",
    "Wrap sync in transaction or use batch commits for data integrity",
    "Log all errors but continue processing to completion"
  ],

  "validation_queries": {
    "total_whims_records": "SELECT COUNT(*) as count FROM goparts.partscan_inventory WHERE source = 'whims'",
    "active_whims_records": "SELECT COUNT(*) as count FROM goparts.partscan_inventory WHERE source = 'whims' AND is_active = 1",
    "whims_by_warehouse": "SELECT Warehouse, COUNT(*) as count FROM goparts.partscan_inventory WHERE source = 'whims' GROUP BY Warehouse",
    "whims_by_marketplace": "SELECT Marketplace, COUNT(*) as count FROM goparts.partscan_inventory WHERE source = 'whims' GROUP BY Marketplace",
    "source_whims_stocks": "SELECT COUNT(*) as count FROM whims_dev.stocks s INNER JOIN whims_dev.products p ON s.product_id = p.id INNER JOIN whims_dev.warehouses w ON s.warehouse_id = w.id WHERE p.status = 'active' AND w.is_active = 1",
    "sample_whims_records": "SELECT ID, PartNumber, Qty, Warehouse, Marketplace, supplier, partslink, uniq_key FROM goparts.partscan_inventory WHERE source = 'whims' LIMIT 10"
  },

  "troubleshooting": {
    "zero_records_synced": [
      "Check database connections to both whims_dev and goparts",
      "Verify query returns data: Run data_extraction_query directly in MySQL",
      "Check for errors in log file",
      "Ensure 'whims' source name is used consistently"
    ],
    "marketplace_incorrect": [
      "Review marketplace_logic priority order",
      "Check supplier values: Should be exact match for DEPO/TYC/PBI",
      "Verify sub_location matches local_location_patterns exactly",
      "Check params JSON to see raw data used for calculation"
    ],
    "duplicate_records": [
      "Check uniq_key format: whims_{product_id}_{warehouse_id}_{location_id}",
      "Ensure no NULL values in product_id, warehouse_id, or location_id",
      "Verify UNIQUE constraint on uniq_key column"
    ],
    "performance_slow": [
      "Add indexes on uniq_key and source columns",
      "Use batch inserts (500-1000 records per batch)",
      "Consider running sync during off-peak hours",
      "Monitor database server resources (CPU, memory, disk I/O)"
    ]
  }
}
