def not_loaded(self): Rule.formula(derive="Tbl.ColA", # or, calling=compute_amount) as_exp="row.ColB + row.ColC") Rule.formula(derive="Tbl.ColB", # or, calling=compute_amount) as_exp="row.ColC") Rule.formula(derive="Tbl.ColC", # or, calling=compute_amount) as_exp="row.ColD") Rule.formula(derive="Tbl.ColD", # or, calling=compute_amount) as_exp="row.ColE") Rule.formula(derive="Tbl.ColE", # or, calling=compute_amount) as_exp="xxx")
def declare_logic(): Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountOwed) Rule.formula(derive=Order.AmountOwed, as_expression=lambda row: row.AmountTotal - row.AmountPaid) Rule.sum(derive=Order.AmountPaid, as_sum_of=PaymentAllocation.AmountAllocated) Rule.formula( derive=PaymentAllocation.AmountAllocated, as_expression=lambda row: min(Decimal(row.Payment.AmountUnAllocated), Decimal(row.Order.AmountOwed))) Rule.early_row_event(on_class=Payment, calling=allocate_payment) """
def declare_logic(): Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountOwed) Rule.formula(derive=Order.AmountOwed, as_expression=lambda row: row.AmountTotal - row.AmountPaid) Rule.sum(derive=Order.AmountPaid, as_sum_of=PaymentAllocation.AmountAllocated) Rule.formula( derive=PaymentAllocation.AmountAllocated, as_expression=lambda row: min(Decimal(row.Payment.AmountUnAllocated), Decimal(row.Order.AmountOwed))) RuleExtension.allocate(provider=Payment, recipients=unpaid_orders, creating_allocation=PaymentAllocation) """
def activate_basic_rules(): def transfer_funds(row: TRANSFERFUND, old_row: TRANSFERFUND, logic_row: LogicRow): if logic_row.ins_upd_dlt == "ins" or True: # logic engine fills parents for insert logic_row.log("Transfer from source to target") fromCustNum = row.FromCustNum toCustNum = row.ToCustNum acctNum = row.FromAcct trans_date = datetime.datetime(2020, 10, 1) transferAmt = row.TransferAmt transID = row.TransId # need to lookup the From Acct to see if it is checking or savings - that way we can reverse the flow deposit = models.SAVINGSTRANS(TransId=transID, CustNum=toCustNum, AcctNum=acctNum, DepositAmt=transferAmt, WithdrawlAmt=0, TransDate=trans_date) logic_row.insert("Deposit to savings", deposit) withdrawl = models.CHECKINGTRANS(TransId=transID, CustNum=fromCustNum, AcctNum=acctNum, DepositAmt=0, WithdrawlAmt=transferAmt, TransDate=trans_date) logic_row.insert("Withdraw from CHECKINGTRANS", withdrawl) Rule.sum(derive=CHECKING.Deposits, as_sum_of=CHECKINGTRANS.DepositAmt) Rule.sum(derive=CHECKING.Withdrawls, as_sum_of=CHECKINGTRANS.WithdrawlAmt) Rule.formula(derive=CHECKING.AvailableBalance, as_expression=lambda row: row.Deposits - row.Withdrawls) Rule.count(derive=CHECKING.ItemCount, as_count_of=CHECKINGTRANS) Rule.sum(derive=CUSTOMER.CheckingAcctBal, as_sum_of=CHECKING.AvailableBalance) Rule.sum(derive=CUSTOMER.SavingsAcctBal, as_sum_of=SAVING.AvailableBalance) Rule.formula(derive=CUSTOMER.TotalBalance, as_expression=lambda row: row.CheckingAcctBal + row.SavingsAcctBal) Rule.constraint(validate=CUSTOMER, as_condition=lambda row: row.CheckingAcctBal >= 0, error_msg="Your Checking balance of ({row.CheckingAcctBal}) is less than 0)") Rule.constraint(validate=CUSTOMER, as_condition=lambda row: row.SavingsAcctBal >= 0, error_msg="Your Savings balance of ({row.SavingsAcctBal}) is less than 0)") Rule.sum(derive=SAVING.Withdrawls, as_sum_of=SAVINGSTRANS.WithdrawlAmt) Rule.sum(derive=SAVING.Deposits, as_sum_of=SAVINGSTRANS.DepositAmt) Rule.formula(derive=SAVING.AvailableBalance, as_expression=lambda row: row.Deposits - row.Withdrawls) Rule.count(derive=SAVING.ItemCount, as_count_of=SAVINGSTRANS) Rule.formula(derive=CHECKINGTRANS.Total, as_expression=lambda row: row.DepositAmt - row.WithdrawlAmt) Rule.formula(derive=SAVINGSTRANS.Total, as_expression=lambda row: row.DepositAmt - row.WithdrawlAmt) Rule.commit_row_event(on_class=TRANSFERFUND, calling=transfer_funds)
def load_rules(self): def my_early_event(row, old_row, logic_row): logic_row.log("early event for *all* tables - good breakpoint, time/date stamping, etc") def check_balance(row: Customer, old_row, logic_row) -> bool: """ Not used... illustrate function alternative (e.g., more complex if/else logic) specify rule with `calling=check_balance` (instead of as_condition) """ return row.Balance <= row.CreditLimit def compute_amount(row: OrderDetail, old_row, logic_row): return row.UnitPrice * row.Quantity Rule.formula(derive="OrderDetail.Amount", calling=compute_amount) Rule.formula(derive="OrderDetail.Amount", calling=lambda Customer: Customer.Quantity * Customer.UnitPrice) Rule.early_row_event(on_class="*", calling=my_early_event) # just for debug Rule.constraint(validate="Customer", calling=check_balance, error_msg="balance ({row.Balance}) exceeds credit ({row.CreditLimit})")
def activate_basic_check_credit_rules(): """ Issues function calls to activate check credit rules, below. These rules are executed not now, but on commits Order is irrelevant - determined by system based on dependency analysis Their inclusion in classes is for doc / convenience, no semantics These rules apply to all transactions (automatic re-use), eg. * place order * change Order Detail product, quantity * add/delete Order Detail * ship / unship order * delete order * move order to new customer, etc """ def units_in_stock(row: Product, old_row: Product, logic_row: LogicRow): result = row.UnitsInStock - (row.UnitsShipped - old_row.UnitsShipped) return result def congratulate_sales_rep(row: Order, old_row: Order, logic_row: LogicRow): if logic_row.ins_upd_dlt == "ins" or True: # logic engine fills parents for insert sales_rep = row.SalesRep # type : Employee if sales_rep is None: logic_row.log("no salesrep for this order") else: logic_row.log(f'Hi, {sales_rep.Manager.FirstName}, congratulate {sales_rep.FirstName} on their new order') Rule.constraint(validate=Customer, as_condition=lambda row: row.Balance <= row.CreditLimit, error_msg="balance ({row.Balance}) exceeds credit ({row.CreditLimit})") Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountTotal, where=lambda row: row.ShippedDate is None) # *not* a sql select sum... Rule.sum(derive=Order.AmountTotal, as_sum_of=OrderDetail.Amount) Rule.formula(derive=OrderDetail.Amount, as_expression=lambda row: row.UnitPrice * row.Quantity) Rule.copy(derive=OrderDetail.UnitPrice, from_parent=Product.UnitPrice) Rule.formula(derive=OrderDetail.ShippedDate, as_exp="row.OrderHeader.ShippedDate") Rule.sum(derive=Product.UnitsShipped, as_sum_of=OrderDetail.Quantity, where="row.ShippedDate is not None") Rule.formula(derive=Product.UnitsInStock, calling=units_in_stock) Rule.commit_row_event(on_class=Order, calling=congratulate_sales_rep) Rule.count(derive=Customer.UnpaidOrderCount, as_count_of=Order, where=lambda row: row.ShippedDate is None) # *not* a sql select sum... Rule.count(derive=Customer.OrderCount, as_count_of=Order)
def declare_logic(): """ Issues function calls to activate rules for check credit (etc), below. These rules are executed not now, but on commits Order is irrelevant - determined by system based on dependency analysis Their inclusion in classes is for doc / convenience, no semantics These rules apply to all transactions (automatic re-use), eg. * place order * change Order Detail product, quantity * add/delete Order Detail * ship / unship order * delete order * move order to new customer, etc Activate rules like this LogicBank.activate(session=session, activator=declare_logic) """ def congratulate_sales_rep(row: Order, old_row: Order, logic_row: LogicRow): if logic_row.ins_upd_dlt == "ins": # logic engine fills parents for insert sales_rep = row.SalesRep # type : Employee if sales_rep is None: logic_row.log("no salesrep for this order") else: logic_row.log(f'Hi, {sales_rep.Manager.FirstName}, congratulate {sales_rep.FirstName} on their new order') Rule.constraint(validate=Customer, as_condition=lambda row: row.Balance <= row.CreditLimit, error_msg="balance ({row.Balance}) exceeds credit ({row.CreditLimit})") Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountTotal, where=lambda row: row.ShippedDate is None) # *not* a sql select sum... Rule.sum(derive=Order.AmountTotal, as_sum_of=OrderDetail.Amount) Rule.formula(derive=OrderDetail.Amount, as_expression=lambda row: row.UnitPrice * row.Quantity) Rule.copy(derive=OrderDetail.UnitPrice, from_parent=Product.UnitPrice) Rule.commit_row_event(on_class=Order, calling=congratulate_sales_rep) Rule.formula(derive=OrderDetail.ShippedDate, as_exp="row.OrderHeader.ShippedDate") def units_in_stock(row: Product, old_row: Product, logic_row: LogicRow): result = row.UnitsInStock - (row.UnitsShipped - old_row.UnitsShipped) return result Rule.sum(derive=Product.UnitsShipped, as_sum_of=OrderDetail.Quantity, where="row.ShippedDate is not None") Rule.formula(derive=Product.UnitsInStock, calling=units_in_stock) Rule.count(derive=Customer.UnpaidOrderCount, as_count_of=Order, where=lambda row: row.ShippedDate is None) # *not* a sql select sum... Rule.count(derive=Customer.OrderCount, as_count_of=Order) Rule.constraint(validate=OrderClass, as_condition=lambda row: row.Id <= 99999, error_msg="Test constraint for className <> tableName") Rule.constraint(validate=Employee, as_condition=lambda row: row.IsCommissioned == 1 or row.order_count == 0, error_msg="{row.LastName} is not commissioned - cannot have orders") Rule.count(derive=Employee.order_count, as_count_of=Order) def raise_over_20_percent(row: Employee, old_row: Employee, logic_row: LogicRow): if logic_row.ins_upd_dlt == "upd" and row.Salary != old_row.Salary: return row.Salary >= Decimal('1.20') * old_row.Salary else: return True Rule.constraint(validate=Employee, calling=raise_over_20_percent, error_msg="{row.LastName} needs a more meaningful raise") def audit_by_event(row: Employee, old_row: Employee, logic_row: LogicRow): tedious = False # tedious code to repeat for every audited class if tedious: # see instead the following rule extension - nw_copy_row if logic_row.are_attributes_changed([Employee.Salary, Employee.Title]): copy_to_logic_row = logic_row.new_logic_row(EmployeeAudit) copy_to_logic_row.link(to_parent=logic_row) copy_to_logic_row.set_same_named_attributes(logic_row) copy_to_logic_row.insert(reason="Manual Copy " + copy_to_logic_row.name) # triggers rules... # logic_row.log("audit_by_event (Manual Copy) complete") Rule.commit_row_event(on_class=Employee, calling=audit_by_event) """ also provided in system version RuleExtension.copy(copy_from=Employee, copy_to=EmployeeAudit, copy_when=lambda logic_row: logic_row.are_attributes_changed([Employee.Salary, Employee.Title])) """ NWRuleExtension.nw_copy_row(copy_from=Employee, copy_to=EmployeeAudit, copy_when=lambda logic_row: logic_row.are_attributes_changed([Employee.Salary, Employee.Title])) def handle_all(logic_row: LogicRow): row = logic_row.row if logic_row.ins_upd_dlt == "ins" and hasattr(row, "CreatedOn"): row.CreatedOn = datetime.datetime.now() logic_row.log("early_row_event_all_classes - handle_all sets 'Created_on"'') Rule.early_row_event_all_classes(early_row_event_all_classes=handle_all)