@@ -6,7 +6,7 @@ import { db } from '@/lib/db';
66import { companies , companyMembers , users , companyShares , companyHoldings , assets as assetsSchema , companyMiningRigs , transactions } from '@/lib/db/schema' ;
77import { getSession } from '../session' ;
88import { revalidatePath } from 'next/cache' ;
9- import { eq , and , desc , or , ilike , notInArray , inArray } from 'drizzle-orm' ;
9+ import { eq , and , desc , or , ilike , notInArray , inArray , sql } from 'drizzle-orm' ;
1010import { getRigById } from '@/lib/mining' ;
1111
1212const createCompanySchema = z . object ( {
@@ -136,7 +136,7 @@ export async function getCompaniesForUserDashboard() {
136136 const membershipsByCompanyId = new Map ( userMemberships . map ( m => [ m . companyId , m ] ) ) ;
137137
138138 const managedCompanies : any [ ] = [ ] ;
139- const investedCompanies : any [ ] = [ ] ;
139+ let investedCompanies : any [ ] = [ ] ;
140140 const otherCompanies : any [ ] = [ ] ;
141141
142142 for ( const company of companiesWithMarketData ) {
@@ -162,6 +162,9 @@ export async function getCompaniesForUserDashboard() {
162162 }
163163 }
164164
165+ const managedCompanyIds = new Set ( managedCompanies . map ( c => c . id ) ) ;
166+ investedCompanies = investedCompanies . filter ( c => ! managedCompanyIds . has ( c . id ) ) ;
167+
165168 return { managedCompanies, investedCompanies, otherCompanies } ;
166169}
167170
@@ -272,7 +275,6 @@ export async function getCompanyById(companyId: number) {
272275
273276export type CompanyWithDetails = NonNullable < Awaited < ReturnType < typeof getCompanyById > > > ;
274277
275-
276278// Universal function to calculate a company's Net Asset Value (NAV)
277279async function getCompanyNAV ( companyId : number , tx : any ) {
278280 const company = await tx . query . companies . findFirst ( {
@@ -303,6 +305,170 @@ async function getCompanyNAV(companyId: number, tx: any) {
303305 return parseFloat ( company . cash ) + holdingsValue + miningRigsValue ;
304306}
305307
308+ export async function buyAssetForCompany ( companyId : number , ticker : string , quantity : number ) : Promise < { success ?: string ; error ?: string } > {
309+ const session = await getSession ( ) ;
310+ if ( ! session ?. id ) return { error : "Non autorisé." } ;
311+ if ( quantity <= 0 ) return { error : "La quantité doit être positive." } ;
312+
313+ try {
314+ const result = await db . transaction ( async ( tx ) => {
315+ const member = await tx . query . companyMembers . findFirst ( { where : and ( eq ( companyMembers . companyId , companyId ) , eq ( companyMembers . userId , session . id ) ) } ) ;
316+ if ( ! member || member . role !== 'ceo' ) throw new Error ( "Seul le PDG peut gérer les actifs de l'entreprise." ) ;
317+
318+ const company = await tx . query . companies . findFirst ( { where : eq ( companies . id , companyId ) , columns : { cash : true } } ) ;
319+ if ( ! company ) throw new Error ( "Entreprise non trouvée." ) ;
320+
321+ const asset = await tx . query . assets . findFirst ( { where : eq ( assetsSchema . ticker , ticker ) } ) ;
322+ if ( ! asset ) throw new Error ( "Actif à acheter non trouvé." ) ;
323+
324+ const tradeValue = parseFloat ( asset . price ) * quantity ;
325+ if ( parseFloat ( company . cash ) < tradeValue ) throw new Error ( "Trésorerie de l'entreprise insuffisante." ) ;
326+
327+ // Update company cash
328+ await tx . update ( companies ) . set ( { cash : sql `${ companies . cash } - ${ tradeValue } ` } ) . where ( eq ( companies . id , companyId ) ) ;
329+
330+ // Add/update holding
331+ const existingHolding = await tx . query . companyHoldings . findFirst ( {
332+ where : and ( eq ( companyHoldings . companyId , companyId ) , eq ( companyHoldings . ticker , ticker ) ) ,
333+ } ) ;
334+
335+ if ( existingHolding ) {
336+ const existingQuantity = parseFloat ( existingHolding . quantity ) ;
337+ const existingAvgCost = parseFloat ( existingHolding . avgCost ) ;
338+ const newTotalQuantity = existingQuantity + quantity ;
339+ const newAvgCost = ( ( existingAvgCost * existingQuantity ) + tradeValue ) / newTotalQuantity ;
340+ await tx . update ( companyHoldings ) . set ( { quantity : newTotalQuantity . toString ( ) , avgCost : newAvgCost . toString ( ) , updatedAt : new Date ( ) } ) . where ( eq ( companyHoldings . id , existingHolding . id ) ) ;
341+ } else {
342+ await tx . insert ( companyHoldings ) . values ( {
343+ companyId : companyId ,
344+ ticker : asset . ticker ,
345+ name : asset . name ,
346+ type : asset . type ,
347+ quantity : quantity . toString ( ) ,
348+ avgCost : asset . price ,
349+ } ) ;
350+ }
351+
352+ // Recalculate NAV and new share price because asset values can change
353+ const companyData = await tx . query . companies . findFirst ( { where : eq ( companies . id , companyId ) } ) ;
354+ const nav = await getCompanyNAV ( companyId , tx ) ;
355+ const totalShares = parseFloat ( companyData ! . totalShares ) ;
356+ if ( totalShares > 0 ) {
357+ const newSharePrice = nav / totalShares ;
358+ await tx . update ( companies ) . set ( { sharePrice : newSharePrice . toString ( ) } ) . where ( eq ( companies . id , companyId ) ) ;
359+ }
360+
361+ return { success : `L'entreprise a acheté ${ quantity } de ${ ticker } .` } ;
362+ } ) ;
363+
364+ revalidatePath ( `/companies/${ companyId } ` ) ;
365+ return result ;
366+ } catch ( error : any ) {
367+ return { error : error . message || "Une erreur est survenue lors de l'achat de l'actif." } ;
368+ }
369+ }
370+
371+ export async function sellAssetForCompany ( companyId : number , holdingId : number , quantity : number ) : Promise < { success ?: string ; error ?: string } > {
372+ const session = await getSession ( ) ;
373+ if ( ! session ?. id ) return { error : "Non autorisé." } ;
374+ if ( quantity <= 0 ) return { error : "La quantité doit être positive." } ;
375+
376+ try {
377+ const result = await db . transaction ( async ( tx ) => {
378+ const member = await tx . query . companyMembers . findFirst ( { where : and ( eq ( companyMembers . companyId , companyId ) , eq ( companyMembers . userId , session . id ) ) } ) ;
379+ if ( ! member || member . role !== 'ceo' ) throw new Error ( "Seul le PDG peut gérer les actifs de l'entreprise." ) ;
380+
381+ const holdingToSell = await tx . query . companyHoldings . findFirst ( {
382+ where : and ( eq ( companyHoldings . id , holdingId ) , eq ( companyHoldings . companyId , companyId ) )
383+ } ) ;
384+
385+ if ( ! holdingToSell ) throw new Error ( "Actif détenu non trouvé." ) ;
386+ if ( parseFloat ( holdingToSell . quantity ) < quantity ) throw new Error ( "Quantité d'actifs de l'entreprise insuffisante pour la vente." ) ;
387+
388+ const asset = await tx . query . assets . findFirst ( { where : eq ( assetsSchema . ticker , holdingToSell . ticker ) } ) ;
389+ if ( ! asset ) throw new Error ( "Actif non trouvé sur le marché." ) ;
390+
391+ const tradeValue = parseFloat ( asset . price ) * quantity ;
392+
393+ await tx . update ( companies ) . set ( { cash : sql `${ companies . cash } + ${ tradeValue } ` } ) . where ( eq ( companies . id , companyId ) ) ;
394+
395+ const newQuantity = parseFloat ( holdingToSell . quantity ) - quantity ;
396+ if ( newQuantity > 1e-9 ) {
397+ await tx . update ( companyHoldings ) . set ( { quantity : newQuantity . toString ( ) , updatedAt : new Date ( ) } ) . where ( eq ( companyHoldings . id , holdingId ) ) ;
398+ } else {
399+ await tx . delete ( companyHoldings ) . where ( eq ( companyHoldings . id , holdingId ) ) ;
400+ }
401+
402+ const companyData = await tx . query . companies . findFirst ( { where : eq ( companies . id , companyId ) } ) ;
403+ const nav = await getCompanyNAV ( companyId , tx ) ;
404+ const totalShares = parseFloat ( companyData ! . totalShares ) ;
405+ if ( totalShares > 0 ) {
406+ const newSharePrice = nav / totalShares ;
407+ await tx . update ( companies ) . set ( { sharePrice : newSharePrice . toString ( ) } ) . where ( eq ( companies . id , companyId ) ) ;
408+ }
409+
410+ return { success : `L'entreprise a vendu ${ quantity } de ${ asset . ticker } .` } ;
411+ } ) ;
412+
413+ revalidatePath ( `/companies/${ companyId } ` ) ;
414+ return result ;
415+
416+ } catch ( error : any ) {
417+ return { error : error . message || "Une erreur est survenue lors de la vente de l'actif." } ;
418+ }
419+ }
420+
421+ export async function buyMiningRigForCompany ( companyId : number , rigId : string ) : Promise < { success ?: string ; error ?: string } > {
422+ const session = await getSession ( ) ;
423+ if ( ! session ?. id ) return { error : "Non autorisé." } ;
424+
425+ const rigToBuy = getRigById ( rigId ) ;
426+ if ( ! rigToBuy ) return { error : "Matériel de minage non valide." } ;
427+
428+ try {
429+ const result = await db . transaction ( async ( tx ) => {
430+ const member = await tx . query . companyMembers . findFirst ( { where : and ( eq ( companyMembers . companyId , companyId ) , eq ( companyMembers . userId , session . id ) ) } ) ;
431+ if ( ! member || member . role !== 'ceo' ) throw new Error ( "Seul le PDG peut acheter du matériel pour l'entreprise." ) ;
432+
433+ const company = await tx . query . companies . findFirst ( { where : eq ( companies . id , companyId ) , columns : { cash : true } } ) ;
434+ if ( ! company ) throw new Error ( "Entreprise non trouvée." ) ;
435+
436+ const companyCash = parseFloat ( company . cash ) ;
437+ if ( companyCash < rigToBuy . price ) throw new Error ( "Trésorerie de l'entreprise insuffisante." ) ;
438+
439+ await tx . update ( companies ) . set ( { cash : sql `${ companies . cash } - ${ rigToBuy . price } ` } ) . where ( eq ( companies . id , companyId ) ) ;
440+
441+ const existingRig = await tx . query . companyMiningRigs . findFirst ( {
442+ where : and ( eq ( companyMiningRigs . companyId , companyId ) , eq ( companyMiningRigs . rigId , rigId ) ) ,
443+ } ) ;
444+
445+ if ( existingRig ) {
446+ await tx . update ( companyMiningRigs ) . set ( { quantity : existingRig . quantity + 1 } ) . where ( eq ( companyMiningRigs . id , existingRig . id ) ) ;
447+ } else {
448+ await tx . insert ( companyMiningRigs ) . values ( { companyId, rigId, quantity : 1 } ) ;
449+ }
450+
451+ const companyData = await tx . query . companies . findFirst ( { where : eq ( companies . id , companyId ) } ) ;
452+ const newNav = await getCompanyNAV ( companyId , tx ) ;
453+ const totalShares = parseFloat ( companyData ! . totalShares ) ;
454+ if ( totalShares > 0 ) {
455+ const newSharePrice = newNav / totalShares ;
456+ await tx . update ( companies ) . set ( { sharePrice : newSharePrice . toString ( ) } ) . where ( eq ( companies . id , companyId ) ) ;
457+ }
458+
459+ return { success : `L'entreprise a acheté un ${ rigToBuy . name } .` } ;
460+ } ) ;
461+
462+ revalidatePath ( `/companies/${ companyId } ` ) ;
463+ return result ;
464+
465+ } catch ( error : any ) {
466+ console . error ( "Error buying mining rig for company:" , error ) ;
467+ return { error : error . message || "Une erreur est survenue lors de l'achat." } ;
468+ }
469+ }
470+
471+
306472export async function investInCompany ( companyId : number , amount : number ) : Promise < { success ?: string ; error ?: string } > {
307473 const session = await getSession ( ) ;
308474 if ( ! session ?. id ) return { error : "Vous devez être connecté pour investir." } ;
@@ -530,56 +696,6 @@ export async function withdrawFromCompanyTreasury(companyId: number, amount: num
530696 }
531697}
532698
533- export async function buyMiningRigForCompany ( companyId : number , rigId : string ) : Promise < { success ?: string ; error ?: string } > {
534- const session = await getSession ( ) ;
535- if ( ! session ?. id ) return { error : "Non autorisé." } ;
536-
537- const rigToBuy = getRigById ( rigId ) ;
538- if ( ! rigToBuy ) return { error : "Matériel de minage non valide." } ;
539-
540- try {
541- const result = await db . transaction ( async ( tx ) => {
542- const member = await tx . query . companyMembers . findFirst ( { where : and ( eq ( companyMembers . companyId , companyId ) , eq ( companyMembers . userId , session . id ) ) } ) ;
543- if ( ! member || member . role !== 'ceo' ) throw new Error ( "Seul le PDG peut acheter du matériel pour l'entreprise." ) ;
544-
545- const company = await tx . query . companies . findFirst ( { where : eq ( companies . id , companyId ) , columns : { cash : true } } ) ;
546- if ( ! company ) throw new Error ( "Entreprise non trouvée." ) ;
547-
548- const companyCash = parseFloat ( company . cash ) ;
549- if ( companyCash < rigToBuy . price ) throw new Error ( "Trésorerie de l'entreprise insuffisante." ) ;
550-
551- await tx . update ( companies ) . set ( { cash : sql `${ companies . cash } - ${ rigToBuy . price } ` } ) . where ( eq ( companies . id , companyId ) ) ;
552-
553- const existingRig = await tx . query . companyMiningRigs . findFirst ( {
554- where : and ( eq ( companyMiningRigs . companyId , companyId ) , eq ( companyMiningRigs . rigId , rigId ) ) ,
555- } ) ;
556-
557- if ( existingRig ) {
558- await tx . update ( companyMiningRigs ) . set ( { quantity : existingRig . quantity + 1 } ) . where ( eq ( companyMiningRigs . id , existingRig . id ) ) ;
559- } else {
560- await tx . insert ( companyMiningRigs ) . values ( { companyId, rigId, quantity : 1 } ) ;
561- }
562-
563- const fullCompany = await tx . query . companies . findFirst ( { where : eq ( companies . id , companyId ) } ) ;
564- const newNav = await getCompanyNAV ( companyId , tx ) ;
565- const totalShares = parseFloat ( fullCompany ! . totalShares ) ;
566- if ( totalShares > 0 ) {
567- const newSharePrice = newNav / totalShares ;
568- await tx . update ( companies ) . set ( { sharePrice : newSharePrice . toString ( ) } ) . where ( eq ( companies . id , companyId ) ) ;
569- }
570-
571- return { success : `L'entreprise a acheté un ${ rigToBuy . name } .` } ;
572- } ) ;
573-
574- revalidatePath ( `/companies/${ companyId } ` ) ;
575- return result ;
576-
577- } catch ( error : any ) {
578- console . error ( "Error buying mining rig for company:" , error ) ;
579- return { error : error . message || "Une erreur est survenue lors de l'achat." } ;
580- }
581- }
582-
583699export async function searchUsersForCompany ( companyId : number , query : string ) : Promise < { id : number , displayName : string , email : string } [ ] > {
584700 if ( ! query || query . length < 2 ) return [ ] ;
585701
@@ -754,3 +870,5 @@ export async function claimCompanyBtc(companyId: number): Promise<{ success?: st
754870 return { error : error . message || "Une erreur est survenue lors de la réclamation des récompenses." } ;
755871 }
756872}
873+
874+
0 commit comments