feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,49 @@
import { Transformer } from '../Transformer/Transformer';
import { ItemEntry } from './models/ItemEntry';
interface ItemEntryTransformerContext{
currencyCode: string;
}
export class ItemEntryTransformer extends Transformer<{}, ItemEntryTransformerContext> {
/**
* Include these attributes to item entry object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['quantityFormatted', 'rateFormatted', 'totalFormatted'];
};
/**
* Retrieves the formatted quantitty of item entry.
* @param {IItemEntry} entry
* @returns {string}
*/
protected quantityFormatted = (entry: ItemEntry): string => {
return this.formatNumber(entry.quantity, { money: false });
};
/**
* Retrieves the formatted rate of item entry.
* @param {IItemEntry} itemEntry -
* @returns {string}
*/
protected rateFormatted = (entry: ItemEntry): string => {
return this.formatNumber(entry.rate, {
currencyCode: this.context.currencyCode,
money: false,
});
};
/**
* Retrieves the formatted total of item entry.
* @param {IItemEntry} entry
* @returns {string}
*/
protected totalFormatted = (entry: ItemEntry): string => {
return this.formatNumber(entry.total, {
currencyCode: this.context.currencyCode,
money: false,
});
};
}

View File

@@ -0,0 +1,25 @@
export type IItemEntryTransactionType = 'SaleInvoice' | 'Bill' | 'SaleReceipt';
export interface IItemEntryDTO {
id?: number;
index?: number;
itemId: number;
landedCost?: boolean;
warehouseId?: number;
sellAccountId?: number;
costAccountId?: number;
projectRefId?: number;
projectRefType?: ProjectLinkRefType;
projectRefInvoicedAmount?: number;
taxRateId?: number;
taxCode?: string;
}
export enum ProjectLinkRefType {
Task = 'TASK',
Bill = 'BILL',
Expense = 'EXPENSE',
}

View File

@@ -0,0 +1,141 @@
import { DiscountType } from '@/common/types/Discount';
import { ApiProperty } from '@nestjs/swagger';
import {
IsEnum,
IsIn,
IsInt,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
} from 'class-validator';
export class ItemEntryDto {
@IsInt()
@ApiProperty({
description: 'The index of the item entry',
example: 1,
})
index: number;
@IsInt()
@IsNotEmpty()
@ApiProperty({
description: 'The id of the item',
example: 1,
})
itemId: number;
@IsNumber()
@IsNotEmpty()
@ApiProperty({
description: 'The rate of the item entry',
example: 1,
})
rate: number;
@IsNumber()
@IsNotEmpty()
@ApiProperty({
description: 'The quantity of the item entry',
example: 1,
})
quantity: number;
@IsOptional()
@IsNumber()
@ApiProperty({
description: 'The discount of the item entry',
example: 1,
})
discount?: number;
@IsOptional()
@IsEnum(DiscountType)
@ApiProperty({
description: 'The type of the discount',
example: DiscountType.Percentage,
})
discountType?: DiscountType = DiscountType.Percentage;
@IsOptional()
@IsString()
@ApiProperty({
description: 'The description of the item entry',
example: 'This is a description',
})
description?: string;
@IsOptional()
@IsString()
@ApiProperty({
description: 'The tax code of the item entry',
example: '123456',
})
taxCode?: string;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The tax rate id of the item entry',
example: 1,
})
taxRateId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The warehouse id of the item entry',
example: 1,
})
warehouseId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The project id of the item entry',
example: 1,
})
projectId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The project ref id of the item entry',
example: 1,
})
projectRefId?: number;
@IsOptional()
@IsString()
@IsIn(['TASK', 'BILL', 'EXPENSE'])
@ApiProperty({
description: 'The project ref type of the item entry',
example: 'TASK',
})
projectRefType?: string;
@IsOptional()
@IsNumber()
@ApiProperty({
description: 'The project ref invoiced amount of the item entry',
example: 100,
})
projectRefInvoicedAmount?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The sell account id of the item entry',
example: 1020,
})
sellAccountId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The cost account id of the item entry',
example: 1021,
})
costAccountId?: number;
}

View File

