Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e7143e3
Enhance financial reporting with contract type integration
anatolyshipitz Aug 27, 2025
dbe678c
fix: Improve date comparison in getContractTypeByDate function
anatolyshipitz Aug 27, 2025
f27b1e6
Add unit tests for getContractTypeByDate function
anatolyshipitz Aug 27, 2025
b0d69d6
Update Dockerfile.n8n to use n8n version 1.109.2 and install addition…
anatolyshipitz Sep 3, 2025
8096856
Add weekly financial report workflow and enhance marginality calculat…
anatolyshipitz Sep 3, 2025
3748744
Refactor date handling in financial queries and clean up code
anatolyshipitz Sep 4, 2025
a0b30f0
Add docker-compose.override.yml and update package dependencies
anatolyshipitz Sep 5, 2025
9931950
Resolve merge conflict in Dockerfile.n8n - use versioned git package
anatolyshipitz Sep 5, 2025
bd8a9d6
Refactor MarginalityResult and EffectiveMarginalityResult interfaces …
anatolyshipitz Sep 5, 2025
b0f6e76
Refactor date handling and contract type resolution in financial repo…
anatolyshipitz Sep 5, 2025
4201e4e
Remove docker-compose.override.yml file to streamline configuration a…
anatolyshipitz Sep 5, 2025
658fbc1
Refactor weekly report workflow initiation in launchWeeklyReport.ts
anatolyshipitz Sep 5, 2025
ad33549
Implement WeeklyFinancialReportCalculations class for improved financ…
anatolyshipitz Sep 5, 2025
4eea60f
Remove unused EffectiveMarginalityCalculator import from WeeklyFinanc…
anatolyshipitz Sep 5, 2025
b966818
Enhance tests for handleRunError function by adding process.exit mocking
anatolyshipitz Sep 5, 2025
595485c
Update WeeklyFinancialReportFormatter to improve notes formatting and…
anatolyshipitz Sep 5, 2025
623c9c9
Add project_hours to TargetUnit and update related calculations
anatolyshipitz Sep 21, 2025
84fda2d
Merge branch 'main' into feature/add-contract-type
anatolyshipitz Sep 21, 2025
e42cb38
Add project_hours to test data in WeeklyFinancialReport and TargetUni…
anatolyshipitz Sep 21, 2025
5fee636
Merge branch 'feature/add-contract-type' of github.com:speedandfuncti…
anatolyshipitz Sep 21, 2025
24bf804
Refactor test data in WeeklyFinancialReportSorting tests
anatolyshipitz Sep 21, 2025
ac509e6
Refactor sorting tests in WeeklyFinancialReportSorting
anatolyshipitz Sep 21, 2025
1b1fb88
Update TargetUnit interfaces and repository for optional project_hour…
anatolyshipitz Sep 21, 2025
24833df
Fix revenue calculation in WeeklyFinancialReportCalculations to handl…
anatolyshipitz Sep 24, 2025
9b8c5b1
Refactor EffectiveMarginalityCalculator and MarginalityCalculator for…
anatolyshipitz Oct 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ const mockTargetUnits: TargetUnit[] = [
];
const mockFinancialsAppData: FinancialsAppData = {
employees: [{ redmine_id: 3, history: { rate: { '2024-06-01': 100 } } }],
projects: [{ redmine_id: 2, history: { rate: { '2024-06-01': 200 } } }],
projects: [
{
name: 'Test Project',
redmine_id: 2,
history: { rate: { '2024-06-01': 200 } },
},
],
};

