diff --git a/packages/server/src/i18n/en/customer.json b/packages/server/src/i18n/en/customer.json new file mode 100644 index 000000000..5f9298937 --- /dev/null +++ b/packages/server/src/i18n/en/customer.json @@ -0,0 +1,5 @@ +{ + "type.business": "Business", + "type.individual": "Individual" +} + diff --git a/packages/server/src/models/Model.ts b/packages/server/src/models/Model.ts index 61ccb4413..28d3ebb3b 100644 --- a/packages/server/src/models/Model.ts +++ b/packages/server/src/models/Model.ts @@ -48,14 +48,30 @@ export class PaginationQueryBuilder< // No relations defined return this.delete(); } + + // Only check HasManyRelation and ManyToManyRelation relations, as BelongsToOneRelation are just + // foreign key references and shouldn't prevent deletion. Only dependent records should block deletion. + const dependentRelationNames = relationNames.filter((name) => { + const relation = relationMappings[name]; + return relation && ( + relation.relation === Model.HasManyRelation || + relation.relation === Model.ManyToManyRelation + ); + }); + + if (dependentRelationNames.length === 0) { + // No dependent relations defined, safe to delete + return this.delete(); + } + const recordQuery = this.clone(); - relationNames.forEach((relationName: string) => { + dependentRelationNames.forEach((relationName: string) => { recordQuery.withGraphFetched(relationName); }); const record = await recordQuery; - const hasRelations = relationNames.some((name) => { + const hasRelations = dependentRelationNames.some((name) => { const val = record[name]; return Array.isArray(val) ? val.length > 0 : val != null; }); diff --git a/packages/server/src/modules/BillPayments/BillPayments.module.ts b/packages/server/src/modules/BillPayments/BillPayments.module.ts index 0516d5c71..5d1494cfc 100644 --- a/packages/server/src/modules/BillPayments/BillPayments.module.ts +++ b/packages/server/src/modules/BillPayments/BillPayments.module.ts @@ -14,6 +14,7 @@ import { BranchesSettingsService } from '../Branches/BranchesSettings'; import { BillPaymentsController } from './BillPayments.controller'; import { BillPaymentGLEntries } from './commands/BillPaymentGLEntries'; import { BillPaymentGLEntriesSubscriber } from './subscribers/BillPaymentGLEntriesSubscriber'; +import { BillPaymentBillSyncSubscriber } from './subscribers/BillPaymentBillSyncSubscriber'; import { LedgerModule } from '../Ledger/Ledger.module'; import { AccountsModule } from '../Accounts/Accounts.module'; import { BillPaymentsExportable } from './queries/BillPaymentsExportable'; @@ -39,6 +40,7 @@ import { BillPaymentsPages } from './commands/BillPaymentsPages.service'; TenancyContext, BillPaymentGLEntries, BillPaymentGLEntriesSubscriber, + BillPaymentBillSyncSubscriber, BillPaymentsExportable, BillPaymentsImportable, GetBillPaymentsService, @@ -52,4 +54,4 @@ import { BillPaymentsPages } from './commands/BillPaymentsPages.service'; ], controllers: [BillPaymentsController], }) -export class BillPaymentsModule {} +export class BillPaymentsModule { } diff --git a/packages/server/src/modules/BillPayments/commands/CreateBillPayment.service.ts b/packages/server/src/modules/BillPayments/commands/CreateBillPayment.service.ts index a521065e8..5b9d05146 100644 --- a/packages/server/src/modules/BillPayments/commands/CreateBillPayment.service.ts +++ b/packages/server/src/modules/BillPayments/commands/CreateBillPayment.service.ts @@ -38,7 +38,7 @@ export class CreateBillPaymentService { @Inject(BillPayment.name) private readonly billPaymentModel: TenantModelProxy, - ) {} + ) { } /** * Creates a new bill payment transcations and store it to the storage @@ -103,11 +103,19 @@ export class CreateBillPaymentService { } as IBillPaymentCreatingPayload); // Writes the bill payment graph to the storage. - const billPayment = await this.billPaymentModel() + const insertedBillPayment = await this.billPaymentModel() .query(trx) .insertGraphAndFetch({ ...billPaymentObj, }); + + // Fetch the bill payment with entries to ensure they're loaded for the subscriber. + const billPayment = await this.billPaymentModel() + .query(trx) + .withGraphFetched('entries') + .findById(insertedBillPayment.id) + .throwIfNotFound(); + // Triggers `onBillPaymentCreated` event. await this.eventPublisher.emitAsync(events.billPayment.onCreated, { billPayment, diff --git a/packages/server/src/modules/BillPayments/commands/EditBillPayment.service.ts b/packages/server/src/modules/BillPayments/commands/EditBillPayment.service.ts index 19116b71f..ffa2285e8 100644 --- a/packages/server/src/modules/BillPayments/commands/EditBillPayment.service.ts +++ b/packages/server/src/modules/BillPayments/commands/EditBillPayment.service.ts @@ -29,7 +29,7 @@ export class EditBillPayment { @Inject(Vendor.name) private readonly vendorModel: TenantModelProxy, - ) {} + ) { } /** * Edits the details of the given bill payment. @@ -116,12 +116,20 @@ export class EditBillPayment { } as IBillPaymentEditingPayload); // Edits the bill payment transaction graph on the storage. - const billPayment = await this.billPaymentModel() + await this.billPaymentModel() .query(trx) - .upsertGraphAndFetch({ + .upsertGraph({ id: billPaymentId, ...billPaymentObj, }); + + // Fetch the bill payment with entries to ensure they're loaded for the subscriber. + const billPayment = await this.billPaymentModel() + .query(trx) + .withGraphFetched('entries') + .findById(billPaymentId) + .throwIfNotFound(); + // Triggers `onBillPaymentEdited` event. await this.eventPublisher.emitAsync(events.billPayment.onEdited, { billPaymentId, diff --git a/packages/server/src/modules/BillPayments/subscribers/BillPaymentBillSyncSubscriber.ts b/packages/server/src/modules/BillPayments/subscribers/BillPaymentBillSyncSubscriber.ts new file mode 100644 index 000000000..ab93f5bf9 --- /dev/null +++ b/packages/server/src/modules/BillPayments/subscribers/BillPaymentBillSyncSubscriber.ts @@ -0,0 +1,80 @@ +import { + IBillPaymentEventCreatedPayload, + IBillPaymentEventDeletedPayload, + IBillPaymentEventEditedPayload, +} from '../types/BillPayments.types'; +import { BillPaymentBillSync } from '../commands/BillPaymentBillSync.service'; +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; + +@Injectable() +export class BillPaymentBillSyncSubscriber { + /** + * @param {BillPaymentBillSync} billPaymentBillSync - Bill payment bill sync service. + */ + constructor(private readonly billPaymentBillSync: BillPaymentBillSync) { } + + /** + * Handle bill increment/decrement payment amount + * once created, edited or deleted. + */ + @OnEvent(events.billPayment.onCreated) + async handleBillIncrementPaymentOnceCreated({ + billPayment, + trx, + }: IBillPaymentEventCreatedPayload) { + // Ensure entries are available - they should be included in insertGraphAndFetch + const entries = billPayment.entries || []; + await this.billPaymentBillSync.saveChangeBillsPaymentAmount( + entries.map((entry) => ({ + billId: entry.billId, + paymentAmount: entry.paymentAmount, + })), + null, + trx, + ); + } + + /** + * Handle bill increment/decrement payment amount once edited. + */ + @OnEvent(events.billPayment.onEdited) + async handleBillIncrementPaymentOnceEdited({ + billPayment, + oldBillPayment, + trx, + }: IBillPaymentEventEditedPayload) { + const entries = billPayment.entries || []; + const oldEntries = oldBillPayment?.entries || null; + + await this.billPaymentBillSync.saveChangeBillsPaymentAmount( + entries.map((entry) => ({ + billId: entry.billId, + paymentAmount: entry.paymentAmount, + })), + oldEntries, + trx, + ); + } + + /** + * Handle revert bills payment amount once bill payment deleted. + */ + @OnEvent(events.billPayment.onDeleted) + async handleBillDecrementPaymentAmount({ + oldBillPayment, + trx, + }: IBillPaymentEventDeletedPayload) { + const oldEntries = oldBillPayment.entries || []; + + await this.billPaymentBillSync.saveChangeBillsPaymentAmount( + oldEntries.map((entry) => ({ + billId: entry.billId, + paymentAmount: 0, + })), + oldEntries, + trx, + ); + } +} diff --git a/packages/server/src/modules/FinancialStatements/modules/JournalSheet/JournalSheet.ts b/packages/server/src/modules/FinancialStatements/modules/JournalSheet/JournalSheet.ts index f21011da1..3278acb37 100644 --- a/packages/server/src/modules/FinancialStatements/modules/JournalSheet/JournalSheet.ts +++ b/packages/server/src/modules/FinancialStatements/modules/JournalSheet/JournalSheet.ts @@ -10,6 +10,7 @@ import { import { FinancialSheet } from '../../common/FinancialSheet'; import { JournalSheetRepository } from './JournalSheetRepository'; import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; +import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils'; export class JournalSheet extends FinancialSheet { readonly query: IJournalReportQuery; @@ -97,10 +98,13 @@ export class JournalSheet extends FinancialSheet { transactionType: groupEntry.transactionType, referenceId: groupEntry.transactionId, - referenceTypeFormatted: this.i18n.t(groupEntry.transactionType), - + referenceTypeFormatted: this.i18n.t( + getTransactionTypeLabel( + groupEntry.transactionType, + groupEntry.transactionSubType, + ), + ), entries: this.entriesMapper(entriesGroup), - currencyCode: this.baseCurrency, credit: totalCredit, diff --git a/packages/server/src/modules/InventoryCost/models/InventoryCostLotTracker.ts b/packages/server/src/modules/InventoryCost/models/InventoryCostLotTracker.ts index b5b529fdc..7b10b9f45 100644 --- a/packages/server/src/modules/InventoryCost/models/InventoryCostLotTracker.ts +++ b/packages/server/src/modules/InventoryCost/models/InventoryCostLotTracker.ts @@ -6,6 +6,7 @@ import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt'; import { Item } from '@/modules/Items/models/Item'; import { BaseModel } from '@/models/Model'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; export class InventoryCostLotTracker extends BaseModel { date: Date; @@ -27,6 +28,7 @@ export class InventoryCostLotTracker extends BaseModel { warehouseId: number; item?: Item; + itemEntry?: ItemEntry; invoice?: SaleInvoice; receipt?: SaleReceipt; diff --git a/packages/server/src/modules/InventoryCost/subscribers/InventoryCost.subscriber.ts b/packages/server/src/modules/InventoryCost/subscribers/InventoryCost.subscriber.ts index 3b0e5c2e4..3db905de1 100644 --- a/packages/server/src/modules/InventoryCost/subscribers/InventoryCost.subscriber.ts +++ b/packages/server/src/modules/InventoryCost/subscribers/InventoryCost.subscriber.ts @@ -20,7 +20,7 @@ export class InventoryCostSubscriber { private readonly itemsQuantitySync: InventoryItemsQuantitySyncService, private readonly inventoryService: InventoryComputeCostService, private readonly importAls: ImportAls, - ) {} + ) { } /** * Sync inventory items quantity once inventory transactions created. @@ -60,7 +60,7 @@ export class InventoryCostSubscriber { * Marks items cost compute running state. */ @OnEvent(events.inventory.onInventoryTransactionsCreated) - async markGlobalSettingsComputeItems({}) { + async markGlobalSettingsComputeItems({ }) { await this.inventoryService.markItemsCostComputeRunning(true); } @@ -68,7 +68,7 @@ export class InventoryCostSubscriber { * Marks items cost compute as completed. */ @OnEvent(events.inventory.onInventoryCostEntriesWritten) - async markGlobalSettingsComputeItemsCompeted({}) { + async markGlobalSettingsComputeItemsCompeted({ }) { await this.inventoryService.markItemsCostComputeRunning(false); } @@ -80,15 +80,13 @@ export class InventoryCostSubscriber { itemId, startingDate, }: IComputeItemCostJobCompletedPayload) { - // const dependsComputeJobs = await this.agenda.jobs({ - // name: 'compute-item-cost', - // nextRunAt: { $ne: null }, - // 'data.tenantId': tenantId, - // }); - // // There is no scheduled compute jobs waiting. - // if (dependsComputeJobs.length === 0) { - // await this.saleInvoicesCost.scheduleWriteJournalEntries(startingDate); - // } + // Convert startingDate to Date if it's a string + const startingDateObj = startingDate instanceof Date + ? startingDate + : new Date(startingDate); + + // Write GL entries for inventory cost lots after cost computation completes + await this.saleInvoicesCost.writeCostLotsGLEntries(startingDateObj); } /** diff --git a/packages/server/src/modules/SaleInvoices/SaleInvoiceCostGLEntries.ts b/packages/server/src/modules/SaleInvoices/SaleInvoiceCostGLEntries.ts index 78053973f..d2e0ca93f 100644 --- a/packages/server/src/modules/SaleInvoices/SaleInvoiceCostGLEntries.ts +++ b/packages/server/src/modules/SaleInvoices/SaleInvoiceCostGLEntries.ts @@ -38,7 +38,8 @@ export class SaleInvoiceCostGLEntries { .modify('filterDateRange', startingDate) .orderBy('date', 'ASC') .withGraphFetched('invoice') - .withGraphFetched('item'); + .withGraphFetched('item') + .withGraphFetched('itemEntry'); const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans); @@ -79,6 +80,9 @@ export class SaleInvoiceCostGLEntries { transactionType: inventoryCostLot.transactionType, transactionId: inventoryCostLot.transactionId, + transactionNumber: inventoryCostLot.invoice.invoiceNo, + referenceNumber: inventoryCostLot.invoice.referenceNo, + date: inventoryCostLot.date, indexGroup: 20, costable: true, @@ -105,6 +109,9 @@ export class SaleInvoiceCostGLEntries { const costAccountId = inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId; + // Get description from item entry if available + const description = inventoryCostLot.itemEntry?.description || null; + // XXX Debit - Cost account. const costEntry = { ...commonEntry, @@ -112,6 +119,7 @@ export class SaleInvoiceCostGLEntries { accountId: costAccountId, accountNormal: AccountNormal.DEBIT, itemId: inventoryCostLot.itemId, + note: description, index: getIndexIncrement(), }; // XXX Credit - Inventory account. @@ -121,6 +129,7 @@ export class SaleInvoiceCostGLEntries { accountId: inventoryCostLot.item.inventoryAccountId, accountNormal: AccountNormal.DEBIT, itemId: inventoryCostLot.itemId, + note: description, index: getIndexIncrement(), }; return [costEntry, inventoryEntry]; diff --git a/packages/server/src/modules/SaleInvoices/subscribers/InvoiceCostGLEntriesSubscriber.ts b/packages/server/src/modules/SaleInvoices/subscribers/InvoiceCostGLEntriesSubscriber.ts index 159e9ebe9..318aee942 100644 --- a/packages/server/src/modules/SaleInvoices/subscribers/InvoiceCostGLEntriesSubscriber.ts +++ b/packages/server/src/modules/SaleInvoices/subscribers/InvoiceCostGLEntriesSubscriber.ts @@ -1,15 +1,18 @@ import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; import { IInventoryCostLotsGLEntriesWriteEvent } from '@/modules/InventoryCost/types/InventoryCost.types'; import { SaleInvoiceCostGLEntries } from '../SaleInvoiceCostGLEntries'; @Injectable() export class InvoiceCostGLEntriesSubscriber { - constructor(private readonly invoiceCostEntries: SaleInvoiceCostGLEntries) {} + constructor(private readonly invoiceCostEntries: SaleInvoiceCostGLEntries) { } /** * Writes the invoices cost GL entries once the inventory cost lots be written. * @param {IInventoryCostLotsGLEntriesWriteEvent} */ + @OnEvent(events.inventory.onCostLotsGLEntriesWrite) async writeInvoicesCostEntriesOnCostLotsWritten({ trx, startingDate, diff --git a/packages/webapp/src/containers/Purchases/PaymentsMade/PaymentsLanding/PaymentMadeActionsBar.tsx b/packages/webapp/src/containers/Purchases/PaymentsMade/PaymentsLanding/PaymentMadeActionsBar.tsx index 869eb0156..197f37cb1 100644 --- a/packages/webapp/src/containers/Purchases/PaymentsMade/PaymentsLanding/PaymentMadeActionsBar.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentsMade/PaymentsLanding/PaymentMadeActionsBar.tsx @@ -40,10 +40,10 @@ import { compose } from '@/utils'; * Payment made actions bar. */ function PaymentMadeActionsBar({ - // #withPaymentMadesActions + // #withPaymentMadeActions setPaymentMadesTableState, - // #withPaymentMades + // #withPaymentMade paymentMadesFilterConditions, // #withSettings @@ -133,7 +133,7 @@ function PaymentMadeActionsBar({ icon={} text={} intent={Intent.DANGER} - // onClick={handleBulkDelete} + // onClick={handleBulkDelete} />