feat: wip migrate to nestjs

This commit is contained in:
Ahmed Bouhuolia
2024-12-26 15:40:29 +02:00
parent a6932d76f3
commit cd84872a61
96 changed files with 2051 additions and 745 deletions

View File

@@ -37,13 +37,15 @@ import { TaxRatesModule } from '../TaxRates/TaxRate.module';
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module'; import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { BranchesModule } from '../Branches/Branches.module'; import { BranchesModule } from '../Branches/Branches.module';
import { WarehousesModule } from '../Warehouses/Warehouses.module'; import { WarehousesModule } from '../Warehouses/Warehouses.module';
import { SaleEstimatesModule } from '../SaleEstimates/SaleEstimates.module';
import { SerializeInterceptor } from '@/common/interceptors/serialize.interceptor'; import { SerializeInterceptor } from '@/common/interceptors/serialize.interceptor';
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module'; import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
import { CustomersModule } from '../Customers/Customers.module'; import { CustomersModule } from '../Customers/Customers.module';
import { VendorsModule } from '../Vendors/Vendors.module'; import { VendorsModule } from '../Vendors/Vendors.module';
import { SaleEstimatesModule } from '../SaleEstimates/SaleEstimates.module';
import { BillsModule } from '../Bills/Bills.module'; import { BillsModule } from '../Bills/Bills.module';
import { BillPaymentsModule } from '../BillPayments/BillPayments.module'; import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module';
// import { BillPaymentsModule } from '../BillPayments/BillPayments.module';
@Module({ @Module({
imports: [ imports: [
@@ -109,9 +111,11 @@ import { BillPaymentsModule } from '../BillPayments/BillPayments.module';
WarehousesModule, WarehousesModule,
CustomersModule, CustomersModule,
VendorsModule, VendorsModule,
SaleInvoicesModule,
SaleEstimatesModule, SaleEstimatesModule,
SaleReceiptsModule,
BillsModule, BillsModule,
BillPaymentsModule, // BillPaymentsModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [ providers: [

View File

@@ -6,7 +6,7 @@ import { AutoIncrementOrdersService } from './AutoIncrementOrders.service';
@Module({ @Module({
imports: [TenancyDatabaseModule], imports: [TenancyDatabaseModule],
controllers: [], controllers: [],
providers: [AutoIncrementOrdersService, TransformerInjectable], providers: [AutoIncrementOrdersService],
exports: [AutoIncrementOrdersService], exports: [AutoIncrementOrdersService],
}) })
export class AutoIncrementOrdersModule {} export class AutoIncrementOrdersModule {}

View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { TransactionLandedCostEntriesService } from './TransactionLandedCostEntries.service';
@Module({
providers: [TransactionLandedCostEntriesService],
exports: [TransactionLandedCostEntriesService],
})
export class BillLandedCostsModule {}

View File

@@ -0,0 +1,75 @@
import { Injectable } from '@nestjs/common';
import { ServiceError } from '../Items/ServiceError';
import { transformToMap } from '@/utils/transform-to-key';
import { ICommonLandedCostEntry, ICommonLandedCostEntryDTO } from './types/BillLandedCosts.types';
const ERRORS = {
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES:
'LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES',
};
@Injectable()
export class TransactionLandedCostEntriesService {
/**
* Validates bill entries that has allocated landed cost amount not deleted.
* @param {ICommonLandedCostEntry[]} oldCommonEntries -
* @param {ICommonLandedCostEntryDTO[]} newBillEntries -
*/
public getLandedCostEntriesDeleted(
oldCommonEntries: ICommonLandedCostEntry[],
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
): ICommonLandedCostEntry[] {
const newBillEntriesById = transformToMap(newCommonEntriesDTO, 'id');
return oldCommonEntries.filter((entry) => {
const newEntry = newBillEntriesById.get(entry.id);
if (entry.allocatedCostAmount > 0 && typeof newEntry === 'undefined') {
return true;
}
return false;
});
}
/**
* Validates the bill entries that have located cost amount should not be deleted.
* @param {ICommonLandedCostEntry[]} oldCommonEntries - Old bill entries.
* @param {ICommonLandedCostEntryDTO[]} newBillEntries - New DTO bill entries.
*/
public validateLandedCostEntriesNotDeleted(
oldCommonEntries: ICommonLandedCostEntry[],
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
): void {
const entriesDeleted = this.getLandedCostEntriesDeleted(
oldCommonEntries,
newCommonEntriesDTO
);
if (entriesDeleted.length > 0) {
throw new ServiceError(ERRORS.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED);
}
}
/**
* Validate allocated cost amount entries should be smaller than new entries amount.
* @param {ICommonLandedCostEntry[]} oldCommonEntries - Old bill entries.
* @param {ICommonLandedCostEntryDTO[]} newBillEntries - New DTO bill entries.
*/
public validateLocatedCostEntriesSmallerThanNewEntries(
oldCommonEntries: ICommonLandedCostEntry[],
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
): void {
const oldBillEntriesById = transformToMap(oldCommonEntries, 'id');
newCommonEntriesDTO.forEach((entry) => {
const oldEntry = oldBillEntriesById.get(entry.id);
if (oldEntry && oldEntry.allocatedCostAmount > entry.amount) {
throw new ServiceError(
ERRORS.LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES
);
}
});
}
}

View File

@@ -4,15 +4,15 @@ import { lowerCase } from 'lodash';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
export class BillLandedCost extends BaseModel { export class BillLandedCost extends BaseModel {
amount: number; amount!: number;
fromTransactionId: number; fromTransactionId!: number;
fromTransactionType: string; fromTransactionType!: string;
fromTransactionEntryId: number; fromTransactionEntryId!: number;
allocationMethod: string; allocationMethod!: string;
costAccountId: number; costAccountId!: number;
description: string; description!: string;
billId: number; billId!: number;
exchangeRate: number; exchangeRate!: number;
/** /**
* Table name * Table name
@@ -60,15 +60,19 @@ export class BillLandedCost extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const BillLandedCostEntry = require('models/BillLandedCostEntry'); const { BillLandedCostEntry } = require('./BillLandedCostEntry');
const Bill = require('models/Bill'); const { Bill } = require('../../Bills/models/Bill');
const ItemEntry = require('models/ItemEntry'); const {
const ExpenseCategory = require('models/ExpenseCategory'); ItemEntry,
} = require('../../TransactionItemEntry/models/ItemEntry');
const {
ExpenseCategory,
} = require('../../Expenses/models/ExpenseCategory.model');
return { return {
bill: { bill: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Bill.default, modelClass: Bill,
join: { join: {
from: 'bill_located_costs.billId', from: 'bill_located_costs.billId',
to: 'bills.id', to: 'bills.id',
@@ -76,7 +80,7 @@ export class BillLandedCost extends BaseModel {
}, },
allocateEntries: { allocateEntries: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: BillLandedCostEntry.default, modelClass: BillLandedCostEntry,
join: { join: {
from: 'bill_located_costs.id', from: 'bill_located_costs.id',
to: 'bill_located_cost_entries.billLocatedCostId', to: 'bill_located_cost_entries.billLocatedCostId',
@@ -84,7 +88,7 @@ export class BillLandedCost extends BaseModel {
}, },
allocatedFromBillEntry: { allocatedFromBillEntry: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: ItemEntry.default, modelClass: ItemEntry,
join: { join: {
from: 'bill_located_costs.fromTransactionEntryId', from: 'bill_located_costs.fromTransactionEntryId',
to: 'items_entries.id', to: 'items_entries.id',
@@ -92,7 +96,7 @@ export class BillLandedCost extends BaseModel {
}, },
allocatedFromExpenseEntry: { allocatedFromExpenseEntry: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: ExpenseCategory.default, modelClass: ExpenseCategory,
join: { join: {
from: 'bill_located_costs.fromTransactionEntryId', from: 'bill_located_costs.fromTransactionEntryId',
to: 'expense_transaction_categories.id', to: 'expense_transaction_categories.id',

View File

@@ -1,7 +1,11 @@
import { BaseModel } from '@/models/Model';
import { Model } from 'objection'; import { Model } from 'objection';
import TenantModel from 'models/TenantModel';
export default class BillLandedCostEntry extends TenantModel { export class BillLandedCostEntry extends BaseModel {
cost!: number;
entryId!: number;
billLocatedCostId!: number;
/** /**
* Table name * Table name
*/ */
@@ -13,12 +17,14 @@ export default class BillLandedCostEntry extends TenantModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const ItemEntry = require('models/ItemEntry'); const {
ItemEntry,
} = require('../../TransactionItemEntry/models/ItemEntry');
return { return {
itemEntry: { itemEntry: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: ItemEntry.default, modelClass: ItemEntry,
join: { join: {
from: 'bill_located_cost_entries.entryId', from: 'bill_located_cost_entries.entryId',
to: 'items_entries.id', to: 'items_entries.id',

View File

@@ -0,0 +1,144 @@
import { Knex } from 'knex';
import { Bill } from '@/modules/Bills/models/Bill';
export interface ILandedCostItemDTO {
entryId: number;
cost: number;
}
export type ILandedCostType = 'Expense' | 'Bill';
export interface ILandedCostDTO {
transactionType: ILandedCostType;
transactionId: number;
transactionEntryId: number;
allocationMethod: string;
description: string;
items: ILandedCostItemDTO[];
}
export interface ILandedCostQueryDTO {
vendorId: number;
fromDate: Date;
toDate: Date;
}
export interface IUnallocatedListCost {
costNumber: string;
costAmount: number;
unallocatedAmount: number;
}
export interface ILandedCostTransactionsQueryDTO {
transactionType: string;
date: Date;
}
export interface ILandedCostEntriesQueryDTO {
transactionType: string;
transactionId: number;
}
export interface ILandedCostTransaction {
id: number;
name: string;
amount: number;
allocatedCostAmount: number;
unallocatedCostAmount: number;
currencyCode: string;
exchangeRate: number;
// formattedAllocatedCostAmount: string;
// formattedAmount: string;
// formattedUnallocatedCostAmount: string;
transactionType: string;
entries?: ILandedCostTransactionEntry[];
}
export interface ILandedCostTransactionEntry {
id: number;
name: string;
code: string;
amount: number;
unallocatedCostAmount: number;
allocatedCostAmount: number;
description: string;
costAccountId: number;
}
export interface ILandedCostTransactionEntryDOJO
extends ILandedCostTransactionEntry {
formattedAmount: string;
formattedUnallocatedCostAmount: string;
formattedAllocatedCostAmount: string;
}
export interface ILandedCostTransactionDOJO extends ILandedCostTransaction {
formattedAmount: string;
formattedUnallocatedCostAmount: string;
formattedAllocatedCostAmount: string;
}
interface ILandedCostEntry {
id: number;
landedCost?: boolean;
}
export interface IBillLandedCostTransaction {
id: number;
fromTransactionId: number;
fromTransactionType: string;
fromTransactionEntryId: number;
billId: number;
allocationMethod: string;
costAccountId: number;
description: string;
amount: number;
localAmount?: number;
currencyCode: string;
exchangeRate: number;
allocateEntries?: IBillLandedCostTransactionEntry[];
}
export interface IBillLandedCostTransactionEntry {
cost: number;
entryId: number;
billLocatedCostId: number;
}
export interface IAllocatedLandedCostDeletedPayload {
tenantId: number;
oldBillLandedCost: IBillLandedCostTransaction;
billId: number;
trx: Knex.Transaction;
}
export interface IAllocatedLandedCostCreatedPayload {
tenantId: number;
bill: Bill;
billLandedCostId: number;
billLandedCost: IBillLandedCostTransaction;
trx: Knex.Transaction;
}
export interface IBillAssociatedLandedCostTransactions {}
interface ICommonEntry {
id?: number;
amount: number;
}
export interface ICommonLandedCostEntry extends ICommonEntry {
landedCost: boolean;
allocatedCostAmount: number;
}
interface ICommonEntryDTO {
id?: number;
amount: number;
}
export interface ICommonLandedCostEntryDTO extends ICommonEntryDTO {
landedCost?: boolean;
}

View File

@@ -4,6 +4,13 @@ import { CreateBillPaymentService } from './commands/CreateBillPayment.service';
import { EditBillPayment } from './commands/EditBillPayment.service'; import { EditBillPayment } from './commands/EditBillPayment.service';
import { GetBillPayment } from './queries/GetBillPayment.service'; import { GetBillPayment } from './queries/GetBillPayment.service';
import { DeleteBillPayment } from './commands/DeleteBillPayment.service'; import { DeleteBillPayment } from './commands/DeleteBillPayment.service';
import { BillPaymentBillSync } from './commands/BillPaymentBillSync.service';
import { GetPaymentBills } from './queries/GetPaymentBills.service';
import { BillPaymentValidators } from './commands/BillPaymentValidators.service';
import { CommandBillPaymentDTOTransformer } from './commands/CommandBillPaymentDTOTransformer.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { BranchTransactionDTOTransformer } from '../Branches/integrations/BranchTransactionDTOTransform';
import { BranchesSettingsService } from '../Branches/BranchesSettings';
@Module({ @Module({
providers: [ providers: [
@@ -12,6 +19,13 @@ import { DeleteBillPayment } from './commands/DeleteBillPayment.service';
EditBillPayment, EditBillPayment,
GetBillPayment, GetBillPayment,
DeleteBillPayment, DeleteBillPayment,
BillPaymentBillSync,
GetPaymentBills,
BillPaymentValidators,
CommandBillPaymentDTOTransformer,
BranchTransactionDTOTransformer,
BranchesSettingsService,
TenancyContext
], ],
controllers: [], controllers: [],
}) })

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { CreateBillPaymentService } from './commands/CreateBillPayment.service'; import { CreateBillPaymentService } from './commands/CreateBillPayment.service';
import { DeleteBillPayment } from './commands/DeleteBillPayment.service'; import { DeleteBillPayment } from './commands/DeleteBillPayment.service';
import { EditBillPayment } from './commands/EditBillPayment.service'; import { EditBillPayment } from './commands/EditBillPayment.service';
import { GetBillPayments } from './GetBillPayments'; // import { GetBillPayments } from './GetBillPayments';
import { GetBillPayment } from './queries/GetBillPayment.service'; import { GetBillPayment } from './queries/GetBillPayment.service';
import { GetPaymentBills } from './queries/GetPaymentBills.service'; import { GetPaymentBills } from './queries/GetPaymentBills.service';
import { IBillPaymentDTO } from './types/BillPayments.types'; import { IBillPaymentDTO } from './types/BillPayments.types';
@@ -17,7 +17,7 @@ export class BillPaymentsApplication {
private createBillPaymentService: CreateBillPaymentService, private createBillPaymentService: CreateBillPaymentService,
private deleteBillPaymentService: DeleteBillPayment, private deleteBillPaymentService: DeleteBillPayment,
private editBillPaymentService: EditBillPayment, private editBillPaymentService: EditBillPayment,
private getBillPaymentsService: GetBillPayments, // private getBillPaymentsService: GetBillPayments,
private getBillPaymentService: GetBillPayment, private getBillPaymentService: GetBillPayment,
private getPaymentBillsService: GetPaymentBills, private getPaymentBillsService: GetPaymentBills,
) {} ) {}

View File

@@ -1,75 +1,75 @@
import { Inject, Service } from 'typedi'; // import { Inject, Service } from 'typedi';
import * as R from 'ramda'; // import * as R from 'ramda';
import { // import {
IBillPayment, // IBillPayment,
IBillPaymentsFilter, // IBillPaymentsFilter,
IPaginationMeta, // IPaginationMeta,
IFilterMeta, // IFilterMeta,
} from '@/interfaces'; // } from '@/interfaces';
import { BillPaymentTransformer } from './queries/BillPaymentTransformer'; // import { BillPaymentTransformer } from './queries/BillPaymentTransformer';
import DynamicListingService from '@/services/DynamicListing/DynamicListService'; // import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import HasTenancyService from '@/services/Tenancy/TenancyService'; // import HasTenancyService from '@/services/Tenancy/TenancyService';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; // import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service() // @Service()
export class GetBillPayments { // export class GetBillPayments {
@Inject() // @Inject()
private tenancy: HasTenancyService; // private tenancy: HasTenancyService;
@Inject() // @Inject()
private dynamicListService: DynamicListingService; // private dynamicListService: DynamicListingService;
@Inject() // @Inject()
private transformer: TransformerInjectable; // private transformer: TransformerInjectable;
/** // /**
* Retrieve bill payment paginted and filterable list. // * Retrieve bill payment paginted and filterable list.
* @param {number} tenantId // * @param {number} tenantId
* @param {IBillPaymentsFilter} billPaymentsFilter // * @param {IBillPaymentsFilter} billPaymentsFilter
*/ // */
public async getBillPayments( // public async getBillPayments(
tenantId: number, // tenantId: number,
filterDTO: IBillPaymentsFilter // filterDTO: IBillPaymentsFilter
): Promise<{ // ): Promise<{
billPayments: IBillPayment[]; // billPayments: IBillPayment[];
pagination: IPaginationMeta; // pagination: IPaginationMeta;
filterMeta: IFilterMeta; // filterMeta: IFilterMeta;
}> { // }> {
const { BillPayment } = this.tenancy.models(tenantId); // const { BillPayment } = this.tenancy.models(tenantId);
// Parses filter DTO. // // Parses filter DTO.
const filter = this.parseListFilterDTO(filterDTO); // const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service. // // Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList( // const dynamicList = await this.dynamicListService.dynamicList(
tenantId, // tenantId,
BillPayment, // BillPayment,
filter // filter
); // );
const { results, pagination } = await BillPayment.query() // const { results, pagination } = await BillPayment.query()
.onBuild((builder) => { // .onBuild((builder) => {
builder.withGraphFetched('vendor'); // builder.withGraphFetched('vendor');
builder.withGraphFetched('paymentAccount'); // builder.withGraphFetched('paymentAccount');
dynamicList.buildQuery()(builder); // dynamicList.buildQuery()(builder);
filter?.filterQuery && filter?.filterQuery(builder); // filter?.filterQuery && filter?.filterQuery(builder);
}) // })
.pagination(filter.page - 1, filter.pageSize); // .pagination(filter.page - 1, filter.pageSize);
// Transformes the bill payments models to POJO. // // Transformes the bill payments models to POJO.
const billPayments = await this.transformer.transform( // const billPayments = await this.transformer.transform(
tenantId, // tenantId,
results, // results,
new BillPaymentTransformer() // new BillPaymentTransformer()
); // );
return { // return {
billPayments, // billPayments,
pagination, // pagination,
filterMeta: dynamicList.getResponseMeta(), // filterMeta: dynamicList.getResponseMeta(),
}; // };
} // }
private parseListFilterDTO(filterDTO) { // private parseListFilterDTO(filterDTO) {
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO); // return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
} // }
} // }

View File

@@ -1,11 +1,16 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Bill } from '../../Bills/models/Bill'; import { Bill } from '../../Bills/models/Bill';
import { IBillPaymentEntryDTO } from '../types/BillPayments.types'; import { IBillPaymentEntryDTO } from '../types/BillPayments.types';
import { entriesAmountDiff } from '@/utils/entries-amount-diff';
import Objection from 'objection';
@Injectable() @Injectable()
export class BillPaymentBillSync { export class BillPaymentBillSync {
constructor(private readonly bill: typeof Bill) {} constructor(
@Inject(Bill.name)
private readonly bill: typeof Bill
) {}
/** /**
* Saves bills payment amount changes different. * Saves bills payment amount changes different.
@@ -18,7 +23,7 @@ export class BillPaymentBillSync {
oldPaymentMadeEntries?: IBillPaymentEntryDTO[], oldPaymentMadeEntries?: IBillPaymentEntryDTO[],
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const opers: Promise<void>[] = []; const opers: Objection.QueryBuilder<Bill, Bill[]>[] = [];
const diffEntries = entriesAmountDiff( const diffEntries = entriesAmountDiff(
paymentMadeEntries, paymentMadeEntries,

View File

@@ -1,7 +1,6 @@
import { Inject, Service } from 'typedi';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { ERRORS } from '../constants'; import { ERRORS } from '../constants';
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Bill } from '../../Bills/models/Bill'; import { Bill } from '../../Bills/models/Bill';
import { BillPayment } from '../models/BillPayment'; import { BillPayment } from '../models/BillPayment';
import { IBillReceivePageEntry } from '../types/BillPayments.types'; import { IBillReceivePageEntry } from '../types/BillPayments.types';

View File

@@ -58,7 +58,7 @@ export class CreateBillPaymentService {
public async createBillPayment( public async createBillPayment(
billPaymentDTO: IBillPaymentDTO, billPaymentDTO: IBillPaymentDTO,
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<IBillPayment> { ): Promise<BillPayment> {
const tenantMeta = await this.tenancyContext.getTenant(true); const tenantMeta = await this.tenancyContext.getTenant(true);
// Retrieves the payment vendor or throw not found error. // Retrieves the payment vendor or throw not found error.

View File

@@ -6,6 +6,9 @@ import { Model, mixin } from 'objection';
// import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants'; // import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
// import ModelSearchable from './ModelSearchable'; // import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { BillPaymentEntry } from './BillPaymentEntry';
import { Vendor } from '@/modules/Vendors/models/Vendor';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
export class BillPayment extends BaseModel{ export class BillPayment extends BaseModel{
vendorId: number; vendorId: number;
@@ -23,6 +26,12 @@ export class BillPayment extends BaseModel{
createdAt?: Date; createdAt?: Date;
updatedAt?: Date; updatedAt?: Date;
branchId?: number;
entries?: BillPaymentEntry[];
vendor?: Vendor;
attachments?: Document[];
/** /**
* Table name * Table name
*/ */

View File

@@ -1,6 +1,8 @@
import { Model } from 'objection'; import { Model } from 'objection';
// import TenantModel from 'models/TenantModel'; // import TenantModel from 'models/TenantModel';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { Bill } from '@/modules/Bills/models/Bill';
import { BillPayment } from './BillPayment';
export class BillPaymentEntry extends BaseModel { export class BillPaymentEntry extends BaseModel {
public billPaymentId: number; public billPaymentId: number;
@@ -8,6 +10,9 @@ export class BillPaymentEntry extends BaseModel {
public paymentAmount: number; public paymentAmount: number;
public index: number; public index: number;
bill?: Bill;
payment?: BillPayment;
/** /**
* Table name * Table name
*/ */
@@ -32,7 +37,7 @@ export class BillPaymentEntry extends BaseModel {
return { return {
payment: { payment: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: BillPayment.default, modelClass: BillPayment,
join: { join: {
from: 'bills_payments_entries.billPaymentId', from: 'bills_payments_entries.billPaymentId',
to: 'bills_payments.id', to: 'bills_payments.id',
@@ -40,7 +45,7 @@ export class BillPaymentEntry extends BaseModel {
}, },
bill: { bill: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Bill.default, modelClass: Bill,
join: { join: {
from: 'bills_payments_entries.billId', from: 'bills_payments_entries.billId',
to: 'bills.id', to: 'bills.id',

View File

@@ -1,15 +1,14 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IBill } from './Bill'; import { Bill } from '@/modules/Bills/models/Bill';
import { AttachmentLinkDTO } from './Attachments';
import { BillPayment } from '../models/BillPayment'; import { BillPayment } from '../models/BillPayment';
import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types';
export interface IBillPaymentEntry { export interface IBillPaymentEntry {
id?: number; id?: number;
billPaymentId: number; billPaymentId: number;
billId: number; billId: number;
paymentAmount: number; paymentAmount: number;
bill?: Bill;
bill?: IBill;
} }
export interface IBillPayment { export interface IBillPayment {
@@ -39,6 +38,7 @@ export interface IBillPaymentEntryDTO {
export interface IBillPaymentDTO { export interface IBillPaymentDTO {
vendorId: number; vendorId: number;
amount: number;
paymentAccountId: number; paymentAccountId: number;
paymentNumber?: string; paymentNumber?: string;
paymentDate: Date; paymentDate: Date;

View File

@@ -23,7 +23,7 @@ export class BillsApplication {
private deleteBillService: DeleteBill, private deleteBillService: DeleteBill,
private getDueBillsService: GetDueBills, private getDueBillsService: GetDueBills,
private openBillService: OpenBillService, private openBillService: OpenBillService,
private getBillPaymentsService: GetBillPayments, // private getBillPaymentsService: GetBillPayments,
) {} ) {}
/** /**
@@ -99,7 +99,7 @@ export class BillsApplication {
* @param {number} tenantId * @param {number} tenantId
* @param {number} billId * @param {number} billId
*/ */
public getBillPayments(billId: number) { // public getBillPayments(billId: number) {
return this.getBillPaymentsService.getBillPayments(billId); // return this.getBillPaymentsService.getBillPayments(billId);
} // }
} }

View File

@@ -0,0 +1,48 @@
import {
Controller,
Post,
Body,
Put,
Param,
Delete,
Get,
} from '@nestjs/common';
import { BillsApplication } from './Bills.application';
import { IBillDTO, IBillEditDTO } from './Bills.types';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('bills')
@PublicRoute()
export class BillsController {
constructor(private billsApplication: BillsApplication) {}
@Post()
createBill(@Body() billDTO: IBillDTO) {
return this.billsApplication.createBill(billDTO);
}
@Put(':id')
editBill(@Param('id') billId: number, @Body() billDTO: IBillEditDTO) {
return this.billsApplication.editBill(billId, billDTO);
}
@Delete(':id')
deleteBill(@Param('id') billId: number) {
return this.billsApplication.deleteBill(billId);
}
@Get(':id')
getBill(@Param('id') billId: number) {
return this.billsApplication.getBill(billId);
}
@Post(':id/open')
openBill(@Param('id') billId: number) {
return this.billsApplication.openBill(billId);
}
@Get('due')
getDueBills(@Body('vendorId') vendorId?: number) {
return this.billsApplication.getDueBills(vendorId);
}
}

View File

@@ -4,14 +4,40 @@ import { CreateBill } from './commands/CreateBill.service';
import { DeleteBill } from './commands/DeleteBill.service'; import { DeleteBill } from './commands/DeleteBill.service';
import { GetBill } from './queries/GetBill'; import { GetBill } from './queries/GetBill';
import { BillDTOTransformer } from './commands/BillDTOTransformer.service'; import { BillDTOTransformer } from './commands/BillDTOTransformer.service';
import { EditBillService } from './commands/EditBill.service';
import { GetDueBills } from './queries/GetDueBills.service';
import { OpenBillService } from './commands/OpenBill.service';
import { BillsValidators } from './commands/BillsValidators.service';
import { ItemsEntriesService } from '../Items/ItemsEntries.service';
import { BranchTransactionDTOTransformer } from '../Branches/integrations/BranchTransactionDTOTransform';
import { BranchesSettingsService } from '../Branches/BranchesSettings';
import { WarehouseTransactionDTOTransform } from '../Warehouses/Integrations/WarehouseTransactionDTOTransform';
import { WarehousesSettings } from '../Warehouses/WarehousesSettings';
import { ItemEntriesTaxTransactions } from '../TaxRates/ItemEntriesTaxTransactions.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { BillsController } from './Bills.controller';
import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module';
@Module({ @Module({
imports: [BillLandedCostsModule],
providers: [ providers: [
TenancyContext,
BillsApplication, BillsApplication,
BranchTransactionDTOTransformer,
WarehouseTransactionDTOTransform,
WarehousesSettings,
ItemEntriesTaxTransactions,
BranchesSettingsService,
CreateBill, CreateBill,
EditBillService,
GetDueBills,
OpenBillService,
GetBill, GetBill,
DeleteBill, DeleteBill,
BillDTOTransformer, BillDTOTransformer,
BillsValidators,
ItemsEntriesService
], ],
controllers: [BillsController],
}) })
export class BillsModule {} export class BillsModule {}

View File

@@ -53,7 +53,7 @@ export interface IBillCreatedPayload {
// tenantId: number; // tenantId: number;
bill: Bill; bill: Bill;
billDTO: IBillDTO; billDTO: IBillDTO;
billId: number; // billId: number;
trx?: Knex.Transaction; trx?: Knex.Transaction;
} }

View File

@@ -1,16 +1,16 @@
import { Inject, Injectable } from '@nestjs/common';
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { Inject, Injectable } from '@nestjs/common';
import * as R from 'ramda'; import * as R from 'ramda';
import * as composeAsync from 'async/compose';
import { formatDateFields } from '@/utils/format-date-fields'; import { formatDateFields } from '@/utils/format-date-fields';
import composeAsync from 'async/compose';
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform'; import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform'; import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform';
import { ItemEntry } from '@/modules/Items/models/ItemEntry'; import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { Item } from '@/modules/Items/models/Item'; import { Item } from '@/modules/Items/models/Item';
import { Vendor } from '@/modules/Vendors/models/Vendor';
import { ItemEntriesTaxTransactions } from '@/modules/TaxRates/ItemEntriesTaxTransactions.service'; import { ItemEntriesTaxTransactions } from '@/modules/TaxRates/ItemEntriesTaxTransactions.service';
import { IBillDTO } from '../Bills.types'; import { IBillDTO } from '../Bills.types';
import { Vendor } from '@/modules/Vendors/models/Vendor';
import { Bill } from '../models/Bill'; import { Bill } from '../models/Bill';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index'; import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@@ -23,8 +23,8 @@ export class BillDTOTransformer {
private taxDTOTransformer: ItemEntriesTaxTransactions, private taxDTOTransformer: ItemEntriesTaxTransactions,
private tenancyContext: TenancyContext, private tenancyContext: TenancyContext,
@Inject(ItemEntry) private itemEntryModel: typeof ItemEntry, @Inject(ItemEntry.name) private itemEntryModel: typeof ItemEntry,
@Inject(Item) private itemModel: typeof Item, @Inject(Item.name) private itemModel: typeof Item,
) {} ) {}
/** /**
@@ -44,7 +44,9 @@ export class BillDTOTransformer {
private getBillLandedCostAmount(billDTO: IBillDTO): number { private getBillLandedCostAmount(billDTO: IBillDTO): number {
const costEntries = billDTO.entries.filter((entry) => entry.landedCost); const costEntries = billDTO.entries.filter((entry) => entry.landedCost);
return this.getBillEntriesTotal(costEntries); // return this.getBillEntriesTotal(costEntries);
return 0;
} }
/** /**
@@ -57,7 +59,7 @@ export class BillDTOTransformer {
billDTO: IBillDTO, billDTO: IBillDTO,
vendor: Vendor, vendor: Vendor,
oldBill?: Bill, oldBill?: Bill,
) { ): Promise<Bill> {
const amount = sumBy(billDTO.entries, (e) => const amount = sumBy(billDTO.entries, (e) =>
this.itemEntryModel.calcAmount(e), this.itemEntryModel.calcAmount(e),
); );
@@ -112,9 +114,9 @@ export class BillDTOTransformer {
return R.compose( return R.compose(
// Associates tax amount withheld to the model. // Associates tax amount withheld to the model.
this.taxDTOTransformer.assocTaxAmountWithheldFromEntries, this.taxDTOTransformer.assocTaxAmountWithheldFromEntries,
this.branchDTOTransform.transformDTO, this.branchDTOTransform.transformDTO<Bill>,
this.warehouseDTOTransform.transformDTO, this.warehouseDTOTransform.transformDTO<Bill>,
)(initialDTO); )(initialDTO) as Bill;
} }
/** /**

View File

@@ -6,6 +6,8 @@ import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types';
import { Item } from '@/modules/Items/models/Item'; import { Item } from '@/modules/Items/models/Item';
import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry'; import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry';
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost'; import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
import { VendorCreditAppliedBill } from '@/modules/VendorCredit/models/VendorCreditAppliedBill';
import { transformToMap } from '@/utils/transform-to-key';
@Injectable() @Injectable()
export class BillsValidators { export class BillsValidators {

View File

@@ -27,7 +27,7 @@ export class CreateBill {
private billModel: typeof Bill, private billModel: typeof Bill,
@Inject(Vendor.name) @Inject(Vendor.name)
private contactModel: typeof Vendor, private vendorModel: typeof Vendor,
) {} ) {}
/** /**
@@ -49,9 +49,8 @@ export class CreateBill {
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<Bill> { ): Promise<Bill> {
// Retrieves the given bill vendor or throw not found error. // Retrieves the given bill vendor or throw not found error.
const vendor = await this.contactModel const vendor = await this.vendorModel
.query() .query()
.modify('vendor')
.findById(billDTO.vendorId) .findById(billDTO.vendorId)
.throwIfNotFound(); .throwIfNotFound();
@@ -70,10 +69,7 @@ export class CreateBill {
billDTO.entries, billDTO.entries,
); );
// Transform the bill DTO to model object. // Transform the bill DTO to model object.
const billObj = await this.transformerDTO.billDTOToModel( const billObj = await this.transformerDTO.billDTOToModel(billDTO, vendor);
billDTO,
vendor,
);
// Write new bill transaction with associated transactions under UOW env. // Write new bill transaction with associated transactions under UOW env.
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
@@ -84,12 +80,11 @@ export class CreateBill {
} as IBillCreatingPayload); } as IBillCreatingPayload);
// Inserts the bill graph object to the storage. // Inserts the bill graph object to the storage.
const bill = await this.billModel.query(trx).upsertGraph(billObj); const bill = await this.billModel.query(trx).upsertGraphAndFetch(billObj);
// Triggers `onBillCreated` event. // Triggers `onBillCreated` event.
await this.eventPublisher.emitAsync(events.bill.onCreated, { await this.eventPublisher.emitAsync(events.bill.onCreated, {
bill, bill,
billId: bill.id,
billDTO, billDTO,
trx, trx,
} as IBillCreatedPayload); } as IBillCreatedPayload);

View File

@@ -55,7 +55,7 @@ export class DeleteBill {
} as IBillEventDeletingPayload); } as IBillEventDeletingPayload);
// Delete all associated bill entries. // Delete all associated bill entries.
await ItemEntry.query(trx) await this.itemEntryModel.query(trx)
.where('reference_type', 'Bill') .where('reference_type', 'Bill')
.where('reference_id', billId) .where('reference_id', billId)
.delete(); .delete();

View File

@@ -12,7 +12,8 @@ import { BillDTOTransformer } from './BillDTOTransformer.service';
import { Bill } from '../models/Bill'; import { Bill } from '../models/Bill';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { Vendor } from '@/modules/Vendors/models/Vendor'; import { Vendor } from '@/modules/Vendors/models/Vendor';
import { Knex } from 'knex';
import { TransactionLandedCostEntriesService } from '@/modules/BillLandedCosts/TransactionLandedCostEntries.service';
@Injectable() @Injectable()
export class EditBillService { export class EditBillService {
@@ -21,7 +22,7 @@ export class EditBillService {
private itemsEntriesService: ItemsEntriesService, private itemsEntriesService: ItemsEntriesService,
private uow: UnitOfWork, private uow: UnitOfWork,
private eventPublisher: EventEmitter2, private eventPublisher: EventEmitter2,
private entriesService: ItemEntries, private transactionLandedCostEntries: TransactionLandedCostEntriesService,
private transformerDTO: BillDTOTransformer, private transformerDTO: BillDTOTransformer,
@Inject(Bill.name) private billModel: typeof Bill, @Inject(Bill.name) private billModel: typeof Bill,
@Inject(Vendor.name) private contactModel: typeof Vendor, @Inject(Vendor.name) private contactModel: typeof Vendor,
@@ -97,12 +98,12 @@ export class EditBillService {
oldBill.paymentAmount oldBill.paymentAmount
); );
// Validate landed cost entries that have allocated cost could not be deleted. // Validate landed cost entries that have allocated cost could not be deleted.
await this.entriesService.validateLandedCostEntriesNotDeleted( await this.transactionLandedCostEntries.validateLandedCostEntriesNotDeleted(
oldBill.entries, oldBill.entries,
billObj.entries billObj.entries
); );
// Validate new landed cost entries should be bigger than new entries. // Validate new landed cost entries should be bigger than new entries.
await this.entriesService.validateLocatedCostEntriesSmallerThanNewEntries( await this.transactionLandedCostEntries.validateLocatedCostEntriesSmallerThanNewEntries(
oldBill.entries, oldBill.entries,
billObj.entries billObj.entries
); );

View File

@@ -8,6 +8,7 @@ import moment from 'moment';
// import { DEFAULT_VIEWS } from '@/services/Purchases/Bills/constants'; // import { DEFAULT_VIEWS } from '@/services/Purchases/Bills/constants';
// import ModelSearchable from './ModelSearchable'; // import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
export class Bill extends BaseModel{ export class Bill extends BaseModel{
public amount: number; public amount: number;
@@ -31,9 +32,14 @@ export class Bill extends BaseModel{
public openedAt: Date | string; public openedAt: Date | string;
public userId: number; public userId: number;
public branchId: number;
public warehouseId: number;
public createdAt: Date; public createdAt: Date;
public updatedAt: Date | null; public updatedAt: Date | null;
public entries?: ItemEntry[];
/** /**
* Timestamps columns. * Timestamps columns.
*/ */
@@ -410,125 +416,125 @@ export class Bill extends BaseModel{
/** /**
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { // static get relationMappings() {
const Vendor = require('models/Vendor'); // const Vendor = require('models/Vendor');
const ItemEntry = require('models/ItemEntry'); // const ItemEntry = require('models/ItemEntry');
const BillLandedCost = require('models/BillLandedCost'); // const BillLandedCost = require('models/BillLandedCost');
const Branch = require('models/Branch'); // const Branch = require('models/Branch');
const Warehouse = require('models/Warehouse'); // const Warehouse = require('models/Warehouse');
const TaxRateTransaction = require('models/TaxRateTransaction'); // const TaxRateTransaction = require('models/TaxRateTransaction');
const Document = require('models/Document'); // const Document = require('models/Document');
const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); // const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
return { // return {
vendor: { // vendor: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: Vendor.default, // modelClass: Vendor.default,
join: { // join: {
from: 'bills.vendorId', // from: 'bills.vendorId',
to: 'contacts.id', // to: 'contacts.id',
}, // },
filter(query) { // filter(query) {
query.where('contact_service', 'vendor'); // query.where('contact_service', 'vendor');
}, // },
}, // },
entries: { // entries: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: ItemEntry.default, // modelClass: ItemEntry.default,
join: { // join: {
from: 'bills.id', // from: 'bills.id',
to: 'items_entries.referenceId', // to: 'items_entries.referenceId',
}, // },
filter(builder) { // filter(builder) {
builder.where('reference_type', 'Bill'); // builder.where('reference_type', 'Bill');
builder.orderBy('index', 'ASC'); // builder.orderBy('index', 'ASC');
}, // },
}, // },
locatedLandedCosts: { // locatedLandedCosts: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: BillLandedCost.default, // modelClass: BillLandedCost.default,
join: { // join: {
from: 'bills.id', // from: 'bills.id',
to: 'bill_located_costs.billId', // to: 'bill_located_costs.billId',
}, // },
}, // },
/** // /**
* Bill may belongs to associated branch. // * Bill may belongs to associated branch.
*/ // */
branch: { // branch: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: Branch.default, // modelClass: Branch.default,
join: { // join: {
from: 'bills.branchId', // from: 'bills.branchId',
to: 'branches.id', // to: 'branches.id',
}, // },
}, // },
/** // /**
* Bill may has associated warehouse. // * Bill may has associated warehouse.
*/ // */
warehouse: { // warehouse: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default, // modelClass: Warehouse.default,
join: { // join: {
from: 'bills.warehouseId', // from: 'bills.warehouseId',
to: 'warehouses.id', // to: 'warehouses.id',
}, // },
}, // },
/** // /**
* Bill may has associated tax rate transactions. // * Bill may has associated tax rate transactions.
*/ // */
taxes: { // taxes: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: TaxRateTransaction.default, // modelClass: TaxRateTransaction.default,
join: { // join: {
from: 'bills.id', // from: 'bills.id',
to: 'tax_rate_transactions.referenceId', // to: 'tax_rate_transactions.referenceId',
}, // },
filter(builder) { // filter(builder) {
builder.where('reference_type', 'Bill'); // builder.where('reference_type', 'Bill');
}, // },
}, // },
/** // /**
* Bill may has many attached attachments. // * Bill may has many attached attachments.
*/ // */
attachments: { // attachments: {
relation: Model.ManyToManyRelation, // relation: Model.ManyToManyRelation,
modelClass: Document.default, // modelClass: Document.default,
join: { // join: {
from: 'bills.id', // from: 'bills.id',
through: { // through: {
from: 'document_links.modelId', // from: 'document_links.modelId',
to: 'document_links.documentId', // to: 'document_links.documentId',
}, // },
to: 'documents.id', // to: 'documents.id',
}, // },
filter(query) { // filter(query) {
query.where('model_ref', 'Bill'); // query.where('model_ref', 'Bill');
}, // },
}, // },
/** // /**
* Bill may belongs to matched bank transaction. // * Bill may belongs to matched bank transaction.
*/ // */
matchedBankTransaction: { // matchedBankTransaction: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: MatchedBankTransaction, // modelClass: MatchedBankTransaction,
join: { // join: {
from: 'bills.id', // from: 'bills.id',
to: 'matched_bank_transactions.referenceId', // to: 'matched_bank_transactions.referenceId',
}, // },
filter(query) { // filter(query) {
query.where('reference_type', 'Bill'); // query.where('reference_type', 'Bill');
}, // },
}, // },
}; // };
} // }
/** /**
* Retrieve the not found bills ids as array that associated to the given vendor. * Retrieve the not found bills ids as array that associated to the given vendor.

View File

@@ -1,9 +1,12 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Bill } from '../models/Bill'; import { Bill } from '../models/Bill';
@Injectable() @Injectable()
export class GetDueBills { export class GetDueBills {
constructor(private billModel: typeof Bill) {} constructor(
@Inject(Bill.name)
private billModel: typeof Bill,
) {}
/** /**
* Retrieve all due bills or for specific given vendor id. * Retrieve all due bills or for specific given vendor id.

View File

@@ -13,6 +13,7 @@ import { ActivateBranches } from './commands/ActivateBranchesFeature.service';
import { BranchesApplication } from './BranchesApplication.service'; import { BranchesApplication } from './BranchesApplication.service';
import { BranchesSettingsService } from './BranchesSettings'; import { BranchesSettingsService } from './BranchesSettings';
import { BranchCommandValidator } from './commands/BranchCommandValidator.service'; import { BranchCommandValidator } from './commands/BranchCommandValidator.service';
import { BranchTransactionDTOTransformer } from './integrations/BranchTransactionDTOTransform';
@Module({ @Module({
imports: [TenancyDatabaseModule], imports: [TenancyDatabaseModule],
@@ -29,7 +30,9 @@ import { BranchCommandValidator } from './commands/BranchCommandValidator.servic
BranchesSettingsService, BranchesSettingsService,
TenancyContext, TenancyContext,
TransformerInjectable, TransformerInjectable,
BranchCommandValidator BranchCommandValidator,
BranchTransactionDTOTransformer
], ],
exports: [BranchTransactionDTOTransformer],
}) })
export class BranchesModule {} export class BranchesModule {}

View File

@@ -4,5 +4,6 @@ import { ChromiumlyTenancy } from './ChromiumlyTenancy.service';
@Module({ @Module({
providers: [ChromiumlyHtmlConvert, ChromiumlyTenancy], providers: [ChromiumlyHtmlConvert, ChromiumlyTenancy],
exports: [ChromiumlyHtmlConvert, ChromiumlyTenancy],
}) })
export class ChromiumlyTenancyModule {} export class ChromiumlyTenancyModule {}

View File

@@ -181,12 +181,12 @@ export class CreditNote extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const AccountTransaction = require('models/AccountTransaction'); const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
const ItemEntry = require('models/ItemEntry'); const { ItemEntry } = require('../../TransactionItemEntry/models/ItemEntry');
const Customer = require('models/Customer'); const { Customer } = require('../../Customers/models/Customer');
const Branch = require('models/Branch'); const { Branch } = require('../../Branches/models/Branch.model');
const Document = require('models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
const Warehouse = require('models/Warehouse'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
return { return {
/** /**
@@ -194,7 +194,7 @@ export class CreditNote extends BaseModel {
*/ */
entries: { entries: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: ItemEntry.default, modelClass: ItemEntry,
join: { join: {
from: 'credit_notes.id', from: 'credit_notes.id',
to: 'items_entries.referenceId', to: 'items_entries.referenceId',
@@ -210,7 +210,7 @@ export class CreditNote extends BaseModel {
*/ */
customer: { customer: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Customer.default, modelClass: Customer,
join: { join: {
from: 'credit_notes.customerId', from: 'credit_notes.customerId',
to: 'contacts.id', to: 'contacts.id',
@@ -225,7 +225,7 @@ export class CreditNote extends BaseModel {
*/ */
transactions: { transactions: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: AccountTransaction.default, modelClass: AccountTransaction,
join: { join: {
from: 'credit_notes.id', from: 'credit_notes.id',
to: 'accounts_transactions.referenceId', to: 'accounts_transactions.referenceId',
@@ -240,7 +240,7 @@ export class CreditNote extends BaseModel {
*/ */
branch: { branch: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Branch.default, modelClass: Branch,
join: { join: {
from: 'credit_notes.branchId', from: 'credit_notes.branchId',
to: 'branches.id', to: 'branches.id',
@@ -252,7 +252,7 @@ export class CreditNote extends BaseModel {
*/ */
warehouse: { warehouse: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default, modelClass: Warehouse,
join: { join: {
from: 'credit_notes.warehouseId', from: 'credit_notes.warehouseId',
to: 'warehouses.id', to: 'warehouses.id',
@@ -264,7 +264,7 @@ export class CreditNote extends BaseModel {
*/ */
attachments: { attachments: {
relation: Model.ManyToManyRelation, relation: Model.ManyToManyRelation,
modelClass: Document.default, modelClass: Document,
join: { join: {
from: 'credit_notes.id', from: 'credit_notes.id',
through: { through: {

View File

@@ -24,13 +24,13 @@ export class CreditNoteAppliedInvoice extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const SaleInvoice = require('models/SaleInvoice'); const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
const CreditNote = require('models/CreditNote'); const { CreditNote } = require('../../CreditNotes/models/CreditNote');
return { return {
saleInvoice: { saleInvoice: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: SaleInvoice.default, modelClass: SaleInvoice,
join: { join: {
from: 'credit_note_applied_invoice.invoiceId', from: 'credit_note_applied_invoice.invoiceId',
to: 'sales_invoices.id', to: 'sales_invoices.id',
@@ -39,7 +39,7 @@ export class CreditNoteAppliedInvoice extends BaseModel {
creditNote: { creditNote: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: CreditNote.default, modelClass: CreditNote,
join: { join: {
from: 'credit_note_applied_invoice.creditNoteId', from: 'credit_note_applied_invoice.creditNoteId',
to: 'credit_notes.id', to: 'credit_notes.id',

View File

@@ -13,8 +13,10 @@ import {
ICustomerNewDTO, ICustomerNewDTO,
ICustomerOpeningBalanceEditDTO, ICustomerOpeningBalanceEditDTO,
} from './types/Customers.types'; } from './types/Customers.types';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('customers') @Controller('customers')
@PublicRoute()
export class CustomersController { export class CustomersController {
constructor(private customersApplication: CustomersApplication) {} constructor(private customersApplication: CustomersApplication) {}

View File

@@ -19,7 +19,7 @@ export class DeleteCustomer {
constructor( constructor(
private uow: UnitOfWork, private uow: UnitOfWork,
private eventPublisher: EventEmitter2, private eventPublisher: EventEmitter2,
@Inject(Customer.name) private contactModel: typeof Customer, @Inject(Customer.name) private customerModel: typeof Customer,
) {} ) {}
/** /**
@@ -31,10 +31,9 @@ export class DeleteCustomer {
customerId: number, customerId: number,
): Promise<void> { ): Promise<void> {
// Retrieve the customer or throw not found service error. // Retrieve the customer or throw not found service error.
const oldCustomer = await this.contactModel const oldCustomer = await this.customerModel
.query() .query()
.findById(customerId) .findById(customerId)
.modify('customer')
.throwIfNotFound(); .throwIfNotFound();
// .queryAndThrowIfHasRelations({ // .queryAndThrowIfHasRelations({
// type: ERRORS.CUSTOMER_HAS_TRANSACTIONS, // type: ERRORS.CUSTOMER_HAS_TRANSACTIONS,
@@ -49,7 +48,7 @@ export class DeleteCustomer {
// Deletes the customer and associated entities under UOW transaction. // Deletes the customer and associated entities under UOW transaction.
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Delete the customer from the storage. // Delete the customer from the storage.
await this.contactModel.query(trx).findById(customerId).delete(); await this.customerModel.query(trx).findById(customerId).delete();
// Throws `onCustomerDeleted` event. // Throws `onCustomerDeleted` event.
await this.eventPublisher.emitAsync(events.customers.onDeleted, { await this.eventPublisher.emitAsync(events.customers.onDeleted, {

View File

@@ -23,7 +23,7 @@ export class EditCustomer {
private uow: UnitOfWork, private uow: UnitOfWork,
private eventPublisher: EventEmitter2, private eventPublisher: EventEmitter2,
private customerDTO: CreateEditCustomerDTO, private customerDTO: CreateEditCustomerDTO,
@Inject(Customer.name) private contactModel: typeof Customer, @Inject(Customer.name) private customerModel: typeof Customer,
) {} ) {}
/** /**
@@ -37,10 +37,9 @@ export class EditCustomer {
customerDTO: ICustomerEditDTO, customerDTO: ICustomerEditDTO,
): Promise<Customer> { ): Promise<Customer> {
// Retrieve the customer or throw not found error. // Retrieve the customer or throw not found error.
const oldCustomer = await this.contactModel const oldCustomer = await this.customerModel
.query() .query()
.findById(customerId) .findById(customerId)
.modify('customer')
.throwIfNotFound(); .throwIfNotFound();
// Transforms the given customer DTO to object. // Transforms the given customer DTO to object.
@@ -56,7 +55,7 @@ export class EditCustomer {
} as ICustomerEventEditingPayload); } as ICustomerEventEditingPayload);
// Edits the customer details on the storage. // Edits the customer details on the storage.
const customer = await this.contactModel const customer = await this.customerModel
.query() .query()
.updateAndFetchById(customerId, { .updateAndFetchById(customerId, {
...customerObj, ...customerObj,

View File

@@ -4,7 +4,7 @@ import { CommandExpenseValidator } from './CommandExpenseValidator.service';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Expense } from '../models/Expense.model'; import { Expense } from '../models/Expense.model';
import ExpenseCategory from '../models/ExpenseCategory.model'; import { ExpenseCategory } from '../models/ExpenseCategory.model';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { import {
IExpenseEventDeletePayload, IExpenseEventDeletePayload,

View File

@@ -1,7 +1,7 @@
import { Model } from 'objection'; import { Model } from 'objection';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
export default class ExpenseCategory extends BaseModel { export class ExpenseCategory extends BaseModel {
amount!: number; amount!: number;
allocatedCostAmount!: number; allocatedCostAmount!: number;
@@ -31,12 +31,12 @@ export default class ExpenseCategory extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const Account = require('models/Account'); const { Account } = require('../../Accounts/models/Account.model');
return { return {
expenseAccount: { expenseAccount: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Account.default, modelClass: Account,
join: { join: {
from: 'expense_transaction_categories.expenseAccountId', from: 'expense_transaction_categories.expenseAccountId',
to: 'accounts.id', to: 'accounts.id',

View File

@@ -1,6 +1,6 @@
import { Transformer } from '@/modules/Transformer/Transformer'; import { Transformer } from '@/modules/Transformer/Transformer';
import ExpenseCategory from '../models/ExpenseCategory.model'; import { ExpenseCategory } from '../models/ExpenseCategory.model';
export class ExpenseCategoryTransformer extends Transformer { export class ExpenseCategoryTransformer extends Transformer {
/** /**

View File

@@ -12,6 +12,7 @@ import { ItemsApplicationService } from './ItemsApplication.service';
import { ItemTransactionsService } from './ItemTransactions.service'; import { ItemTransactionsService } from './ItemTransactions.service';
import { GetItemService } from './GetItem.service'; import { GetItemService } from './GetItem.service';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { ItemsEntriesService } from './ItemsEntries.service';
@Module({ @Module({
imports: [TenancyDatabaseModule], imports: [TenancyDatabaseModule],
@@ -28,6 +29,8 @@ import { TransformerInjectable } from '../Transformer/TransformerInjectable.serv
ItemTransactionsService, ItemTransactionsService,
TenancyContext, TenancyContext,
TransformerInjectable, TransformerInjectable,
ItemsEntriesService
], ],
exports: [ItemsEntriesService]
}) })
export class ItemsModule {} export class ItemsModule {}

View File

@@ -213,7 +213,7 @@ export class ItemsEntriesService {
/** /**
* Sets the cost/sell accounts to the invoice entries. * Sets the cost/sell accounts to the invoice entries.
*/ */
public async setItemsEntriesDefaultAccounts(entries: ItemEntry[]) { public setItemsEntriesDefaultAccounts = async (entries: ItemEntry[]) => {
const entriesItemsIds = entries.map((e) => e.itemId); const entriesItemsIds = entries.map((e) => e.itemId);
const items = await this.itemModel.query().whereIn('id', entriesItemsIds); const items = await this.itemModel.query().whereIn('id', entriesItemsIds);

View File

@@ -13,6 +13,9 @@ export class ItemEntry extends BaseModel {
public costAccountId: number; public costAccountId: number;
public taxRateId: number; public taxRateId: number;
public landedCost!: boolean;
public allocatedCostAmount!: number;
/** /**
* Table name. * Table name.
* @returns {string} * @returns {string}

View File

@@ -1,7 +1,7 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import composeAsync from 'async/compose'; import * as composeAsync from 'async/compose';
import { import {
IPaymentReceivedCreateDTO, IPaymentReceivedCreateDTO,
IPaymentReceivedEditDTO, IPaymentReceivedEditDTO,
@@ -78,6 +78,6 @@ export class PaymentReceiveDTOTransformer {
return R.compose( return R.compose(
this.branchDTOTransform.transformDTO<PaymentReceived> this.branchDTOTransform.transformDTO<PaymentReceived>
)(initialAsyncDTO); )(initialAsyncDTO) as PaymentReceived;
} }
} }

View File

@@ -6,6 +6,7 @@ import { Model, mixin } from 'objection';
// import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants'; // import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
// import ModelSearchable from './ModelSearchable'; // import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { PaymentReceivedEntry } from './PaymentReceivedEntry';
export class PaymentReceived extends BaseModel { export class PaymentReceived extends BaseModel {
customerId: number; customerId: number;
@@ -25,6 +26,8 @@ export class PaymentReceived extends BaseModel {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
entries?: PaymentReceivedEntry[];
/** /**
* Table name. * Table name.
*/ */
@@ -65,17 +68,17 @@ export class PaymentReceived extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const PaymentReceiveEntry = require('models/PaymentReceiveEntry'); const { PaymentReceivedEntry } = require('./PaymentReceivedEntry');
const AccountTransaction = require('models/AccountTransaction'); const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
const Customer = require('models/Customer'); const { Customer } = require('../../Customers/models/Customer');
const Account = require('models/Account'); const { Account } = require('../../Accounts/models/Account.model');
const Branch = require('models/Branch'); const { Branch } = require('../../Branches/models/Branch.model');
const Document = require('models/Document'); // const Document = require('../../Documents/models/Document');
return { return {
customer: { customer: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Customer.default, modelClass: Customer,
join: { join: {
from: 'payment_receives.customerId', from: 'payment_receives.customerId',
to: 'contacts.id', to: 'contacts.id',
@@ -84,17 +87,19 @@ export class PaymentReceived extends BaseModel {
query.where('contact_service', 'customer'); query.where('contact_service', 'customer');
}, },
}, },
depositAccount: { depositAccount: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Account.default, modelClass: Account,
join: { join: {
from: 'payment_receives.depositAccountId', from: 'payment_receives.depositAccountId',
to: 'accounts.id', to: 'accounts.id',
}, },
}, },
entries: { entries: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: PaymentReceiveEntry.default, modelClass: PaymentReceivedEntry,
join: { join: {
from: 'payment_receives.id', from: 'payment_receives.id',
to: 'payment_receives_entries.paymentReceiveId', to: 'payment_receives_entries.paymentReceiveId',
@@ -103,9 +108,10 @@ export class PaymentReceived extends BaseModel {
query.orderBy('index', 'ASC'); query.orderBy('index', 'ASC');
}, },
}, },
transactions: { transactions: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: AccountTransaction.default, modelClass: AccountTransaction,
join: { join: {
from: 'payment_receives.id', from: 'payment_receives.id',
to: 'accounts_transactions.referenceId', to: 'accounts_transactions.referenceId',
@@ -120,7 +126,7 @@ export class PaymentReceived extends BaseModel {
*/ */
branch: { branch: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Branch.default, modelClass: Branch,
join: { join: {
from: 'payment_receives.branchId', from: 'payment_receives.branchId',
to: 'branches.id', to: 'branches.id',
@@ -130,21 +136,21 @@ export class PaymentReceived extends BaseModel {
/** /**
* Payment transaction may has many attached attachments. * Payment transaction may has many attached attachments.
*/ */
attachments: { // attachments: {
relation: Model.ManyToManyRelation, // relation: Model.ManyToManyRelation,
modelClass: Document.default, // modelClass: Document.default,
join: { // join: {
from: 'payment_receives.id', // from: 'payment_receives.id',
through: { // through: {
from: 'document_links.modelId', // from: 'document_links.modelId',
to: 'document_links.documentId', // to: 'document_links.documentId',
}, // },
to: 'documents.id', // to: 'documents.id',
}, // },
filter(query) { // filter(query) {
query.where('model_ref', 'PaymentReceive'); // query.where('model_ref', 'PaymentReceive');
}, // },
}, // },
}; };
} }

View File

@@ -1,10 +1,14 @@
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { Model, mixin } from 'objection'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { Model } from 'objection';
export class PaymentReceivedEntry extends BaseModel { export class PaymentReceivedEntry extends BaseModel {
paymentReceiveId: number; paymentReceiveId: number;
invoiceId: number; invoiceId: number;
paymentAmount: number; paymentAmount: number;
index: number;
invoice?: SaleInvoice;
/** /**
* Table name * Table name
@@ -24,15 +28,15 @@ export class PaymentReceivedEntry extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const PaymentReceive = require('models/PaymentReceive'); const { PaymentReceived } = require('./PaymentReceived');
const SaleInvoice = require('models/SaleInvoice'); const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
return { return {
/** /**
*/ */
payment: { payment: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: PaymentReceive.default, modelClass: PaymentReceived,
join: { join: {
from: 'payment_receives_entries.paymentReceiveId', from: 'payment_receives_entries.paymentReceiveId',
to: 'payment_receives.id', to: 'payment_receives.id',
@@ -44,7 +48,7 @@ export class PaymentReceivedEntry extends BaseModel {
*/ */
invoice: { invoice: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: SaleInvoice.default, modelClass: SaleInvoice,
join: { join: {
from: 'payment_receives_entries.invoiceId', from: 'payment_receives_entries.invoiceId',
to: 'sales_invoices.id', to: 'sales_invoices.id',

View File

@@ -1,8 +1,7 @@
import { Inject, Service } from 'typedi'; import { Inject, Injectable } from '@nestjs/common';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { IPaymentReceivePageEntry } from '../types/PaymentReceived.types'; import { IPaymentReceivePageEntry } from '../types/PaymentReceived.types';
import { ERRORS } from '../constants'; import { ERRORS } from '../constants';
import { Injectable } from '@nestjs/common';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { PaymentReceived } from '../models/PaymentReceived'; import { PaymentReceived } from '../models/PaymentReceived';
import { ServiceError } from '@/modules/Items/ServiceError'; import { ServiceError } from '@/modules/Items/ServiceError';

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
import { PaymentReceived } from './models/PaymentReceived'; import { PaymentReceived } from './models/PaymentReceived';
import { import {
PaymentReceivedPdfTemplateAttributes, PaymentReceivedPdfTemplateAttributes,

View File

@@ -0,0 +1,7 @@
export class PaymentServicesModule {}

View File

@@ -0,0 +1,60 @@
import { BaseModel } from "@/models/Model";
export class PaymentIntegration extends BaseModel {
paymentEnabled!: boolean;
payoutEnabled!: boolean;
static get tableName() {
return 'payment_integrations';
}
static get idColumn() {
return 'id';
}
static get virtualAttributes() {
return ['fullEnabled'];
}
static get jsonAttributes() {
return ['options'];
}
get fullEnabled() {
return this.paymentEnabled && this.payoutEnabled;
}
static get modifiers() {
return {
/**
* Query to filter enabled payment and payout.
*/
fullEnabled(query) {
query.where('paymentEnabled', true).andWhere('payoutEnabled', true);
},
};
}
static get jsonSchema() {
return {
type: 'object',
required: ['name', 'service'],
properties: {
id: { type: 'integer' },
service: { type: 'string' },
paymentEnabled: { type: 'boolean' },
payoutEnabled: { type: 'boolean' },
accountId: { type: 'string' },
options: {
type: 'object',
properties: {
bankAccountId: { type: 'number' },
clearingAccountId: { type: 'number' },
},
},
createdAt: { type: 'string', format: 'date-time' },
updatedAt: { type: 'string', format: 'date-time' },
},
};
}
}

View File

@@ -0,0 +1,47 @@
import { Model } from 'objection';
import { BaseModel } from '@/models/Model';
export class TransactionPaymentServiceEntry extends BaseModel {
/**
* Table name
*/
static get tableName() {
return 'transactions_payment_methods';
}
/**
* Json schema of the model.
*/
static get jsonSchema() {
return {
type: 'object',
required: ['paymentIntegrationId'],
properties: {
id: { type: 'integer' },
referenceId: { type: 'integer' },
referenceType: { type: 'string' },
paymentIntegrationId: { type: 'integer' },
enable: { type: 'boolean' },
options: { type: 'object' },
},
};
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const { PaymentIntegration } = require('./PaymentIntegration.model');
return {
paymentIntegration: {
relation: Model.BelongsToOneRelation,
modelClass: PaymentIntegration,
join: {
from: 'transactions_payment_methods.paymentIntegrationId',
to: 'payment_integrations.id',
},
},
};
}
}

View File

@@ -9,8 +9,15 @@ import { EditPdfTemplateService } from './commands/EditPdfTemplate.service';
import { PdfTemplateApplication } from './PdfTemplate.application'; import { PdfTemplateApplication } from './PdfTemplate.application';
import { PdfTemplatesController } from './PdfTemplates.controller'; import { PdfTemplatesController } from './PdfTemplates.controller';
import { GetPdfTemplateService } from './queries/GetPdfTemplate.service'; import { GetPdfTemplateService } from './queries/GetPdfTemplate.service';
import { BrandingTemplateDTOTransformer } from './BrandingTemplateDTOTransformer';
import { GetOrganizationBrandingAttributesService } from './queries/GetOrganizationBrandingAttributes.service';
@Module({ @Module({
exports: [
GetPdfTemplateService,
BrandingTemplateDTOTransformer,
GetOrganizationBrandingAttributesService,
],
imports: [TenancyDatabaseModule], imports: [TenancyDatabaseModule],
controllers: [PdfTemplatesController], controllers: [PdfTemplatesController],
providers: [ providers: [
@@ -22,6 +29,8 @@ import { GetPdfTemplateService } from './queries/GetPdfTemplate.service';
AssignPdfTemplateDefaultService, AssignPdfTemplateDefaultService,
TenancyContext, TenancyContext,
TransformerInjectable, TransformerInjectable,
BrandingTemplateDTOTransformer,
GetOrganizationBrandingAttributesService,
], ],
}) })
export class PdfTemplatesModule {} export class PdfTemplatesModule {}

View File

@@ -208,23 +208,23 @@ export class SaleEstimate extends BaseModel {
*/ */
static get relationMappings() { static get relationMappings() {
const { ItemEntry } = require('../../Items/models/ItemEntry'); const { ItemEntry } = require('../../Items/models/ItemEntry');
// const Customer = require('models/Customer'); const { Customer } = require('../../Customers/models/Customer');
// const Branch = require('models/Branch'); const { Branch } = require('../../Branches/models/Branch.model');
// const Warehouse = require('models/Warehouse'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
// const Document = require('models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
return { return {
// customer: { customer: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Customer.default, modelClass: Customer,
// join: { join: {
// from: 'sales_estimates.customerId', from: 'sales_estimates.customerId',
// to: 'contacts.id', to: 'contacts.id',
// }, },
// filter(query) { filter(query) {
// query.where('contact_service', 'customer'); query.where('contact_service', 'customer');
// }, },
// }, },
entries: { entries: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: ItemEntry, modelClass: ItemEntry,
@@ -239,48 +239,48 @@ export class SaleEstimate extends BaseModel {
}, },
}, },
// /** /**
// * Sale estimate may belongs to branch. * Sale estimate may belongs to branch.
// */ */
// branch: { branch: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Branch.default, modelClass: Branch,
// join: { join: {
// from: 'sales_estimates.branchId', from: 'sales_estimates.branchId',
// to: 'branches.id', to: 'branches.id',
// }, },
// }, },
// /** /**
// * Sale estimate may has associated warehouse. * Sale estimate may has associated warehouse.
// */ */
// warehouse: { warehouse: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Warehouse.default, modelClass: Warehouse,
// join: { join: {
// from: 'sales_estimates.warehouseId', from: 'sales_estimates.warehouseId',
// to: 'warehouses.id', to: 'warehouses.id',
// }, },
// }, },
// /** /**
// * Sale estimate transaction may has many attached attachments. * Sale estimate transaction may has many attached attachments.
// */ */
// attachments: { attachments: {
// relation: Model.ManyToManyRelation, relation: Model.ManyToManyRelation,
// modelClass: Document.default, modelClass: Document,
// join: { join: {
// from: 'sales_estimates.id', from: 'sales_estimates.id',
// through: { through: {
// from: 'document_links.modelId', from: 'document_links.modelId',
// to: 'document_links.documentId', to: 'document_links.documentId',
// }, },
// to: 'documents.id', to: 'documents.id',
// }, },
// filter(query) { filter(query) {
// query.where('model_ref', 'SaleEstimate'); query.where('model_ref', 'SaleEstimate');
// }, },
// }, },
}; };
} }

View File

@@ -1,8 +1,9 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IItemEntryDTO } from '../TransactionItemEntry/ItemEntry.types'; import { IItemEntryDTO } from '../TransactionItemEntry/ItemEntry.types';
import { AttachmentLinkDTO } from '../Attachments/Attachments.types'; import { AttachmentLinkDTO } from '../Attachments/Attachments.types';
import SaleInvoice from './models/SaleInvoice'; import { SaleInvoice } from './models/SaleInvoice';
import { SystemUser } from '../System/models/SystemUser'; // import SaleInvoice from './models/SaleInvoice';
// import { SystemUser } from '../System/models/SystemUser';
// import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces'; // import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
// import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable'; // import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
// import { IDynamicListFilter } from '@/interfaces/DynamicFilter'; // import { IDynamicListFilter } from '@/interfaces/DynamicFilter';

View File

@@ -16,8 +16,10 @@ import {
InvoiceNotificationType, InvoiceNotificationType,
} from './SaleInvoice.types'; } from './SaleInvoice.types';
import { SaleInvoiceApplication } from './SaleInvoices.application'; import { SaleInvoiceApplication } from './SaleInvoices.application';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('sale-invoices') @Controller('sale-invoices')
@PublicRoute()
export class SaleInvoicesController { export class SaleInvoicesController {
constructor(private saleInvoiceApplication: SaleInvoiceApplication) {} constructor(private saleInvoiceApplication: SaleInvoiceApplication) {}

View File

@@ -16,10 +16,33 @@ import { GetSaleInvoicesPayable } from './queries/GetSaleInvoicesPayable.service
import { GetSaleInvoiceState } from './queries/GetSaleInvoiceState.service'; import { GetSaleInvoiceState } from './queries/GetSaleInvoiceState.service';
import { SaleInvoicePdf } from './queries/SaleInvoicePdf.service'; import { SaleInvoicePdf } from './queries/SaleInvoicePdf.service';
import { SaleInvoiceApplication } from './SaleInvoices.application'; import { SaleInvoiceApplication } from './SaleInvoices.application';
import { ItemsEntriesService } from '../Items/ItemsEntries.service';
import { CommandSaleInvoiceValidators } from './commands/CommandSaleInvoiceValidators.service';
import { CommandSaleInvoiceDTOTransformer } from './commands/CommandSaleInvoiceDTOTransformer.service';
import { SaleEstimateValidators } from '../SaleEstimates/commands/SaleEstimateValidators.service';
import { UnlinkConvertedSaleEstimate } from '../SaleEstimates/commands/UnlinkConvertedSaleEstimate.service';
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
import { SaleInvoicePdfTemplate } from './queries/SaleInvoicePdfTemplate.service';
import { WriteoffSaleInvoice } from './commands/WriteoffSaleInvoice.service';
import { GetInvoicePaymentsService } from './queries/GetInvoicePayments.service';
import { BranchesModule } from '../Branches/Branches.module';
import { WarehousesModule } from '../Warehouses/Warehouses.module';
import { TaxRatesModule } from '../TaxRates/TaxRate.module';
import { SaleInvoicesController } from './SaleInvoices.controller';
@Module({ @Module({
imports: [TenancyDatabaseModule], imports: [
controllers: [], TenancyDatabaseModule,
PdfTemplatesModule,
AutoIncrementOrdersModule,
ChromiumlyTenancyModule,
BranchesModule,
WarehousesModule,
TaxRatesModule,
],
controllers: [SaleInvoicesController],
providers: [ providers: [
CreateSaleInvoice, CreateSaleInvoice,
EditSaleInvoice, EditSaleInvoice,
@@ -37,6 +60,14 @@ import { SaleInvoiceApplication } from './SaleInvoices.application';
SaleInvoiceApplication, SaleInvoiceApplication,
TenancyContext, TenancyContext,
TransformerInjectable, TransformerInjectable,
ItemsEntriesService,
CommandSaleInvoiceValidators,
CommandSaleInvoiceDTOTransformer,
SaleEstimateValidators,
UnlinkConvertedSaleEstimate,
SaleInvoicePdfTemplate,
WriteoffSaleInvoice,
GetInvoicePaymentsService,
], ],
}) })
export class SaleInvoicesModule {} export class SaleInvoicesModule {}

View File

@@ -1,8 +1,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import * as R from 'ramda'; import * as R from 'ramda';
import moment from 'moment'; import * as moment from 'moment';
import composeAsync from 'async/compose'; import * as composeAsync from 'async/compose';
import { import {
ISaleInvoiceCreateDTO, ISaleInvoiceCreateDTO,
ISaleInvoiceEditDTO, ISaleInvoiceEditDTO,

View File

@@ -1,11 +1,14 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { SaleInvoice } from '../models/SaleInvoice'; import { SaleInvoice } from '../models/SaleInvoice';
import { ServiceError } from '@/modules/Items/ServiceError'; import { ServiceError } from '@/modules/Items/ServiceError';
import { ERRORS } from '../constants'; import { ERRORS } from '../constants';
@Injectable() @Injectable()
export class CommandSaleInvoiceValidators { export class CommandSaleInvoiceValidators {
constructor(private readonly saleInvoiceModel: typeof SaleInvoice) {} constructor(
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: typeof SaleInvoice,
) {}
/** /**
* Validates the given invoice is existance. * Validates the given invoice is existance.

View File

@@ -14,22 +14,23 @@ import { ServiceError } from '@/modules/Items/ServiceError';
import { ERRORS } from '../constants'; import { ERRORS } from '../constants';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { PaymentReceivedEntry } from '@/modules/PaymentReceived/models/PaymentReceivedEntry'; import { PaymentReceivedEntry } from '@/modules/PaymentReceived/models/PaymentReceivedEntry';
import CreditNoteAppliedInvoice from '@/modules/CreditNotes/models/CreditNoteAppliedInvoice'; import { CreditNoteAppliedInvoice } from '@/modules/CreditNotes/models/CreditNoteAppliedInvoice';
@Injectable() @Injectable()
export class DeleteSaleInvoice { export class DeleteSaleInvoice {
constructor( constructor(
@Inject(PaymentReceivedEntry)
private paymentReceivedEntryModel: typeof PaymentReceivedEntry,
@Inject(CreditNoteAppliedInvoice)
private creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
@Inject(SaleInvoice)
private saleInvoiceModel: typeof SaleInvoice,
private unlockEstimateFromInvoice: UnlinkConvertedSaleEstimate, private unlockEstimateFromInvoice: UnlinkConvertedSaleEstimate,
private eventPublisher: EventEmitter2, private eventPublisher: EventEmitter2,
private uow: UnitOfWork, private uow: UnitOfWork,
@Inject(PaymentReceivedEntry.name)
private paymentReceivedEntryModel: typeof PaymentReceivedEntry,
@Inject(CreditNoteAppliedInvoice.name)
private creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
@Inject(SaleInvoice.name)
private saleInvoiceModel: typeof SaleInvoice,
) {} ) {}
/** /**
@@ -42,7 +43,7 @@ export class DeleteSaleInvoice {
.query() .query()
.where('invoice_id', saleInvoiceId); .where('invoice_id', saleInvoiceId);
if (entries.length > 0) { if (entries.length > 0) {
throw new ServiceError(ERRORS.INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES); throw new ServiceError(ERRORS.INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES);
} }
return entries; return entries;

View File

@@ -16,8 +16,8 @@ export class GenerateShareLink {
private uow: UnitOfWork, private uow: UnitOfWork,
private eventPublisher: EventEmitter2, private eventPublisher: EventEmitter2,
private transformer: TransformerInjectable, private transformer: TransformerInjectable,
@Inject(SaleInvoice) private saleInvoiceModel: typeof SaleInvoice, @Inject(SaleInvoice.name) private saleInvoiceModel: typeof SaleInvoice,
@Inject(PaymentLink) private paymentLinkModel: typeof PaymentLink, @Inject(PaymentLink.name) private paymentLinkModel: typeof PaymentLink,
) {} ) {}
/** /**

View File

@@ -1,6 +1,6 @@
import { mixin, Model, raw } from 'objection'; import { mixin, Model, raw } from 'objection';
import { castArray, takeWhile } from 'lodash'; import { castArray, takeWhile } from 'lodash';
import moment from 'moment'; import moment, { MomentInput, unitOfTime } from 'moment';
// import TenantModel from 'models/TenantModel'; // import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting'; // import ModelSetting from './ModelSetting';
// import SaleInvoiceMeta from './SaleInvoice.Settings'; // import SaleInvoiceMeta from './SaleInvoice.Settings';
@@ -9,7 +9,7 @@ import moment from 'moment';
// import ModelSearchable from './ModelSearchable'; // import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
export class SaleInvoice extends BaseModel{ export class SaleInvoice extends BaseModel {
public taxAmountWithheld: number; public taxAmountWithheld: number;
public balance: number; public balance: number;
public paymentAmount: number; public paymentAmount: number;
@@ -19,7 +19,7 @@ export class SaleInvoice extends BaseModel{
public isInclusiveTax: boolean; public isInclusiveTax: boolean;
public dueDate: Date; public dueDate: Date;
public deliveredAt: Date; public deliveredAt: Date | string;
public currencyCode: string; public currencyCode: string;
public invoiceDate: Date; public invoiceDate: Date;
@@ -262,13 +262,18 @@ export class SaleInvoice extends BaseModel{
COALESCE(PAYMENT_AMOUNT, 0) - COALESCE(PAYMENT_AMOUNT, 0) -
COALESCE(WRITTENOFF_AMOUNT, 0) - COALESCE(WRITTENOFF_AMOUNT, 0) -
COALESCE(CREDITED_AMOUNT, 0) > 0 COALESCE(CREDITED_AMOUNT, 0) > 0
`) `),
); );
}, },
/** /**
* Filters the invoices between the given date range. * Filters the invoices between the given date range.
*/ */
filterDateRange(query, startDate, endDate, type = 'day') { filterDateRange(
query,
startDate: MomentInput,
endDate?: MomentInput,
type: unitOfTime.StartOf = 'day',
) {
const dateFormat = 'YYYY-MM-DD'; const dateFormat = 'YYYY-MM-DD';
const fromDate = moment(startDate).startOf(type).format(dateFormat); const fromDate = moment(startDate).startOf(type).format(dateFormat);
const toDate = moment(endDate).endOf(type).format(dateFormat); const toDate = moment(endDate).endOf(type).format(dateFormat);
@@ -418,213 +423,221 @@ export class SaleInvoice extends BaseModel{
/** /**
* Relationship mapping. * Relationship mapping.
*/ */
// static get relationMappings() { static get relationMappings() {
// const AccountTransaction = require('models/AccountTransaction'); const {
// const ItemEntry = require('models/ItemEntry'); AccountTransaction,
// const Customer = require('models/Customer'); } = require('../../Accounts/models/AccountTransaction.model');
// const InventoryCostLotTracker = require('models/InventoryCostLotTracker'); const {
// const PaymentReceiveEntry = require('models/PaymentReceiveEntry'); ItemEntry,
// const Branch = require('models/Branch'); } = require('../../TransactionItemEntry/models/ItemEntry');
// const Warehouse = require('models/Warehouse'); const { Customer } = require('../../Customers/models/Customer');
// const Account = require('models/Account'); // const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
// const TaxRateTransaction = require('models/TaxRateTransaction'); const {
// const Document = require('models/Document'); PaymentReceivedEntry,
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); } = require('../../PaymentReceived/models/PaymentReceivedEntry');
// const { const { Branch } = require('../../Branches/models/Branch.model');
// TransactionPaymentServiceEntry, const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
// } = require('models/TransactionPaymentServiceEntry'); const { Account } = require('../../Accounts/models/Account.model');
// const { PdfTemplate } = require('models/PdfTemplate'); // const TaxRateTransaction = require('../../Tax');
const { Document } = require('../../ChromiumlyTenancy/models/Document');
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
const {
TransactionPaymentServiceEntry,
} = require('../../PaymentServices/models/TransactionPaymentServiceEntry.model');
const {
PdfTemplateModel,
} = require('../../PdfTemplate/models/PdfTemplate');
// return { return {
// /** /**
// * Sale invoice associated entries. * Sale invoice associated entries.
// */ */
// entries: { entries: {
// relation: Model.HasManyRelation, relation: Model.HasManyRelation,
// modelClass: ItemEntry.default, modelClass: ItemEntry,
// join: { join: {
// from: 'sales_invoices.id', from: 'sales_invoices.id',
// to: 'items_entries.referenceId', to: 'items_entries.referenceId',
// }, },
// filter(builder) { filter(builder) {
// builder.where('reference_type', 'SaleInvoice'); builder.where('reference_type', 'SaleInvoice');
// builder.orderBy('index', 'ASC'); builder.orderBy('index', 'ASC');
// }, },
// }, },
// /** /**
// * Belongs to customer model. * Belongs to customer model.
// */ */
// customer: { customer: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Customer.default, modelClass: Customer,
// join: { join: {
// from: 'sales_invoices.customerId', from: 'sales_invoices.customerId',
// to: 'contacts.id', to: 'contacts.id',
// }, },
// filter(query) { filter(query) {
// query.where('contact_service', 'Customer'); query.where('contact_service', 'Customer');
// }, },
// }, },
// /** /**
// * Invoice has associated account transactions. * Invoice has associated account transactions.
// */ */
// transactions: { transactions: {
// relation: Model.HasManyRelation, relation: Model.HasManyRelation,
// modelClass: AccountTransaction.default, modelClass: AccountTransaction,
// join: { join: {
// from: 'sales_invoices.id', from: 'sales_invoices.id',
// to: 'accounts_transactions.referenceId', to: 'accounts_transactions.referenceId',
// }, },
// filter(builder) { filter(builder) {
// builder.where('reference_type', 'SaleInvoice'); builder.where('reference_type', 'SaleInvoice');
// }, },
// }, },
// /** /**
// * Invoice may has associated cost transactions. * Invoice may has associated cost transactions.
// */ */
// costTransactions: { // costTransactions: {
// relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
// modelClass: InventoryCostLotTracker.default, // modelClass: InventoryCostLotTracker.default,
// join: { // join: {
// from: 'sales_invoices.id', // from: 'sales_invoices.id',
// to: 'inventory_cost_lot_tracker.transactionId', // to: 'inventory_cost_lot_tracker.transactionId',
// }, // },
// filter(builder) { // filter(builder) {
// builder.where('transaction_type', 'SaleInvoice'); // builder.where('transaction_type', 'SaleInvoice');
// }, // },
// }, // },
// /** /**
// * Invoice may has associated payment entries. * Invoice may has associated payment entries.
// */ */
// paymentEntries: { paymentEntries: {
// relation: Model.HasManyRelation, relation: Model.HasManyRelation,
// modelClass: PaymentReceiveEntry.default, modelClass: PaymentReceivedEntry,
// join: { join: {
// from: 'sales_invoices.id', from: 'sales_invoices.id',
// to: 'payment_receives_entries.invoiceId', to: 'payment_receives_entries.invoiceId',
// }, },
// }, },
// /** /**
// * Invoice may has associated branch. * Invoice may has associated branch.
// */ */
// branch: { branch: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Branch.default, modelClass: Branch,
// join: { join: {
// from: 'sales_invoices.branchId', from: 'sales_invoices.branchId',
// to: 'branches.id', to: 'branches.id',
// }, },
// }, },
// /** /**
// * Invoice may has associated warehouse. * Invoice may has associated warehouse.
// */ */
// warehouse: { warehouse: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Warehouse.default, modelClass: Warehouse,
// join: { join: {
// from: 'sales_invoices.warehouseId', from: 'sales_invoices.warehouseId',
// to: 'warehouses.id', to: 'warehouses.id',
// }, },
// }, },
// /** /**
// * Invoice may has associated written-off expense account. * Invoice may has associated written-off expense account.
// */ */
// writtenoffExpenseAccount: { writtenoffExpenseAccount: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Account.default, modelClass: Account,
// join: { join: {
// from: 'sales_invoices.writtenoffExpenseAccountId', from: 'sales_invoices.writtenoffExpenseAccountId',
// to: 'accounts.id', to: 'accounts.id',
// }, },
// }, },
// /** /**
// * Invoice may has associated tax rate transactions. * Invoice may has associated tax rate transactions.
// */ */
// taxes: { // taxes: {
// relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
// modelClass: TaxRateTransaction.default, // modelClass: TaxRateTransaction.default,
// join: { // join: {
// from: 'sales_invoices.id', // from: 'sales_invoices.id',
// to: 'tax_rate_transactions.referenceId', // to: 'tax_rate_transactions.referenceId',
// }, // },
// filter(builder) { // filter(builder) {
// builder.where('reference_type', 'SaleInvoice'); // builder.where('reference_type', 'SaleInvoice');
// }, // },
// }, // },
// /** /**
// * Sale invoice transaction may has many attached attachments. * Sale invoice transaction may has many attached attachments.
// */ */
// attachments: { attachments: {
// relation: Model.ManyToManyRelation, relation: Model.ManyToManyRelation,
// modelClass: Document.default, modelClass: Document,
// join: { join: {
// from: 'sales_invoices.id', from: 'sales_invoices.id',
// through: { through: {
// from: 'document_links.modelId', from: 'document_links.modelId',
// to: 'document_links.documentId', to: 'document_links.documentId',
// }, },
// to: 'documents.id', to: 'documents.id',
// }, },
// filter(query) { filter(query) {
// query.where('model_ref', 'SaleInvoice'); query.where('model_ref', 'SaleInvoice');
// }, },
// }, },
// /** /**
// * Sale invocie may belongs to matched bank transaction. * Sale invocie may belongs to matched bank transaction.
// */ */
// matchedBankTransaction: { // matchedBankTransaction: {
// relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
// modelClass: MatchedBankTransaction, // modelClass: MatchedBankTransaction,
// join: { // join: {
// from: 'sales_invoices.id', // from: 'sales_invoices.id',
// to: 'matched_bank_transactions.referenceId', // to: 'matched_bank_transactions.referenceId',
// }, // },
// filter(query) { // filter(query) {
// query.where('reference_type', 'SaleInvoice'); // query.where('reference_type', 'SaleInvoice');
// }, // },
// }, // },
// /** /**
// * Sale invoice may belongs to payment methods entries. * Sale invoice may belongs to payment methods entries.
// */ */
// paymentMethods: { paymentMethods: {
// relation: Model.HasManyRelation, relation: Model.HasManyRelation,
// modelClass: TransactionPaymentServiceEntry, modelClass: TransactionPaymentServiceEntry,
// join: { join: {
// from: 'sales_invoices.id', from: 'sales_invoices.id',
// to: 'transactions_payment_methods.referenceId', to: 'transactions_payment_methods.referenceId',
// }, },
// beforeInsert: (model) => { beforeInsert: (model) => {
// model.referenceType = 'SaleInvoice'; model.referenceType = 'SaleInvoice';
// }, },
// filter: (query) => { filter: (query) => {
// query.where('reference_type', 'SaleInvoice'); query.where('reference_type', 'SaleInvoice');
// }, },
// }, },
// /** /**
// * Sale invoice may belongs to pdf branding template. * Sale invoice may belongs to pdf branding template.
// */ */
// pdfTemplate: { pdfTemplate: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: PdfTemplate, modelClass: PdfTemplateModel,
// join: { join: {
// from: 'sales_invoices.pdfTemplateId', from: 'sales_invoices.pdfTemplateId',
// to: 'pdf_templates.id', to: 'pdf_templates.id',
// } },
// }, },
// }; };
// } }
/** /**
* Change payment amount. * Change payment amount.
@@ -642,9 +655,9 @@ export class SaleInvoice extends BaseModel{
/** /**
* Sale invoice meta. * Sale invoice meta.
*/ */
static get meta() { // static get meta() {
return SaleInvoiceMeta; // return SaleInvoiceMeta;
} // }
static dueAmountFieldSortQuery(query, role) { static dueAmountFieldSortQuery(query, role) {
query.modify('sortByDueAmount', role.order); query.modify('sortByDueAmount', role.order);
@@ -653,9 +666,9 @@ export class SaleInvoice extends BaseModel{
/** /**
* Retrieve the default custom views, roles and columns. * Retrieve the default custom views, roles and columns.
*/ */
static get defaultViews() { // static get defaultViews() {
return DEFAULT_VIEWS; // return DEFAULT_VIEWS;
} // }
/** /**
* Model searchable. * Model searchable.

View File

@@ -47,7 +47,7 @@ export class SaleInvoicePdf {
public async getSaleInvoicePdf(invoiceId: number): Promise<[Buffer, string]> { public async getSaleInvoicePdf(invoiceId: number): Promise<[Buffer, string]> {
const filename = await this.getInvoicePdfFilename(invoiceId); const filename = await this.getInvoicePdfFilename(invoiceId);
const htmlContent = await this.saleInvoiceHtml(invoiceId); const htmlContent = await this.getSaleInvoiceHtml(invoiceId);
// Converts the given html content to pdf document. // Converts the given html content to pdf document.
const buffer = await this.chromiumlyTenancy.convertHtmlContent(htmlContent); const buffer = await this.chromiumlyTenancy.convertHtmlContent(htmlContent);

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
import { pickBy } from 'lodash'; import { pickBy } from 'lodash';
import { InvoicePdfTemplateAttributes, ISaleInvoice } from '@/interfaces'; import { InvoicePdfTemplateAttributes, ISaleInvoice } from '@/interfaces';
import { contactAddressTextFormat } from '@/utils/address-text-format'; import { contactAddressTextFormat } from '@/utils/address-text-format';

View File

@@ -0,0 +1,57 @@
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Post,
Put,
} from '@nestjs/common';
import { ISaleReceiptDTO } from './types/SaleReceipts.types';
import { SaleReceiptApplication } from './SaleReceiptApplication.service';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('sale-receipts')
@PublicRoute()
export class SaleReceiptsController {
constructor(private saleReceiptApplication: SaleReceiptApplication) {}
@Post()
createSaleReceipt(@Body() saleReceiptDTO: ISaleReceiptDTO) {
return this.saleReceiptApplication.createSaleReceipt(saleReceiptDTO);
}
@Put(':id')
editSaleReceipt(
@Param('id', ParseIntPipe) id: number,
@Body() saleReceiptDTO: ISaleReceiptDTO,
) {
return this.saleReceiptApplication.editSaleReceipt(id, saleReceiptDTO);
}
@Get(':id')
getSaleReceipt(@Param('id', ParseIntPipe) id: number) {
return this.saleReceiptApplication.getSaleReceipt(id);
}
@Delete(':id')
deleteSaleReceipt(@Param('id', ParseIntPipe) id: number) {
return this.saleReceiptApplication.deleteSaleReceipt(id);
}
@Post(':id/close')
closeSaleReceipt(@Param('id', ParseIntPipe) id: number) {
return this.saleReceiptApplication.closeSaleReceipt(id);
}
@Get(':id/pdf')
getSaleReceiptPdf(@Param('id', ParseIntPipe) id: number) {
return this.saleReceiptApplication.getSaleReceiptPdf(0, id);
}
@Get('state')
getSaleReceiptState() {
return this.saleReceiptApplication.getSaleReceiptState();
}
}

View File

@@ -5,15 +5,47 @@ import { EditSaleReceipt } from './commands/EditSaleReceipt.service';
import { GetSaleReceipt } from './queries/GetSaleReceipt.service'; import { GetSaleReceipt } from './queries/GetSaleReceipt.service';
import { DeleteSaleReceipt } from './commands/DeleteSaleReceipt.service'; import { DeleteSaleReceipt } from './commands/DeleteSaleReceipt.service';
import { CloseSaleReceipt } from './commands/CloseSaleReceipt.service'; import { CloseSaleReceipt } from './commands/CloseSaleReceipt.service';
import { SaleReceiptsPdfService } from './queries/SaleReceiptsPdf.service';
import { GetSaleReceiptState } from './queries/GetSaleReceiptState.service';
import { ItemsModule } from '../Items/items.module';
import { SaleReceiptDTOTransformer } from './commands/SaleReceiptDTOTransformer.service';
import { SaleReceiptValidators } from './commands/SaleReceiptValidators.service';
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
import { TemplateInjectableModule } from '../TemplateInjectable/TemplateInjectable.module';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { SaleReceiptBrandingTemplate } from './queries/SaleReceiptBrandingTemplate.service';
import { BranchesModule } from '../Branches/Branches.module';
import { WarehousesModule } from '../Warehouses/Warehouses.module';
import { SaleReceiptIncrement } from './commands/SaleReceiptIncrement.service';
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
import { SaleReceiptsController } from './SaleReceipts.controller';
@Module({ @Module({
controllers: [SaleReceiptsController],
imports: [
ItemsModule,
ChromiumlyTenancyModule,
TemplateInjectableModule,
BranchesModule,
WarehousesModule,
PdfTemplatesModule,
AutoIncrementOrdersModule
],
providers: [ providers: [
TenancyContext,
SaleReceiptValidators,
SaleReceiptApplication, SaleReceiptApplication,
CreateSaleReceipt, CreateSaleReceipt,
EditSaleReceipt, EditSaleReceipt,
GetSaleReceipt, GetSaleReceipt,
DeleteSaleReceipt, DeleteSaleReceipt,
CloseSaleReceipt, CloseSaleReceipt,
SaleReceiptsPdfService,
GetSaleReceiptState,
SaleReceiptDTOTransformer,
SaleReceiptBrandingTemplate,
SaleReceiptIncrement,
], ],
}) })
export class SaleReceiptsModule {} export class SaleReceiptsModule {}

View File

@@ -1,7 +1,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as R from 'ramda'; import * as R from 'ramda';
import { sumBy, omit } from 'lodash'; import { sumBy, omit } from 'lodash';
import composeAsync from 'async/compose'; import * as composeAsync from 'async/compose';
import moment from 'moment'; import moment from 'moment';
import { SaleReceiptIncrement } from './SaleReceiptIncrement.service'; import { SaleReceiptIncrement } from './SaleReceiptIncrement.service';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
@@ -18,6 +18,15 @@ import { Customer } from '@/modules/Customers/models/Customer';
@Injectable() @Injectable()
export class SaleReceiptDTOTransformer { export class SaleReceiptDTOTransformer {
/**
* @param {ItemsEntriesService} itemsEntriesService - Items entries service.
* @param {BranchTransactionDTOTransformer} branchDTOTransform - Branch transaction DTO transformer.
* @param {WarehouseTransactionDTOTransform} warehouseDTOTransform - Warehouse transaction DTO transformer.
* @param {SaleReceiptValidators} validators - Sale receipt validators.
* @param {SaleReceiptIncrement} receiptIncrement - Sale receipt increment.
* @param {BrandingTemplateDTOTransformer} brandingTemplatesTransformer - Branding template DTO transformer.
* @param {typeof ItemEntry} itemEntryModel - Item entry model.
*/
constructor( constructor(
private readonly itemsEntriesService: ItemsEntriesService, private readonly itemsEntriesService: ItemsEntriesService,
private readonly branchDTOTransform: BranchTransactionDTOTransformer, private readonly branchDTOTransform: BranchTransactionDTOTransformer,
@@ -60,10 +69,9 @@ export class SaleReceiptDTOTransformer {
reference_type: 'SaleReceipt', reference_type: 'SaleReceipt',
...entry, ...entry,
})); }));
const asyncEntries = await composeAsync( const asyncEntries = await composeAsync(
// Sets default cost and sell account to receipt items entries. // Sets default cost and sell account to receipt items entries.
this.itemsEntriesService.setItemsEntriesDefaultAccounts(), this.itemsEntriesService.setItemsEntriesDefaultAccounts,
)(initialEntries); )(initialEntries);
const entries = R.compose( const entries = R.compose(
@@ -97,6 +105,6 @@ export class SaleReceiptDTOTransformer {
return R.compose( return R.compose(
this.branchDTOTransform.transformDTO<SaleReceipt>, this.branchDTOTransform.transformDTO<SaleReceipt>,
this.warehouseDTOTransform.transformDTO<SaleReceipt>, this.warehouseDTOTransform.transformDTO<SaleReceipt>,
)(initialAsyncDTO); )(initialAsyncDTO) as SaleReceipt;
} }
} }

View File

@@ -12,8 +12,8 @@ export class SaleReceiptValidators {
* @param {typeof Account} accountModel - Account model. * @param {typeof Account} accountModel - Account model.
*/ */
constructor( constructor(
@Inject(SaleReceipt) private saleReceiptModel: typeof SaleReceipt, @Inject(SaleReceipt.name) private saleReceiptModel: typeof SaleReceipt,
@Inject(Account) private accountModel: typeof Account, @Inject(Account.name) private accountModel: typeof Account,
) {} ) {}
/** /**

View File

@@ -119,18 +119,18 @@ export class SaleReceipt extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const Customer = require('@/modules/Customers/models/Customer'); const { Customer } = require('../../Customers/models/Customer');
const Account = require('@/modules/Accounts/models/Account.model'); const { Account } = require('../../Accounts/models/Account.model');
const AccountTransaction = require('@/modules/AccountsTransactions/models/AccountTransaction.model'); const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
const ItemEntry = require('@/modules/ItemsEntries/models/ItemEntry'); const { ItemEntry } = require('../../TransactionItemEntry/models/ItemEntry');
const Branch = require('@/modules/Branches/models/Branch'); const { Branch } = require('../../Branches/models/Branch.model');
const Document = require('@/modules/Documents/models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
const Warehouse = require('@/modules/Warehouses/models/Warehouse'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
return { return {
customer: { customer: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Customer.default, modelClass: Customer,
join: { join: {
from: 'sales_receipts.customerId', from: 'sales_receipts.customerId',
to: 'contacts.id', to: 'contacts.id',
@@ -142,7 +142,7 @@ export class SaleReceipt extends BaseModel {
depositAccount: { depositAccount: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Account.default, modelClass: Account,
join: { join: {
from: 'sales_receipts.depositAccountId', from: 'sales_receipts.depositAccountId',
to: 'accounts.id', to: 'accounts.id',
@@ -151,7 +151,7 @@ export class SaleReceipt extends BaseModel {
entries: { entries: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: ItemEntry.default, modelClass: ItemEntry,
join: { join: {
from: 'sales_receipts.id', from: 'sales_receipts.id',
to: 'items_entries.referenceId', to: 'items_entries.referenceId',
@@ -164,7 +164,7 @@ export class SaleReceipt extends BaseModel {
transactions: { transactions: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: AccountTransaction.default, modelClass: AccountTransaction,
join: { join: {
from: 'sales_receipts.id', from: 'sales_receipts.id',
to: 'accounts_transactions.referenceId', to: 'accounts_transactions.referenceId',
@@ -179,7 +179,7 @@ export class SaleReceipt extends BaseModel {
*/ */
branch: { branch: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Branch.default, modelClass: Branch,
join: { join: {
from: 'sales_receipts.branchId', from: 'sales_receipts.branchId',
to: 'branches.id', to: 'branches.id',
@@ -191,7 +191,7 @@ export class SaleReceipt extends BaseModel {
*/ */
warehouse: { warehouse: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default, modelClass: Warehouse,
join: { join: {
from: 'sales_receipts.warehouseId', from: 'sales_receipts.warehouseId',
to: 'warehouses.id', to: 'warehouses.id',
@@ -203,7 +203,7 @@ export class SaleReceipt extends BaseModel {
*/ */
attachments: { attachments: {
relation: Model.ManyToManyRelation, relation: Model.ManyToManyRelation,
modelClass: Document.default, modelClass: Document,
join: { join: {
from: 'sales_receipts.id', from: 'sales_receipts.id',
through: { through: {

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { SaleReceiptTransformer } from './SaleReceiptTransformer'; import { SaleReceiptTransformer } from './SaleReceiptTransformer';
import { SaleReceiptValidators } from '../commands/SaleReceiptValidators.service'; import { SaleReceiptValidators } from '../commands/SaleReceiptValidators.service';
import { SaleReceipt } from '../models/SaleReceipt'; import { SaleReceipt } from '../models/SaleReceipt';
@@ -7,6 +7,7 @@ import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectab
@Injectable() @Injectable()
export class GetSaleReceipt { export class GetSaleReceipt {
constructor( constructor(
@Inject(SaleReceipt.name)
private readonly saleReceiptModel: typeof SaleReceipt, private readonly saleReceiptModel: typeof SaleReceipt,
private readonly transformer: TransformerInjectable, private readonly transformer: TransformerInjectable,
private readonly validators: SaleReceiptValidators, private readonly validators: SaleReceiptValidators,
@@ -26,11 +27,11 @@ export class GetSaleReceipt {
.withGraphFetched('depositAccount') .withGraphFetched('depositAccount')
.withGraphFetched('branch') .withGraphFetched('branch')
.withGraphFetched('attachments') .withGraphFetched('attachments')
.throwIfNotFound() .throwIfNotFound();
return this.transformer.transform( return this.transformer.transform(
saleReceipt, saleReceipt,
new SaleReceiptTransformer() new SaleReceiptTransformer(),
); );
} }
} }

View File

@@ -15,7 +15,7 @@ export class ItemEntriesTaxTransactions {
* @param model * @param model
* @returns * @returns
*/ */
public assocTaxAmountWithheldFromEntries(model: any) { public assocTaxAmountWithheldFromEntries = (model: any) => {
const entries = model.entries.map((entry) => const entries = model.entries.map((entry) =>
this.itemEntryModel.fromJson(entry), this.itemEntryModel.fromJson(entry),
); );

View File

@@ -10,6 +10,7 @@ import { EditTaxRateService } from './commands/EditTaxRate.service';
import { CommandTaxRatesValidators } from './commands/CommandTaxRatesValidator.service'; import { CommandTaxRatesValidators } from './commands/CommandTaxRatesValidator.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TaxRatesApplication } from './TaxRate.application'; import { TaxRatesApplication } from './TaxRate.application';
import { ItemEntriesTaxTransactions } from './ItemEntriesTaxTransactions.service';
@Module({ @Module({
imports: [], imports: [],
@@ -24,7 +25,9 @@ import { TaxRatesApplication } from './TaxRate.application';
CommandTaxRatesValidators, CommandTaxRatesValidators,
TransformerInjectable, TransformerInjectable,
TenancyContext, TenancyContext,
TaxRatesApplication TaxRatesApplication,
ItemEntriesTaxTransactions
], ],
exports: [ItemEntriesTaxTransactions],
}) })
export class TaxRatesModule {} export class TaxRatesModule {}

View File

@@ -1,7 +1,9 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TemplateInjectable } from './TemplateInjectable.service'; import { TemplateInjectable } from './TemplateInjectable.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Module({ @Module({
providers: [TemplateInjectable], providers: [TemplateInjectable, TenancyContext],
exports: [TemplateInjectable]
}) })
export class TemplateInjectableModule {} export class TemplateInjectableModule {}

View File

@@ -7,7 +7,7 @@ import { Account } from '@/modules/Accounts/models/Account.model';
import { ItemEntry } from '@/modules/Items/models/ItemEntry'; import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
import { Expense } from '@/modules/Expenses/models/Expense.model'; import { Expense } from '@/modules/Expenses/models/Expense.model';
import ExpenseCategory from '@/modules/Expenses/models/ExpenseCategory.model'; import { ExpenseCategory } from '@/modules/Expenses/models/ExpenseCategory.model';
import { ItemCategory } from '@/modules/ItemCategories/models/ItemCategory.model'; import { ItemCategory } from '@/modules/ItemCategories/models/ItemCategory.model';
import { TaxRateModel } from '@/modules/TaxRates/models/TaxRate.model'; import { TaxRateModel } from '@/modules/TaxRates/models/TaxRate.model';
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate'; import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
@@ -20,6 +20,18 @@ import { Contact } from '@/modules/Contacts/models/Contact';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document'; import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
import { DocumentLink } from '@/modules/ChromiumlyTenancy/models/DocumentLink'; import { DocumentLink } from '@/modules/ChromiumlyTenancy/models/DocumentLink';
import { Vendor } from '@/modules/Vendors/models/Vendor'; import { Vendor } from '@/modules/Vendors/models/Vendor';
import { Bill } from '@/modules/Bills/models/Bill';
import { BillPayment } from '@/modules/BillPayments/models/BillPayment';
import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry';
import { BillLandedCostEntry } from '@/modules/BillLandedCosts/models/BillLandedCostEntry';
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
import { VendorCreditAppliedBill } from '@/modules/VendorCredit/models/VendorCreditAppliedBill';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { PaymentReceivedEntry } from '@/modules/PaymentReceived/models/PaymentReceivedEntry';
import { CreditNoteAppliedInvoice } from '@/modules/CreditNotes/models/CreditNoteAppliedInvoice';
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
import { PaymentLink } from '@/modules/PaymentLinks/models/PaymentLink';
import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt';
const models = [ const models = [
Item, Item,
@@ -39,7 +51,19 @@ const models = [
Contact, Contact,
Document, Document,
DocumentLink, DocumentLink,
Vendor Vendor,
Bill,
BillPayment,
BillPaymentEntry,
BillLandedCost,
BillLandedCostEntry,
VendorCreditAppliedBill,
SaleInvoice,
PaymentReceivedEntry,
CreditNoteAppliedInvoice,
CreditNote,
PaymentLink,
SaleReceipt
]; ];
const modelProviders = models.map((model) => { const modelProviders = models.map((model) => {

View File

@@ -124,121 +124,121 @@ export class ItemEntry extends BaseModel {
/** /**
* Item entry relations. * Item entry relations.
*/ */
static get relationMappings() { // static get relationMappings() {
const Item = require('models/Item'); // const Item = require('models/Item');
const BillLandedCostEntry = require('models/BillLandedCostEntry'); // const BillLandedCostEntry = require('models/BillLandedCostEntry');
const SaleInvoice = require('models/SaleInvoice'); // const SaleInvoice = require('models/SaleInvoice');
const Bill = require('models/Bill'); // const Bill = require('models/Bill');
const SaleReceipt = require('models/SaleReceipt'); // const SaleReceipt = require('models/SaleReceipt');
const SaleEstimate = require('models/SaleEstimate'); // const SaleEstimate = require('models/SaleEstimate');
const ProjectTask = require('models/Task'); // const ProjectTask = require('models/Task');
const Expense = require('models/Expense'); // const Expense = require('models/Expense');
const TaxRate = require('models/TaxRate'); // const TaxRate = require('models/TaxRate');
return { // return {
item: { // item: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: Item.default, // modelClass: Item.default,
join: { // join: {
from: 'items_entries.itemId', // from: 'items_entries.itemId',
to: 'items.id', // to: 'items.id',
}, // },
}, // },
allocatedCostEntries: { // allocatedCostEntries: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: BillLandedCostEntry.default, // modelClass: BillLandedCostEntry.default,
join: { // join: {
from: 'items_entries.referenceId', // from: 'items_entries.referenceId',
to: 'bill_located_cost_entries.entryId', // to: 'bill_located_cost_entries.entryId',
}, // },
}, // },
invoice: { // invoice: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: SaleInvoice.default, // modelClass: SaleInvoice.default,
join: { // join: {
from: 'items_entries.referenceId', // from: 'items_entries.referenceId',
to: 'sales_invoices.id', // to: 'sales_invoices.id',
}, // },
}, // },
bill: { // bill: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: Bill.default, // modelClass: Bill.default,
join: { // join: {
from: 'items_entries.referenceId', // from: 'items_entries.referenceId',
to: 'bills.id', // to: 'bills.id',
}, // },
}, // },
estimate: { // estimate: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: SaleEstimate.default, // modelClass: SaleEstimate.default,
join: { // join: {
from: 'items_entries.referenceId', // from: 'items_entries.referenceId',
to: 'sales_estimates.id', // to: 'sales_estimates.id',
}, // },
}, // },
/** // /**
* Sale receipt reference. // * Sale receipt reference.
*/ // */
receipt: { // receipt: {
relation: Model.BelongsToOneRelation, // relation: Model.BelongsToOneRelation,
modelClass: SaleReceipt.default, // modelClass: SaleReceipt.default,
join: { // join: {
from: 'items_entries.referenceId', // from: 'items_entries.referenceId',
to: 'sales_receipts.id', // to: 'sales_receipts.id',
}, // },
}, // },
/** // /**
* Project task reference. // * Project task reference.
*/ // */
projectTaskRef: { // projectTaskRef: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: ProjectTask.default, // modelClass: ProjectTask.default,
join: { // join: {
from: 'items_entries.projectRefId', // from: 'items_entries.projectRefId',
to: 'tasks.id', // to: 'tasks.id',
}, // },
}, // },
/** // /**
* Project expense reference. // * Project expense reference.
*/ // */
projectExpenseRef: { // projectExpenseRef: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: Expense.default, // modelClass: Expense.default,
join: { // join: {
from: 'items_entries.projectRefId', // from: 'items_entries.projectRefId',
to: 'expenses_transactions.id', // to: 'expenses_transactions.id',
}, // },
}, // },
/** // /**
* Project bill reference. // * Project bill reference.
*/ // */
projectBillRef: { // projectBillRef: {
relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
modelClass: Bill.default, // modelClass: Bill.default,
join: { // join: {
from: 'items_entries.projectRefId', // from: 'items_entries.projectRefId',
to: 'bills.id', // to: 'bills.id',
}, // },
}, // },
/** // /**
* Tax rate reference. // * Tax rate reference.
*/ // */
tax: { // tax: {
relation: Model.HasOneRelation, // relation: Model.HasOneRelation,
modelClass: TaxRate.default, // modelClass: TaxRate.default,
join: { // join: {
from: 'items_entries.taxRateId', // from: 'items_entries.taxRateId',
to: 'tax_rates.id', // to: 'tax_rates.id',
}, // },
}, // },
}; // };
} // }
} }

View File

@@ -4,8 +4,8 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Global() @Global()
@Module({ @Module({
providers: [TransformerInjectable], providers: [TransformerInjectable, TenancyContext],
exports: [TransformerInjectable], exports: [TransformerInjectable],
imports: [TenancyContext], imports: [],
}) })
export class TransformerModule {} export class TransformerModule {}

View File

@@ -0,0 +1,296 @@
import { Model, raw, mixin } from 'objection';
// import TenantModel from 'models/TenantModel';
// import BillSettings from './Bill.Settings';
// import ModelSetting from './ModelSetting';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import { DEFAULT_VIEWS } from '@/services/Purchases/VendorCredits/constants';
// import ModelSearchable from './ModelSearchable';
// import VendorCreditMeta from './VendorCredit.Meta';
import { BaseModel } from '@/models/Model';
export class VendorCredit extends BaseModel {
vendorId: number;
amount: number;
currencyCode: string;
vendorCreditDate: Date;
vendorCreditNumber: string;
referenceNo: string;
refundedAmount: number;
invoicedAmount: number;
exchangeRate: number;
note: string;
openedAt: Date;
userId: number;
/**
* Table name
*/
static get tableName() {
return 'vendor_credits';
}
/**
* Vendor credit amount in local currency.
* @returns {number}
*/
get localAmount() {
return this.amount * this.exchangeRate;
}
/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Filters the credit notes in draft status.
*/
draft(query) {
query.where('opened_at', null);
},
/**
* Filters the published vendor credits.
*/
published(query) {
query.whereNot('opened_at', null);
},
/**
* Filters the open vendor credits.
*/
open(query) {
query
.where(
raw(`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICED_AMOUNT) <
COALESCE(AMOUNT)`),
)
.modify('published');
},
/**
* Filters the closed vendor credits.
*/
closed(query) {
query
.where(
raw(`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICED_AMOUNT) =
COALESCE(AMOUNT)`),
)
.modify('published');
},
/**
* Status filter.
*/
filterByStatus(query, filterType) {
switch (filterType) {
case 'draft':
query.modify('draft');
break;
case 'published':
query.modify('published');
break;
case 'open':
default:
query.modify('open');
break;
case 'closed':
query.modify('closed');
break;
}
},
/**
*
*/
sortByStatus(query, order) {
query.orderByRaw(
`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICED_AMOUNT) = COALESCE(AMOUNT) ${order}`,
);
},
};
}
/**
* Timestamps columns.
*/
get timestamps() {
return ['createdAt', 'updatedAt'];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [
'localAmount',
'isDraft',
'isPublished',
'isOpen',
'isClosed',
'creditsRemaining',
];
}
/**
* Detarmines whether the vendor credit is draft.
* @returns {boolean}
*/
get isDraft() {
return !this.openedAt;
}
/**
* Detarmines whether vendor credit is published.
* @returns {boolean}
*/
get isPublished() {
return !!this.openedAt;
}
/**
* Detarmines whether the credit note is open.
* @return {boolean}
*/
get isOpen() {
return !!this.openedAt && this.creditsRemaining > 0;
}
/**
* Detarmines whether the credit note is closed.
* @return {boolean}
*/
get isClosed() {
return this.openedAt && this.creditsRemaining === 0;
}
/**
* Retrieve the credits remaining.
* @returns {number}
*/
get creditsRemaining() {
return Math.max(this.amount - this.refundedAmount - this.invoicedAmount, 0);
}
/**
* Bill model settings.
*/
// static get meta() {
// return BillSettings;
// }
/**
* Relationship mapping.
*/
// static get relationMappings() {
// const Vendor = require('models/Vendor');
// const ItemEntry = require('models/ItemEntry');
// const Branch = require('models/Branch');
// const Document = require('models/Document');
// const Warehouse = require('models/Warehouse');
// return {
// vendor: {
// relation: Model.BelongsToOneRelation,
// modelClass: Vendor.default,
// join: {
// from: 'vendor_credits.vendorId',
// to: 'contacts.id',
// },
// filter(query) {
// query.where('contact_service', 'vendor');
// },
// },
// entries: {
// relation: Model.HasManyRelation,
// modelClass: ItemEntry.default,
// join: {
// from: 'vendor_credits.id',
// to: 'items_entries.referenceId',
// },
// filter(builder) {
// builder.where('reference_type', 'VendorCredit');
// builder.orderBy('index', 'ASC');
// },
// },
// /**
// * Vendor credit may belongs to branch.
// */
// branch: {
// relation: Model.BelongsToOneRelation,
// modelClass: Branch.default,
// join: {
// from: 'vendor_credits.branchId',
// to: 'branches.id',
// },
// },
// /**
// * Vendor credit may has associated warehouse.
// */
// warehouse: {
// relation: Model.BelongsToOneRelation,
// modelClass: Warehouse.default,
// join: {
// from: 'vendor_credits.warehouseId',
// to: 'warehouses.id',
// },
// },
// /**
// * Vendor credit may has many attached attachments.
// */
// attachments: {
// relation: Model.ManyToManyRelation,
// modelClass: Document.default,
// join: {
// from: 'vendor_credits.id',
// through: {
// from: 'document_links.modelId',
// to: 'document_links.documentId',
// },
// to: 'documents.id',
// },
// filter(query) {
// query.where('model_ref', 'VendorCredit');
// },
// },
// };
// }
/**
*
*/
// static get meta() {
// return VendorCreditMeta;
// }
/**
* Retrieve the default custom views, roles and columns.
*/
// static get defaultViews() {
// return DEFAULT_VIEWS;
// }
/**
* Model search attributes.
*/
static get searchRoles() {
return [
{ fieldKey: 'credit_number', comparator: 'contains' },
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
];
}
/**
* Prevents mutate base currency since the model is not empty.
* @returns {boolean}
*/
static get preventMutateBaseCurrency() {
return true;
}
}

View File

@@ -0,0 +1,50 @@
import { mixin, Model } from 'objection';
// import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model';
export class VendorCreditAppliedBill extends BaseModel {
/**
* Table name
*/
static get tableName() {
return 'vendor_credit_applied_bill';
}
/**
* Timestamps columns.
*/
get timestamps() {
return ['created_at', 'updated_at'];
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const { Bill } = require('../../Bills/models/Bill');
const { VendorCredit } = require('../../VendorCredit/models/VendorCredit');
return {
bill: {
relation: Model.BelongsToOneRelation,
modelClass: Bill,
join: {
from: 'vendor_credit_applied_bill.billId',
to: 'bills.id',
},
},
vendorCredit: {
relation: Model.BelongsToOneRelation,
modelClass: VendorCredit,
join: {
from: 'vendor_credit_applied_bill.vendorCreditId',
to: 'vendor_credits.id',
},
},
};
}
}

View File

@@ -13,8 +13,10 @@ import {
IVendorNewDTO, IVendorNewDTO,
IVendorOpeningBalanceEditDTO, IVendorOpeningBalanceEditDTO,
} from './types/Vendors.types'; } from './types/Vendors.types';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('vendors') @Controller('vendors')
@PublicRoute()
export class VendorsController { export class VendorsController {
constructor(private vendorsApplication: VendorsApplication) {} constructor(private vendorsApplication: VendorsApplication) {}

View File

@@ -19,7 +19,7 @@ export class DeleteVendorService {
constructor( constructor(
private eventPublisher: EventEmitter2, private eventPublisher: EventEmitter2,
private uow: UnitOfWork, private uow: UnitOfWork,
@Inject(Vendor.name) private contactModel: typeof Vendor, @Inject(Vendor.name) private vendorModel: typeof Vendor,
) {} ) {}
/** /**
@@ -29,9 +29,8 @@ export class DeleteVendorService {
*/ */
public async deleteVendor(vendorId: number) { public async deleteVendor(vendorId: number) {
// Retrieves the old vendor or throw not found service error. // Retrieves the old vendor or throw not found service error.
const oldVendor = await this.contactModel const oldVendor = await this.vendorModel
.query() .query()
.modify('vendor')
.findById(vendorId) .findById(vendorId)
.throwIfNotFound(); .throwIfNotFound();
// .queryAndThrowIfHasRelations({ // .queryAndThrowIfHasRelations({
@@ -47,7 +46,7 @@ export class DeleteVendorService {
// Deletes vendor contact under unit-of-work. // Deletes vendor contact under unit-of-work.
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Deletes the vendor contact from the storage. // Deletes the vendor contact from the storage.
await this.contactModel.query(trx).findById(vendorId).delete(); await this.vendorModel.query(trx).findById(vendorId).delete();
// Triggers `onVendorDeleted` event. // Triggers `onVendorDeleted` event.
await this.eventPublisher.emitAsync(events.vendors.onDeleted, { await this.eventPublisher.emitAsync(events.vendors.onDeleted, {

View File

@@ -170,7 +170,7 @@ export class Vendor extends BaseModel {
return { return {
bills: { bills: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: Bill.default, modelClass: Bill,
join: { join: {
from: 'contacts.id', from: 'contacts.id',
to: 'bills.vendorId', to: 'bills.vendorId',
@@ -178,7 +178,7 @@ export class Vendor extends BaseModel {
}, },
overdueBills: { overdueBills: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: Bill.default, modelClass: Bill,
join: { join: {
from: 'contacts.id', from: 'contacts.id',
to: 'bills.vendorId', to: 'bills.vendorId',

View File

@@ -18,7 +18,6 @@ export class GetVendorService {
const vendor = await this.vendorModel const vendor = await this.vendorModel
.query() .query()
.findById(vendorId) .findById(vendorId)
.modify('vendor')
.throwIfNotFound(); .throwIfNotFound();
// Transformes the vendor. // Transformes the vendor.

View File

@@ -16,6 +16,7 @@ import { ActivateWarehousesService } from './commands/ActivateWarehouses.service
import { CreateInitialWarehouse } from './commands/CreateInitialWarehouse.service'; import { CreateInitialWarehouse } from './commands/CreateInitialWarehouse.service';
import { WarehousesSettings } from './WarehousesSettings'; import { WarehousesSettings } from './WarehousesSettings';
import { I18nContext } from 'nestjs-i18n'; import { I18nContext } from 'nestjs-i18n';
import { WarehouseTransactionDTOTransform } from './Integrations/WarehouseTransactionDTOTransform';
@Module({ @Module({
imports: [TenancyDatabaseModule], imports: [TenancyDatabaseModule],
@@ -36,6 +37,8 @@ import { I18nContext } from 'nestjs-i18n';
I18nContext, I18nContext,
TenancyContext, TenancyContext,
TransformerInjectable, TransformerInjectable,
WarehouseTransactionDTOTransform
], ],
exports: [WarehouseTransactionDTOTransform],
}) })
export class WarehousesModule {} export class WarehousesModule {}

View File

@@ -0,0 +1,9 @@
export const transformToMap = (objects, key) => {
const map = new Map();
objects.forEach((object) => {
map.set(object[key], object);
});
return map;
};

View File

@@ -0,0 +1,82 @@
import * as request from 'supertest';
import { faker } from '@faker-js/faker';
import { app } from './init-app-test';
describe('Customers (e2e)', () => {
it('/customers (POST)', () => {
return request(app.getHttpServer())
.post('/customers')
.set('organization-id', '4064541lv40nhca')
.send({
customerType: 'business',
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
})
.expect(201);
});
it('/customers/:id (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/customers')
.set('organization-id', '4064541lv40nhca')
.send({
customerType: 'business',
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
});
const customerId = response.body.id;
return request(app.getHttpServer())
.get(`/customers/${customerId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/customers/:id (DELETE)', async () => {
const response = await request(app.getHttpServer())
.post('/customers')
.set('organization-id', '4064541lv40nhca')
.send({
customerType: 'business',
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
});
const customerId = response.body.id;
return request(app.getHttpServer())
.delete(`/customers/${customerId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/customers/:id (PUT)', async () => {
const response = await request(app.getHttpServer())
.post('/customers')
.set('organization-id', '4064541lv40nhca')
.send({
customerType: 'business',
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
});
const customerId = response.body.id;
return request(app.getHttpServer())
.put(`/customers/${customerId}`)
.set('organization-id', '4064541lv40nhca')
.send({
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
})
.expect(200);
});
});

View File

@@ -0,0 +1,33 @@
import * as request from 'supertest';
import { faker } from '@faker-js/faker';
import { app } from './init-app-test';
describe('Sale Invoices (e2e)', () => {
it('/sale-invoices (POST)', () => {
return request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send({
customerId: 2,
invoiceDate: '2023-01-01',
dueDate: '2023-02-01',
invoiceNo: 'INV-002005',
referenceNo: 'REF-000201',
delivered: true,
discountType: 'percentage',
discount: 10,
branchId: 1,
warehouseId: 1,
entries: [
{
index: 1,
itemId: 1001,
quantity: 2,
rate: 1000,
description: 'Item description...',
},
],
})
.expect(201);
});
});

View File

@@ -0,0 +1,80 @@
import * as request from 'supertest';
import { faker } from '@faker-js/faker';
import { app } from './init-app-test';
const receiptRequest = {
customerId: 2,
depositAccountId: 1000,
receiptDate: '2022-02-02',
referenceNo: '123',
receiptNumber: faker.string.uuid(),
branchId: 1,
warehouseId: 1,
discount: 100,
discountType: 'amount',
entries: [
{
index: 1,
itemId: 1001,
quantity: 1,
rate: 2000,
description: 'asdfsadf',
},
],
};
describe('Sale Receipts (e2e)', () => {
it('/sale-reeipts (POST)', () => {
return request(app.getHttpServer())
.post('/sale-receipts')
.set('organization-id', '4064541lv40nhca')
.send(receiptRequest)
.expect(201);
});
it('/sale-receipts/:id (DELETE)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-receipts')
.set('organization-id', '4064541lv40nhca')
.send(receiptRequest);
const receiptId = response.body.id;
return request(app.getHttpServer())
.delete(`/sale-receipts/${receiptId}`)
.set('organization-id', '4064541lv40nhca')
.send();
});
it('/sale-receipts/:id (PUT)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-receipts')
.set('organization-id', '4064541lv40nhca')
.send(receiptRequest);
const receiptId = response.body.id;
return request(app.getHttpServer())
.delete(`/sale-receipts/${receiptId}`)
.set('organization-id', '4064541lv40nhca')
.send({
...receiptRequest,
referenceNo: '321',
})
.expect(200);
});
it('/sale-receipts/:id (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-receipts')
.set('organization-id', '4064541lv40nhca')
.send(receiptRequest);
const receiptId = response.body.id;
return request(app.getHttpServer())
.get(`/sale-receipts/${receiptId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
});

View File

@@ -0,0 +1,75 @@
import * as request from 'supertest';
import { faker } from '@faker-js/faker';
import { app } from './init-app-test';
describe('Vendors (e2e)', () => {
it('/vendors (POST)', () => {
return request(app.getHttpServer())
.post('/vendors')
.set('organization-id', '4064541lv40nhca')
.send({
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
})
.expect(201);
});
it('/vendors/:id (PUT)', async () => {
const response = await request(app.getHttpServer())
.post('/vendors')
.set('organization-id', '4064541lv40nhca')
.send({
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
});
const vendorId = response.body.id;
return request(app.getHttpServer())
.put(`/vendors/${vendorId}`)
.set('organization-id', '4064541lv40nhca')
.send({
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
})
.expect(200);
});
it('/vendors/:id (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/vendors')
.set('organization-id', '4064541lv40nhca')
.send({
displayName: faker.commerce.productName(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
});
const vendorId = response.body.id;
return request(app.getHttpServer())
.get(`/vendors/${vendorId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/vendors/:id (DELETE)', async () => {
const response = await request(app.getHttpServer())
.post('/vendors')
.set('organization-id', '4064541lv40nhca')
.send({
displayName: faker.commerce.productName(),
});
const vendorId = response.body.id;
return request(app.getHttpServer())
.delete(`/vendors/${vendorId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
});