init data-connector
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
ArgsType,
|
||||
Field,
|
||||
Float,
|
||||
InputType,
|
||||
Int,
|
||||
ObjectType,
|
||||
} from '@nestjs/graphql';
|
||||
import { PaginationArgs } from 'src/core/base/pagination.input';
|
||||
import { AccountingFilter } from './accounting-filter';
|
||||
import { AccountingState } from './accounting-state';
|
||||
|
||||
@InputType()
|
||||
export class AccountingFilterArgs implements AccountingFilter {
|
||||
@Field(() => [String], { nullable: true })
|
||||
deviation!: 'pos' | 'neg' | 'both'[];
|
||||
@Field(() => [String], { nullable: true })
|
||||
missing?: 'information' | 'documents'[];
|
||||
// @Field(() => IntervalObjectType, { nullable: true })
|
||||
// dateRange?: Interval;
|
||||
}
|
||||
|
||||
@ArgsType()
|
||||
export class AccountingTourArgs extends PaginationArgs {
|
||||
@Field(() => AccountingState)
|
||||
state!: AccountingState;
|
||||
@Field(() => AccountingFilterArgs, { nullable: true })
|
||||
filters!: AccountingFilterArgs;
|
||||
}
|
||||
|
||||
@ObjectType('TourStateMeta')
|
||||
export class TourStateMetaObjectType {
|
||||
@Field(() => Int)
|
||||
count!: number;
|
||||
@Field(() => Float, { nullable: true })
|
||||
revenueSum?: number;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Prisma, TicketValidationState } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
|
||||
export interface AccountingFilter {
|
||||
deviation?: 'pos' | 'neg' | 'both'[];
|
||||
missing?: 'information' | 'documents'[];
|
||||
dateRange?: Interval;
|
||||
}
|
||||
|
||||
export function createPrismaQueryFromAccountingFilters(
|
||||
filter: AccountingFilter,
|
||||
): Prisma.TourWhereInput[] {
|
||||
const conditions: Prisma.TourWhereInput[] = [];
|
||||
|
||||
Object.entries(filter ?? {}).forEach(([property, args]) => {
|
||||
switch (property) {
|
||||
case 'deviation':
|
||||
if (args.includes('pos'))
|
||||
conditions.push({
|
||||
revenueDeviation: { gt: 0 },
|
||||
});
|
||||
else if (args.includes('neg'))
|
||||
conditions.push({
|
||||
revenueDeviation: { lt: 0 },
|
||||
});
|
||||
else if (args.includes('both'))
|
||||
conditions.push({
|
||||
revenueDeviation: { not: 0 },
|
||||
});
|
||||
return;
|
||||
case 'missing':
|
||||
if (args.includes('information'))
|
||||
conditions.push({
|
||||
Ticket: {
|
||||
currentState: {
|
||||
in: [
|
||||
TicketValidationState.CREATED,
|
||||
TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
else if (args.includes('documents'))
|
||||
conditions.push({
|
||||
Ticket: {
|
||||
currentState: {
|
||||
in: [
|
||||
TicketValidationState.TD_UNCERTAIN,
|
||||
TicketValidationState.DOCUMENTS_MISSING,
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
case 'dateRange':
|
||||
conditions.push({
|
||||
AND: [
|
||||
{ startDate: { gte: new Date(args.from) } },
|
||||
{ startDate: { lt: new Date(args.to) } },
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
return conditions;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum AccountingState {
|
||||
Billable = 'Billable',
|
||||
PreparedForBilling = 'PreparedForBilling',
|
||||
Billed = 'Billed',
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { TourStateMetaObjectType } from './accounting-arg.types';
|
||||
import {
|
||||
AccountingFilter,
|
||||
createPrismaQueryFromAccountingFilters,
|
||||
} from './accounting-filter';
|
||||
import { AccountingState } from './accounting-state';
|
||||
|
||||
@Injectable()
|
||||
export class AccountingValidationRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findByState(
|
||||
state: AccountingState,
|
||||
filter: AccountingFilter,
|
||||
take?: number,
|
||||
skip?: number,
|
||||
) {
|
||||
const checkValue = this.getCheckValueForAccountingState(state);
|
||||
const query: Prisma.TourFindManyArgs = {
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: {
|
||||
check: checkValue,
|
||||
...(checkValue === 2 && !filter?.deviation
|
||||
? { revenueDeviation: { not: 0 } }
|
||||
: {}),
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
AND: [
|
||||
...createPrismaQueryFromAccountingFilters(filter),
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{ patientId: null },
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
};
|
||||
|
||||
const [tours, count] = await this.prisma.$transaction([
|
||||
this.prisma.tour.findMany({ ...query }),
|
||||
this.prisma.tour.count({ where: query.where }),
|
||||
]);
|
||||
return {
|
||||
tours,
|
||||
total: count,
|
||||
};
|
||||
}
|
||||
|
||||
async calculateTourStateMeta(
|
||||
state: AccountingState,
|
||||
): Promise<TourStateMetaObjectType> {
|
||||
const checkValue = this.getCheckValueForAccountingState(state);
|
||||
const ticketWithCurrentStateAndTours: Prisma.TourWhereInput = {
|
||||
check: checkValue,
|
||||
...(checkValue === 2 ? { revenueDeviation: { not: 0 } } : {}),
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{ patientId: null },
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const [count, revenueSum] = await this.prisma.$transaction([
|
||||
this.prisma.tour.count({
|
||||
where: { ...ticketWithCurrentStateAndTours },
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenue: true,
|
||||
},
|
||||
where: {
|
||||
...ticketWithCurrentStateAndTours,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
count,
|
||||
revenueSum: revenueSum._sum.revenue ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
findTicketByTicketId(ticketId: string) {
|
||||
return this.prisma.ticket.findFirst({
|
||||
where: {
|
||||
id: ticketId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private getCheckValueForAccountingState(state: AccountingState): number {
|
||||
switch (state) {
|
||||
case AccountingState.Billable:
|
||||
return 0;
|
||||
case AccountingState.PreparedForBilling:
|
||||
return 1;
|
||||
case AccountingState.Billed:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { Args, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql';
|
||||
import { TicketObjectType } from '../app-ticket-system/ticket.object-type';
|
||||
import {
|
||||
AccountingTourArgs,
|
||||
TourStateMetaObjectType,
|
||||
} from './accounting-arg.types';
|
||||
import { AccountingState } from './accounting-state';
|
||||
import { AccountingValidationService } from './accounting-validation.service';
|
||||
import { PaginatedTourObjectType } from './accounting.object-type';
|
||||
import { TourObjectType } from './tour.object-type';
|
||||
|
||||
@Resolver(() => TourObjectType)
|
||||
export class AccountingValidationResolver {
|
||||
constructor(private readonly service: AccountingValidationService) {}
|
||||
|
||||
@Query(() => PaginatedTourObjectType)
|
||||
accountingTours(
|
||||
@Args() { state, filters, cursor, take }: AccountingTourArgs,
|
||||
) {
|
||||
return this.service.paginateToursByState(state, filters, take, cursor);
|
||||
}
|
||||
|
||||
@Query(() => TourStateMetaObjectType)
|
||||
accountingStateMeta(
|
||||
@Args('state', { type: () => AccountingState })
|
||||
accountingState: AccountingState,
|
||||
) {
|
||||
return this.service.getAccountingStateMeta(accountingState);
|
||||
}
|
||||
|
||||
@ResolveField(() => TicketObjectType, { nullable: true })
|
||||
ticket(@Parent() tour: TourObjectType) {
|
||||
return this.service.findTicketByTicketId(tour.ticketId);
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { TourStateMetaObjectType } from './accounting-arg.types';
|
||||
import { AccountingFilter } from './accounting-filter';
|
||||
import { AccountingState } from './accounting-state';
|
||||
import { AccountingValidationRepository } from './accounting-validation.repository';
|
||||
import { PaginatedTourObjectType } from './accounting.object-type';
|
||||
import {
|
||||
decodeOffsetCursor,
|
||||
encodeCursor,
|
||||
} from 'src/core/base/pagination.util';
|
||||
|
||||
@Injectable()
|
||||
export class AccountingValidationService {
|
||||
constructor(private readonly repository: AccountingValidationRepository) {}
|
||||
|
||||
async paginateToursByState(
|
||||
state: AccountingState,
|
||||
filter: AccountingFilter,
|
||||
take: number = 10,
|
||||
cursor?: string,
|
||||
): Promise<PaginatedTourObjectType> {
|
||||
const skip = cursor ? decodeOffsetCursor(cursor) : 0;
|
||||
const { tours, total } = await this.repository.findByState(
|
||||
state,
|
||||
filter,
|
||||
take,
|
||||
skip,
|
||||
);
|
||||
|
||||
const prevPage = skip - take < 0 ? 0 : skip - take;
|
||||
const nextPage = skip + take < total ? skip + take : skip;
|
||||
|
||||
return {
|
||||
nodes: tours,
|
||||
prevCursor: encodeCursor(prevPage),
|
||||
endCursor: encodeCursor(nextPage),
|
||||
hasNextPage: nextPage !== skip,
|
||||
totalCount: total,
|
||||
};
|
||||
}
|
||||
|
||||
async getAccountingStateMeta(
|
||||
accountingState: AccountingState,
|
||||
): Promise<TourStateMetaObjectType> {
|
||||
return this.repository.calculateTourStateMeta(accountingState);
|
||||
}
|
||||
|
||||
findTicketByTicketId(ticketId?: string | null) {
|
||||
if (!ticketId) return;
|
||||
return this.repository.findTicketByTicketId(ticketId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
import { AccountingState } from './accounting-state';
|
||||
import { Paginated } from 'src/core/base/pagination.input';
|
||||
import { TourObjectType } from './tour.object-type';
|
||||
|
||||
@ObjectType('PaginatedTour')
|
||||
export class PaginatedTourObjectType extends Paginated(TourObjectType) {}
|
||||
|
||||
registerEnumType(AccountingState, {
|
||||
name: 'AccountingState',
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { AccountingValidationRepository } from './accounting-validation.repository';
|
||||
import { AccountingValidationResolver } from './accounting-validation.resolver';
|
||||
import { AccountingValidationService } from './accounting-validation.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [
|
||||
AccountingValidationRepository,
|
||||
AccountingValidationResolver,
|
||||
AccountingValidationService,
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
export class ApplicationAccountingModule {}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Field, Float, Int, ObjectType } from '@nestjs/graphql';
|
||||
import { Tour } from '@prisma/client';
|
||||
|
||||
@ObjectType('Tour')
|
||||
export class TourObjectType implements Tour {
|
||||
@Field()
|
||||
id: string;
|
||||
@Field()
|
||||
done: boolean;
|
||||
@Field()
|
||||
direction: string;
|
||||
@Field({ nullable: true })
|
||||
carName: string | null;
|
||||
@Field()
|
||||
operationId: string;
|
||||
@Field({ nullable: true })
|
||||
driverId: string | null;
|
||||
@Field({ nullable: true })
|
||||
driverName: string | null;
|
||||
@Field({ nullable: true })
|
||||
codriverId: string | null;
|
||||
@Field({ nullable: true })
|
||||
codriverName: string | null;
|
||||
@Field({ nullable: true })
|
||||
patientId: string | null;
|
||||
@Field({ nullable: true })
|
||||
patientName: string | null;
|
||||
@Field({ nullable: true })
|
||||
patientSurname: string | null;
|
||||
@Field({ nullable: true })
|
||||
patientStreet: string | null;
|
||||
@Field({ nullable: true })
|
||||
patientZip: string | null;
|
||||
@Field({ nullable: true })
|
||||
patientCity: string | null;
|
||||
@Field({ nullable: true })
|
||||
healthInsurance: string | null;
|
||||
@Field({ nullable: true })
|
||||
healthInsuranceNumber: string | null;
|
||||
@Field({ nullable: true })
|
||||
category: string | null;
|
||||
@Field({ nullable: true })
|
||||
transportType: string | null;
|
||||
@Field({ nullable: true })
|
||||
ordinanceType: string | null;
|
||||
@Field({ nullable: true })
|
||||
startInstitution: string | null;
|
||||
@Field({ nullable: true })
|
||||
startStreet: string | null;
|
||||
@Field({ nullable: true })
|
||||
startZip: string | null;
|
||||
@Field({ nullable: true })
|
||||
startCity: string | null;
|
||||
@Field({ nullable: true })
|
||||
targetInstitution: string | null;
|
||||
@Field({ nullable: true })
|
||||
targetStreet: string | null;
|
||||
@Field({ nullable: true })
|
||||
targetZip: string | null;
|
||||
@Field({ nullable: true })
|
||||
targetCity: string | null;
|
||||
@Field({ nullable: true })
|
||||
startDate: Date;
|
||||
@Field({ nullable: true })
|
||||
startBegin: string | null;
|
||||
@Field({ nullable: true })
|
||||
startEnd: string | null;
|
||||
@Field({ nullable: true })
|
||||
target: string | null;
|
||||
@Field({ nullable: true })
|
||||
targetBegin: string | null;
|
||||
@Field({ nullable: true })
|
||||
empty: string | null;
|
||||
@Field(() => Int)
|
||||
check: number;
|
||||
@Field(() => Float)
|
||||
occupiedKm: number | null;
|
||||
@Field(() => Float)
|
||||
totalKm: number | null;
|
||||
@Field()
|
||||
type: string | null;
|
||||
@Field({ nullable: true })
|
||||
rangeEndDate: Date | null;
|
||||
|
||||
@Field(() => Float)
|
||||
revenueDispoLive: number | null;
|
||||
@Field(() => Float)
|
||||
revenue: number | null;
|
||||
@Field(() => Float)
|
||||
consumptionCosts: number | null;
|
||||
@Field(() => Float)
|
||||
revenueDeviation: number;
|
||||
@Field({ nullable: true })
|
||||
billDate: Date | null;
|
||||
@Field({ nullable: true })
|
||||
billNumber: string | null;
|
||||
|
||||
@Field({ nullable: true })
|
||||
createdAt: Date | null;
|
||||
@Field({ nullable: true })
|
||||
deletedAt: Date | null;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
hasInfection: boolean | null;
|
||||
@Field({ nullable: true })
|
||||
infectionName: string | null;
|
||||
|
||||
@Field({ nullable: true })
|
||||
ticketId: string | null;
|
||||
}
|
||||
Reference in New Issue
Block a user