Initial commit: Clean Mold Cost Calculator application
- Flask-based mold cost calculation application - PostgreSQL database with SQLAlchemy ORM - Docker/Podman containerization - Comprehensive documentation in docs/ - Clean, organized codebase without obsolete files - Production-ready deployment configuration - Enhanced pytest configuration with coverage reporting - Master/Development branch structure
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
from ..utils.cost_factor_loader import get_cost_factors
|
||||
|
||||
def calculate_base_price(mold_main_type, mold_sub_type, mold_specific_type, sliders_count, cavity_count, digital_texture_type, complexity_high_sidewall=False):
|
||||
"""
|
||||
Calculate the base price for a mold based on various factors.
|
||||
|
||||
Args:
|
||||
mold_main_type (str): The main type of the mold
|
||||
mold_sub_type (str): The sub type of the mold
|
||||
mold_specific_type (str): The specific type of the mold
|
||||
sliders_count (int): Number of sliders
|
||||
cavity_count (int): Number of cavities
|
||||
digital_texture_type (str): Type of digital texture
|
||||
complexity_high_sidewall (bool): Whether the mold has high sidewall complexity
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing all cost components
|
||||
"""
|
||||
# Get cost factors from database (or fallback to defaults)
|
||||
cost_factors, tax_rates, default_net_base = get_cost_factors()
|
||||
|
||||
# Start with the default net base
|
||||
base_price = default_net_base
|
||||
|
||||
# Apply mold type factor
|
||||
if mold_specific_type in cost_factors['mold']:
|
||||
base_price *= cost_factors['mold'][mold_specific_type]
|
||||
|
||||
# Calculate complexity cost
|
||||
complexity_cost = 0
|
||||
# Add slider complexity
|
||||
if sliders_count > 0:
|
||||
complexity_cost += base_price * (sliders_count * cost_factors['complexity']['slider'])
|
||||
|
||||
# Add cavity complexity
|
||||
if cavity_count > 1:
|
||||
complexity_cost += base_price * ((cavity_count - 1) * cost_factors['complexity']['cavity'])
|
||||
|
||||
# Add high sidewall complexity
|
||||
if complexity_high_sidewall:
|
||||
complexity_cost += base_price * cost_factors['complexity']['high_sidewall']
|
||||
|
||||
# Calculate texture cost
|
||||
texture_cost = 0
|
||||
if digital_texture_type in cost_factors['texture']:
|
||||
texture_cost = base_price * cost_factors['texture'][digital_texture_type]
|
||||
|
||||
# Calculate total cost
|
||||
total_cost = base_price + complexity_cost + texture_cost
|
||||
|
||||
return {
|
||||
'base_price': round(base_price, 2),
|
||||
'complexity_cost': round(complexity_cost, 2),
|
||||
'texture_cost': round(texture_cost, 2),
|
||||
'total_cost': round(total_cost, 2)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
from ..models import CostFactor
|
||||
from .. import db
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Single source of truth for default cost factors
|
||||
DEFAULT_COST_FACTORS = [
|
||||
# Mold Type Factors
|
||||
{'category': 'mold', 'name': 'Flat', 'factor_value': 1.0, 'description': 'Standard flat outsole'},
|
||||
{'category': 'mold', 'name': 'Flat with Top/Heel Tip', 'factor_value': 1.25, 'description': 'Flat with additional tip features'},
|
||||
{'category': 'mold', 'name': 'Top PL', 'factor_value': 1.05, 'description': 'Top part line mold'},
|
||||
{'category': 'mold', 'name': 'Visible PL', 'factor_value': 1.28, 'description': 'Visible part line mold'},
|
||||
{'category': 'mold', 'name': 'Short PL (Heel/Forefoot)', 'factor_value': 1.18, 'description': 'Short part line for specific areas'},
|
||||
{'category': 'mold', 'name': 'Wave PL', 'factor_value': 1.32, 'description': 'Wave pattern part line'},
|
||||
{'category': 'mold', 'name': 'Multi-color PL', 'factor_value': 1.35, 'description': 'Multi-color part line mold'},
|
||||
{'category': 'mold', 'name': '2 Plates Mold', 'factor_value': 1.0, 'description': 'Standard 2-plate configuration'},
|
||||
{'category': 'mold', 'name': '3 Plates Mold', 'factor_value': 1.35, 'description': 'Complex 3-plate configuration'},
|
||||
{'category': 'mold', 'name': '2 Plates with in-mold channel', 'factor_value': 1.25, 'description': '2-plate with channel features'},
|
||||
{'category': 'mold', 'name': 'Breathable + in-mold channel', 'factor_value': 1.45, 'description': 'Breathable with channel features'},
|
||||
{'category': 'mold', 'name': 'Sandal with Upper Bandage', 'factor_value': 1.15, 'description': 'Sandal with bandage features'},
|
||||
{'category': 'mold', 'name': '2 Plate without Upper Bandage', 'factor_value': 1.05, 'description': 'Standard 2-plate sandal'},
|
||||
{'category': 'mold', 'name': 'Co-shot mold', 'factor_value': 1.25, 'description': 'Multi-material injection'},
|
||||
{'category': 'mold', 'name': 'Foaming mold', 'factor_value': 1.4, 'description': 'Foam injection process'},
|
||||
|
||||
# Complexity Factors
|
||||
{'category': 'complexity', 'name': 'high_sidewall', 'factor_value': 0.18, 'calculation_formula': 'Base Price × 0.18', 'description': 'Additional cost for high sidewall molds'},
|
||||
{'category': 'complexity', 'name': 'slider', 'factor_value': 0.1, 'calculation_formula': 'Base Price × (Number of Sliders × 0.10)', 'description': 'Cost per slider added to mold'},
|
||||
{'category': 'complexity', 'name': 'cavity', 'factor_value': 0.12, 'calculation_formula': 'Base Price × ((Number of Cavities - 1) × 0.12)', 'description': 'Cost for additional cavities beyond 1'},
|
||||
|
||||
# Texture Factors
|
||||
{'category': 'texture', 'name': 'adidas_digital', 'factor_value': 0.0, 'percentage': '0%', 'description': 'No additional cost for standard adidas texture'},
|
||||
{'category': 'texture', 'name': 'customized texture', 'factor_value': 0.15, 'percentage': '15%', 'description': '15% of base price for custom texture'},
|
||||
{'category': 'texture', 'name': 'laser', 'factor_value': 0.2, 'percentage': '20%', 'description': '20% of base price for laser texture'},
|
||||
|
||||
# Tax Rates
|
||||
{'category': 'tax', 'name': 'China', 'factor_value': 0.06, 'percentage': '6%', 'description': '6% VAT for China'},
|
||||
{'category': 'tax', 'name': 'Vietnam', 'factor_value': 0.10, 'percentage': '10%', 'description': '10% VAT for Vietnam'},
|
||||
{'category': 'tax', 'name': 'Indonesia', 'factor_value': 0.07, 'percentage': '7%', 'description': '7% VAT for Indonesia'},
|
||||
|
||||
# Base Price
|
||||
{'category': 'base', 'name': 'DEFAULT_NET_BASE', 'factor_value': 12.5, 'description': 'Default base price for calculations'}
|
||||
]
|
||||
|
||||
def load_cost_factors_from_db():
|
||||
"""
|
||||
Load cost factors from database and return them in the same format as constants.
|
||||
Falls back to hardcoded values if database is not available.
|
||||
"""
|
||||
try:
|
||||
# Get all active cost factors
|
||||
factors = CostFactor.query.filter_by(is_active=True).all()
|
||||
|
||||
if not factors:
|
||||
logger.warning("No cost factors found in database, using hardcoded defaults")
|
||||
return get_default_cost_factors()
|
||||
|
||||
# Build the cost factors structure
|
||||
cost_factors = {
|
||||
'mold': {},
|
||||
'complexity': {},
|
||||
'texture': {}
|
||||
}
|
||||
|
||||
tax_rates = {}
|
||||
default_net_base = 12.5 # Default value
|
||||
|
||||
for factor in factors:
|
||||
if factor.category == 'mold':
|
||||
cost_factors['mold'][factor.name] = factor.factor_value
|
||||
elif factor.category == 'complexity':
|
||||
cost_factors['complexity'][factor.name] = factor.factor_value
|
||||
elif factor.category == 'texture':
|
||||
cost_factors['texture'][factor.name] = factor.factor_value
|
||||
elif factor.category == 'tax':
|
||||
tax_rates[factor.name] = factor.factor_value
|
||||
elif factor.category == 'base':
|
||||
default_net_base = factor.factor_value
|
||||
|
||||
logger.info(f"Loaded {len(factors)} cost factors from database")
|
||||
return cost_factors, tax_rates, default_net_base
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading cost factors from database: {str(e)}")
|
||||
logger.info("Falling back to hardcoded cost factors")
|
||||
return get_default_cost_factors()
|
||||
|
||||
def get_default_cost_factors():
|
||||
"""Return hardcoded cost factors as fallback - derived from single source of truth"""
|
||||
cost_factors = {
|
||||
'mold': {},
|
||||
'complexity': {},
|
||||
'texture': {}
|
||||
}
|
||||
|
||||
tax_rates = {}
|
||||
default_net_base = 12.5
|
||||
|
||||
# Build the structure from the single source of truth
|
||||
for factor_data in DEFAULT_COST_FACTORS:
|
||||
category = factor_data['category']
|
||||
name = factor_data['name']
|
||||
value = factor_data['factor_value']
|
||||
|
||||
if category == 'mold':
|
||||
cost_factors['mold'][name] = value
|
||||
elif category == 'complexity':
|
||||
cost_factors['complexity'][name] = value
|
||||
elif category == 'texture':
|
||||
cost_factors['texture'][name] = value
|
||||
elif category == 'tax':
|
||||
tax_rates[name] = value
|
||||
elif category == 'base':
|
||||
default_net_base = value
|
||||
|
||||
return cost_factors, tax_rates, default_net_base
|
||||
|
||||
def get_cost_factors():
|
||||
"""Get current cost factors (from database or defaults)"""
|
||||
return load_cost_factors_from_db()
|
||||
|
||||
def initialize_default_cost_factors():
|
||||
"""Initialize the database with default cost factors if none exist"""
|
||||
try:
|
||||
if CostFactor.query.count() == 0:
|
||||
logger.info("Initializing database with default cost factors")
|
||||
|
||||
# Use the single source of truth
|
||||
for factor_data in DEFAULT_COST_FACTORS:
|
||||
factor = CostFactor(**factor_data)
|
||||
db.session.add(factor)
|
||||
|
||||
db.session.commit()
|
||||
logger.info(f"Initialized {len(DEFAULT_COST_FACTORS)} default cost factors")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing default cost factors: {str(e)}")
|
||||
db.session.rollback()
|
||||
raise
|
||||
@@ -0,0 +1,64 @@
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
from ctypes.util import find_library
|
||||
from pathlib import Path
|
||||
|
||||
class DLLLoaderError(Exception):
|
||||
"""自定义DLL加载异常"""
|
||||
pass
|
||||
|
||||
def setup_dependencies(dll_paths=None):
|
||||
"""配置DLL搜索路径"""
|
||||
if dll_paths is None:
|
||||
dll_paths = [
|
||||
Path(__file__).parent / 'libs',
|
||||
Path(os.getenv('LOCALAPPDATA')) / 'MSYS2' / 'mingw64' / 'bin'
|
||||
]
|
||||
|
||||
os.environ['PATH'] = os.pathsep.join([
|
||||
str(p.resolve()) for p in dll_paths if p.exists()
|
||||
]) + os.pathsep + os.environ.get('PATH', '')
|
||||
|
||||
def load_dll(dll_name, mode=ctypes.DEFAULT_MODE, handle_errors=True):
|
||||
"""增强版DLL加载函数"""
|
||||
try:
|
||||
# 尝试标准加载方式
|
||||
dll = ctypes.CDLL(dll_name, mode=mode)
|
||||
print(f"✅ 成功加载 {dll_name}")
|
||||
return dll
|
||||
except OSError as e:
|
||||
if handle_errors:
|
||||
# 尝试通过PATH查找
|
||||
lib_path = find_library(dll_name)
|
||||
if lib_path:
|
||||
return ctypes.CDLL(lib_path, mode=mode)
|
||||
|
||||
# 尝试MSYS2路径
|
||||
msys_path = Path(os.getenv('LOCALAPPDATA')) / 'MSYS2' / 'mingw64' / 'bin' / dll_name
|
||||
if msys_path.exists():
|
||||
return ctypes.CDLL(str(msys_path), mode=mode)
|
||||
|
||||
# 生成详细错误报告
|
||||
path_list = os.environ.get('PATH', '').split(os.pathsep)
|
||||
raise DLLLoaderError(
|
||||
f"无法加载 {dll_name}\n"
|
||||
f"错误详情: {str(e)}\n"
|
||||
f"搜索路径:\n• " + "\n• ".join(path_list)
|
||||
)
|
||||
else:
|
||||
raise
|
||||
|
||||
# 自动配置默认路径
|
||||
try:
|
||||
setup_dependencies()
|
||||
except Exception as e:
|
||||
print(f"⚠️ 初始化依赖失败: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 自测试代码
|
||||
try:
|
||||
test_dll = load_dll('gtk-3-0.dll')
|
||||
print(f"测试DLL句柄: {test_dll._handle}")
|
||||
except DLLLoaderError as e:
|
||||
print(f"❌ 自测试失败: {e}")
|
||||
Reference in New Issue
Block a user