Source: tree/rebuildFees.js

import {HEX2uuid,query} from '@commtool/sql-query'
import {addUpdateList, addUpdateEntry} from '../server.ws.js'
import { publishEvent } from '../utils/events.js';
import { publishChangeEvent} from '../Router/person.js'
import _ from 'lodash'
import { diff } from 'deep-object-diff';
import { errorLoggerUpdate } from '../utils/requestLogger.js';
import { getPersonFees } from '../utils/getFamilyFee.js';

/**
 * Rebuilds the fee structure for a given family by UID.
 *
 * This function fetches all family members, recalculates their fee indices,
 * updates the fee address if necessary, and persists any changes to the database.
 * It also publishes relevant events if the fee structure has changed.
 * This has to be called if a family cahnged, e.g. a new member was added or removed,
 *
 * @async
 * @param {Buffer} UIDfamily - The UID of the family whose fees are to be rebuilt.
 * @param {string} organization - The organization UID for multi-tenant context.
 * @returns {Promise<boolean>} Returns true if the operation was successful, false otherwise.
 *
 * @throws Will log and handle errors internally if any database or processing error occurs.
 * @publishes events to notify about changes in the family fee structure.
 * @updates UI for all  family members with the new fee structure.
 */
const rebuildFees=async (UIDfamily, organization)=>
{
    try {
        const family= await query(`SELECT Member.UID,Member.Display,  Member.Data, ObjectBase.Type, ObjectBase.Title, 
            Links.Type AS membership
            FROM Member 
            INNER JOIN ObjectBase ON (ObjectBase.UID=Member.UID)
            INNER JOIN Links ON (Links.UID=Member.UID AND Links.Type IN ('family','familyFees')) 
            WHERE Links.UIDTarget=?
            ORDER BY CAST(JSON_VALUE(Member.Data,'$.birthday') AS SIGNED) ASC `,[UIDfamily],{cast:['json'],log:false})
       
        if(family.length===0)
            return false

        // get family Member data
        const fr= await query(`SELECT Member.UID,Member.Data FROM Member WHERE UID=?`,[UIDfamily], {cast:['json']})
        if(fr.length===0)
        {
            throw('fetching family data failed '+UIDfamily)

        }
        const famData=fr[0].Data
        // force family address update, if currently set fee address is the first member of the family with fees
        const oldFirstMember=famData.fees?.find(f=>f && f.fees)
        if(oldFirstMember && oldFirstMember.fees && oldFirstMember.UID===famData.feeAddress?.UID)
        {
            // remove the feeAddress
            famData.feeAddress=undefined
        }

        let familyIndex=0
        let rebateIndex=0
        let fees=[]
        for(const person of family)
        {
            const UIDperson=HEX2uuid(person.UID)
            const origData=_.cloneDeep(person.Data)
            ++familyIndex
            if(person.Type==='person')
            {
                // this person is not extern
                if(person.membership==='familyFees')
                    ++rebateIndex

                person.Data={...person.Data,family:person.membership==='familyFees' ? rebateIndex : 1 }
                fees[familyIndex]={
                                    UID:UIDperson,
                                    Display: person.Display,
                                    Title: person.Title,
                                    fees: []
                                }
                fees[familyIndex].fees=await getPersonFees(person)
                // if no feeAddress is set, we have to set the feeAddress to this (first) person with fees
                if(!famData.feeAddress)
                {
                    // set the feeAddress to this person
                    famData.feeAddress={
                        UID:UIDperson,
                        Display: person.Display,
                        Title: person.Title
                    }
                }
             
            } 
            else
            {
                // extern persons of a family are skipped for fees and for calculating the fee index
                person.Data={...person.Data,family:undefined}
                fees[familyIndex]={
                    UID:UIDperson,
                    Display: person.Display,
                    Title: person.Title
                }
              
            }
            
            await addUpdateList(person.UID)
            // update person Data with the new family index
            if(!_.isEqual(origData,person.Data))
            {
                query('UPDATE Member SET Data =? WHERE UID=?',[JSON.stringify(person.Data),person.UID])
                // now update all backends, where the family member is currently displayed
                addUpdateEntry(person.UID,{person:{...person,Type:person.Type,Data:person.Data,UID: HEX2uuid(person.UID)}})

                publishChangeEvent(person,diff(origData,person.Data),Date.now()/1000, organization)
            }


        
        }

        // store fees in family
                  
        query(`UPDATE Member SET Data= ? WHERE Member.UID=?`,[JSON.stringify({...famData, fees:fees}),UIDfamily])
        if(JSON.stringify(fees)!==JSON.stringify(famData.fees))
        {
            publishEvent(`/change/family/${HEX2uuid(UIDfamily)}`, {
                organization: organization,
                data: fees
            })
            addUpdateList(family.map(f=>f.UID))
        }
        
        return true
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }
   
}


/**
 * Rebuilds the fees for all families that have a member in the specified group.
 *
 * This has to be called, if the fees of a group have changed, so that all families with members in this group
 * will have their fees recalculated according to the new group fee rules.
 * @async
 * @param {Buffer} UIDgroup - The unique identifier of the group whose families' fees need to be rebuilt.
 * @param {string} organization - The organization UID for multi-tenant context.
 * @returns {Promise<void>} Resolves when the fees have been rebuilt for all relevant families.
 * @throws Will log an error if the database query or fee rebuilding fails.
 */
export const rebuildFeesGroup=async (UIDgroup, organization)=>
{
    // req can be req or root UID 
    try {
        // rebuild the fees of all families, having a member in this group
        const families= await query (`SELECT Family.UID FROM Member AS Family INNER JOIN
                    Links AS FamLink   ON FamLink.UIDTarget=Family.UID 
                    INNER JOIN Links AS MLink ON (FamLink.UID=MLink.UID AND MLink.Type IN ('member','memberA'))
                    WHERE FamLink.Type='familyFees' AND MLink.UIDTarget= ?
                    GROUP BY FamLink.UIDTarget`,[UIDgroup])
        for(const family of families )
        {
            if(family.UID)
                rebuildFees(family.UID, organization)
        }
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }
}


export default rebuildFees