@@ -62,7 +62,6 @@ export async function createCompany(values: z.infer<typeof createCompanySchema>)
6262 role : 'ceo' ,
6363 } ) ;
6464
65- // The creator is also the first shareholder
6665 await tx . insert ( companyShares ) . values ( {
6766 companyId : newCompany . id ,
6867 userId : session . id ,
@@ -86,77 +85,71 @@ export async function createCompany(values: z.infer<typeof createCompanySchema>)
8685}
8786
8887export async function getCompaniesForUserDashboard ( ) {
89- const session = await getSession ( ) ;
90-
91- const allCompanies = await db . query . companies . findMany ( {
92- orderBy : ( companies , { desc } ) => [ desc ( companies . createdAt ) ] ,
93- } ) ;
94-
95- const companiesWithMarketData = allCompanies . map ( company => {
96- const cash = parseFloat ( company . cash ) ;
97- const totalShares = parseFloat ( company . totalShares ) ;
98- const sharePrice = parseFloat ( company . sharePrice ) ;
99- const marketCap = totalShares * sharePrice ;
100-
101- return {
102- ...company ,
103- cash : cash ,
104- marketCap : marketCap ,
105- sharePrice : sharePrice ,
106- totalShares : totalShares ,
107- }
108- } ) ;
109-
110-
111- if ( ! session ?. id ) {
112- return {
113- managedCompanies : [ ] ,
114- investedCompanies : [ ] ,
115- otherCompanies : companiesWithMarketData ,
116- } ;
117- }
88+ const session = await getSession ( ) ;
11889
119- const [ userMemberships , userShares ] = await Promise . all ( [
120- db . query . companyMembers . findMany ( { where : eq ( companyMembers . userId , session . id ) } ) ,
121- db . query . companyShares . findMany ( { where : eq ( companyShares . userId , session . id ) } ) ,
122- ] ) ;
90+ const allCompanies = await db . query . companies . findMany ( {
91+ orderBy : ( companies , { desc } ) => [ desc ( companies . createdAt ) ] ,
92+ } ) ;
12393
124- const managedCompanyIds = new Set ( userMemberships . map ( m => m . companyId ) ) ;
125- const investedCompanyIds = new Set ( userShares . map ( s => s . companyId ) ) ;
94+ const companiesWithMarketData = allCompanies . map ( company => {
95+ const cash = parseFloat ( company . cash ) ;
96+ const totalShares = parseFloat ( company . totalShares ) ;
97+ const sharePrice = parseFloat ( company . sharePrice ) ;
98+ const marketCap = totalShares * sharePrice ;
12699
127- const managedCompanies : any [ ] = [ ] ;
128- const investedCompanies : any [ ] = [ ] ;
129- const otherCompanies : any [ ] = [ ] ;
100+ return {
101+ ...company ,
102+ cash : cash ,
103+ marketCap : marketCap ,
104+ sharePrice : sharePrice ,
105+ totalShares : totalShares ,
106+ }
107+ } ) ;
130108
131- companiesWithMarketData . forEach ( company => {
132- const isManaged = managedCompanyIds . has ( company . id ) ;
133- const isInvested = investedCompanyIds . has ( company . id ) ;
109+ if ( ! session ?. id ) {
110+ return {
111+ managedCompanies : [ ] ,
112+ investedCompanies : [ ] ,
113+ otherCompanies : companiesWithMarketData ,
114+ } ;
115+ }
134116
135- if ( isManaged ) {
136- const membership = userMemberships . find ( m => m . companyId === company . id ) ! ;
137- const shareData = userShares . find ( s => s . companyId === company . id ) ;
138- const sharesHeld = parseFloat ( shareData ?. quantity || '0' ) ;
139-
140- managedCompanies . push ( {
141- ...company ,
142- role : membership . role ,
143- sharesHeld : sharesHeld ,
144- sharesValue : sharesHeld * company . sharePrice ,
145- } ) ;
146- } else if ( isInvested ) {
147- const share = userShares . find ( s => s . companyId === company . id ) ! ;
148- const sharesHeld = parseFloat ( share . quantity ) ;
149- investedCompanies . push ( {
150- ...company ,
151- sharesHeld : sharesHeld ,
152- sharesValue : sharesHeld * company . sharePrice ,
153- } ) ;
154- } else {
155- otherCompanies . push ( company ) ;
117+ const [ userMemberships , userShares ] = await Promise . all ( [
118+ db . query . companyMembers . findMany ( { where : eq ( companyMembers . userId , session . id ) } ) ,
119+ db . query . companyShares . findMany ( { where : eq ( companyShares . userId , session . id ) } ) ,
120+ ] ) ;
121+
122+ const sharesByCompanyId = new Map ( userShares . map ( s => [ s . companyId , s ] ) ) ;
123+ const membershipsByCompanyId = new Map ( userMemberships . map ( m => [ m . companyId , m ] ) ) ;
124+
125+ const managedCompanies : any [ ] = [ ] ;
126+ const investedCompanies : any [ ] = [ ] ;
127+ const otherCompanies : any [ ] = [ ] ;
128+
129+ for ( const company of companiesWithMarketData ) {
130+ const membership = membershipsByCompanyId . get ( company . id ) ;
131+ const shareData = sharesByCompanyId . get ( company . id ) ;
132+ const sharesHeld = parseFloat ( shareData ?. quantity || '0' ) ;
133+
134+ if ( membership ) {
135+ managedCompanies . push ( {
136+ ...company ,
137+ role : membership . role ,
138+ sharesHeld : sharesHeld ,
139+ sharesValue : sharesHeld * company . sharePrice ,
140+ } ) ;
141+ } else if ( shareData ) {
142+ investedCompanies . push ( {
143+ ...company ,
144+ sharesHeld : sharesHeld ,
145+ sharesValue : sharesHeld * company . sharePrice ,
146+ } ) ;
147+ } else {
148+ otherCompanies . push ( company ) ;
149+ }
156150 }
157- } ) ;
158151
159- return { managedCompanies, investedCompanies, otherCompanies } ;
152+ return { managedCompanies, investedCompanies, otherCompanies } ;
160153}
161154
162155export type ManagedCompany = Awaited < ReturnType < typeof getCompaniesForUserDashboard > > [ 'managedCompanies' ] [ 0 ] ;
@@ -680,7 +673,7 @@ export async function applyMarketImpactToCompany(ticker: string, tradeValue: num
680673 revalidatePath ( '/' ) ;
681674 revalidatePath ( '/companies' , 'layout' ) ;
682675
683- } catch ( error ) {
676+ } catch ( error : any ) {
684677 console . error ( `Error applying market impact to ${ ticker } :` , error ) ;
685678 }
686679}
@@ -790,3 +783,73 @@ export async function sellAssetForCompany(companyId: number, holdingId: number,
790783 return { error : error . message || "Une erreur est survenue lors de la vente." } ;
791784 }
792785}
786+
787+ export async function claimCompanyBtc ( companyId : number ) : Promise < { success ?: string ; error ?: string } > {
788+ const session = await getSession ( ) ;
789+ if ( ! session ?. id ) return { error : "Non autorisé." } ;
790+
791+ try {
792+ const result = await db . transaction ( async ( tx ) => {
793+ const member = await tx . query . companyMembers . findFirst ( {
794+ where : and ( eq ( companyMembers . companyId , companyId ) , eq ( companyMembers . userId , session . id ) )
795+ } ) ;
796+ if ( ! member || member . role !== 'ceo' ) {
797+ throw new Error ( "Seul le PDG peut réclamer les récompenses de minage." ) ;
798+ }
799+
800+ const company = await tx . query . companies . findFirst ( {
801+ where : eq ( companies . id , companyId ) ,
802+ columns : { unclaimedBtc : true }
803+ } ) ;
804+ if ( ! company ) throw new Error ( "Entreprise non trouvée." ) ;
805+
806+ const amountBtc = parseFloat ( company . unclaimedBtc ) ;
807+ if ( amountBtc < 1e-9 ) { // Avoid claiming dust
808+ throw new Error ( "Pas assez de BTC à réclamer." ) ;
809+ }
810+
811+ const btcAsset = await tx . query . assets . findFirst ( { where : eq ( assetsSchema . ticker , 'BTC' ) } ) ;
812+ if ( ! btcAsset ) throw new Error ( "L'actif BTC n'a pas été trouvé dans le système." ) ;
813+
814+ const existingHolding = await tx . query . companyHoldings . findFirst ( {
815+ where : and ( eq ( companyHoldings . companyId , companyId ) , eq ( companyHoldings . ticker , 'BTC' ) )
816+ } ) ;
817+
818+ if ( existingHolding ) {
819+ const newQuantity = parseFloat ( existingHolding . quantity ) + amountBtc ;
820+ // We don't change the average cost as these are mined "for free" (in-game)
821+ await tx . update ( companyHoldings )
822+ . set ( { quantity : newQuantity . toString ( ) , updatedAt : new Date ( ) } )
823+ . where ( eq ( companyHoldings . id , existingHolding . id ) ) ;
824+ } else {
825+ await tx . insert ( companyHoldings ) . values ( {
826+ companyId : companyId ,
827+ ticker : 'BTC' ,
828+ name : 'Bitcoin' ,
829+ type : 'Crypto' ,
830+ quantity : amountBtc . toString ( ) ,
831+ avgCost : '0' ,
832+ } ) ;
833+ }
834+
835+ // Reset unclaimed BTC for the company
836+ await tx . update ( companies )
837+ . set ( { unclaimedBtc : '0' , lastMiningUpdateAt : new Date ( ) } )
838+ . where ( eq ( companies . id , companyId ) ) ;
839+
840+ return { success : `Vous avez réclamé ${ amountBtc . toFixed ( 8 ) } BTC pour l'entreprise.` } ;
841+ } ) ;
842+
843+ revalidatePath ( `/companies/${ companyId } ` ) ;
844+ return result ;
845+
846+ } catch ( error : any ) {
847+ console . error ( "Error claiming company BTC: " , error ) ;
848+ // This is a server action called from a form, so we can't easily return the error to a toast.
849+ // It will fail and the user will see the old value. They can try again.
850+ // For a better UX, we would need a client component with state management.
851+ // But for now, this is a safe failure mode.
852+ // To show an error, we would have to redirect with a query param or similar.
853+ return { error : error . message || "Une erreur est survenue lors de la réclamation des récompenses." } ;
854+ }
855+ }
0 commit comments