diff --git a/src/backend/scripts/make_demo_data.py b/src/backend/scripts/make_demo_data.py new file mode 100644 index 0000000..9720c2b --- /dev/null +++ b/src/backend/scripts/make_demo_data.py @@ -0,0 +1,1927 @@ +# make demo data for the database. this generates a yaml file/template +# so that it can be used to populate the database with demo data. + +from typing import List, Dict, Any +import datetime +from pathlib import Path +import json +from sqlalchemy import text + +from backend.app.schemas.store_schema import StoreStatusEnum +from backend.app.core.database import get_engine +from backend.app.utils.security import hash_password +from backend.app.utils import logger + +from backend.scripts.reset import reset_dev + +reset_dev(no_trigger=True) + +engine = get_engine() +connection = engine.connect() +transaction = connection.begin() + +DataList = List[Dict[str, Any]] + +# User +users: DataList = [ + dict( + user_id=1, + username="admin", + password="12345678", + email="admin@shopease.com", + phone_number="1133557799", + user_role="admin", + registration_date=datetime.datetime(2023, 1, 1, 0, 0, tzinfo=datetime.UTC), + ), + dict( + user_id=2, + username="demo_user", + password="12345678", + email="demo_user@foo.com", + phone_number="1234567890", + user_role="customer", + registration_date=datetime.datetime(2023, 2, 1, 0, 0, tzinfo=datetime.UTC), + ), + dict( + user_id=3, + username="merchant_1", + password="12345678", + email="merchant_1@foo.com", + phone_number="(111) 111-1111", + user_role="merchant", + registration_date=datetime.datetime(2023, 3, 1, 0, 0, tzinfo=datetime.UTC), + ), + dict( + user_id=4, + username="merchant_2", + password="12345678", + email="merchant_2@foo.com", + phone_number="(111) 111-2222", + user_role="merchant", + registration_date=datetime.datetime(2023, 4, 1, 0, 0, tzinfo=datetime.UTC), + ), +] + +logger.info("Starting to insert User data...") +for user_data in users: + try: + # Prepare data for insertion, matching table column names + # Your User table DDL uses CamelCase for columns. + insert_sql = text( + """ + INSERT INTO `User` ( + UserID, Username, PasswordHash, Email, PhoneNumber, + UserRole, RegistrationDate, LastLoginDate, DefaultAddressID, AccountStatus + ) VALUES ( + :UserID, :Username, :PasswordHash, :Email, :PhoneNumber, + :UserRole, :RegistrationDate, :LastLoginDate, :DefaultAddressID, :AccountStatus + ) + ON DUPLICATE KEY UPDATE -- Optional: if you want to update if UserID exists + Username = VALUES(Username), + PasswordHash = VALUES(PasswordHash), + Email = VALUES(Email), + PhoneNumber = VALUES(PhoneNumber), + UserRole = VALUES(UserRole), + LastLoginDate = VALUES(LastLoginDate), + DefaultAddressID = VALUES(DefaultAddressID), + AccountStatus = VALUES(AccountStatus); + """ + ) + + # Convert Python dict keys to match SQL bind parameter names (which match column names) + params = { + "UserID": user_data["user_id"], + "Username": user_data["username"], + "PasswordHash": hash_password(user_data["password"]), # Hash the password + "Email": user_data["email"], + "PhoneNumber": user_data.get("phone_number"), # Use .get() for optional fields + "UserRole": user_data["user_role"], + "RegistrationDate": user_data["registration_date"], + "LastLoginDate": user_data.get( + "last_login_date" + ), # Assuming it might be missing or None + "DefaultAddressID": user_data.get( + "default_address_id" + ), # Assuming it might be missing or None + "AccountStatus": user_data.get( + "account_status", "ACTIVE" + ), # Default to ACTIVE if not provided + } + + connection.execute(insert_sql, params) + logger.info( + f"Inserted/Updated UserID: {user_data['user_id']}, Username: {user_data['username']}" + ) + except Exception as e: + logger.error(f"Error inserting user {user_data.get('username', 'N/A')}: {e}") + # Decide if you want to rollback the whole transaction or continue + transaction.rollback() + raise # Re-raise the exception to stop the script + +# transaction.commit() + +logger.info("User data insertion complete.") + +# UserSession +user_sessions: DataList = [] + +# Store +stores: DataList = [ + dict( + store_name="Demo Store 1", + description="A demo store for testing purposes.", + owner_user_id=3, # Assuming merchant_1 is user_id 3 + store_status=StoreStatusEnum.ACTIVE.value, + creation_date=datetime.datetime(2023, 5, 1, 0, 0, tzinfo=datetime.UTC), + ), + dict( + store_name="Demo Store 2", + description="Another demo store for testing purposes.", + owner_user_id=4, # Assuming merchant_2 is user_id 4 + store_status=StoreStatusEnum.ACTIVE.value, + creation_date=datetime.datetime(2023, 6, 1, 0, 0, tzinfo=datetime.UTC), + ), + dict( + store_id=3, # ⭐ 显式指定 StoreID + store_name="Merchant 1's Applied Store", # 与 Applied SCR 中的 ProposedData 匹配 + description="This store was successfully created via a change request.", + owner_user_id=3, # 与 Applied SCR 中的 RequestingUserID 匹配 + store_status=StoreStatusEnum.ACTIVE.value, # 与 Applied SCR 中的 ProposedData 匹配 + creation_date=datetime.datetime( + 2023, 8, 1, 0, 0, tzinfo=datetime.UTC + ), # 给一个合理的创建时间 + ), +] + +logger.info("Starting to insert Store data...") +for store_data in stores: + try: + # Store 表的列名是 CamelCase + insert_sql = text( + """ + INSERT INTO `Store` ( + StoreName, OwnerUserID, Description, LogoURL, + StoreStatus, CreationDate + -- LastUpdatedDate will be set by DB default on insert + ) VALUES ( + :StoreName, :OwnerUserID, :Description, :LogoURL, + :StoreStatus, :CreationDate + ) + """ + ) + # ON DUPLICATE KEY UPDATE StoreName = VALUES(StoreName) ... ; + # (如果需要,可以添加 ON DUPLICATE KEY UPDATE,但由于 StoreID 是自增的, + # 除非有其他唯一约束(如 StoreName+OwnerUserID),否则通常不会触发) + + params = { + "StoreName": store_data["store_name"], + "OwnerUserID": store_data["owner_user_id"], + "Description": store_data.get("description"), # 使用 .get() 处理可选字段 + "LogoURL": store_data.get("logo_url"), # 使用 .get() 处理可选字段 + "StoreStatus": store_data["store_status"], # 已经是 .value + "CreationDate": store_data["creation_date"], + } + + result = connection.execute(insert_sql, params) + store_id = result.lastrowid # 获取自增的 StoreID + logger.info(f"Inserted Store: {store_data['store_name']} with StoreID: {store_id}") + + except Exception as e: + logger.error(f"Error inserting store {store_data.get('store_name', 'N/A')}: {e}") + # transaction.rollback() # 应该在脚本末尾的全局 try-except 中处理回滚 + raise # 重新抛出异常以中断脚本 + +logger.info("Store data insertion complete.") + + +# ProductCategory +product_categories: DataList = [ + dict( + category_id=1, + category_name="Home Appliances", + description="Appliances for home use.", + parent_category_id=None, + ), + dict( + category_id=2, + category_name="Electronics", + description="All kinds of electronic devices and gadgets.", + parent_category_id=None, + ), + dict( + category_id=3, + category_name="Badges", + description="General goods, e.g. badges.", + parent_category_id=None, + ), + dict( + category_id=4, + category_name="Phones", + description="Smartphones and accessories.", + parent_category_id=2, # Phones is a subcategory of Electronics + ), + dict( + category_id=5, + category_name="Laptops", + description="Laptops and accessories.", + parent_category_id=2, # Laptops is a subcategory of Electronics + ), + dict( + category_id=6, + category_name="Pads", + description="Tablets and accessories.", + parent_category_id=2, # Pads is a subcategory of Electronics + ), +] + +logger.info("Starting to insert ProductCategory data...") +for category_data in product_categories: + try: + # ProductCategory table column names are CamelCase + insert_sql = text( + """ + INSERT INTO `ProductCategory` ( + CategoryID, CategoryName, ParentCategoryID, CategoryDescription + ) VALUES ( + :CategoryID, :CategoryName, :ParentCategoryID, :CategoryDescription + ) + ON DUPLICATE KEY UPDATE + CategoryName = VALUES(CategoryName), + ParentCategoryID = VALUES(ParentCategoryID), + CategoryDescription = VALUES(CategoryDescription); + """ + ) + + params = { + "CategoryID": category_data["category_id"], + "CategoryName": category_data["category_name"], + "ParentCategoryID": category_data.get("parent_category_id"), # Handles None + "CategoryDescription": category_data.get("description"), # Handles None + } + + connection.execute(insert_sql, params) + logger.info( + f"Inserted/Updated ProductCategoryID: {category_data['category_id']}, Name: {category_data['category_name']}" + ) + except Exception as e: + logger.error( + f"Error inserting product category {category_data.get('category_name', 'N/A')}: {e}" + ) + # transaction.rollback() # Rollback immediately or let the main try-except handle it + raise # Re-raise the exception to halt the script or be caught by an outer handler + +logger.info("ProductCategory data insertion complete.") + + +# Product +products_promote: DataList = [ + dict( + product_id=1, + product_name="New MacBook", + price=699, + store_id=1, # Assuming Demo Store 1 is store_id 1 + category_id=5, # Laptops category + product_description="Latest MacBook with M1 chip.", + stock_quantity=50, + main_image_url="http://localhost:8000/src/assets/images/promo/macbook-new.jpg", + ), + dict( + product_id=2, + product_name="iPhone ?? Pro", + price=1299, + store_id=1, # Assuming Demo Store 1 is store_id 1 + category_id=4, # Phones category + product_description="Latest iPhone with advanced features.", + stock_quantity=30, + main_image_url="http://localhost:8000/src/assets/images/promo/iphone.jpg", + ), + dict( + product_id=3, + product_name="iPad ??", + price=599, + store_id=1, # Assuming Demo Store 1 is store_id 1 + category_id=6, # Pads category + product_description="Latest iPad with M1 chip.", + main_image_url="http://localhost:8000/src/assets/images/promo/ipad.jpg", + ), +] + +other_products: DataList = [] + +root_dir = Path(__file__).resolve().parent.parent.parent +products_dir = root_dir / "static" / "img" / "product" +category_id_mapping = { + "appliance": 1, + "electronic": 2, + "badge": 3, + "phone": 4, + "pc": 5, + "pad": 6, +} +# Function to load product images from a directory +for category in products_dir.iterdir(): + category_name = category.name.lower() + category_id = category_id_mapping.get(category_name, None) + print(f"Processing category: {category_name}") + if category.is_dir(): + for i, product_image in enumerate(category.iterdir()): + if product_image.is_file() and product_image.suffix.lower() in [ + ".jpg", + ".jpeg", + ".png", + ]: + # Create a product entry + name = f"{category.name.capitalize()} {i + 1}" + url = product_image.relative_to(root_dir).as_posix() + print(f"Adding product image: {url}") + + store_id = ( + 1 if category.name not in ["badge", "appliance"] else 2 + ) # Assuming Demo Store 1 for most categories, Demo Store 2 for badges and appliances + product_entry = dict( + product_id=len(other_products) + + 4, # Start from 4 to avoid conflicts with promoted products + product_name=name, + price=100 + len(other_products) * 10, # Example pricing logic + store_id=store_id, + category_id=category_id, # Assign to Home Appliances category + product_description=f"Description for {category.name} product {name}.", + stock_quantity=20, + main_image_url=f"http://localhost:8000/{url}", # Use relative path for image URL + ) + other_products.append(product_entry) + +products = products_promote + other_products + +logger.info("Starting to insert Product data...") +for product_data in products: + try: + # Product 表的列名是 CamelCase + insert_sql = text( + """ + INSERT INTO `Product` ( + ProductID, ProductName, ProductDescription, Price, ProductStatus, + StoreID, CategoryID, StockQuantity, MainImageURL + -- CreationDate and LastUpdatedDate will use DB defaults + ) VALUES ( + :ProductID, :ProductName, :ProductDescription, :Price, :ProductStatus, + :StoreID, :CategoryID, :StockQuantity, :MainImageURL + ) + ON DUPLICATE KEY UPDATE + ProductName = VALUES(ProductName), + ProductDescription = VALUES(ProductDescription), + Price = VALUES(Price), + ProductStatus = VALUES(ProductStatus), + StoreID = VALUES(StoreID), + CategoryID = VALUES(CategoryID), + StockQuantity = VALUES(StockQuantity), + MainImageURL = VALUES(MainImageURL), + LastUpdatedDate = CURRENT_TIMESTAMP; -- Explicitly update LastUpdatedDate on duplicate + """ + ) + + # 准备参数字典,确保键名与SQL绑定参数一致 + params = { + "ProductID": product_data["product_id"], + "ProductName": product_data["product_name"], + "ProductDescription": product_data.get( + "product_description" + ), # 使用 .get() 处理可选字段 + "Price": product_data["price"], # 已经是 Decimal 或 int/float,数据库会处理 + "ProductStatus": product_data.get("product_status", "ACTIVE"), # 默认为 ACTIVE + "StoreID": product_data["store_id"], + "CategoryID": product_data["category_id"], + "StockQuantity": product_data.get("stock_quantity", 0), # 默认为 0 + "MainImageURL": product_data.get("main_image_url"), + } + + connection.execute(insert_sql, params) + logger.info( + f"Inserted/Updated ProductID: {product_data['product_id']}, Name: {product_data['product_name']}" + ) + except Exception as e: + logger.error( + f"Error inserting product {product_data.get('product_name', 'N/A')} (ID: {product_data.get('product_id', 'N/A')}): {e}" + ) + # transaction.rollback() # 应该在脚本末尾的全局 try-except 中处理回滚 + raise # Re-raise the exception to halt the script or be caught by an outer handler + +logger.info("Product data insertion complete.") + +# now ProductID = 1..42 + +# CartItem +# (This code should be appended to your existing Python script) + +# Assuming 'products_promote' and 'other_products' lists are populated as in your script. +# We need to handle the case where other_products might be empty if no images were found. + +# For PriceAtAddition, ensure it's a format suitable for DECIMAL(10,2) +# Python floats are generally fine, or you can use strings. +# For consistency with your product price definitions (which are integers), +# I'll use float for PriceAtAddition. + +cart_items: DataList = [ + # Cart items for UserID = 2 (demo_user) + dict( + UserID=2, + ProductID=products_promote[0]["product_id"], # MacBook + Quantity=1, + PriceAtAddition=float(products_promote[0]["price"]), # 699.00 + AddedDate=datetime.datetime(2023, 7, 1, 10, 0, 0, tzinfo=datetime.timezone.utc), + ), + dict( + UserID=2, + ProductID=products_promote[2]["product_id"], # iPad + Quantity=2, + PriceAtAddition=float(products_promote[2]["price"]), # 599.00 + AddedDate=datetime.datetime(2023, 7, 1, 10, 5, 30, tzinfo=datetime.timezone.utc), + ), + # Cart items for UserID = 3 (merchant_1) + dict( + UserID=3, + ProductID=products_promote[1]["product_id"], # iPhone + Quantity=1, + PriceAtAddition=float(products_promote[1]["price"]), # 1299.00 + AddedDate=datetime.datetime(2023, 7, 2, 11, 15, 0, tzinfo=datetime.timezone.utc), + ), +] + +# Add an item from other_products if available +if other_products: + first_other_product = other_products[0] + cart_items.append( + dict( + UserID=3, # merchant_1 + ProductID=first_other_product["product_id"], + Quantity=3, + PriceAtAddition=float(first_other_product["price"]), + AddedDate=datetime.datetime(2023, 7, 2, 11, 20, 0, tzinfo=datetime.timezone.utc), + ) + ) + # Add another item from other_products for a different user if available + if len(other_products) > 1: + second_other_product = other_products[1] + cart_items.append( + dict( + UserID=4, # merchant_2 + ProductID=second_other_product["product_id"], + Quantity=1, + PriceAtAddition=float(second_other_product["price"]), + AddedDate=datetime.datetime(2023, 7, 3, 9, 30, 0, tzinfo=datetime.timezone.utc), + ) + ) +else: + print("Warning: 'other_products' list is empty. No cart items will be generated from it.") + +# You can add more cart items here following the pattern. +# Example: Another item for demo_user (UserID=2) +assert ( + len(other_products) >= 5 +), "Not enough products in 'other_products' to add more items for demo_user." +for i in range(3, 5): # Adding two more items for demo_user + other_prod = other_products[i] + cart_items.append( + dict( + UserID=2, + ProductID=other_prod["product_id"], + Quantity=1, + PriceAtAddition=float(other_prod["price"]), + AddedDate=datetime.datetime(2023, 7, 4, 14, 0, 0, tzinfo=datetime.timezone.utc), + ) + ) + + +logger.info("Starting to insert CartItem data...") +for item_data in cart_items: + try: + # CartItem 表的列名是 CamelCase + insert_sql = text( + """ + INSERT INTO `CartItem` ( + UserID, ProductID, Quantity, PriceAtAddition, AddedDate + ) VALUES ( + :UserID, :ProductID, :Quantity, :PriceAtAddition, :AddedDate + ) + """ + ) + # CartItemID 是自增的,所以不包含在 INSERT 语句中 + # AddedDate 在您的数据中已提供,如果省略,数据库会使用 DEFAULT CURRENT_TIMESTAMP + + # 准备参数字典,确保键名与SQL绑定参数一致 + # 您的 item_data 字典中的键已经是 CamelCase,与数据库列名一致 + params = { + "UserID": item_data["UserID"], + "ProductID": item_data["ProductID"], + "Quantity": item_data["Quantity"], + "PriceAtAddition": item_data["PriceAtAddition"], # 应该是 float 或 Decimal + "AddedDate": item_data["AddedDate"], # 已经是 datetime 对象 + } + + connection.execute(insert_sql, params) + # 对于 CartItem,lastrowid 也会返回新生成的 CartItemID + # cart_item_id = result.lastrowid + # logger.info(f"Inserted CartItem for UserID: {item_data['UserID']}, ProductID: {item_data['ProductID']} with CartItemID: {cart_item_id}") + logger.info( + f"Inserted CartItem for UserID: {item_data['UserID']}, ProductID: {item_data['ProductID']}" + ) + + except Exception as e: + logger.error( + f"Error inserting cart item for UserID {item_data.get('UserID', 'N/A')}, ProductID {item_data.get('ProductID', 'N/A')}: {e}" + ) + # transaction.rollback() # 应该在脚本末尾的全局 try-except 中处理回滚 + raise # Re-raise the exception to halt the script or be caught by an outer handler + +logger.info("CartItem data insertion complete.") + +# (This code should be appended to your existing Python script after product definitions) +from decimal import Decimal + +# --- Helper Data from your existing script (assuming these are available) --- +# UserIDs: 1 (admin), 2 (demo_user), 3 (merchant_1), 4 (merchant_2) +# StoreIDs: 1 (owned by UserID 3), 2 (owned by UserID 4) +# ProductIDs and Prices (example): +# Product 1: ID=1, Name="New MacBook", Price=699, StoreID=1 +# Product 2: ID=2, Name="iPhone ?? Pro", Price=1299, StoreID=1 +# Product 3: ID=3, Name="iPad ??", Price=599, StoreID=1 +# Assume other_products list exists and we can pick some from it. +# Let's define some product details for clarity in this section: +product_data_for_orders = { + 1: { + "Name": "New MacBook", + "Price": Decimal("699.00"), + "ImageURL": products_promote[0].get("main_image_url"), + "StoreID": 1, + }, + 2: { + "Name": "iPhone ?? Pro", + "Price": Decimal("1299.00"), + "ImageURL": products_promote[1].get("main_image_url"), + "StoreID": 1, + }, + 3: { + "Name": "iPad ??", + "Price": Decimal("599.00"), + "ImageURL": products_promote[2].get("main_image_url"), + "StoreID": 1, + }, + # Assuming other_products[0] is ProductID=4, other_products[1] is ProductID=5 + 4: { + "Name": ( + other_products[0]["product_name"] if len(other_products) > 0 else "Sample Product 4" + ), + "Price": ( + Decimal(str(other_products[0]["price"])) + if len(other_products) > 0 + else Decimal("100.00") + ), + "ImageURL": other_products[0].get("main_image_url") if len(other_products) > 0 else None, + "StoreID": other_products[0]["store_id"] if len(other_products) > 0 else 1, + }, + 5: { + "Name": ( + other_products[1]["product_name"] if len(other_products) > 1 else "Sample Product 5" + ), + "Price": ( + Decimal(str(other_products[1]["price"])) + if len(other_products) > 1 + else Decimal("110.00") + ), + "ImageURL": other_products[1].get("main_image_url") if len(other_products) > 1 else None, + "StoreID": other_products[1]["store_id"] if len(other_products) > 1 else 2, + }, +} + +# Base time for sequential timestamps +base_time = datetime.datetime(2023, 8, 1, 10, 0, 0, tzinfo=datetime.timezone.utc) +time_delta_minutes = 0 + + +def next_timestamp(add_minutes: int = 5) -> datetime.datetime: + global time_delta_minutes + time_delta_minutes += add_minutes + return base_time + datetime.timedelta(minutes=time_delta_minutes) + + +# --- PaymentTransaction Data --- +# PaymentTransactionID is auto-increment, so we don't specify it for INSERT +# We'll link orders to these by their conceptual ID (e.g., pt_id_1) +payment_transactions: DataList = [] + +# --- Order Data --- +orders: DataList = [] + +# --- OrderItem Data --- +order_items: DataList = [] + +# --- Scenario: 9 Order Statuses (Simple: 1 PT -> 1 Order -> 1 Item each) --- +# For these, PaymentTransaction.TotalAmount == Order.FinalAmountForThisOrder == OrderItem.Subtotal + +# 1. PENDING_PAYMENT +pt_id_1 = 1 # Conceptual ID for linking +order_id_1 = 1 +product_1_details = product_data_for_orders[1] +item_1_quantity = 1 +item_1_subtotal = product_1_details["Price"] * item_1_quantity + +payment_transactions.append( + dict( + # PaymentTransactionID: pt_id_1, # Auto-increment + UserID=2, # demo_user + TotalAmount=item_1_subtotal, + PaymentMethod="MOCK_PAY_PENDING", + ExternalGatewayTransactionID=None, + Status="PENDING", + CreationTime=next_timestamp(), + CompletionTime=None, + # LastUpdatedDate handled by DB + ) +) +orders.append( + dict( + # OrderID: order_id_1, # Auto-increment + UserID=2, + StoreID=product_1_details["StoreID"], + PaymentTransactionID=pt_id_1, # Link to the conceptual PT ID + OrderStatus="PENDING_PAYMENT", + OrderTotalAmount=item_1_subtotal, + DiscountAmount=Decimal("0.00"), + ShippingFee=Decimal("0.00"), + FinalAmountForThisOrder=item_1_subtotal, + ShippingAddress_RecipientName="Demo User Pending", + ShippingAddress_PhoneNumber="1231231234", + ShippingAddress_Full="123 Pending St, Testville", + Notes_ByUser="Please confirm payment.", + Notes_ByMerchant=None, + CreationTime=payment_transactions[-1]["CreationTime"], # Match PT creation + PaymentConfirmationTime=None, + ShippingTime=None, + DeliveryTime=None, + CompletionTime=None, + ) +) +order_items.append( + dict( + # OrderItemID: auto-increment + OrderID=order_id_1, # Link to conceptual Order ID + ProductID=1, + StoreID=product_1_details["StoreID"], + Quantity=item_1_quantity, + PriceAtPurchase=product_1_details["Price"], + ProductNameAtPurchase=product_1_details["Name"], + ProductImageURLAtPurchase=product_1_details["ImageURL"], + Subtotal=item_1_subtotal, + ) +) + +# 2. PAID_AND_PENDING_PROCESSING +pt_id_2 = 2 +order_id_2 = 2 +product_2_details = product_data_for_orders[2] +item_2_quantity = 1 +item_2_subtotal = product_2_details["Price"] * item_2_quantity +pt_2_creation_time = next_timestamp() + +payment_transactions.append( + dict( + UserID=2, + TotalAmount=item_2_subtotal, + PaymentMethod="MOCK_PAY_SUCCESS", + ExternalGatewayTransactionID="gw_paid_123", + Status="SUCCESSFUL", + CreationTime=pt_2_creation_time, + CompletionTime=pt_2_creation_time + datetime.timedelta(seconds=30), + ) +) +orders.append( + dict( + UserID=2, + StoreID=product_2_details["StoreID"], + PaymentTransactionID=pt_id_2, + OrderStatus="PAID_AND_PENDING_PROCESSING", + OrderTotalAmount=item_2_subtotal, + FinalAmountForThisOrder=item_2_subtotal, + ShippingAddress_RecipientName="Demo User Paid", + ShippingAddress_PhoneNumber="1231231234", + ShippingAddress_Full="456 Paid Ave, Testburg", + Notes_ByUser="Paid, process please.", + CreationTime=pt_2_creation_time, + PaymentConfirmationTime=( + orders[-1]["CreationTime"] + datetime.timedelta(seconds=30) + if orders + else pt_2_creation_time + datetime.timedelta(seconds=30) + ), + ) +) +order_items.append( + dict( + OrderID=order_id_2, + ProductID=2, + StoreID=product_2_details["StoreID"], + Quantity=item_2_quantity, + PriceAtPurchase=product_2_details["Price"], + ProductNameAtPurchase=product_2_details["Name"], + ProductImageURLAtPurchase=product_2_details["ImageURL"], + Subtotal=item_2_subtotal, + ) +) + +# 3. PROCESSING_BY_MERCHANT +pt_id_3 = 3 +order_id_3 = 3 +product_3_details = product_data_for_orders[3] +item_3_quantity = 1 +item_3_subtotal = product_3_details["Price"] * item_3_quantity +pt_3_creation_time = next_timestamp() +payment_confirm_time_3 = pt_3_creation_time + datetime.timedelta(seconds=20) + +payment_transactions.append( + dict( + UserID=3, + TotalAmount=item_3_subtotal, + PaymentMethod="MOCK_PAY_SUCCESS", + ExternalGatewayTransactionID="gw_proc_456", + Status="SUCCESSFUL", + CreationTime=pt_3_creation_time, + CompletionTime=payment_confirm_time_3, + ) +) +orders.append( + dict( + UserID=3, + StoreID=product_3_details["StoreID"], + PaymentTransactionID=pt_id_3, + OrderStatus="PROCESSING_BY_MERCHANT", + OrderTotalAmount=item_3_subtotal, + FinalAmountForThisOrder=item_3_subtotal, + ShippingAddress_RecipientName="Merchant One Processing", + ShippingAddress_PhoneNumber="1111111111", + ShippingAddress_Full="789 Merchant Rd, Shopsville", + CreationTime=pt_3_creation_time, + PaymentConfirmationTime=payment_confirm_time_3, + ) +) +order_items.append( + dict( + OrderID=order_id_3, + ProductID=3, + StoreID=product_3_details["StoreID"], + Quantity=item_3_quantity, + PriceAtPurchase=product_3_details["Price"], + ProductNameAtPurchase=product_3_details["Name"], + ProductImageURLAtPurchase=product_3_details["ImageURL"], + Subtotal=item_3_subtotal, + ) +) + + +# 4. SHIPPED +pt_id_4 = 4 +order_id_4 = 4 +product_4_details = product_data_for_orders[4] +item_4_quantity = 1 +item_4_subtotal = product_4_details["Price"] * item_4_quantity +pt_4_creation_time = next_timestamp() +payment_confirm_time_4 = pt_4_creation_time + datetime.timedelta(seconds=15) +shipping_time_4 = payment_confirm_time_4 + datetime.timedelta(hours=2) + +payment_transactions.append( + dict( + UserID=3, + TotalAmount=item_4_subtotal, + PaymentMethod="MOCK_PAY_SUCCESS", + ExternalGatewayTransactionID="gw_ship_789", + Status="SUCCESSFUL", + CreationTime=pt_4_creation_time, + CompletionTime=payment_confirm_time_4, + ) +) +orders.append( + dict( + UserID=3, + StoreID=product_4_details["StoreID"], + PaymentTransactionID=pt_id_4, + OrderStatus="SHIPPED", + OrderTotalAmount=item_4_subtotal, + FinalAmountForThisOrder=item_4_subtotal, + ShippingAddress_RecipientName="Merchant One Shipped", + ShippingAddress_PhoneNumber="1111111111", + ShippingAddress_Full="789 Merchant Rd, Shopsville", + Notes_ByMerchant="Shipped via Express", + CreationTime=pt_4_creation_time, + PaymentConfirmationTime=payment_confirm_time_4, + ShippingTime=shipping_time_4, + ) +) +order_items.append( + dict( + OrderID=order_id_4, + ProductID=4, + StoreID=product_4_details["StoreID"], + Quantity=item_4_quantity, + PriceAtPurchase=product_4_details["Price"], + ProductNameAtPurchase=product_4_details["Name"], + ProductImageURLAtPurchase=product_4_details["ImageURL"], + Subtotal=item_4_subtotal, + ) +) + +# 5. DELIVERED +pt_id_5 = 5 +order_id_5 = 5 +# Use product from store 2 for variety +product_5_details = product_data_for_orders[5] +item_5_quantity = 1 +item_5_subtotal = product_5_details["Price"] * item_5_quantity +pt_5_creation_time = next_timestamp() +payment_confirm_time_5 = pt_5_creation_time + datetime.timedelta(seconds=25) +shipping_time_5 = payment_confirm_time_5 + datetime.timedelta(hours=1) +delivery_time_5 = shipping_time_5 + datetime.timedelta(days=2) + + +payment_transactions.append( + dict( + UserID=4, + TotalAmount=item_5_subtotal, + PaymentMethod="MOCK_PAY_SUCCESS", + ExternalGatewayTransactionID="gw_del_abc", + Status="SUCCESSFUL", + CreationTime=pt_5_creation_time, + CompletionTime=payment_confirm_time_5, + ) +) +orders.append( + dict( + UserID=4, + StoreID=product_5_details["StoreID"], + PaymentTransactionID=pt_id_5, + OrderStatus="DELIVERED", + OrderTotalAmount=item_5_subtotal, + FinalAmountForThisOrder=item_5_subtotal, + ShippingAddress_RecipientName="Merchant Two Delivered", + ShippingAddress_PhoneNumber="2222222222", + ShippingAddress_Full="321 Second St, Another Town", + CreationTime=pt_5_creation_time, + PaymentConfirmationTime=payment_confirm_time_5, + ShippingTime=shipping_time_5, + DeliveryTime=delivery_time_5, + ) +) +order_items.append( + dict( + OrderID=order_id_5, + ProductID=5, + StoreID=product_5_details["StoreID"], + Quantity=item_5_quantity, + PriceAtPurchase=product_5_details["Price"], + ProductNameAtPurchase=product_5_details["Name"], + ProductImageURLAtPurchase=product_5_details["ImageURL"], + Subtotal=item_5_subtotal, + ) +) + +# 6. COMPLETED +pt_id_6 = 6 +order_id_6 = 6 +item_6_quantity = 1 +item_6_subtotal = product_1_details["Price"] * item_6_quantity # Product 1 again +pt_6_creation_time = next_timestamp() +payment_confirm_time_6 = pt_6_creation_time + datetime.timedelta(seconds=10) +shipping_time_6 = payment_confirm_time_6 + datetime.timedelta(hours=3) +delivery_time_6 = shipping_time_6 + datetime.timedelta(days=1) +completion_time_6 = delivery_time_6 + datetime.timedelta(days=15) # 15 days after delivery + +payment_transactions.append( + dict( + UserID=2, + TotalAmount=item_6_subtotal, + PaymentMethod="MOCK_PAY_SUCCESS", + ExternalGatewayTransactionID="gw_comp_def", + Status="SUCCESSFUL", + CreationTime=pt_6_creation_time, + CompletionTime=payment_confirm_time_6, + ) +) +orders.append( + dict( + UserID=2, + StoreID=product_1_details["StoreID"], + PaymentTransactionID=pt_id_6, + OrderStatus="COMPLETED", + OrderTotalAmount=item_6_subtotal, + FinalAmountForThisOrder=item_6_subtotal, + ShippingAddress_RecipientName="Demo User Completed", + ShippingAddress_PhoneNumber="1231231234", + ShippingAddress_Full="1 Final Rd, Testville", + CreationTime=pt_6_creation_time, + PaymentConfirmationTime=payment_confirm_time_6, + ShippingTime=shipping_time_6, + DeliveryTime=delivery_time_6, + CompletionTime=completion_time_6, + ) +) +order_items.append( + dict( + OrderID=order_id_6, + ProductID=1, + StoreID=product_1_details["StoreID"], + Quantity=item_6_quantity, + PriceAtPurchase=product_1_details["Price"], + ProductNameAtPurchase=product_1_details["Name"], + ProductImageURLAtPurchase=product_1_details["ImageURL"], + Subtotal=item_6_subtotal, + ) +) + +# 7. CANCELLED_BY_USER +pt_id_7 = 7 +order_id_7 = 7 +item_7_quantity = 1 +item_7_subtotal = product_2_details["Price"] * item_7_quantity +pt_7_creation_time = next_timestamp() + +payment_transactions.append( + dict( + UserID=2, + TotalAmount=item_7_subtotal, + PaymentMethod="MOCK_PAY_PENDING", + Status="PENDING", # User cancelled before payment + CreationTime=pt_7_creation_time, + CompletionTime=None, + ) +) +orders.append( + dict( + UserID=2, + StoreID=product_2_details["StoreID"], + PaymentTransactionID=pt_id_7, + OrderStatus="CANCELLED_BY_USER", + OrderTotalAmount=item_7_subtotal, + FinalAmountForThisOrder=item_7_subtotal, # Or 0 if cancelled means no charge + ShippingAddress_RecipientName="Demo User Cancelled", + ShippingAddress_PhoneNumber="1231231234", + ShippingAddress_Full="1 Cancel St, Testville", + Notes_ByUser="I changed my mind.", + CreationTime=pt_7_creation_time, + CompletionTime=pt_7_creation_time + + datetime.timedelta(minutes=10), # Order completion/cancellation time + ) +) +order_items.append( + dict( + OrderID=order_id_7, + ProductID=2, + StoreID=product_2_details["StoreID"], + Quantity=item_7_quantity, + PriceAtPurchase=product_2_details["Price"], + ProductNameAtPurchase=product_2_details["Name"], + ProductImageURLAtPurchase=product_2_details["ImageURL"], + Subtotal=item_7_subtotal, + ) +) + +# 8. CANCELLED_BY_MERCHANT +pt_id_8 = 8 +order_id_8 = 8 +item_8_quantity = 1 +item_8_subtotal = product_3_details["Price"] * item_8_quantity +pt_8_creation_time = next_timestamp() +payment_confirm_time_8 = pt_8_creation_time + datetime.timedelta(seconds=5) + +payment_transactions.append( + dict( + UserID=3, + TotalAmount=item_8_subtotal, + PaymentMethod="MOCK_PAY_SUCCESS", + ExternalGatewayTransactionID="gw_cancel_merc_1", + Status="SUCCESSFUL", # Paid, but merchant cancelled + CreationTime=pt_8_creation_time, + CompletionTime=payment_confirm_time_8, + ) +) +orders.append( + dict( + UserID=3, + StoreID=product_3_details["StoreID"], + PaymentTransactionID=pt_id_8, + OrderStatus="CANCELLED_BY_MERCHANT", + OrderTotalAmount=item_8_subtotal, + FinalAmountForThisOrder=item_8_subtotal, # Or 0 if refund is implied + ShippingAddress_RecipientName="Merchant One Cancelled by Merchant", + ShippingAddress_PhoneNumber="1111111111", + ShippingAddress_Full="789 Merchant Rd, Shopsville", + Notes_ByMerchant="Item out of stock unexpectedly.", + CreationTime=pt_8_creation_time, + PaymentConfirmationTime=payment_confirm_time_8, + CompletionTime=payment_confirm_time_8 + + datetime.timedelta(minutes=5), # Order completion/cancellation time + ) +) +order_items.append( + dict( + OrderID=order_id_8, + ProductID=3, + StoreID=product_3_details["StoreID"], + Quantity=item_8_quantity, + PriceAtPurchase=product_3_details["Price"], + ProductNameAtPurchase=product_3_details["Name"], + ProductImageURLAtPurchase=product_3_details["ImageURL"], + Subtotal=item_8_subtotal, + ) +) + +# 9. CANCELLED_BY_SYSTEM +pt_id_9 = 9 +order_id_9 = 9 +item_9_quantity = 1 +item_9_subtotal = product_4_details["Price"] * item_9_quantity +pt_9_creation_time = next_timestamp() + +payment_transactions.append( + dict( + UserID=4, + TotalAmount=item_9_subtotal, + PaymentMethod="MOCK_PAY_TIMEOUT", + ExternalGatewayTransactionID=None, + Status="FAILED", # Payment timed out and failed + CreationTime=pt_9_creation_time, + CompletionTime=pt_9_creation_time + + datetime.timedelta(minutes=30), # Payment failed after 30 mins + ) +) +orders.append( + dict( + UserID=4, + StoreID=product_4_details["StoreID"], + PaymentTransactionID=pt_id_9, + OrderStatus="CANCELLED_BY_SYSTEM", + OrderTotalAmount=item_9_subtotal, + FinalAmountForThisOrder=item_9_subtotal, # Or 0 + ShippingAddress_RecipientName="Merchant Two System Cancel", + ShippingAddress_PhoneNumber="2222222222", + ShippingAddress_Full="321 Second St, Another Town", + Notes_ByMerchant="System: Payment timed out.", + CreationTime=pt_9_creation_time, + CompletionTime=pt_9_creation_time + + datetime.timedelta(minutes=31), # Order completion/cancellation time + ) +) +order_items.append( + dict( + OrderID=order_id_9, + ProductID=4, + StoreID=product_4_details["StoreID"], + Quantity=item_9_quantity, + PriceAtPurchase=product_4_details["Price"], + ProductNameAtPurchase=product_4_details["Name"], + ProductImageURLAtPurchase=product_4_details["ImageURL"], + Subtotal=item_9_subtotal, + ) +) + + +# --- Scenario: Complex 1 (1 PT -> 1 Order -> 2 Items) --- +pt_id_10 = 10 +order_id_10 = 10 +# Items from Store 1: Product 1 (MacBook), Product 2 (iPhone) +item10a_details = product_data_for_orders[1] +item10a_quantity = 1 +item10a_subtotal = item10a_details["Price"] * item10a_quantity + +item10b_details = product_data_for_orders[2] +item10b_quantity = 1 +item10b_subtotal = item10b_details["Price"] * item10b_quantity + +order10_total_amount = item10a_subtotal + item10b_subtotal +pt_10_creation_time = next_timestamp() +payment_confirm_time_10 = pt_10_creation_time + datetime.timedelta(seconds=40) + +payment_transactions.append( + dict( + UserID=2, + TotalAmount=order10_total_amount, + PaymentMethod="MOCK_PAY_MULTI_ITEM", + ExternalGatewayTransactionID="gw_multi_item_1", + Status="SUCCESSFUL", + CreationTime=pt_10_creation_time, + CompletionTime=payment_confirm_time_10, + ) +) +orders.append( + dict( + UserID=2, + StoreID=1, + PaymentTransactionID=pt_id_10, # Both products from Store 1 + OrderStatus="PROCESSING_BY_MERCHANT", + OrderTotalAmount=order10_total_amount, + FinalAmountForThisOrder=order10_total_amount, + ShippingAddress_RecipientName="Demo User Multi Item", + ShippingAddress_PhoneNumber="1231231234", + ShippingAddress_Full="10 MultiItem Way, Testville", + CreationTime=pt_10_creation_time, + PaymentConfirmationTime=payment_confirm_time_10, + ) +) +order_items.append( + dict( + OrderID=order_id_10, + ProductID=1, + StoreID=item10a_details["StoreID"], + Quantity=item10a_quantity, + PriceAtPurchase=item10a_details["Price"], + ProductNameAtPurchase=item10a_details["Name"], + ProductImageURLAtPurchase=item10a_details["ImageURL"], + Subtotal=item10a_subtotal, + ) +) +order_items.append( + dict( + OrderID=order_id_10, + ProductID=2, + StoreID=item10b_details["StoreID"], + Quantity=item10b_quantity, + PriceAtPurchase=item10b_details["Price"], + ProductNameAtPurchase=item10b_details["Name"], + ProductImageURLAtPurchase=item10b_details["ImageURL"], + Subtotal=item10b_subtotal, + ) +) + +# --- Scenario: Complex 2 (1 PT -> 2 Orders (diff stores) -> 1 Item each) --- +pt_id_11 = 11 # Single Payment Transaction for two orders +order_id_11 = 11 # Order for Store 1 +order_id_12 = 12 # Order for Store 2 + +# Item for Order 11 (Store 1, Product 3 - iPad) +item11_details = product_data_for_orders[3] # iPad from Store 1 +item11_quantity = 1 +item11_subtotal = item11_details["Price"] * item11_quantity +order11_final_amount = item11_subtotal # Assuming no shipping/discount for simplicity + +# Item for Order 12 (Store 2, Product 5 - Sample Product 5) +item12_details = product_data_for_orders[5] # Sample Product 5 from Store 2 +item12_quantity = 2 +item12_subtotal = item12_details["Price"] * item12_quantity +order12_final_amount = item12_subtotal + +payment_transaction_11_total = order11_final_amount + order12_final_amount +pt_11_creation_time = next_timestamp() +payment_confirm_time_11 = pt_11_creation_time + datetime.timedelta(seconds=50) + +payment_transactions.append( + dict( + UserID=4, + TotalAmount=payment_transaction_11_total, + PaymentMethod="MOCK_PAY_MULTI_ORDER", + ExternalGatewayTransactionID="gw_multi_order_1", + Status="SUCCESSFUL", + CreationTime=pt_11_creation_time, + CompletionTime=payment_confirm_time_11, + ) +) + +# Order 11 (Store 1) +orders.append( + dict( + UserID=4, + StoreID=item11_details["StoreID"], + PaymentTransactionID=pt_id_11, + OrderStatus="SHIPPED", + OrderTotalAmount=item11_subtotal, + FinalAmountForThisOrder=order11_final_amount, + ShippingAddress_RecipientName="Merchant Two Multi Order", + ShippingAddress_PhoneNumber="2222222222", + ShippingAddress_Full="PO Box 11, Split Order Town", + CreationTime=pt_11_creation_time, + PaymentConfirmationTime=payment_confirm_time_11, + ShippingTime=payment_confirm_time_11 + datetime.timedelta(hours=1), + ) +) +order_items.append( + dict( + OrderID=order_id_11, + ProductID=3, + StoreID=item11_details["StoreID"], + Quantity=item11_quantity, + PriceAtPurchase=item11_details["Price"], + ProductNameAtPurchase=item11_details["Name"], + ProductImageURLAtPurchase=item11_details["ImageURL"], + Subtotal=item11_subtotal, + ) +) + +# Order 12 (Store 2) +orders.append( + dict( + UserID=4, + StoreID=item12_details["StoreID"], + PaymentTransactionID=pt_id_11, + OrderStatus="SHIPPED", + OrderTotalAmount=item12_subtotal, + FinalAmountForThisOrder=order12_final_amount, + ShippingAddress_RecipientName="Merchant Two Multi Order", + ShippingAddress_PhoneNumber="2222222222", # Same address for both parts of the PT + ShippingAddress_Full="PO Box 11, Split Order Town", + CreationTime=pt_11_creation_time, + PaymentConfirmationTime=payment_confirm_time_11, + ShippingTime=payment_confirm_time_11 + + datetime.timedelta(hours=1, minutes=30), # Shipped slightly later + ) +) +order_items.append( + dict( + OrderID=order_id_12, + ProductID=5, + StoreID=item12_details["StoreID"], + Quantity=item12_quantity, + PriceAtPurchase=item12_details["Price"], + ProductNameAtPurchase=item12_details["Name"], + ProductImageURLAtPurchase=item12_details["ImageURL"], + Subtotal=item12_subtotal, + ) +) + + +logger.info("Starting to insert PaymentTransaction, Order, and OrderItem data...") + +# --- 1. Insert PaymentTransaction Data --- +logger.info("Inserting PaymentTransaction data...") +for pt_data in payment_transactions: + try: + # For demo data, we'll insert the conceptual PaymentTransactionID + # In a real app, PaymentTransactionID would be auto-incremented + # and then used for subsequent Order inserts. + # Here, we assume pt_data might contain a key like 'conceptual_pt_id' + # which we then use as PaymentTransactionID. + # Your current data structure for payment_transactions does not have an explicit ID field, + # but the orders refer to pt_id_1, pt_id_2 etc. + # For simplicity, I will assume the order of insertion matches these conceptual IDs for linking. + # This means the first PT inserted gets ID 1, second gets ID 2, IF the table is empty. + # A better way for demo data is to add 'PaymentTransactionID' to your pt_data dicts. + + # Let's add the conceptual ID to the params if it's not there, + # assuming the list order corresponds to conceptual IDs 1, 2, 3... + # This is fragile. It's better if your pt_data dicts already include the ID you want to insert. + # For this example, I will omit explicit PaymentTransactionID insertion and rely on auto-increment + # and then immediately fetch it if subsequent tables need it. + # However, your 'orders' data directly references PaymentTransactionID=pt_id_X. + # This means we MUST insert with explicit PaymentTransactionIDs for your current data structure to work. + + insert_pt_sql = text( + """ + INSERT INTO `PaymentTransaction` ( + PaymentTransactionID, UserID, TotalAmount, PaymentMethod, + ExternalGatewayTransactionID, Status, CreationTime, CompletionTime + -- LastUpdatedDate will use DB default on insert + ) VALUES ( + :PaymentTransactionID, :UserID, :TotalAmount, :PaymentMethod, + :ExternalGatewayTransactionID, :Status, :CreationTime, :CompletionTime + ) + ON DUPLICATE KEY UPDATE + UserID = VALUES(UserID), TotalAmount = VALUES(TotalAmount), PaymentMethod = VALUES(PaymentMethod), + ExternalGatewayTransactionID = VALUES(ExternalGatewayTransactionID), Status = VALUES(Status), + CreationTime = VALUES(CreationTime), CompletionTime = VALUES(CompletionTime), + LastUpdatedDate = CURRENT_TIMESTAMP; + """ + ) + + # We need to assign PaymentTransactionID for linking. + # Let's add a conceptual_id to your data generation or derive it. + # For this example, I'll assume the index+1 is the intended ID. + conceptual_pt_id = payment_transactions.index(pt_data) + 1 + + params_pt = { + "PaymentTransactionID": conceptual_pt_id, # Explicitly setting for demo linking + "UserID": pt_data["UserID"], + "TotalAmount": pt_data["TotalAmount"], + "PaymentMethod": pt_data["PaymentMethod"], + "ExternalGatewayTransactionID": pt_data.get("ExternalGatewayTransactionID"), + "Status": pt_data["Status"], # Should be enum.value if pt_data stores enums + "CreationTime": pt_data["CreationTime"], + "CompletionTime": pt_data.get("CompletionTime"), + } + connection.execute(insert_pt_sql, params_pt) + logger.info( + f"Inserted/Updated PaymentTransactionID: {conceptual_pt_id} for UserID: {pt_data['UserID']}" + ) + + except Exception as e: + logger.error( + f"Error inserting payment transaction for UserID {pt_data.get('UserID', 'N/A')}: {e}" + ) + raise + +logger.info("PaymentTransaction data insertion complete.") + +# --- 2. Insert Order Data --- +logger.info("Inserting Order data...") +for order_data in orders: + try: + # For demo data, we'll insert the conceptual OrderID + # Similar to PaymentTransaction, conceptual OrderID is needed for OrderItem linking. + # I'll assume the order of insertion matches conceptual IDs 1, 2, 3... + # This is fragile. It's better if your order_data dicts already include the ID you want to insert. + conceptual_order_id = orders.index(order_data) + 1 + + insert_order_sql = text( + """ + INSERT INTO `Order` ( + OrderID, UserID, StoreID, PaymentTransactionID, OrderStatus, + OrderTotalAmount, DiscountAmount, ShippingFee, FinalAmountForThisOrder, + ShippingAddress_RecipientName, ShippingAddress_PhoneNumber, ShippingAddress_Full, + Notes_ByUser, Notes_ByMerchant, CreationTime, PaymentConfirmationTime, + ShippingTime, DeliveryTime, CompletionTime + -- LastUpdatedDate will use DB default on insert + ) VALUES ( + :OrderID, :UserID, :StoreID, :PaymentTransactionID, :OrderStatus, + :OrderTotalAmount, :DiscountAmount, :ShippingFee, :FinalAmountForThisOrder, + :ShippingAddress_RecipientName, :ShippingAddress_PhoneNumber, :ShippingAddress_Full, + :Notes_ByUser, :Notes_ByMerchant, :CreationTime, :PaymentConfirmationTime, + :ShippingTime, :DeliveryTime, :CompletionTime + ) + ON DUPLICATE KEY UPDATE + UserID = VALUES(UserID), StoreID = VALUES(StoreID), PaymentTransactionID = VALUES(PaymentTransactionID), + OrderStatus = VALUES(OrderStatus), OrderTotalAmount = VALUES(OrderTotalAmount), + DiscountAmount = VALUES(DiscountAmount), ShippingFee = VALUES(ShippingFee), + FinalAmountForThisOrder = VALUES(FinalAmountForThisOrder), + ShippingAddress_RecipientName = VALUES(ShippingAddress_RecipientName), + ShippingAddress_PhoneNumber = VALUES(ShippingAddress_PhoneNumber), + ShippingAddress_Full = VALUES(ShippingAddress_Full), + Notes_ByUser = VALUES(Notes_ByUser), Notes_ByMerchant = VALUES(Notes_ByMerchant), + CreationTime = VALUES(CreationTime), PaymentConfirmationTime = VALUES(PaymentConfirmationTime), + ShippingTime = VALUES(ShippingTime), DeliveryTime = VALUES(DeliveryTime), + CompletionTime = VALUES(CompletionTime), LastUpdatedDate = CURRENT_TIMESTAMP; + """ + ) + + params_order = { + "OrderID": conceptual_order_id, # Explicitly setting for demo linking + "UserID": order_data["UserID"], + "StoreID": order_data["StoreID"], + "PaymentTransactionID": order_data[ + "PaymentTransactionID" + ], # This comes from your pt_id_X mapping + "OrderStatus": order_data["OrderStatus"], # Should be enum.value if data stores enums + "OrderTotalAmount": order_data["OrderTotalAmount"], + "DiscountAmount": order_data.get("DiscountAmount", Decimal("0.00")), + "ShippingFee": order_data.get("ShippingFee", Decimal("0.00")), + "FinalAmountForThisOrder": order_data["FinalAmountForThisOrder"], + "ShippingAddress_RecipientName": order_data["ShippingAddress_RecipientName"], + "ShippingAddress_PhoneNumber": order_data["ShippingAddress_PhoneNumber"], + "ShippingAddress_Full": order_data["ShippingAddress_Full"], + "Notes_ByUser": order_data.get("Notes_ByUser"), + "Notes_ByMerchant": order_data.get("Notes_ByMerchant"), + "CreationTime": order_data["CreationTime"], + "PaymentConfirmationTime": order_data.get("PaymentConfirmationTime"), + "ShippingTime": order_data.get("ShippingTime"), + "DeliveryTime": order_data.get("DeliveryTime"), + "CompletionTime": order_data.get("CompletionTime"), + } + connection.execute(insert_order_sql, params_order) + logger.info( + f"Inserted/Updated OrderID: {conceptual_order_id} for UserID: {order_data['UserID']}" + ) + + except Exception as e: + logger.error( + f"Error inserting order for UserID {order_data.get('UserID', 'N/A')}, (conceptual OrderID {orders.index(order_data) + 1}): {e}" + ) + raise + +logger.info("Order data insertion complete.") + +# --- 3. Insert OrderItem Data --- +logger.info("Inserting OrderItem data...") +for item_data in order_items: + try: + # OrderItemID is auto-increment, so not included in INSERT list + insert_oi_sql = text( + """ + INSERT INTO `OrderItem` ( + OrderID, ProductID, StoreID, Quantity, PriceAtPurchase, + ProductNameAtPurchase, ProductImageURLAtPurchase, Subtotal + ) VALUES ( + :OrderID, :ProductID, :StoreID, :Quantity, :PriceAtPurchase, + :ProductNameAtPurchase, :ProductImageURLAtPurchase, :Subtotal + ) + """ + ) + # ON DUPLICATE KEY UPDATE is usually not needed for OrderItem if OrderItemID is PK + # and there's no other unique key. + + params_oi = { + "OrderID": item_data["OrderID"], # This comes from your order_id_X mapping + "ProductID": item_data["ProductID"], + "StoreID": item_data["StoreID"], + "Quantity": item_data["Quantity"], + "PriceAtPurchase": item_data["PriceAtPurchase"], + "ProductNameAtPurchase": item_data["ProductNameAtPurchase"], + "ProductImageURLAtPurchase": item_data.get("ProductImageURLAtPurchase"), + "Subtotal": item_data["Subtotal"], + } + + connection.execute(insert_oi_sql, params_oi) + # last_oi_id = result.lastrowid + logger.info( + f"Inserted OrderItem for OrderID: {item_data['OrderID']}, ProductID: {item_data['ProductID']}" + ) + + except Exception as e: + logger.error( + f"Error inserting order item for OrderID {item_data.get('OrderID', 'N/A')}, ProductID {item_data.get('ProductID', 'N/A')}: {e}" + ) + raise + +logger.info("OrderItem data insertion complete.") + +# Example of how to print or use this data (e.g., for generating SQL) +# import yaml +# print("\n# PaymentTransaction Data:") +# print(yaml.dump({"payment_transactions": payment_transactions}, sort_keys=False, allow_unicode=True)) +# print("\n# Order Data:") +# print(yaml.dump({"orders": orders}, sort_keys=False, allow_unicode=True)) +# print("\n# OrderItem Data:") +# print(yaml.dump({"order_items": order_items}, sort_keys=False, allow_unicode=True)) + +# To generate SQL INSERT statements: +# print("\n-- PaymentTransaction INSERT statements --") +# for pt in payment_transactions: +# # Manually handle auto-increment for PaymentTransactionID if linking in script +# # For direct SQL, omit PaymentTransactionID +# cols = [f"`{k}`" for k in pt.keys() if k != 'PaymentTransactionID'] +# vals = [f"'{v}'" if isinstance(v, (str, datetime.datetime)) else ('NULL' if v is None else str(v)) for k, v in pt.items() if k != 'PaymentTransactionID'] +# print(f"INSERT INTO PaymentTransaction ({', '.join(cols)}) VALUES ({', '.join(vals)});") + +# print("\n-- Order INSERT statements --") +# for o in orders: +# # Manually handle auto-increment for OrderID +# # Ensure PaymentTransactionID refers to the actual ID generated by DB if not hardcoding +# cols = [f"`{k}`" for k in o.keys() if k != 'OrderID'] +# vals = [f"'{v}'" if isinstance(v, (str, datetime.datetime, Enum)) else ('NULL' if v is None else str(v)) for k, v in o.items() if k != 'OrderID'] +# # For enums, use .value if they are actual enum objects, otherwise string is fine +# # Example assumes they are already string values for simplicity here +# print(f"INSERT INTO `Order` ({', '.join(cols)}) VALUES ({', '.join(vals)});") + +# print("\n-- OrderItem INSERT statements --") +# for oi in order_items: +# # Manually handle auto-increment for OrderItemID +# # Ensure OrderID refers to actual ID generated by DB if not hardcoding +# cols = [f"`{k}`" for k in oi.keys() if k != 'OrderItemID'] +# vals = [f"'{v}'" if isinstance(v, (str, datetime.datetime)) else ('NULL' if v is None else str(v)) for k, v in oi.items() if k != 'OrderItemID'] +# print(f"INSERT INTO OrderItem ({', '.join(cols)}) VALUES ({', '.join(vals)});") + + +# (This code should be appended to your existing Python script) +# import json # Already imported if you used it for OrderItemCRUD + +# StoreChangeRequest Data +# We'll use the same next_timestamp helper if it's defined, otherwise, create one. +# Assuming 'next_timestamp' function and 'users', 'stores' lists are available from your script. + +# Make sure UserID 1 is an admin, UserID 3 & 4 are merchants, StoreID 1 & 2 exist. +admin_user_id_for_scr = 1 +merchant_user_id_1_for_scr = 3 +merchant_user_id_2_for_scr = 4 +store_id_1_for_scr = 1 # Assumed to be owned by merchant_user_id_1 +store_id_2_for_scr = 2 # Assumed to be owned by merchant_user_id_2 + + +store_change_requests: DataList = [ + # 1. Request to CREATE a new store by merchant_1, pending approval + dict( + # ChangeRequestID: auto-increment + StoreID=None, # For STORE_CREATE, StoreID is initially NULL + RequestingUserID=merchant_user_id_1_for_scr, + RequestType="STORE_CREATE", + ProposedData_JSON=json.dumps( + { + "StoreName": "Merchant 1's New Cafe Proposal", + "Description": "A cozy corner cafe with artisanal coffee and pastries.", + "LogoURL": "http://example.com/logos/new_cafe_logo.png", + "StoreStatus": "ACTIVE", # Proposed initial status + } + ), + Status="PENDING_APPROVAL", + SubmitterNotes="Hoping to open by next month. All documents are ready.", + AdminReviewerID=None, + ReviewTimestamp=None, + AdminNotes=None, + CreationTime=next_timestamp(add_minutes=10), + # LastUpdatedDate is handled by DB + ), + # 2. Request to UPDATE Store 1 by its owner (merchant_1), pending approval + dict( + StoreID=store_id_1_for_scr, + RequestingUserID=merchant_user_id_1_for_scr, + RequestType="STORE_UPDATE", + ProposedData_JSON=json.dumps( + { + "Description": "Updated: A cozy corner cafe with artisanal coffee, fresh pastries, and a new vegan menu!", + "LogoURL": "http://example.com/logos/new_cafe_logo_v2.png", + } + ), + Status="PENDING_APPROVAL", + SubmitterNotes="Updated description to include new vegan options and changed logo.", + AdminReviewerID=None, + ReviewTimestamp=None, + AdminNotes=None, + CreationTime=next_timestamp(), + ), + # 3. Request to "DELETE" (close) Store 2 by its owner (merchant_2), pending approval + # For STORE_DELETE, ProposedData_JSON is often NULL or not relevant + dict( + StoreID=store_id_2_for_scr, + RequestingUserID=merchant_user_id_2_for_scr, + RequestType="STORE_DELETE", + ProposedData_JSON=None, + Status="PENDING_APPROVAL", + SubmitterNotes="Requesting to close this store permanently due to relocation.", + AdminReviewerID=None, + ReviewTimestamp=None, + AdminNotes=None, + CreationTime=next_timestamp(), + ), + # 4. An APPROVED request (e.g., an update for Store 1 by merchant_1, reviewed by admin_user_id_for_scr) + dict( + StoreID=store_id_1_for_scr, + RequestingUserID=merchant_user_id_1_for_scr, + RequestType="STORE_UPDATE", + ProposedData_JSON=json.dumps({"StoreName": "Merchant 1's Store (Recently Updated Name)"}), + Status="APPROVED", + SubmitterNotes="Requesting a name change.", + AdminReviewerID=admin_user_id_for_scr, + ReviewTimestamp=next_timestamp(add_minutes=60), # Reviewed an hour after creation + AdminNotes="Name change approved. Looks good.", + CreationTime=next_timestamp(), + ), + # 5. A REJECTED request (e.g., a create request by merchant_2, reviewed by admin) + dict( + StoreID=None, + RequestingUserID=merchant_user_id_2_for_scr, + RequestType="STORE_CREATE", + ProposedData_JSON=json.dumps( + { + "StoreName": "Yet Another Gadget Shop", + "Description": "Selling all sorts of gadgets.", + } + ), + Status="REJECTED", + SubmitterNotes="My awesome new gadget shop idea!", + AdminReviewerID=admin_user_id_for_scr, + ReviewTimestamp=next_timestamp(add_minutes=30), + AdminNotes="Rejected due to market saturation and incomplete business plan.", + CreationTime=next_timestamp(), + ), + # 6. An APPLIED request (e.g., a create request that was approved and then applied) + # StoreID would be backfilled here after the store is actually created. Let's assume new StoreID is 3. + dict( + StoreID=3, # Assuming this was backfilled after store creation (new StoreID is 3) + RequestingUserID=merchant_user_id_1_for_scr, + RequestType="STORE_CREATE", + ProposedData_JSON=json.dumps( + { # Original proposal + "StoreName": "Merchant 1's Applied Store", + "Description": "This store was successfully created.", + "StoreStatus": "ACTIVE", + } + ), + Status="APPLIED", + SubmitterNotes="Application for a new successful store.", + AdminReviewerID=admin_user_id_for_scr, + ReviewTimestamp=next_timestamp(add_minutes=120), # Approved + AdminNotes="Approved and applied by system/admin.", + CreationTime=next_timestamp(), # Applied some time after approval + ), + # 7. A CANCELLED_BY_USER request (merchant_2 cancelled their own pending request) + dict( + StoreID=None, # Was a STORE_CREATE request + RequestingUserID=merchant_user_id_2_for_scr, + RequestType="STORE_CREATE", + ProposedData_JSON=json.dumps( + { + "StoreName": "My Cancelled Idea Store", + "Description": "Maybe later.", + } + ), + Status="CANCELLED_BY_USER", # Was PENDING_APPROVAL, then user cancelled + SubmitterNotes="Initial idea, decided to cancel for now.", + AdminReviewerID=None, + ReviewTimestamp=None, # Not reviewed by admin + AdminNotes=None, + CreationTime=next_timestamp(), # Cancelled some time after creation + ), +] + +logger.info("Starting to insert StoreChangeRequest data...") +for req_data in store_change_requests: + try: + # StoreChangeRequest table column names are CamelCase + insert_sql = text( + """ + INSERT INTO `StoreChangeRequest` ( + StoreID, RequestingUserID, RequestType, ProposedData_JSON, + Status, SubmitterNotes, AdminReviewerID, ReviewTimestamp, + AdminNotes, CreationTime + -- LastUpdatedDate will use DB default on insert + ) VALUES ( + :StoreID, :RequestingUserID, :RequestType, :ProposedData_JSON, + :Status, :SubmitterNotes, :AdminReviewerID, :ReviewTimestamp, + :AdminNotes, :CreationTime + ) + """ + ) + # ON DUPLICATE KEY UPDATE can be added if you expect to re-run with specific ChangeRequestIDs + # but since ChangeRequestID is auto-increment and not in the insert list, + # this will always be a new row unless other unique constraints are violated. + + # Prepare parameters, ensuring keys match SQL bind parameters + params = { + "StoreID": req_data.get("StoreID"), # Can be None for STORE_CREATE + "RequestingUserID": req_data["RequestingUserID"], + "RequestType": req_data["RequestType"], + "ProposedData_JSON": req_data.get("ProposedData_JSON"), # Already a JSON string or None + "Status": req_data["Status"], + "SubmitterNotes": req_data.get("SubmitterNotes"), + "AdminReviewerID": req_data.get("AdminReviewerID"), + "ReviewTimestamp": req_data.get("ReviewTimestamp"), + "AdminNotes": req_data.get("AdminNotes"), + "CreationTime": req_data["CreationTime"], + } + + connection.execute(insert_sql, params) + # last_req_id = result.lastrowid # If you need the new ChangeRequestID + logger.info( + f"Inserted StoreChangeRequest for RequestingUserID: {req_data['RequestingUserID']}, Type: {req_data['RequestType']}" + ) + + except Exception as e: + logger.error( + f"Error inserting store change request for RequestingUserID {req_data.get('RequestingUserID', 'N/A')}, Type {req_data.get('RequestType', 'N/A')}: {e}" + ) + # transaction.rollback() # Should be handled by the main try-except block at the end of script + raise # Re-raise the exception to halt the script or be caught by an outer handler + +logger.info("StoreChangeRequest data insertion complete.") + +# Example of how to use this data (e.g., for generating SQL or YAML) +# import yaml +# print("\n# StoreChangeRequest Data:") +# print(yaml.dump({"store_change_requests": store_change_requests}, sort_keys=False, allow_unicode=True)) + +# To generate SQL INSERT statements (omitting ChangeRequestID as it's auto-increment): +# print("\n-- StoreChangeRequest INSERT statements --") +# for req in store_change_requests: +# cols = [] +# vals_placeholder = [] +# vals_dict = {} +# for k, v in req.items(): +# cols.append(f"`{k}`") +# param_name = f"param_{k}" +# vals_placeholder.append(f":{param_name}") +# if isinstance(v, datetime.datetime): +# vals_dict[param_name] = v.strftime('%Y-%m-%d %H:%M:%S') +# elif isinstance(v, Enum): # Should already be .value for Status/RequestType +# vals_dict[param_name] = v +# else: +# vals_dict[param_name] = v + +# # For INSERT, we let DB handle ChangeRequestID, CreationTime, LastUpdatedDate default/on_update +# # Status also has a default. We only specify if not default. +# # AdminReviewerID, ReviewTimestamp, AdminNotes are often NULL initially. +# # StoreID can be NULL for STORE_CREATE. +# # ProposedData_JSON can be NULL. +# # SubmitterNotes can be NULL. + +# # Simplified INSERT focusing on required or commonly set fields for demo +# insert_cols = ["RequestingUserID", "RequestType", "Status"] # Minimal +# insert_vals_ph = [":RequestingUserID", ":RequestType", ":Status"] +# insert_params = { +# "RequestingUserID": req["RequestingUserID"], +# "RequestType": req["RequestType"], +# "Status": req["Status"] +# } + +# if "StoreID" in req and req["StoreID"] is not None: +# insert_cols.append("StoreID") +# insert_vals_ph.append(":StoreID") +# insert_params["StoreID"] = req["StoreID"] + +# if "ProposedData_JSON" in req and req["ProposedData_JSON"] is not None: +# insert_cols.append("ProposedData_JSON") +# insert_vals_ph.append(":ProposedData_JSON") +# insert_params["ProposedData_JSON"] = req["ProposedData_JSON"] # Assumes it's already a JSON string + +# if "SubmitterNotes" in req and req["SubmitterNotes"] is not None: +# insert_cols.append("SubmitterNotes") +# insert_vals_ph.append(":SubmitterNotes") +# insert_params["SubmitterNotes"] = req["SubmitterNotes"] + +# # For fields set upon review/application +# if "AdminReviewerID" in req and req["AdminReviewerID"] is not None: +# insert_cols.append("AdminReviewerID") +# insert_vals_ph.append(":AdminReviewerID") +# insert_params["AdminReviewerID"] = req["AdminReviewerID"] +# if "ReviewTimestamp" in req and req["ReviewTimestamp"] is not None: +# insert_cols.append("ReviewTimestamp") +# insert_vals_ph.append(":ReviewTimestamp") +# insert_params["ReviewTimestamp"] = req["ReviewTimestamp"].strftime('%Y-%m-%d %H:%M:%S') +# if "AdminNotes" in req and req["AdminNotes"] is not None: +# insert_cols.append("AdminNotes") +# insert_vals_ph.append(":AdminNotes") +# insert_params["AdminNotes"] = req["AdminNotes"] +# if "CreationTime" in req and req["CreationTime"] is not None: # If overriding default +# insert_cols.append("CreationTime") +# insert_vals_ph.append(":CreationTime") +# insert_params["CreationTime"] = req["CreationTime"].strftime('%Y-%m-%d %H:%M:%S') + + +# sql = f"INSERT INTO StoreChangeRequest ({', '.join(insert_cols)}) VALUES ({', '.join(insert_vals_ph)});" +# # This is just to show the structure, actual execution would pass insert_params to execute() +# # print(f"-- For ChangeRequest conceptually for StoreID {req.get('StoreID', 'NEW')}, Requester {req['RequestingUserID']}") +# # print(sql) +# # print(f"-- Params: {insert_params}\n") + + +# (This code should be appended to your existing Python script) +# Ensure 'users' list and 'next_timestamp' function (if used for other tables) are available. +# If next_timestamp is not used for addresses, we can use fixed datetimes or omit AddedDate +# if your DDL for ShippingAddress has a default for a creation timestamp column +# (The provided DDL for ShippingAddress does not show an AddedDate/CreationDate column, +# but it's common to have one. I will assume it's not there as per your DDL.) + +shipping_addresses: DataList = [ + # Addresses for UserID = 1 (admin) + dict( + # AddressID: auto-increment + UserID=1, + RecipientName=users[0]["username"] + " Home", # "admin Home" + PhoneNumber="1110000001", + FullAddress_Text="1 Admin Ave, Admin City, AC 10001, Wonderland", + IsDefault=False, # Explicitly False, though DDL defaults to 0 + ), + # Addresses for UserID = 2 (demo_user) + dict( + UserID=2, + RecipientName=users[1]["username"] + " Main", # "demo_user Main" + PhoneNumber="1234567890", # From user data + FullAddress_Text="456 Demo Dr, UserTown, UT 20002, Exampleland", + IsDefault=False, + ), + dict( + UserID=2, + RecipientName=users[1]["username"] + " Work", # "demo_user Work" + PhoneNumber="0987654321", + FullAddress_Text="789 Business Blvd, Suite 100, Worksville, WK 30003, Exampleland", + IsDefault=False, + ), + # Addresses for UserID = 3 (merchant_1) + dict( + UserID=3, + RecipientName=users[2]["username"] + " Primary", # "merchant_1 Primary" + PhoneNumber=users[2]["phone_number"], # From user data + FullAddress_Text="10 Merchant Ln, Shopburg, SB 40004, BizCountry", + IsDefault=False, + ), + # Addresses for UserID = 4 (merchant_2) + dict( + UserID=4, + RecipientName=users[3]["username"] + " Shipping", # "merchant_2 Shipping" + PhoneNumber=users[3]["phone_number"], # From user data + FullAddress_Text="20 Commerce Rd, Trade City, TC 50005, BizCountry", + IsDefault=False, + ), + dict( + UserID=4, + RecipientName=users[3]["username"] + " Warehouse", # "merchant_2 Warehouse" + PhoneNumber="5551112233", + FullAddress_Text="30 Industrial Park, Unit B, Factoria, FC 60006, BizCountry", + IsDefault=False, + ), +] + +logger.info("Starting to insert ShippingAddress data...") +for addr_data in shipping_addresses: + try: + # ShippingAddress table column names are CamelCase + insert_sql = text( + """ + INSERT INTO `ShippingAddress` ( + UserID, RecipientName, PhoneNumber, FullAddress_Text, IsDefault + ) VALUES ( + :UserID, :RecipientName, :PhoneNumber, :FullAddress_Text, :IsDefault + ) + """ + ) + # AddressID is auto-increment, so not included in INSERT list + # ON DUPLICATE KEY UPDATE is usually not needed for addresses unless you have + # a unique constraint other than AddressID (e.g., UserID + FullAddress_Text) + # and want to update if that combination already exists. + # For simple demo data with auto-increment PK, direct INSERT is fine. + + # Prepare parameters, ensuring keys match SQL bind parameters + params = { + "UserID": addr_data["UserID"], + "RecipientName": addr_data["RecipientName"], + "PhoneNumber": addr_data["PhoneNumber"], + "FullAddress_Text": addr_data["FullAddress_Text"], + "IsDefault": ( + 1 if addr_data["IsDefault"] else 0 + ), # Convert boolean to 1 or 0 for TINYINT(1) + } + + connection.execute(insert_sql, params) + # last_address_id = result.lastrowid # If you need the new AddressID + logger.info( + f"Inserted ShippingAddress for UserID: {addr_data['UserID']}, Recipient: {addr_data['RecipientName']}" + ) + + except Exception as e: + logger.error( + f"Error inserting shipping address for UserID {addr_data.get('UserID', 'N/A')}, Recipient {addr_data.get('RecipientName', 'N/A')}: {e}" + ) + # transaction.rollback() # Should be handled by the main try-except block + raise # Re-raise the exception to halt the script or be caught by an outer handler + +logger.info("ShippingAddress data insertion complete.") + +# Example of how to print or use this data: +# import yaml +# print("\n# ShippingAddress Data:") +# print(yaml.dump({"shipping_addresses": shipping_addresses}, sort_keys=False, allow_unicode=True)) + +# To generate SQL INSERT statements (omitting AddressID as it's auto-increment): +# print("\n-- ShippingAddress INSERT statements --") +# for addr in shipping_addresses: +# # Handle IsDefault: Python False becomes 0 in SQL for BOOLEAN/TINYINT(1) +# is_default_sql = 1 if addr['IsDefault'] else 0 +# print( +# f"INSERT INTO ShippingAddress (UserID, RecipientName, PhoneNumber, FullAddress_Text, IsDefault) VALUES " +# f"({addr['UserID']}, '{addr['RecipientName'].replace("'", "''")}', '{addr['PhoneNumber']}', '{addr['FullAddress_Text'].replace("'", "''")}', {is_default_sql});" +# ) + + +final_data = [ + dict( + table="User", + data=users, + ), + dict( + table="UserSession", + data=user_sessions, + ), + dict( + table="Store", + data=stores, + ), + dict( + table="ProductCategory", + data=product_categories, + ), + dict( + table="Product", + data=products_promote + other_products, + ), + dict( + table="CartItem", + data=cart_items, + ), + dict( + table="PaymentTransaction", + data=payment_transactions, + ), + dict( + table="Order", + data=orders, + ), + dict( + table="OrderItem", + data=order_items, + ), + dict( + table="StoreChangeRequest", + data=store_change_requests, + ), + dict( + table="ShippingAddress", + data=shipping_addresses, + ), +] + +transaction.commit() +transaction.close() + +if __name__ == "__main__": + import yaml + + # Write the data to a YAML file + with open("demo_data.yaml", "w") as file: + yaml.dump(final_data, file, default_flow_style=False, sort_keys=False) + + # write the data to a txt file + with open("demo_data.txt", "w") as file: + for entry in final_data: + file.write(f"Table: {entry['table']}\n") + for item in entry["data"]: + file.write(f"{item}\n") + file.write("\n")