Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"cidr-matcher": "^2.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"date-fns": "^2.29.3",
"jest-mock-extended": "2.0.4",
"passport": "^0.5.2",
"passport-jwt": "^4.0.0",
Expand All @@ -52,6 +53,7 @@
},
"devDependencies": {
"@nestjs/cli": "^8.0.0",
"@nestjs/mapped-types": "^1.2.0",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.4.4",
"@types/bcrypt": "^5.0.0",
Expand Down
21 changes: 21 additions & 0 deletions server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ model Staff {
activityEntries ActivityEntry[]
Ledger Ledger[]
Audit Audit[]
Visit Visit[]
}

model Variant {
Expand Down Expand Up @@ -168,6 +169,7 @@ model Customer {
updatedAt DateTime @updatedAt
activityEntries ActivityEntry[]
Ledger Ledger[]
Visit Visit[]
}

model ActivityEntry {
Expand All @@ -187,6 +189,8 @@ model ActivityEntry {
// The reason why the charge is 1 to many because we can't have more than one charge that is
// not linked to an activity entry (have activityEntryId as null).
charge Ledger[]
Visit Visit? @relation(fields: [visitId], references: [id])
visitId String? @db.ObjectId
}

model Ledger {
Expand All @@ -200,6 +204,8 @@ model Ledger {
customerId String @db.ObjectId
staffId String @db.ObjectId
activityEntryId String? @db.ObjectId
Visit Visit? @relation(fields: [visitId], references: [id])
visitId String? @db.ObjectId
}

model Audit {
Expand All @@ -212,3 +218,18 @@ model Audit {
createdBy Staff @relation(fields: [staffId], references: [id])
createdAt DateTime @default(now())
}

model Visit {
id String @id @default(auto()) @map("_id") @db.ObjectId
visitDate String
customer Customer @relation(fields: [customerId], references: [id])
customerId String @db.ObjectId
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy Staff @relation(fields: [staffId], references: [id])
staffId String @db.ObjectId
activityEntries ActivityEntry[]
charge Ledger[]
fees Int @default(0)
tip Int @default(0)
}
2 changes: 2 additions & 0 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ThrottlerBehindProxyGuard } from './auth/throttle.guard';
import { AuditInterceptor } from './interceptors/audit.interceptor';
import { VisitModule } from './models/visit/visit.module';

@Module({
imports: [
Expand All @@ -28,6 +29,7 @@ import { AuditInterceptor } from './interceptors/audit.interceptor';
AnalyticsModule,
AuditModule,
AuthModule,
VisitModule,
CustomerModule,
FeatureModule,
FormModule,
Expand Down
36 changes: 34 additions & 2 deletions server/src/models/activity-entry/activity-entry.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Auth } from 'src/auth/role.decorator';
import { GetUser } from 'src/auth/user.decorator';
import { TransformInterceptor } from 'src/interceptors/transform.interceptor';
import { PrismaService } from 'src/prisma.service';
import { getTodayDateString } from 'src/utils/dates';
import { LedgerService } from '../ledger/ledger.service';
import { ActivityEntryChargeDto, ActivityEntryDto } from './activity-entry.dto';
import { ActivityEntryService } from './activity-entry.service';
Expand Down Expand Up @@ -135,15 +136,46 @@ export class ActivityEntryController {
}

@Auth(Actions.WRITE, [Features.Entry])
@Post()
@Auditable()
createActivityEntry(@Body() body: ActivityEntryDto, @Request() { user }) {
@Post()
async createActivityEntry(
@Body() body: ActivityEntryDto,
@Request() { user },
) {
const visitFromToday = await this.prisma.visit.findFirst({
where: {
visitDate: {
equals: getTodayDateString(),
},
customer: {
id: body.customerId,
},
},
});
return this.service.createActivityEntry({
author: {
connect: {
id: user.id,
},
},
Visit: {
connectOrCreate: {
where: {
id: visitFromToday.id,
},
create: {
customer: {
connect: {
id: body.customerId,
},
},
visitDate: getTodayDateString(),
createdBy: {
connect: user.id,
},
},
},
},
customer: {
connect: {
id: body.customerId,
Expand Down
11 changes: 11 additions & 0 deletions server/src/models/visit/dto/create-visit.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IsISO8601, IsMongoId, IsOptional, IsString } from 'class-validator';

export class CreateVisitDto {
@IsString()
@IsMongoId()
customerId: string;

@IsISO8601()
@IsOptional()
visitDate?: string;
}
17 changes: 17 additions & 0 deletions server/src/models/visit/dto/filter-visit.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IsEnum, IsISO8601, IsMongoId, IsOptional } from 'class-validator';

export type VisitStatus = 'open' | 'closed';

export class FilterVisitDto {
@IsOptional()
@IsMongoId()
customerId?: string;

@IsISO8601()
@IsOptional()
visitDate?: string;

@IsOptional()
@IsEnum(['open', 'closed'])
status?: VisitStatus;
}
15 changes: 15 additions & 0 deletions server/src/models/visit/dto/update-visit.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IsArray, IsISO8601, IsMongoId } from 'class-validator';

export class UpdateVisitDto {
@IsMongoId()
customerId: string;

@IsArray()
@IsMongoId({
each: true,
})
activityEntryIds: string[];

@IsISO8601()
visitDate: string;
}
56 changes: 56 additions & 0 deletions server/src/models/visit/entities/visit.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Prisma } from '@prisma/client';

export const VISIT_SELECT: Prisma.VisitSelect = {
id: true,
visitDate: true,
updatedAt: true,
createdAt: true,
activityEntries: {
select: {
id: true,
author: {
select: {
id: true,
firstName: true,
lastName: true,
email: true,
},
},
activity: {
select: {
id: true,
name: true,
price: true,
},
},
products: {
select: {
id: true,
price: true,
product: {
select: {
name: true,
brand: true,
},
},
},
},
},
},
charge: {
select: {
id: true,
amount: true,
description: true,
createdDt: true,
},
},
customer: {
select: {
id: true,
firstName: true,
lastName: true,
email: true,
},
},
} as const;
20 changes: 20 additions & 0 deletions server/src/models/visit/visit.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { VisitController } from './visit.controller';
import { VisitService } from './visit.service';

describe('VisitController', () => {
let controller: VisitController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [VisitController],
providers: [VisitService],
}).compile();

controller = module.get<VisitController>(VisitController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
62 changes: 62 additions & 0 deletions server/src/models/visit/visit.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
Controller,
Get,
Post,
Body,
Param,
Delete,
UseInterceptors,
Put,
Query,
ValidationPipe,
} from '@nestjs/common';
import { VisitService } from './visit.service';
import { CreateVisitDto } from './dto/create-visit.dto';
import { UpdateVisitDto } from './dto/update-visit.dto';
import { TransformInterceptor } from 'src/interceptors/transform.interceptor';
import { Auth } from 'src/auth/role.decorator';
import { Actions, Features } from 'src/auth/constants';
import { GetUser } from 'src/auth/user.decorator';
import { Staff } from '@prisma/client';
import { Auditable } from 'src/auth/audit.decorator';
import { VALIDATION_PIPE_OPTION } from 'src/utils/consts';
import { FilterVisitDto } from './dto/filter-visit.dto';

@Controller('visits')
@UseInterceptors(TransformInterceptor)
export class VisitController {
constructor(private readonly visitService: VisitService) {}

@Auth(Actions.WRITE, [Features.Entry])
@Auditable()
@Post()
create(@Body() createVisitDto: CreateVisitDto, @GetUser() { id }: Staff) {
return this.visitService.create(createVisitDto, id);
}

@Auth(Actions.READ, [Features.Entry])
@Get()
findAll(
@Query(new ValidationPipe(VALIDATION_PIPE_OPTION)) params: FilterVisitDto,
) {
return this.visitService.findAll(params);
}

@Auth(Actions.READ, [Features.Entry])
@Get(':id')
findOne(@Param('id') id: string) {
return this.visitService.findOne(id);
}

@Auth(Actions.WRITE, [Features.Entry])
@Auditable()
@Put(':id')
update(@Param('id') id: string, @Body() updateVisitDto: UpdateVisitDto) {
return this.visitService.update(id, updateVisitDto);
}

@Delete(':id')
remove(@Param('id') id: string) {
return this.visitService.remove(id);
}
}
12 changes: 12 additions & 0 deletions server/src/models/visit/visit.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { VisitService } from './visit.service';
import { VisitController } from './visit.controller';
import { PrismaService } from 'src/prisma.service';
import { AuthModule } from 'src/auth/auth.module';

@Module({
controllers: [VisitController],
providers: [VisitService, PrismaService],
imports: [AuthModule],
})
export class VisitModule {}
18 changes: 18 additions & 0 deletions server/src/models/visit/visit.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { VisitService } from './visit.service';

describe('VisitService', () => {
let service: VisitService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [VisitService],
}).compile();

service = module.get<VisitService>(VisitService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
Loading