From a9c68547727090b863921a81cbbe9f68fa980e3d Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 17 Feb 2026 20:22:28 +0330 Subject: [PATCH] fix: bill calc --- database/seeders/README-TEST-COMPANIES.md | 38 ------ .../seeders/cleanup-test-companies.seeder.ts | 44 ------- database/seeders/test-companies.seeder.ts | 120 ------------------ package.json | 3 - src/modules/bills/bills.service.ts | 13 +- 5 files changed, 6 insertions(+), 212 deletions(-) delete mode 100644 database/seeders/README-TEST-COMPANIES.md delete mode 100644 database/seeders/cleanup-test-companies.seeder.ts delete mode 100644 database/seeders/test-companies.seeder.ts diff --git a/database/seeders/README-TEST-COMPANIES.md b/database/seeders/README-TEST-COMPANIES.md deleted file mode 100644 index 8f6fff3..0000000 --- a/database/seeders/README-TEST-COMPANIES.md +++ /dev/null @@ -1,38 +0,0 @@ -# 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 haven’t). - -## 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). diff --git a/database/seeders/cleanup-test-companies.seeder.ts b/database/seeders/cleanup-test-companies.seeder.ts deleted file mode 100644 index e56b020..0000000 --- a/database/seeders/cleanup-test-companies.seeder.ts +++ /dev/null @@ -1,44 +0,0 @@ -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 { - 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.`); - } -} diff --git a/database/seeders/test-companies.seeder.ts b/database/seeders/test-companies.seeder.ts deleted file mode 100644 index 4232ac6..0000000 --- a/database/seeders/test-companies.seeder.ts +++ /dev/null @@ -1,120 +0,0 @@ -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 { - 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`); - } -} diff --git a/package.json b/package.json index 8a1ff56..5d44a01 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,6 @@ "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", "seed:run": "mikro-orm seeder:run --config=database/mikro-orm.config.ts", - "seed:test-companies": "mikro-orm seeder:run --config=database/mikro-orm.config.ts -- --class=TestCompaniesSeeder", - "seed:cleanup-test-companies": "mikro-orm seeder:run --config=database/mikro-orm.config.ts -- --class=CleanupTestCompaniesSeeder", "db:dump": "node scripts/db-dump.js", "db:restore": "node scripts/db-restore.js" }, @@ -74,7 +72,6 @@ "decimal.js": "^10.5.0", "exceljs": "^4.4.0", "dotenv": "^16.5.0", - "exceljs": "^4.4.0", "fastify": "^5.3.3", "langchain": "^0.3.27", "nodemailer": "^7.0.3", diff --git a/src/modules/bills/bills.service.ts b/src/modules/bills/bills.service.ts index dfe2a9b..2194957 100644 --- a/src/modules/bills/bills.service.ts +++ b/src/modules/bills/bills.service.ts @@ -50,11 +50,11 @@ export class BillsService { for (const value of values) { const waterCost = new Decimal(waterRate).mul(value.waterUsage); - const sewageCost = waterCost.mul(waterCost); - const subtotal = waterCost.add(sewageCost); + const sewageCost = waterCost.mul(0.3); + const subtotal = waterCost.plus(sewageCost); const tax = subtotal.mul(0.1); - const totalPrice = subtotal.add(tax); + const totalPrice = subtotal.plus(tax); const company = companyMap.get(value.companyId); if (!company) { throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND); @@ -73,7 +73,7 @@ export class BillsService { const invoiceItem = em.create(InvoiceItem, { name: "قبض آب", count: value.waterUsage, - unitPrice: new Decimal(waterRate), + unitPrice: totalPrice, discount: new Decimal(0), totalPrice, invoice, @@ -167,15 +167,14 @@ export class BillsService { for (const company of companies) { const subtotal = new Decimal(chargeRate).mul(company.metrage); - const tax = subtotal.mul(0.1); - const totalPrice = subtotal.add(tax); + const totalPrice = subtotal; const invoice = em.create(Invoice, { company, totalPrice, originalPrice: totalPrice, dueDate, - tax, + tax: new Decimal(0), business, bill, });