Thank you for your answers!
What I was trying to say was, that we have to be careful when dealing with _ItemHandles, because they do not represent a single item but a complete stack of items. For items that cannot stack like armor or weapons of course, this makes no difference, but it does for all items that are stackable, like basically all ingredients. Which matters for you because you need to deal with ingredients in your crfting mod.
I really have to think about my item transfer plans. Weapon transfer should be okay. But for other items I need handles. And it's quite strange what happens (just for testing purposes):
IF
GlobalEventSet("TransferItemsTest")
AND
ItemTemplateIsInCharacterInventory(CHARACTER_Player1,"CON_Potion_Empty_A_cd6e86ca-e9be-444e-a7df-d295ec6bb578",_Amount)
AND
GetItemhandleForItemTemplateInInventory(CHARACTER_Player1,"CON_Potion_Empty_A_cd6e86ca-e9be-444e-a7df-d295ec6bb578",_Handle)
THEN
ItemHandleToCharacter(_Handle,CHARACTER_Player2,_Amount);
If all items with this template are one stack it works fine. If there are several stacks only one stack gets transferred, so I have to repeat the event (or the process, what I do in my actual script) until all stacks are transferred.
It seems the engine only picks one handle of several handles (for several stacks) or there is just one handle for one stack and the other stacks are unhandled (would not make sense?).
I'm sure all stacks have handles but for the needs Larian had at some points, it was probably enough to return one handle. And it looks like Larian never implement things a more generic way but for a current need.
(This also answers one of my questions, if the GetHandle... query could ever return a tuple if multiple stacks were present: the answer is no then.)
If you wanted to make sure you transfer ALL stacks, you could use a recursive procedure.
I use '-1' as _Amount parameter for the move call, because it transfers a complete stack without the need to pass a specific amount.
(Sorry, I HAD to use spaces between parameters where you didn't because I always find code without them hard to read outside of syntax-coloring places. I also always 'qualify' my own stuff so that chances for name conflicts are lower, thus the name 'AbraxasProc_...' ;-)
IF
GlobalEventSet( "TransferItemsTest" )
AND
ItemTemplateIsInCharacterInventory( CHARACTER_Player1, "CON_Potion_Empty_A_cd6e86ca-e9be-444e-a7df-d295ec6bb578", _Amount )
AND
_Amount > 0
THEN
AbraxasProc_Move_All_Templates( CHARACTER_Player1, CHARACTER_Player2, "CON_Potion_Empty_A_cd6e86ca-e9be-444e-a7df-d295ec6bb578" );
// passing source and target as well to make it more generic
PROC
AbraxasProc_Move_All_Templates( (CHARACTER)_CharSrc, (CHARACTER)_CharTrg, (STRING)_Template )
AND
_CharSrc != _CharTrg // to avoid endless loops
AND
ItemTemplateIsInCharacterInventory( _CharSrc, _Template, _Amount )
AND
_Amount > 0
AND
GetItemhandleForItemTemplateInInventory( _CharSrc, _Template, _Handle )
THEN
ItemHandleToCharacter( _Handle, _CharTrg, -1 );
AbraxasProc_Move_All_Templates( _CharSrc, _CharTrg, _Template );
The GenerateItems() call looks weird because it takes two characters as parameters, but maybe that is only a relic from previous games. Trade treasure does not change if you trade with different characters, unless a generation is explicitely triggered by character level differences.
I have no idea why the call requires two characters.
Maybe it checks the player to relate item level and player level for item generation?
That makes sense, thanks for bringing this idea up

I really wish Raze had more time to dig info from Belgium ;-)