import json import re from app.extensions import db from app.models.lv import LVPosition def execute_rules(form_data, rules_json, company_id): rules = json.loads(rules_json) if isinstance(rules_json, str) else rules_json positions = [] for rule in rules: if not _evaluate_conditions(rule.get('conditions', {}), form_data): continue for action in rule.get('actions', []): pos = _create_position(action, form_data, company_id) if pos: positions.append(pos) return positions def _evaluate_conditions(conditions, form_data): if not conditions or not conditions.get('items'): return True operator = conditions.get('operator', 'and') items = conditions.get('items', []) results = [] for cond in items: field = cond.get('field', '') op = cond.get('operator', 'eq') value = cond.get('value', '') value2 = cond.get('value2', '') raw = form_data.get(field, '') if op == 'is_checked': results.append(raw == 'an') elif op == 'is_empty': results.append(raw == '' or raw is None) elif op == 'not_empty': results.append(raw != '' and raw is not None) else: try: f_val = _to_float(raw) f_cond = _to_float(value) if op == 'eq': results.append(f_val == f_cond) elif op == 'neq': results.append(f_val != f_cond) elif op == 'gt': results.append(f_val > f_cond) elif op == 'gte': results.append(f_val >= f_cond) elif op == 'lt': results.append(f_val < f_cond) elif op == 'lte': results.append(f_val <= f_cond) elif op == 'between': f_val2 = _to_float(value2) results.append(f_cond <= f_val <= f_val2) else: results.append(str(raw) == str(value)) except (ValueError, TypeError): results.append(str(raw) == str(value)) if operator == 'or': return any(results) return all(results) def _create_position(action, form_data, company_id): pos_nr = action.get('pos_nr', '') if not pos_nr: return None lv_lookup = action.get('lv_lookup', True) columns = action.get('columns', {}) # Default values pos = { 'pos_nr': pos_nr, 'kurztext': '', 'einheit': 'ST', 'menge': 1, 'faktor': 1, 'laenge': 0, 'breite': 0, 'tiefe': 0, 'bemerkung': '', 'einzelpreis': 0, } # LV lookup if lv_lookup: lv_pos = LVPosition.query.filter_by(company_id=company_id, pos_nr=pos_nr).first() if lv_pos: pos['kurztext'] = lv_pos.kurztext or '' pos['einheit'] = lv_pos.einheit or 'ST' pos['einzelpreis'] = lv_pos.einzelpreis or 0 # Apply overrides for col_key, col_def in columns.items(): if col_key not in pos and col_key != 'pos_nr': continue val = _resolve_value(col_def, form_data) if val is not None: if col_key in ('menge', 'faktor', 'laenge', 'breite', 'tiefe', 'einzelpreis'): pos[col_key] = _to_float(val) else: pos[col_key] = str(val) return pos def _resolve_value(col_def, form_data): if not col_def: return None ctype = col_def.get('type', 'fixed') value = col_def.get('value', '') if ctype == 'fixed': return value elif ctype == 'field': return form_data.get(value, '') elif ctype == 'formula': return _eval_formula(value, form_data) return value def _eval_formula(expr, form_data): expr = str(expr) def _replace_field(m): fname = m.group(1) raw = form_data.get(fname, '0') return str(raw).replace(',', '.') expr = re.sub(r'\[([^\]]+)\]', _replace_field, expr) safe = re.sub(r'[^\d\+\-\*\/\(\)\. ]', '', expr) if not safe.strip(): return 0 try: return eval(safe, {'__builtins__': {}}, {}) except Exception: return 0 def _to_float(val): if val is None or val == '': return 0.0 return float(str(val).replace(',', '.').replace(' ', ''))