Skip to content

Commit

Permalink
Merge pull request #193 from autentia/feature/expand_project_types
Browse files Browse the repository at this point in the history
Add project types attending to billing
  • Loading branch information
fjmpaez authored May 31, 2024
2 parents 1b70a08 + 39ed832 commit b1eca85
Show file tree
Hide file tree
Showing 28 changed files with 258 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { StatusBadge } from './status-badge'
import { useIsMobile } from '../../../../../../shared/hooks/use-is-mobile'
import { Project } from '../../../../../shared/project/domain/project'
import { ProjectOrganizationFilters } from '../../../../../shared/project/domain/project-organization-filters'
import { ProjectBillingType } from '../../../../../shared/project/domain/project-billing-type'

interface Props {
onProjectClicked(project: Project): void
Expand Down Expand Up @@ -93,11 +94,13 @@ export const ProjectsTable: FC<Props> = (props) => {
render: (open: boolean) => <StatusBadge status={open} />
},
{
title: 'projects.billable',
dataIndex: 'billable',
key: 'billable',
title: 'projects.projectType',
dataIndex: 'projectBillingType',
key: 'projectBillingType',
showLabelInMobile: true,
render: (billable: boolean) => (billable ? t('projects.yes') : t('projects.no'))
render: (projectBillingType: ProjectBillingType) => {
return t('projects.' + projectBillingType.name)
}
},
{
title: 'projects.blocking_date',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { chrono } from '../../../../../shared/utils/chrono'
import { Project } from '../../../../shared/project/domain/project'
import { ProjectBillingType } from '../../../../shared/project/domain/project-billing-type'

export interface AdaptedProjects {
key: number
organization: string
project: string
startDate: string
open: boolean
billable: boolean
projectBillingType: ProjectBillingType
blockDate: string
action: Project
}
Expand All @@ -23,7 +24,7 @@ export const adaptProjectsToTable = (
project: project.name,
startDate: project.startDate ? chrono(project.startDate).format('dd/MM/yyyy') : '-',
open: project.open,
billable: project.billable,
projectBillingType: project.projectBillingType,
blockDate: project.blockDate ? chrono(project.blockDate).format('dd/MM/yyyy') : '-',
action: project
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function setup() {

const newSubcontractedActivity: NewSubcontractedActivity = {
description: 'any-description',
billable: true,
projectRoleId: 1,
duration: 333,
month: '2024-05'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function setup() {
const updateSubcontractedActivity: UpdateSubcontractedActivity = {
id: 1,
description: 'Minutes activity',
billable: true,
projectRoleId: 1,
duration: 555,
month: '2024-05'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { SubcontractedActivityWithProjectRoleId } from './subcontracted-activity

export type NewSubcontractedActivity = Pick<
SubcontractedActivityWithProjectRoleId,
'description' | 'projectRoleId' | 'duration' | 'month'
'description' | 'billable' | 'projectRoleId' | 'duration' | 'month'
>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface SubcontractedActivity {
id: Id
description: string
userId: Id
billable: boolean
organization: Organization
project: LiteProjectWithOrganizationId
projectRole: LiteProjectRoleWithProjectId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { SubcontractedActivityWithProjectRoleId } from './subcontracted-activity

export type UpdateSubcontractedActivity = Pick<
SubcontractedActivityWithProjectRoleId,
'id' | 'description' | 'projectRoleId' | 'duration' | 'month'
'id' | 'description' | 'billable' | 'projectRoleId' | 'duration' | 'month'
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import ActivitiesPage from '../ui/activities-page'

describe('Change billable type activity', () => {
it('should put not billable when no billable project', () => {
setup()
cy.findByTestId('show_activity_modal').click()
cy.findByRole('button', { name: 'Add role' }).click()
cy.findByTestId('organization_field').type('Test organization\n')
cy.findByTestId('project_field').type('Proyecto C\n')

cy.findByText('No billable').should('exist')
})

it('should closed price when billable project', () => {
setup()
cy.findByTestId('show_activity_modal').click()
cy.findByRole('button', { name: 'Add role' }).click()
cy.findByTestId('organization_field').type('Test organization\n')
cy.findByTestId('project_field').type('Proyecto B\n')

cy.findByText('Closed price').should('exist')
})

it('should change billable field when billable project', () => {
setup()
cy.findByTestId('show_activity_modal').click()
cy.findByRole('button', { name: 'Add role' }).click()
cy.findByTestId('organization_field').type('Test organization\n')
cy.findByTestId('project_field').type('Proyecto A\n')

cy.findByText('Billable').should('exist')
})
})

function setup() {
cy.mount(<ActivitiesPage />)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ describe('ActivitiesListAdapter', () => {
name: 'Test organization'
},
project: {
billable: false,
id: 2,
name: 'Billable project',
organizationId: 1
organizationId: 1,
projectBillingType: {
billableByDefault: true,
name: 'CLOSED_PRICE',
type: 'ALWAYS'
}
},
projectRole: {
id: 3,
Expand All @@ -50,9 +54,13 @@ describe('ActivitiesListAdapter', () => {
name: 'Test organization'
},
project: {
billable: false,
id: 1,
name: 'No billable project'
name: 'No billable project',
projectBillingType: {
billableByDefault: false,
name: 'NO_BILLABLE',
type: 'NEVER'
}
},
timeInfo: {
timeUnit: TimeUnits.DAYS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { GetAutofillHours } from './utils/get-autofill-hours'
import { GetInitialActivityFormValues } from './utils/get-initial-activity-form-values'
import { TimeUnits } from '../../../../../../../shared/types/time-unit'
import { NonHydratedProjectRole } from '../../../../project-role/domain/non-hydrated-project-role'
import { ProjectBillableType } from '../../../../../../shared/project/domain/project-billable-type'

export const ACTIVITY_FORM_ID = 'activity-form-id'

Expand Down Expand Up @@ -217,6 +218,11 @@ export const ActivityForm: FC<ActivityFormProps> = (props) => {
[isHourlyProject, startTime, date, endTime, startDate, endDate]
)

const billableType: ProjectBillableType =
(showRecentRole
? recentProjectRole?.project.projectBillingType.type
: project?.projectBillingType.type) ?? 'OPTIONAL'

useEffect(() => {
function setBillableProjectOnChange() {
if (showRecentRole) {
Expand All @@ -225,7 +231,10 @@ export const ActivityForm: FC<ActivityFormProps> = (props) => {
return
}

setValue('billable', recentProjectRole?.project?.billable || false)
setValue(
'billable',
recentProjectRole?.project?.projectBillingType.billableByDefault || false
)
return
}

Expand All @@ -234,7 +243,7 @@ export const ActivityForm: FC<ActivityFormProps> = (props) => {
return
}

setValue('billable', project?.billable || false)
setValue('billable', project?.projectBillingType.billableByDefault || false)
}

setBillableProjectOnChange()
Expand Down Expand Up @@ -350,7 +359,7 @@ export const ActivityForm: FC<ActivityFormProps> = (props) => {
)}
</Flex>

{!isReadOnly && (
{!isReadOnly && billableType === 'OPTIONAL' && (
<Box gridArea="billable">
<Controller
control={control}
Expand All @@ -372,6 +381,18 @@ export const ActivityForm: FC<ActivityFormProps> = (props) => {
</Box>
)}

{!isReadOnly && billableType === 'NEVER' && (
<Box gridArea="billable">
<span>{t('projects.NO_BILLABLE')}</span>
</Box>
)}

{!isReadOnly && billableType === 'ALWAYS' && (
<Box gridArea="billable">
<span>{t('projects.CLOSED_PRICE')}</span>
</Box>
)}

<ActivityTextArea
{...register('description')}
control={control}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class GetInitialActivityFormValues {
startDate: chrono(startDate).format(chrono.DATE_FORMAT),
endDate: chrono(startDate).format(chrono.DATE_FORMAT),
description: '',
billable: recentRole?.project.billable ?? false,
billable: recentRole?.project.projectBillingType.billableByDefault ?? false,
recentProjectRole: recentRole,
showRecentRole: true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('SubcontractedActivitiesListAdapter', () => {
const expected: AdaptedSubcontractedActivity = {
key: subcontractedActivity.id,
id: subcontractedActivity.id,
billable: subcontractedActivity.billable,
month: subcontractedActivity.month,
duration: subcontractedActivity.duration + 'h',
organization: organization.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const subcontractedActivitiesListAdapter = (
return {
key: activity.id,
id: activity.id,
billable: activity.billable,
month: activity.month,
duration: activity.duration + 'h',
organization: activity.organization.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SubcontractedActivity } from '../../../../domain/subcontracted-activity
export interface AdaptedSubcontractedActivity {
key: number
id: number
billable: boolean
month: string
duration: string
organization: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as yup from 'yup'
import { Project } from '../../../../../../shared/project/domain/project'

export interface SubcontractedActivityFormSchema {
billable: boolean
description: string
organization?: Organization
project?: Project
Expand All @@ -18,6 +19,7 @@ const MAX_DESCRIPTION_LENGTH = 2048
export const SubcontractedActivityFormValidationSchema: yup.ObjectSchema<SubcontractedActivityFormSchema> =
yup
.object({
billable: yup.boolean().required(i18n.t('form_errors.field_required')),
description: yup
.string()
.required(i18n.t('form_errors.field_required'))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Box, Flex, Grid } from '@chakra-ui/react'
import { useForm } from 'react-hook-form'
import { Box, Checkbox, Flex, Grid } from '@chakra-ui/react'
import { Controller, useForm, useWatch } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import { UserSettings } from '../../../../../../shared/user/features/settings/domain/user-settings'
import { FC, useMemo } from 'react'
import { FC, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useGetUseCase } from '../../../../../../../shared/arch/hooks/use-get-use-case'
import { useResolve } from '../../../../../../../shared/di/use-resolve'
Expand All @@ -23,6 +23,7 @@ import { SubcontractedActivity } from '../../../domain/subcontracted-activity'
import { MonthField } from '../../../../../../../shared/components/form-fields/month-field'
import { NumberField } from '../../../../../../../shared/components/form-fields/number-field'
import { SelectRoleSectionWithoutRecentRole } from './components/role-selection-without-recent-roles'
import { ProjectBillableType } from '../../../../../../shared/project/domain/project-billable-type'

export const SUBCONTRACTED_ACTIVITY_FORM_ID = 'subcontracted-activity-form-id'

Expand Down Expand Up @@ -83,13 +84,19 @@ export const SubcontractedActivityForm: FC<SubcontractedActivityFormProps> = (pr
register,
handleSubmit,
control,
setValue,
formState: { errors }
} = useForm<SubcontractedActivityFormSchema>({
defaultValues: initialFormValues,
resolver: yupResolver(SubcontractedActivityFormValidationSchema),
mode: 'onSubmit'
})

const [project] = useWatch({
control,
name: ['project']
})

const onSubmit = async (data: SubcontractedActivityFormSchema) => {
const projectRoleId = data.projectRole!.id
const isNewActivity = subcontractedActivity?.id === undefined
Expand All @@ -98,6 +105,7 @@ export const SubcontractedActivityForm: FC<SubcontractedActivityFormProps> = (pr
if (isNewActivity && data.duration != null) {
const newSubcontractedActivity: NewSubcontractedActivity = {
description: data.description,
billable: data.billable,
projectRoleId: projectRoleId,
duration: data.duration,
month: data.month
Expand All @@ -115,6 +123,7 @@ export const SubcontractedActivityForm: FC<SubcontractedActivityFormProps> = (pr
const updateSubcontractedActivity: UpdateSubcontractedActivity = {
id: subcontractedActivity!.id,
description: data.description,
billable: data.billable,
projectRoleId: projectRoleId,
duration: data.duration!,
month: data.month
Expand All @@ -131,6 +140,21 @@ export const SubcontractedActivityForm: FC<SubcontractedActivityFormProps> = (pr
}
}

const billableType: ProjectBillableType = project?.projectBillingType.type ?? 'OPTIONAL'

useEffect(() => {
function setBillableProjectOnChange() {
if (subcontractedActivity && subcontractedActivity?.project.id === project?.id) {
setValue('billable', subcontractedActivity?.billable || false)
return
}

setValue('billable', project?.projectBillingType.billableByDefault || false)
}

setBillableProjectOnChange()
}, [subcontractedActivity, project, setValue])

return (
<Grid
templateColumns="repeat(6, [col] 1fr)"
Expand Down Expand Up @@ -187,6 +211,40 @@ export const SubcontractedActivityForm: FC<SubcontractedActivityFormProps> = (pr
/>
</Box>

{!isReadOnly && billableType == 'OPTIONAL' && (
<Box gridArea="billable">
<Controller
control={control}
name="billable"
render={({ field: { onChange, onBlur, value, ref } }) => (
<Checkbox
defaultChecked={value}
isChecked={value}
onChange={onChange}
onBlur={onBlur}
ref={ref}
colorScheme="brand"
isDisabled={isReadOnly}
>
{t('activity_form.billable')}
</Checkbox>
)}
/>
</Box>
)}

{!isReadOnly && billableType == 'NEVER' && (
<Box gridArea="billable">
<span>{t('projects.NO_BILLABLE')}</span>
</Box>
)}

{!isReadOnly && billableType == 'ALWAYS' && (
<Box gridArea="billable">
<span>{t('projects.CLOSED_PRICE')}</span>
</Box>
)}

<ActivityTextArea
{...register('description')}
control={control}
Expand Down
Loading

0 comments on commit b1eca85

Please sign in to comment.