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