add: migration

This commit is contained in:
2026-02-17 16:04:49 +03:30
parent 2555cc12d1
commit 7ccf822a5c
18 changed files with 790 additions and 74 deletions
+507
View File
@@ -239,6 +239,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -441,6 +450,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -691,6 +709,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -919,6 +946,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -1148,6 +1184,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -1375,6 +1420,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -1556,6 +1610,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -1805,6 +1868,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -2033,6 +2105,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -2357,6 +2438,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -2566,6 +2656,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -2818,6 +2917,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -3077,6 +3185,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -3276,6 +3393,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -3710,6 +3836,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -4037,6 +4172,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -4279,6 +4423,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -4478,6 +4631,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -4842,6 +5004,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -5144,6 +5315,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -5385,6 +5565,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -5594,6 +5783,279 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
"items": [
"active",
"closed",
"archived"
]
},
"message_type": {
"name": "message_type",
"schema": "public",
"items": [
"user",
"bot",
"system"
]
},
"message_status": {
"name": "message_status",
"schema": "public",
"items": [
"sent",
"delivered",
"read",
"failed"
]
}
}
},
{
"columns": {
"id": {
"name": "id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "current_timestamp",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "current_timestamp",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
},
"reject_reason": {
"name": "reject_reason",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"request_status": {
"name": "request_status",
"type": "company_request_status",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"nativeEnumName": "company_request_status",
"default": "'PENDING'",
"enumItems": [
"PENDING",
"APPROVED",
"REJECTED"
],
"mappedType": "enum"
},
"company_id": {
"name": "company_id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
},
"business_id": {
"name": "business_id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
}
},
"name": "company_request",
"schema": "public",
"indexes": [
{
"keyName": "company_request_pkey",
"columnNames": [
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {
"company_request_company_id_foreign": {
"constraintName": "company_request_company_id_foreign",
"columnNames": [
"company_id"
],
"localTableName": "public.company_request",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.company",
"updateRule": "cascade"
},
"company_request_business_id_foreign": {
"constraintName": "company_request_business_id_foreign",
"columnNames": [
"business_id"
],
"localTableName": "public.company_request",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.business",
"updateRule": "cascade"
},
"company_request_user_id_foreign": {
"constraintName": "company_request_user_id_foreign",
"columnNames": [
"user_id"
],
"localTableName": "public.company_request",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.user",
"updateRule": "cascade"
}
},
"nativeEnums": {
"role_enum": {
"name": "role_enum",
"schema": "public",
"items": [
"ADMIN",
"USER"
]
},
"notif_type": {
"name": "notif_type",
"schema": "public",
"items": [
"OTP",
"USER_LOGIN",
"ANNOUNCEMENT",
"WALLET_CHARGE",
"WALLET_DEDUCTION",
"BILL_INVOICE_REMINDER",
"BILL_INVOICE",
"APPROVE_INVOICE",
"CREATE_INVOICE",
"INVOICE_OVERDUE",
"CREATE_SERVICE",
"UNBLOCK_SERVICE",
"BLOCK_SERVICE",
"ANSWER_TICKET",
"CREATE_TICKET",
"ASSIGN_TICKET",
"RECURRING_INVOICE",
"PAYMENT_REMINDER",
"PAYMENT_CANCELLATION",
"NEW_CUSTOMER",
"NEW_TICKET",
"NEW_CRITICISM"
]
},
"company_status": {
"name": "company_status",
"schema": "public",
"items": [
"APPROVED",
"PENDING",
"REJECTED"
]
},
"ticket_status": {
"name": "ticket_status",
"schema": "public",
"items": [
"PENDING",
"ANSWERED",
"CLOSED"
]
},
"ticket_priority": {
"name": "ticket_priority",
"schema": "public",
"items": [
"LOW",
"MEDIUM",
"HIGH"
]
},
"invoice_status": {
"name": "invoice_status",
"schema": "public",
"items": [
"DRAFT",
"PENDING",
"WAIT_PAYMENT",
"PAID",
"ARCHIVED",
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -5803,6 +6265,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -6036,6 +6507,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -6316,6 +6796,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -6537,6 +7026,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -6644,6 +7142,15 @@
"OVERDUE"
]
},
"company_request_status": {
"name": "company_request_status",
"schema": "public",
"items": [
"PENDING",
"APPROVED",
"REJECTED"
]
},
"chat_session_status": {
"name": "chat_session_status",
"schema": "public",
@@ -1,13 +0,0 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20260217121029_meterageREquired extends Migration {
override async up(): Promise<void> {
this.addSql(`select 1`);
}
override async down(): Promise<void> {
this.addSql(`select 1`);
}
}
@@ -0,0 +1,28 @@
import { Migration } from "@mikro-orm/migrations";
export class Migration20260217122512_meterageREquired extends Migration {
override async up(): Promise<void> {
this.addSql(`alter table "company" alter column "metrage" type int using ("metrage"::int);`);
this.addSql(`update "company" set "metrage" = 0 where "metrage" is null;`);
this.addSql(`alter table "company" alter column "metrage" set not null;`);
this.addSql(`alter table "invoice" alter column "total_price" type numeric using ("total_price"::numeric);`);
this.addSql(`alter table "invoice" alter column "original_price" type numeric using ("original_price"::numeric);`);
this.addSql(`alter table "invoice" alter column "tax" type numeric using ("tax"::numeric);`);
this.addSql(`alter table "invoice" alter column "late_fee" type numeric using ("late_fee"::numeric);`);
this.addSql(`alter table "payment" alter column "amount" type numeric using ("amount"::numeric);`);
}
override async down(): Promise<void> {
this.addSql(`alter table "company" alter column "metrage" type int4 using ("metrage"::int4);`);
this.addSql(`alter table "company" alter column "metrage" drop not null;`);
this.addSql(`alter table "invoice" alter column "total_price" type numeric using ("total_price"::numeric);`);
this.addSql(`alter table "invoice" alter column "original_price" type numeric using ("original_price"::numeric);`);
this.addSql(`alter table "invoice" alter column "tax" type numeric using ("tax"::numeric);`);
this.addSql(`alter table "invoice" alter column "late_fee" type numeric using ("late_fee"::numeric);`);
this.addSql(`alter table "payment" alter column "amount" type numeric using ("amount"::numeric);`);
}
}
+2
View File
@@ -1,4 +1,5 @@
import { Options } from "@mikro-orm/core";
import { Migrator } from "@mikro-orm/migrations";
import { PostgreSqlDriver } from "@mikro-orm/postgresql";
import dotenv from "dotenv";
@@ -6,6 +7,7 @@ dotenv.config();
const config: Options = {
driver: PostgreSqlDriver,
extensions: [Migrator],
dbName: process.env.DB_NAME,
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
+38
View File
@@ -0,0 +1,38 @@
# Test companies seed (production-safe)
Add about **1000 test companies** for a specific business, then remove them in one go.
- **Business ID:** `e5e760b9-46a5-4194-bb3d-00a46c24d72a`
- All test rows use the prefix **`TEST_SEED_`** (company name, emails, etc.) so they are easy to identify and delete without touching real data.
## 1. Seed test companies
From project root:
```bash
pnpm run build
pnpm run seed:test-companies
```
Requirements:
- Business `e5e760b9-46a5-4194-bb3d-00a46c24d72a` must exist.
- At least one **Industry** must exist for that business (create one in the app if needed).
- **Role** `USER` must exist (run `pnpm run seed:run` once if you havent).
## 2. After testing: remove test data
Same DB, same business:
```bash
pnpm run seed:cleanup-test-companies
```
This **soft-deletes** all companies whose name starts with `TEST_SEED_` and their linked users for that business. No schema changes, no hard deletes.
## Changing business ID or count
Edit the constants at the top of:
- `database/seeders/test-companies.seeder.ts``TEST_BUSINESS_ID`, `TEST_COMPANIES_COUNT`
- `database/seeders/cleanup-test-companies.seeder.ts``TEST_BUSINESS_ID` (must match).
@@ -0,0 +1,44 @@
import { EntityManager } from "@mikro-orm/postgresql";
import { Seeder } from "@mikro-orm/seeder";
import { Logger } from "@nestjs/common";
import dayjs from "dayjs";
import { TEST_SEED_PREFIX } from "./test-companies.seeder";
import { Company } from "../../src/modules/companies/entities/company.entity";
/** Must match the business ID used in TestCompaniesSeeder. */
const TEST_BUSINESS_ID = "e5e760b9-46a5-4194-bb3d-00a46c24d72a";
export class CleanupTestCompaniesSeeder extends Seeder {
private readonly logger = new Logger(CleanupTestCompaniesSeeder.name);
async run(em: EntityManager): Promise<void> {
this.logger.log(`Cleaning up test companies (name like '${TEST_SEED_PREFIX}%') for business ${TEST_BUSINESS_ID}...`);
const companies = await em.find(
Company,
{
name: { $like: `${TEST_SEED_PREFIX}%` },
business: { id: TEST_BUSINESS_ID },
deletedAt: null,
},
{ populate: ["user"] },
);
if (companies.length === 0) {
this.logger.log("No test companies found. Nothing to delete.");
return;
}
const now = dayjs().toDate();
for (const company of companies) {
company.deletedAt = now;
if (company.user?.deletedAt == null) {
company.user.deletedAt = now;
}
}
await em.flush();
this.logger.log(`Soft-deleted ${companies.length} test companies and their users.`);
}
}
+120
View File
@@ -0,0 +1,120 @@
import { EntityManager } from "@mikro-orm/postgresql";
import { Seeder } from "@mikro-orm/seeder";
import { Logger } from "@nestjs/common";
import { genSalt, hash } from "bcrypt";
import { Business } from "../../src/modules/businesses/entities/business.entity";
import { CompanyProduct } from "../../src/modules/companies/entities/company-product.entity";
import { CompanyService } from "../../src/modules/companies/entities/company-service.entity";
import { Company } from "../../src/modules/companies/entities/company.entity";
import { CompanyStatus } from "../../src/modules/companies/enums/company-status.enum";
import { Industry } from "../../src/modules/industries/entities/industry.entity";
import { Role } from "../../src/modules/users/entities/role.entity";
import { User } from "../../src/modules/users/entities/user.entity";
import { RoleEnum } from "../../src/modules/users/enums/role.enum";
/** Business ID to seed test companies for. Change or use env if needed. */
const TEST_BUSINESS_ID = "e5e760b9-46a5-4194-bb3d-00a46c24d72a";
/** All test companies/users use this prefix so they can be safely deleted later. */
export const TEST_SEED_PREFIX = "TEST_SEED_";
const TEST_COMPANIES_COUNT = 1000;
const BATCH_SIZE = 50;
const TEST_PASSWORD = "TestPassword1!";
export class TestCompaniesSeeder extends Seeder {
private readonly logger = new Logger(TestCompaniesSeeder.name);
async run(em: EntityManager): Promise<void> {
const business = await em.findOne(Business, { id: TEST_BUSINESS_ID, deletedAt: null });
if (!business) {
this.logger.error(`Business not found: ${TEST_BUSINESS_ID}. Aborting.`);
throw new Error(`Business not found: ${TEST_BUSINESS_ID}`);
}
const industry = await em.findOne(Industry, { business: { id: TEST_BUSINESS_ID }, deletedAt: null });
if (!industry) {
this.logger.error(`No industry found for business ${TEST_BUSINESS_ID}. Create an industry first.`);
throw new Error("No industry found for business");
}
const role = await em.findOne(Role, { name: RoleEnum.USER });
if (!role) {
this.logger.error("USER role not found. Run default seed first (RoleSeeder).");
throw new Error("USER role not found");
}
const hashedPassword = await hash(TEST_PASSWORD, await genSalt(10));
const baseDate = "2020-01-01";
const baseUrl = "https://example.com";
this.logger.log(`Seeding ${TEST_COMPANIES_COUNT} test companies for business ${TEST_BUSINESS_ID}...`);
for (let i = 1; i <= TEST_COMPANIES_COUNT; i++) {
const pad4 = String(i).padStart(4, "0");
const pad5 = String(i).padStart(5, "0");
const pad7 = String(i).padStart(7, "0");
const phone = `0912${pad7}`;
const email = `test.seed.${i}@test.local`;
const nationalCode = `0000${pad5}`;
const userName = `testseed${i}`;
const user = em.create(User, {
firstName: "Test",
lastName: `User${i}`,
email,
phone,
userName,
nationalCode,
password: hashedPassword,
role,
business,
});
await em.persist(user);
const company = em.create(Company, {
name: `${TEST_SEED_PREFIX}Company_${pad4}`,
chiefExecutiveOfficer: `Test CEO ${i}`,
email,
phone,
identificationNumber: `TESTSEEDID${pad5}`,
dateOfEstablishment: new Date(baseDate),
address: `${TEST_SEED_PREFIX} Address ${i}`,
mapAddressLink: `${baseUrl}/map`,
description: `${TEST_SEED_PREFIX} Description ${i}`,
websiteUrl: `${baseUrl}`,
profileImageUrl: `${baseUrl}/profile.png`,
coverImageUrl: `${baseUrl}/cover.png`,
metrage: 100,
isActive: true,
status: CompanyStatus.APPROVED,
industry,
business,
user,
});
const product = em.create(CompanyProduct, {
title: `${TEST_SEED_PREFIX}Product_${i}`,
imageUrl: `${baseUrl}/product.png`,
company,
});
const service = em.create(CompanyService, {
title: `${TEST_SEED_PREFIX}Service_${i}`,
imageUrl: `${baseUrl}/service.png`,
company,
});
company.products.add(product);
company.services.add(service);
await em.persist(company);
if (i % BATCH_SIZE === 0) {
await em.flush();
this.logger.log(` ... ${i}/${TEST_COMPANIES_COUNT}`);
}
}
await em.flush();
this.logger.log(`Done. Created ${TEST_COMPANIES_COUNT} test companies (name like '${TEST_SEED_PREFIX}%').`);
this.logger.warn(`To remove them later, run: pnpm run seed:cleanup-test-companies`);
}
}
+1 -1
View File
@@ -23,7 +23,7 @@
"orm:create": "mikro-orm migration:create --config=database/mikro-orm.config.ts",
"orm:up": "mikro-orm migration:up --config=database/mikro-orm.config.ts",
"orm:down": "mikro-orm migration:down --config=database/mikro-orm.config.ts",
"orm:generate": "mikro-orm migration:create --blank --config=database/mikro-orm.config.ts",
"orm:generate": "mikro-orm migration:create --config=database/mikro-orm.config.ts",
"orm:update": "mikro-orm schema:update --run --config=database/mikro-orm.config.ts",
"orm:drop": "mikro-orm schema:drop --run --drop-migrations-table --drop-db --config=database/mikro-orm.config.ts",
"orm:seed": "mikro-orm seeder:create --config=database/mikro-orm.config.ts",
+1 -5
View File
@@ -32,11 +32,7 @@ export class BillsController {
@UseInterceptors(FileInterceptor("file"))
@ApiBody({ type: CreateWaterBillFromExcelDto })
@Post("water/excel")
createWaterBillByExcel(
@UploadedFile() file: IFile,
@Body() dto: CreateWaterBillFromExcelDto,
@BusinessDec() business: Business,
) {
createWaterBillByExcel(@UploadedFile() file: IFile, @Body() dto: CreateWaterBillFromExcelDto, @BusinessDec() business: Business) {
return this.billsService.createWaterBillByExcel(file, dto, business);
}
+10 -6
View File
@@ -1,4 +1,5 @@
import { MikroOrmModule } from "@mikro-orm/nestjs";
import { BullModule } from "@nestjs/bullmq";
import { Module } from "@nestjs/common";
import { BillsController } from "./bills.controller";
@@ -6,17 +7,20 @@ import { BillsService } from "./bills.service";
import { Bill } from "./entities/bill.entity";
import { BusinessesModule } from "../businesses/businesses.module";
import { CompaniesModule } from "../companies/companies.module";
import { NotificationModule } from "../notifications/notifications.module";
import { InvoicesModule } from "../invoices/invoices.module";
import { BullModule } from "@nestjs/bullmq";
import { INVOICE } from "../invoices/constants";
import { InvoicesModule } from "../invoices/invoices.module";
import { NotificationModule } from "../notifications/notifications.module";
@Module({
imports: [
BullModule.registerQueue({ name: INVOICE.QUEUE_NAME }),
MikroOrmModule.forFeature([Bill]), CompaniesModule, BusinessesModule
, NotificationModule, InvoicesModule],
MikroOrmModule.forFeature([Bill]),
CompaniesModule,
BusinessesModule,
NotificationModule,
InvoicesModule,
],
controllers: [BillsController],
providers: [BillsService],
})
export class BillsModule { }
export class BillsModule {}
+8 -14
View File
@@ -1,11 +1,11 @@
import { EntityManager } from "@mikro-orm/postgresql";
import { InjectQueue } from "@nestjs/bullmq";
import { BadRequestException, Injectable } from "@nestjs/common";
import { Queue } from "bullmq";
import dayjs from "dayjs";
import Decimal from "decimal.js";
import { Workbook } from "exceljs";
import { IFile } from "../utils/interfaces/IFile";
import { BillListQueryDto } from "./dto/bill-list-query.dto";
import { CreateChargeBillDto } from "./dto/create-charge-bill.dto";
import { CreateWaterBillDto, CreateWaterBillFromExcelDto } from "./dto/create-water-bill.dto";
@@ -16,13 +16,11 @@ import { CompanyMessage } from "../../common/enums/message.enum";
import { Business } from "../businesses/entities/business.entity";
import { Company } from "../companies/entities/company.entity";
import { CompaniesService } from "../companies/services/companies.service";
import { INVOICE } from "../invoices/constants";
import { InvoiceItem } from "../invoices/entities/invoice-item.entity";
import { Invoice } from "../invoices/entities/invoice.entity";
import { NotificationQueue } from "../notifications/queue/notification.queue";
import { InjectQueue } from "@nestjs/bullmq";
import { INVOICE } from "../invoices/constants";
import { Queue } from "bullmq";
import { IFile } from "../utils/interfaces/IFile";
@Injectable()
export class BillsService {
@@ -33,8 +31,7 @@ export class BillsService {
@InjectQueue(INVOICE.QUEUE_NAME)
private readonly invoiceQueue: Queue,
private readonly notificationQueue: NotificationQueue,
) { }
) {}
async createWaterBill(dto: CreateWaterBillDto, business: Business) {
const { values, waterRate, dueDays } = dto;
@@ -96,7 +93,7 @@ export class BillsService {
}
private async addInvoiceReminderJobs(billId: string) {
const invoices = await this.em.find(Invoice, { bill: { id: billId } }, { populate: ["items", "company","company.user"] });
const invoices = await this.em.find(Invoice, { bill: { id: billId } }, { populate: ["items", "company", "company.user"] });
for (const invoice of invoices) {
await this.notificationQueue.addInvoiceCreationNotification(invoice.company.user.id, {
@@ -196,7 +193,7 @@ export class BillsService {
await em.flush();
await em.commit();
await this.addInvoiceReminderJobs(bill.id)
await this.addInvoiceReminderJobs(bill.id);
return bill;
} catch (error) {
@@ -223,10 +220,7 @@ export class BillsService {
}
async remove(id: string, businessId: string) {
const bill = await this.billRepository.findOne(
{ id, business: { id: businessId } },
{ populate: ["invoices"] },
);
const bill = await this.billRepository.findOne({ id, business: { id: businessId } }, { populate: ["invoices"] });
if (!bill) {
throw new BadRequestException("Bill not found");
}
@@ -21,6 +21,5 @@ export class Bill extends BaseEntity {
@Property({ persist: false })
invoiceCount?: number;
[EntityRepositoryType]?: BillsRepository;
}
@@ -5,53 +5,50 @@ import { BillListQueryDto } from "../dto/bill-list-query.dto";
import { Bill } from "../entities/bill.entity";
export class BillsRepository extends EntityRepository<Bill> {
async getBillsList(queryDto: BillListQueryDto, businessId: string) {
const { limit, skip } = PaginationUtils(queryDto);
const knex = this.em.getKnex();
const query = knex
.select('b.*')
.select(knex.raw('COUNT(i.id) as invoice_count'))
.from('bill as b')
.leftJoin('invoice as i', 'i.bill_id', 'b.id')
.where('b.business_id', businessId)
.groupBy('b.id')
.orderBy('b.created_at', 'desc')
.select("b.*")
.select(knex.raw("COUNT(i.id) as invoice_count"))
.from("bill as b")
.leftJoin("invoice as i", "i.bill_id", "b.id")
.where("b.business_id", businessId)
.groupBy("b.id")
.orderBy("b.created_at", "desc")
.limit(limit)
.offset(skip);
// Add filters
if (queryDto.type) {
query.andWhere('b.type', queryDto.type);
query.andWhere("b.type", queryDto.type);
}
if (queryDto.since) {
query.andWhere('b.created_at', '>=', new Date(queryDto.since));
query.andWhere("b.created_at", ">=", new Date(queryDto.since));
}
if (queryDto.to) {
query.andWhere('b.created_at', '<=', new Date(queryDto.to));
query.andWhere("b.created_at", "<=", new Date(queryDto.to));
}
// Execute the query
const results = await query;
// Get total count for pagination
const countQuery = knex('bill')
.count('* as total')
.where('business_id', businessId);
const countQuery = knex("bill").count("* as total").where("business_id", businessId);
if (queryDto.type) {
countQuery.andWhere('type', queryDto.type);
countQuery.andWhere("type", queryDto.type);
}
if (queryDto.since) {
countQuery.andWhere('created_at', '>=', new Date(queryDto.since));
countQuery.andWhere("created_at", ">=", new Date(queryDto.since));
}
if (queryDto.to) {
countQuery.andWhere('created_at', '<=', new Date(queryDto.to));
countQuery.andWhere("created_at", "<=", new Date(queryDto.to));
}
const totalResult = await countQuery;
@@ -125,7 +125,7 @@ export class CreateCompanyRequestDto extends PickType(CreateCompanyDto, [
"industryId",
"products",
"services",
"metrage"
"metrage",
]) {
@IsNotEmpty({ message: CompanyMessage.CHIEF_EXECUTIVE_REQUIRED })
@IsString({ message: CompanyMessage.CHIEF_EXECUTIVE_MUST_BE_STRING })
+1 -1
View File
@@ -3,7 +3,7 @@ import { Module } from "@nestjs/common";
import { CompaniesController } from "./companies.controller";
import { CompanyProduct } from "./entities/company-product.entity";
import { CompanyRequest } from "./entities/company-request.enitiy";
import { CompanyRequest } from "./entities/company-request.entity";
import { CompanyService } from "./entities/company-service.entity";
import { Company } from "./entities/company.entity";
import { CompaniesService } from "./services/companies.service";
@@ -1,5 +1,5 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import { CompanyRequest } from "../entities/company-request.enitiy";
import { CompanyRequest } from "../entities/company-request.entity";
export class CompanyRequestRepository extends EntityRepository<CompanyRequest> {}
@@ -14,7 +14,7 @@ import { CompanyListPublicQueryDto, CompanyListQueryDto } from "../DTO/company-l
import { ChangeCompanyRequestStatusDto, CreateCompanyDto, CreateCompanyRequestDto } from "../DTO/create-company.dto";
import { UpdateCompanyDto, UpdateUserCompanyDto } from "../DTO/update-company.dto";
import { CompanyProduct } from "../entities/company-product.entity";
import { CompanyRequest } from "../entities/company-request.enitiy";
import { CompanyRequest } from "../entities/company-request.entity";
import { CompanyService } from "../entities/company-service.entity";
import { Company } from "../entities/company.entity";
import { CompanyRequestStatus } from "../enums/company-request-status.enum";