describe('sendReportToSlack', () => {
Expand Down
10 changes: 8 additions & 2 deletions workers/main/src/services/FinApp/FinAppRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('FinAppRepository', () => {
expect(result).toEqual(mockEmployees);
expect(vi.mocked(EmployeeModel).find).toHaveBeenCalledWith(
{ redmine_id: { $in: [1] } },
{ 'redmine_id': 1, 'history.rate': 1 },
{ 'redmine_id': 1, 'history.rate': 1, 'history.contractType': 1 },
);
});

Expand All @@ -120,7 +120,13 @@ describe('FinAppRepository', () => {
expect(result).toEqual(mockProjects);
expect(vi.mocked(ProjectModel).find).toHaveBeenCalledWith(
{ redmine_id: { $in: [550] } },
{ 'name': 1, 'redmine_id': 1, 'quick_books_id': 1, 'history.rate': 1 },
{
'name': 1,
'redmine_id': 1,
'quick_books_id': 1,
'history.rate': 1,
'history.contractType': 1,
},
);
});

Expand Down
10 changes: 8 additions & 2 deletions workers/main/src/services/FinApp/FinAppRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class FinAppRepository implements IFinAppRepository {
try {
return await EmployeeModel.find(
{ redmine_id: { $in: redmineIds } },
{ 'redmine_id': 1, 'history.rate': 1 },
{ 'redmine_id': 1, 'history.rate': 1, 'history.contractType': 1 },
).lean<Employee[]>();
} catch (error) {
throw new FinAppRepositoryError(
Expand All @@ -21,7 +21,13 @@ export class FinAppRepository implements IFinAppRepository {
try {
return await ProjectModel.find(
{ redmine_id: { $in: redmineIds } },
{ 'name': 1, 'redmine_id': 1, 'quick_books_id': 1, 'history.rate': 1 },
{
'name': 1,
'redmine_id': 1,
'quick_books_id': 1,
'history.rate': 1,
'history.contractType': 1,
},
).lean<Project[]>();
} catch (error) {
throw new FinAppRepositoryError(
Expand Down
1 change: 1 addition & 0 deletions workers/main/src/services/FinApp/FinAppSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Employee, Project } from './types';
export const historySchema = new mongoose.Schema(
{
rate: { type: Map, of: Number },
contractType: { type: Map, of: String },
},
{ _id: false },
);
Expand Down
23 changes: 23 additions & 0 deletions workers/main/src/services/FinApp/FinAppUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function getContractTypeByDate(
contractTypeHistory: { [date: string]: string } | undefined,
date: string,
): string | undefined {
if (!contractTypeHistory) {
return undefined;
}

const sortedDates = Object.keys(contractTypeHistory).sort(
(a, b) => new Date(a).getTime() - new Date(b).getTime(),
);
let lastContractType: string | undefined = undefined;

for (const contractDate of sortedDates) {
if (contractDate <= date) {
lastContractType = contractTypeHistory[contractDate];
} else {
break;
}
}

return lastContractType;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
Comment thread
anatolyshipitz marked this conversation as resolved.
1 change: 1 addition & 0 deletions workers/main/src/services/FinApp/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface History {
rate: { [date: string]: number };
contractType?: { [date: string]: string };
}

export interface Employee {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface FormatDetailInput {
effectiveRevenue: number;
effectiveMargin: number;
effectiveMarginality: number;
contractType?: string;
}
Comment thread
anatolyshipitz marked this conversation as resolved.

const spacer = ' '.repeat(4);
Expand All @@ -42,9 +43,11 @@ export class WeeklyFinancialReportFormatter {
effectiveRevenue,
effectiveMargin,
effectiveMarginality,
contractType,
}: FormatDetailInput) =>
`*${groupName}*\n` +
`${spacer}period: ${currentQuarter}\n` +
`${spacer}contract type: ${contractType || 'n/a'}\n` +
`${spacer}total hours: ${groupTotalHours.toFixed(1)}\n` +
`${spacer}revenue: ${formatCurrency(groupTotalRevenue)}\n` +
`${spacer}COGS: ${formatCurrency(groupTotalCogs)}\n` +
Expand Down Expand Up @@ -130,9 +133,8 @@ export class WeeklyFinancialReportFormatter {

return (
'\n*Notes:*\n' +
'1. *Contract Type* is not implemented\n' +
`2. *Effective Revenue* calculated for the last ${qboConfig.effectiveRevenueMonths} months (${startDate} - ${endDate})\n` +
'3. *Dept Tech* hours are not implemented\n\n' +
`1. *Effective Revenue* calculated for the last ${qboConfig.effectiveRevenueMonths} months (${startDate} - ${endDate})\n` +
'2. *Dept Tech* hours are not implemented\n\n' +
`*Legend*: Marginality :arrowup: ≥${HIGH_MARGINALITY_THRESHOLD}% :large_yellow_circle: ${MEDIUM_MARGINALITY_THRESHOLD}-${HIGH_MARGINALITY_THRESHOLD - 1}% :arrowdown: <${MEDIUM_MARGINALITY_THRESHOLD}%`
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ describe('WeeklyFinancialReportRepository', () => {
expect(details).toContain('Margin');
expect(details).toContain('Marginality');
expect(details).toContain('Notes:');
expect(details).toContain('Contract Type');
expect(details).toContain('Effective Revenue');
expect(details).toContain('Dept Tech');
expect(details).toContain('Legend');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getRateByDate } from '../../common/formatUtils';
import type { TargetUnit } from '../../common/types';
import type { Employee, Project } from '../FinApp';
import { getContractTypeByDate } from '../FinApp/FinAppUtils';
import { GroupAggregator } from './GroupAggregator';
import {
AggregateGroupDataInput,
Expand All @@ -23,6 +24,7 @@ interface GroupData {
effectiveMargin: number;
effectiveMarginality: number;
marginality: MarginalityResult;
contractType?: string;
}

export class WeeklyFinancialReportRepository
Expand Down Expand Up @@ -132,6 +134,7 @@ export class WeeklyFinancialReportRepository
effectiveRevenue,
effectiveMargin,
effectiveMarginality,
contractType,
} = this.aggregateGroupData({ groupUnits, employees, projects });
const marginality = MarginalityCalculator.calculate(
groupTotalRevenue,
Expand All @@ -147,6 +150,7 @@ export class WeeklyFinancialReportRepository
effectiveMargin,
effectiveMarginality,
marginality,
contractType,
};
}

Expand Down Expand Up @@ -184,6 +188,7 @@ export class WeeklyFinancialReportRepository
effectiveRevenue: group.effectiveRevenue,
effectiveMargin: group.effectiveMargin,
effectiveMarginality: group.effectiveMarginality,
contractType: group.contractType,
});
}

Expand Down Expand Up @@ -242,6 +247,7 @@ export class WeeklyFinancialReportRepository
employees,
projects,
}: AggregateGroupDataInput) {
let contractType: string | undefined;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
let groupTotalCogs = 0;
let groupTotalRevenue = 0;
let effectiveRevenue = 0;
Expand All @@ -261,6 +267,11 @@ export class WeeklyFinancialReportRepository
effectiveRevenue += project.effectiveRevenue || 0;
processedProjects.add(project.redmine_id);
}

contractType = getContractTypeByDate(
project?.history?.contractType,
date,
);
}

const effectiveMargin = effectiveRevenue - groupTotalCogs;
Expand All @@ -273,6 +284,7 @@ export class WeeklyFinancialReportRepository
effectiveRevenue,
effectiveMargin,
effectiveMarginality,
contractType,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,26 @@ const createLevelTestData = () => ({
{ redmine_id: 103, history: { rate: { '2024-01-01': 50 } } },
],
projects: [
{ redmine_id: 10, history: { rate: { '2024-01-01': 100 } } }, // 50% marginality (Low)
{ redmine_id: 20, history: { rate: { '2024-01-01': 200 } } }, // 75% marginality (High)
{ redmine_id: 30, history: { rate: { '2024-01-01': 150 } } }, // 67% marginality (Medium)
{ redmine_id: 40, history: { rate: { '2024-01-01': 180 } } }, // 72% marginality (High)
{
name: 'Project X',
redmine_id: 10,
history: { rate: { '2024-01-01': 100 } },
}, // 50% marginality (Low)
{
name: 'Project Y',
redmine_id: 20,
history: { rate: { '2024-01-01': 200 } },
}, // 75% marginality (High)
{
name: 'Project Z',
redmine_id: 30,
history: { rate: { '2024-01-01': 150 } },
}, // 67% marginality (Medium)
{
name: 'Project W',
redmine_id: 40,
history: { rate: { '2024-01-01': 180 } },
}, // 72% marginality (High)
],
});

Expand Down
Loading