@@ -0,0 +1,294 @@
import { DiscountType } from '@/common/types/Discount';
import { BaseModel } from '@/models/Model';
import { BillLandedCostEntry } from '@/modules/BillLandedCosts/models/BillLandedCostEntry';
import { Item } from '@/modules/Items/models/Item';
import {
getExlusiveTaxAmount,
getInclusiveTaxAmount,
} from '@/modules/TaxRates/utils';
import { Model } from 'objection';
export class ItemEntry extends BaseModel {
public referenceType: string;
public referenceId: string;
public index: number;
public itemId: number;
public description: string;
public sellAccountId: number;
public costAccountId: number;
public quantity: number;
public rate: number;
public taxRate: number;
public isInclusiveTax: number;
public landedCost: boolean;
public allocatedCostAmount: number;
public discountType: DiscountType;
public discount: number;
public adjustment: number;
public taxRateId: number;
public warehouseId: number;
item: Item;
allocatedCostEntries: BillLandedCostEntry[];
/**
* Table name.
* @returns {string}
*/
static get tableName() {
return 'items_entries';
}
/**
* Timestamps columns.
* @returns {string[]}
*/
get timestamps() {
return ['created_at', 'updated_at'];
}
/**
* Virtual attributes.
* @returns {string[]}
*/
static get virtualAttributes() {
return [
// Amount (qty * rate)
'amount',
'taxAmount',
// Subtotal (qty * rate) + (tax inclusive)
'subtotalInclusingTax',
// Subtotal Tax Exclusive (Subtotal - Tax Amount)
'subtotalExcludingTax',
// Subtotal (qty * rate) + (tax inclusive)
'subtotal',
// Discount (Is percentage ? amount * discount : discount)
'discountAmount',
// Total (Subtotal - Discount)
'total',
];
}
/**
* Item entry total.
* Amount of item entry includes tax and subtracted discount amount.
* @returns {number}
*/
get total() {
return this.subtotal - this.discountAmount;
}
/**
* Total (excluding tax).
* @returns {number}
*/
get totalExcludingTax() {
return this.subtotalExcludingTax - this.discountAmount;
}
/**
* Item entry amount.
* Amount of item entry that may include or exclude tax.
* @returns {number}
*/
get amount() {
return this.quantity * this.rate;
}
/**
* Subtotal amount (tax inclusive).
* @returns {number}
*/
get subtotal() {
return this.subtotalInclusingTax;
}
/**
* Item entry amount including tax.
* @returns {number}
*/
get subtotalInclusingTax() {
return this.isInclusiveTax ? this.amount : this.amount + this.taxAmount;
}
/**
* Subtotal amount (tax exclusive).
* @returns {number}
*/
get subtotalExcludingTax() {
return this.isInclusiveTax ? this.amount - this.taxAmount : this.amount;
}
/**
* Discount amount.
* @returns {number}
*/
get discountAmount() {
return this.discountType === DiscountType.Percentage
? this.amount * (this.discount / 100)
: this.discount;
}
/**
* Tag rate fraction.
* @returns {number}
*/
get tagRateFraction() {
return this.taxRate / 100;
}
/**
* Tax amount withheld.
* @returns {number}
*/
get taxAmount() {
return this.isInclusiveTax
? getInclusiveTaxAmount(this.amount, this.taxRate)
: getExlusiveTaxAmount(this.amount, this.taxRate);
}
static calcAmount(itemEntry) {
const { discount, quantity, rate } = itemEntry;
const total = quantity * rate;
return discount ? total - total * discount * 0.01 : total;
}
/**
* Item entry relations.
*/
static get relationMappings() {
const { Item } = require('../../Items/models/Item');
const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
const {
BillLandedCostEntry,
} = require('../../BillLandedCosts/models/BillLandedCostEntry');
const { Bill } = require('../../Bills/models/Bill');
const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt');
const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate');
const { TaxRateModel } = require('../../TaxRates/models/TaxRate.model');
// const { Expense } = require('../../Expenses/models/Expense.model');
// const ProjectTask = require('models/Task');
return {
item: {
relation: Model.BelongsToOneRelation,
modelClass: Item,
join: {
from: 'items_entries.itemId',
to: 'items.id',
},
},
allocatedCostEntries: {
relation: Model.HasManyRelation,
modelClass: BillLandedCostEntry,
join: {
from: 'items_entries.referenceId',
to: 'bill_located_cost_entries.entryId',
},
},
invoice: {
relation: Model.BelongsToOneRelation,
modelClass: SaleInvoice,
join: {
from: 'items_entries.referenceId',
to: 'sales_invoices.id',
},
},
bill: {
relation: Model.BelongsToOneRelation,
modelClass: Bill,
join: {
from: 'items_entries.referenceId',
to: 'bills.id',
},
},
estimate: {
relation: Model.BelongsToOneRelation,
modelClass: SaleEstimate,
join: {
from: 'items_entries.referenceId',
to: 'sales_estimates.id',
},
},
/**
* Sale receipt reference.
*/
receipt: {
relation: Model.BelongsToOneRelation,
modelClass: SaleReceipt,
join: {
from: 'items_entries.referenceId',
to: 'sales_receipts.id',
},
},
/**
* Project task reference.
*/
// projectTaskRef: {
// relation: Model.HasManyRelation,
// modelClass: ProjectTask.default,
// join: {
// from: 'items_entries.projectRefId',
// to: 'tasks.id',
// },
// },
/**
* Project expense reference.
*/
// projectExpenseRef: {
// relation: Model.HasManyRelation,
// modelClass: Expense.default,
// join: {
// from: 'items_entries.projectRefId',
// to: 'expenses_transactions.id',
// },
// },
/**
* Project bill reference.
*/
// projectBillRef: {
// relation: Model.HasManyRelation,
// modelClass: Bill,
// join: {
// from: 'items_entries.projectRefId',
// to: 'bills.id',
// },
// },
/**
* Tax rate reference.
*/
tax: {
relation: Model.HasOneRelation,
modelClass: TaxRateModel,
join: {
from: 'items_entries.taxRateId',
to: 'tax_rates.id',
},
},
};
}
}