@@ -21,7 +21,8 @@ import {
2121 IReportDayData ,
2222 IReportDayGroupByEmployee ,
2323 IGetEmployeeHourlyRateInput ,
24- IEmployeeHourlyRate
24+ IEmployeeHourlyRate ,
25+ IReportDayGroupByProject
2526} from '@gauzy/contracts' ;
2627import { filter , tap } from 'rxjs/operators' ;
2728import { compareDate , distinctUntilChange , extractNumber , isEmpty , isNotEmpty } from '@gauzy/ui-core/common' ;
@@ -52,6 +53,7 @@ import { InvoiceExpensesSelectorComponent } from '../../table-components/invoice
5253import {
5354 InvoiceApplyTaxDiscountComponent ,
5455 InvoiceProductsSelectorComponent ,
56+ InvoiceProjectFilterComponent ,
5557 InvoiceProjectsSelectorComponent ,
5658 InvoiceTasksSelectorComponent
5759} from '../../table-components' ;
@@ -231,6 +233,26 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
231233 selectedEmployee : [ { value : this . selectedEmployee ?. name , disabled : true } ] ,
232234 tags : [ ]
233235 } ) ;
236+
237+ this . subscribeInvoiceTypeChanges ( ) ;
238+ }
239+
240+ private subscribeInvoiceTypeChanges ( ) {
241+ this . form
242+ . get ( 'invoiceType' )
243+ ?. valueChanges . pipe ( untilDestroyed ( this ) )
244+ . subscribe ( ( value ) => {
245+ const projectControl = this . form . get ( 'project' ) ;
246+
247+ if ( value === InvoiceTypeEnum . BY_PROJECT_HOURS ) {
248+ projectControl ?. setValidators ( Validators . required ) ;
249+ } else {
250+ projectControl ?. clearValidators ( ) ;
251+ projectControl ?. setValue ( null ) ;
252+ }
253+
254+ projectControl ?. updateValueAndValidity ( ) ;
255+ } ) ;
234256 }
235257
236258 loadSmartTable ( ) {
@@ -286,8 +308,17 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
286308 component : InvoiceProjectsSelectorComponent
287309 } ,
288310 valuePrepareFunction : ( cell ) => {
289- const project = cell ;
290- return `${ project . name } ` ;
311+ return cell ?. name ?? '' ;
312+ } ,
313+ filter : {
314+ type : 'custom' ,
315+ component : InvoiceProjectFilterComponent
316+ } ,
317+ filterFunction : ( value , search ) => {
318+ if ( ! search || ! value ) {
319+ return true ;
320+ }
321+ return value . name . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ;
291322 }
292323 } ;
293324 break ;
@@ -714,7 +745,7 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
714745 }
715746 }
716747
717- private loadInvoiceTimeLogsData ( ) : Observable < number > {
748+ private loadInvoiceTimeLogsDataByEmployee ( ) : Observable < number > {
718749 const request : IGetInvoiceTimeLogs = {
719750 organizationId : this . organization ?. id ,
720751 tenantId : this . store . user . tenantId ,
@@ -739,6 +770,35 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
739770 ) ;
740771 }
741772
773+ private loadInvoiceTimeLogsDataByProjects ( ) : Observable < {
774+ [ projectId : string ] : number ;
775+ } > {
776+ const request : IGetInvoiceTimeLogs = {
777+ organizationId : this . organization ?. id ,
778+ tenantId : this . store . user . tenantId ,
779+ startDate : this . selectedDateRange . startDate . toISOString ( ) ,
780+ endDate : this . selectedDateRange . endDate . toISOString ( ) ,
781+ employeeIds : [ this . selectedEmployee ?. employee ?. id ] ,
782+ groupBy : 'project' ,
783+ relations : [ 'project' ]
784+ } ;
785+
786+ return this . invoiceTimeLogsService . getInvoiceTimeLogs ( request ) . pipe (
787+ map ( ( data : IReportDayGroupByProject [ ] ) => {
788+ const projectSums : { [ projectId : string ] : number } = { } ;
789+
790+ data . forEach ( ( item ) => {
791+ if ( item . project ) {
792+ projectSums [ item . project . id ] = this . hoursDurationFormatPipe . transform ( item . sum ) ;
793+ }
794+ } ) ;
795+
796+ return projectSums ;
797+ } ) ,
798+ untilDestroyed ( this )
799+ ) ;
800+ }
801+
742802 private loadInvoiceEmployeeRateData ( ) : Observable < number > {
743803 const request : IGetEmployeeHourlyRateInput = {
744804 organizationId : this . organization . id ,
@@ -785,15 +845,6 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
785845 this . _getInvoiceNumber ( ) ;
786846 }
787847
788- private getAllProjects ( ) {
789- const { id : organizationId } = this . organization ;
790- const { tenantId } = this . store . user ;
791-
792- this . organizationProjectsService . getAll ( [ ] , { organizationId, tenantId } ) . then ( ( { items } ) => {
793- this . projects = JSON . parse ( JSON . stringify ( items ) ) ;
794- } ) ;
795- }
796-
797848 private getAllProducts ( ) {
798849 const { id : organizationId } = this . organization ;
799850 const { tenantId } = this . store . user ;
@@ -820,7 +871,7 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
820871 } ) ;
821872 }
822873
823- onTypeChange ( $event ) {
874+ async onTypeChange ( $event ) {
824875 this . invoiceType = $event ;
825876
826877 this . isEmployeeHourTable = false ;
@@ -835,7 +886,11 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
835886 break ;
836887 case InvoiceTypeEnum . BY_PROJECT_HOURS :
837888 this . isProjectHourTable = true ;
838- this . getAllProjects ( ) ;
889+ this . projects = await this . organizationProjectsService . getAssignedProjects (
890+ this . organization ?. id ,
891+ this . store . user ?. tenantId ,
892+ this . store . user ?. employee ?. id
893+ ) ;
839894 break ;
840895 case InvoiceTypeEnum . BY_TASK_HOURS :
841896 this . isTaskHourTable = true ;
@@ -887,11 +942,11 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
887942 break ;
888943 case InvoiceTypeEnum . BY_PROJECT_HOURS :
889944 if ( isNotEmpty ( this . selectedProjects ) ) {
945+ const { timeMap, rate } = await this . getProjectsData ( ) ;
890946 for ( const project of this . selectedProjects ) {
891- const data = this . createInvoiceData ( project , fakePrice , fakeQuantity ) ;
947+ const time = timeMap [ project . id ] ?? 0 ;
948+ const data = this . createInvoiceData ( project , rate , time ) ;
892949 invoiceData . push ( data ) ;
893- fakePrice ++ ;
894- fakeQuantity ++ ;
895950 }
896951 }
897952 break ;
@@ -953,7 +1008,7 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
9531008 private getEmployeeData ( ) {
9541009 return new Promise < { time : number ; rate : number } > ( ( resolve , reject ) => {
9551010 forkJoin ( {
956- time : this . loadInvoiceTimeLogsData ( ) ,
1011+ time : this . loadInvoiceTimeLogsDataByEmployee ( ) ,
9571012 rate : this . loadInvoiceEmployeeRateData ( )
9581013 } )
9591014 . pipe ( untilDestroyed ( this ) )
@@ -969,6 +1024,30 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
9691024 } ) ;
9701025 }
9711026
1027+ private getProjectsData ( ) {
1028+ return new Promise < {
1029+ timeMap : {
1030+ [ projectId : string ] : number ;
1031+ } ;
1032+ rate : number ;
1033+ } > ( ( resolve , reject ) => {
1034+ forkJoin ( {
1035+ timeMap : this . loadInvoiceTimeLogsDataByProjects ( ) ,
1036+ rate : this . loadInvoiceEmployeeRateData ( )
1037+ } )
1038+ . pipe ( untilDestroyed ( this ) )
1039+ . subscribe ( {
1040+ next : ( { timeMap, rate } ) => {
1041+ resolve ( { timeMap, rate } ) ;
1042+ } ,
1043+ error : ( err ) => {
1044+ this . toastrService . error ( err ?. message || 'An unknown error occurred' ) ;
1045+ reject ( new Error ( `Error: ${ err ?. message || 'An unknown error occurred' } ` ) ) ;
1046+ }
1047+ } ) ;
1048+ } ) ;
1049+ }
1050+
9721051 // Helper function to create invoice data objects
9731052 private createInvoiceData ( selectedItem , price : number , quantity : number , currency ?: string ) {
9741053 return {
@@ -1098,7 +1177,6 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
10981177 selectedItem : newData . selectedItem !== undefined ? newData . selectedItem : lastSourceData . selectedItem
10991178 } ;
11001179 const quantityIsValid = / ^ \d * \. ? \d + $ / . test ( newData . quantity ) && ! / ^ 0 \d + / . test ( newData . quantity ) ;
1101-
11021180 if (
11031181 quantityIsValid &&
11041182 Number . isFinite ( + newData . quantity ) &&
@@ -1107,6 +1185,7 @@ export class InvoiceAddByRoleComponent extends PaginationFilterBaseComponent imp
11071185 ) {
11081186 newData = { ...newData , price : extractNumber ( newData . price ) } ;
11091187 const itemTotal = + newData . quantity * + extractNumber ( newData . price ) ;
1188+ newData . totalValue = itemTotal ;
11101189 this . subtotal += itemTotal ;
11111190 await event . confirm . resolve ( newData ) ;
11121191 await this . calculateTotal ( ) ;
0 commit comments