fix: bill calc
This commit is contained in:
@@ -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).
|
|
||||||
@@ -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<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.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<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`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,8 +28,6 @@
|
|||||||
"orm:drop": "mikro-orm schema:drop --run --drop-migrations-table --drop-db --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",
|
"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: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:dump": "node scripts/db-dump.js",
|
||||||
"db:restore": "node scripts/db-restore.js"
|
"db:restore": "node scripts/db-restore.js"
|
||||||
},
|
},
|
||||||
@@ -74,7 +72,6 @@
|
|||||||
"decimal.js": "^10.5.0",
|
"decimal.js": "^10.5.0",
|
||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"exceljs": "^4.4.0",
|
|
||||||
"fastify": "^5.3.3",
|
"fastify": "^5.3.3",
|
||||||
"langchain": "^0.3.27",
|
"langchain": "^0.3.27",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.3",
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ export class BillsService {
|
|||||||
|
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
const waterCost = new Decimal(waterRate).mul(value.waterUsage);
|
const waterCost = new Decimal(waterRate).mul(value.waterUsage);
|
||||||
const sewageCost = waterCost.mul(waterCost);
|
const sewageCost = waterCost.mul(0.3);
|
||||||
const subtotal = waterCost.add(sewageCost);
|
const subtotal = waterCost.plus(sewageCost);
|
||||||
|
|
||||||
const tax = subtotal.mul(0.1);
|
const tax = subtotal.mul(0.1);
|
||||||
const totalPrice = subtotal.add(tax);
|
const totalPrice = subtotal.plus(tax);
|
||||||
const company = companyMap.get(value.companyId);
|
const company = companyMap.get(value.companyId);
|
||||||
if (!company) {
|
if (!company) {
|
||||||
throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
||||||
@@ -73,7 +73,7 @@ export class BillsService {
|
|||||||
const invoiceItem = em.create(InvoiceItem, {
|
const invoiceItem = em.create(InvoiceItem, {
|
||||||
name: "قبض آب",
|
name: "قبض آب",
|
||||||
count: value.waterUsage,
|
count: value.waterUsage,
|
||||||
unitPrice: new Decimal(waterRate),
|
unitPrice: totalPrice,
|
||||||
discount: new Decimal(0),
|
discount: new Decimal(0),
|
||||||
totalPrice,
|
totalPrice,
|
||||||
invoice,
|
invoice,
|
||||||
@@ -167,15 +167,14 @@ export class BillsService {
|
|||||||
|
|
||||||
for (const company of companies) {
|
for (const company of companies) {
|
||||||
const subtotal = new Decimal(chargeRate).mul(company.metrage);
|
const subtotal = new Decimal(chargeRate).mul(company.metrage);
|
||||||
const tax = subtotal.mul(0.1);
|
const totalPrice = subtotal;
|
||||||
const totalPrice = subtotal.add(tax);
|
|
||||||
|
|
||||||
const invoice = em.create(Invoice, {
|
const invoice = em.create(Invoice, {
|
||||||
company,
|
company,
|
||||||
totalPrice,
|
totalPrice,
|
||||||
originalPrice: totalPrice,
|
originalPrice: totalPrice,
|
||||||
dueDate,
|
dueDate,
|
||||||
tax,
|
tax: new Decimal(0),
|
||||||
business,
|
business,
|
||||||
bill,
|
bill,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user