diff --git a/.all-contributorsrc b/.all-contributorsrc
index a2e0d8553..fa713e515 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -150,6 +150,15 @@
"contributions": [
"bug"
]
+ },
+ {
+ "login": "Champetaman",
+ "name": "Camilo Oviedo",
+ "avatar_url": "https://avatars.githubusercontent.com/u/64604272?v=4",
+ "profile": "https://www.camilooviedo.com/",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 463957cf3..061f908b5 100644
--- a/README.md
+++ b/README.md
@@ -129,6 +129,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Denis 🐛 |
 Sachin Mittal 🐛 |
+  Camilo Oviedo 💻 |
diff --git a/packages/server/src/interfaces/PaymentReceive.ts b/packages/server/src/interfaces/PaymentReceive.ts
index e9910b709..b7c216abd 100644
--- a/packages/server/src/interfaces/PaymentReceive.ts
+++ b/packages/server/src/interfaces/PaymentReceive.ts
@@ -67,7 +67,7 @@ export interface IPaymentReceivedEntry {
export interface IPaymentReceivedEntryDTO {
id?: number;
index: number;
- paymentReceiveId: number;
+ paymentReceiveId?: number;
invoiceId: number;
paymentAmount: number;
}
diff --git a/packages/server/src/services/Banking/Matching/GetMatchedTransactionsByBills.ts b/packages/server/src/services/Banking/Matching/GetMatchedTransactionsByBills.ts
index 394fecba5..b9b85cb8c 100644
--- a/packages/server/src/services/Banking/Matching/GetMatchedTransactionsByBills.ts
+++ b/packages/server/src/services/Banking/Matching/GetMatchedTransactionsByBills.ts
@@ -1,18 +1,25 @@
import { Inject, Service } from 'typedi';
import { initialize } from 'objection';
+import { Knex } from 'knex';
+import { first } from 'lodash';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
-import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
-import HasTenancyService from '@/services/Tenancy/TenancyService';
+import {
+ GetMatchedTransactionsFilter,
+ IMatchTransactionDTO,
+ MatchedTransactionPOJO,
+} from './types';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
+import { CreateBillPayment } from '@/services/Purchases/BillPayments/CreateBillPayment';
+import { IBillPaymentDTO } from '@/interfaces';
@Service()
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
@Inject()
- private tenancy: HasTenancyService;
+ private transformer: TransformerInjectable;
@Inject()
- private transformer: TransformerInjectable;
+ private createPaymentMadeService: CreateBillPayment;
/**
* Retrieves the matched transactions.
@@ -71,4 +78,62 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
new GetMatchedTransactionBillsTransformer()
);
}
+
+ /**
+ * Creates the common matched transaction.
+ * @param {number} tenantId
+ * @param {Array} uncategorizedTransactionIds
+ * @param {IMatchTransactionDTO} matchTransactionDTO
+ * @param {Knex.Transaction} trx
+ */
+ public async createMatchedTransaction(
+ tenantId: number,
+ uncategorizedTransactionIds: Array,
+ matchTransactionDTO: IMatchTransactionDTO,
+ trx?: Knex.Transaction
+ ): Promise {
+ await super.createMatchedTransaction(
+ tenantId,
+ uncategorizedTransactionIds,
+ matchTransactionDTO,
+ trx
+ );
+ const { Bill, UncategorizedCashflowTransaction, MatchedBankTransaction } =
+ this.tenancy.models(tenantId);
+
+ const uncategorizedTransactionId = first(uncategorizedTransactionIds);
+ const uncategorizedTransaction =
+ await UncategorizedCashflowTransaction.query(trx)
+ .findById(uncategorizedTransactionId)
+ .throwIfNotFound();
+
+ const bill = await Bill.query(trx)
+ .findById(matchTransactionDTO.referenceId)
+ .throwIfNotFound();
+
+ const createPaymentMadeDTO: IBillPaymentDTO = {
+ vendorId: bill.vendorId,
+ paymentAccountId: uncategorizedTransaction.accountId,
+ paymentDate: uncategorizedTransaction.date,
+ exchangeRate: 1,
+ entries: [
+ {
+ paymentAmount: bill.dueAmount,
+ billId: bill.id,
+ },
+ ],
+ branchId: bill.branchId,
+ };
+ // Create a new bill payment associated to the matched bill.
+ const billPayment = await this.createPaymentMadeService.createBillPayment(
+ tenantId,
+ createPaymentMadeDTO,
+ trx
+ );
+ // Link the create bill payment with matched transaction.
+ await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
+ referenceType: 'BillPayment',
+ referenceId: billPayment.id,
+ }, trx);
+ }
}
diff --git a/packages/server/src/services/Banking/Matching/GetMatchedTransactionsByInvoices.ts b/packages/server/src/services/Banking/Matching/GetMatchedTransactionsByInvoices.ts
index 88cc72c1e..1d7baee7a 100644
--- a/packages/server/src/services/Banking/Matching/GetMatchedTransactionsByInvoices.ts
+++ b/packages/server/src/services/Banking/Matching/GetMatchedTransactionsByInvoices.ts
@@ -1,22 +1,26 @@
import { Inject, Service } from 'typedi';
import { initialize } from 'objection';
+import { Knex } from 'knex';
+import { first } from 'lodash';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
import {
GetMatchedTransactionsFilter,
+ IMatchTransactionDTO,
MatchedTransactionPOJO,
MatchedTransactionsPOJO,
} from './types';
-import HasTenancyService from '@/services/Tenancy/TenancyService';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
+import { CreatePaymentReceived } from '@/services/Sales/PaymentReceived/CreatePaymentReceived';
+import { IPaymentReceivedCreateDTO } from '@/interfaces';
@Service()
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
@Inject()
- protected tenancy: HasTenancyService;
+ protected transformer: TransformerInjectable;
@Inject()
- protected transformer: TransformerInjectable;
+ protected createPaymentReceivedService: CreatePaymentReceived;
/**
* Retrieves the matched transactions.
@@ -78,4 +82,64 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
new GetMatchedTransactionInvoicesTransformer()
);
}
+
+ /**
+ * Creates the common matched transaction.
+ * @param {number} tenantId
+ * @param {Array} uncategorizedTransactionIds
+ * @param {IMatchTransactionDTO} matchTransactionDTO
+ * @param {Knex.Transaction} trx
+ */
+ public async createMatchedTransaction(
+ tenantId: number,
+ uncategorizedTransactionIds: Array,
+ matchTransactionDTO: IMatchTransactionDTO,
+ trx?: Knex.Transaction
+ ) {
+ await super.createMatchedTransaction(
+ tenantId,
+ uncategorizedTransactionIds,
+ matchTransactionDTO,
+ trx
+ );
+ const { SaleInvoice, UncategorizedCashflowTransaction, MatchedBankTransaction } =
+ this.tenancy.models(tenantId);
+
+ const uncategorizedTransactionId = first(uncategorizedTransactionIds);
+ const uncategorizedTransaction =
+ await UncategorizedCashflowTransaction.query(trx)
+ .findById(uncategorizedTransactionId)
+ .throwIfNotFound();
+
+ const invoice = await SaleInvoice.query(trx)
+ .findById(matchTransactionDTO.referenceId)
+ .throwIfNotFound();
+
+ const createPaymentReceivedDTO: IPaymentReceivedCreateDTO = {
+ customerId: invoice.customerId,
+ paymentDate: uncategorizedTransaction.date,
+ amount: invoice.dueAmount,
+ depositAccountId: uncategorizedTransaction.accountId,
+ entries: [
+ {
+ index: 1,
+ invoiceId: invoice.id,
+ paymentAmount: invoice.dueAmount,
+ },
+ ],
+ branchId: invoice.branchId,
+ };
+ // Create a payment received associated to the matched invoice.
+ const paymentReceived = await this.createPaymentReceivedService.createPaymentReceived(
+ tenantId,
+ createPaymentReceivedDTO,
+ {},
+ trx
+ );
+ // Link the create payment received with matched invoice transaction.
+ await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
+ referenceType: 'PaymentReceive',
+ referenceId: paymentReceived.id,
+ }, trx)
+ }
}
diff --git a/packages/webapp/src/components/Dropzone/Dropzone.module.css b/packages/webapp/src/components/Dropzone/Dropzone.module.css
index 63a207805..907edbf30 100644
--- a/packages/webapp/src/components/Dropzone/Dropzone.module.css
+++ b/packages/webapp/src/components/Dropzone/Dropzone.module.css
@@ -1,5 +1,3 @@
-
-
.root {
padding: 20px;
border: 2px dotted #c5cbd3;
@@ -9,4 +7,14 @@
flex-direction: column;
background: #fff;
position: relative;
+ transition: background-color 0.3s ease, border-color 0.3s ease;
+
+ &.dropzoneAccept {
+ border-color: rgb(0, 82, 204);
+ background: rgba(0, 82, 204, 0.05);
+ }
+ &.dropzoneReject {
+ border-color: #AC2F33;
+ background: rgba(172, 47, 51, 0.05)
+ }
}
\ No newline at end of file
diff --git a/packages/webapp/src/components/Dropzone/Dropzone.tsx b/packages/webapp/src/components/Dropzone/Dropzone.tsx
index 8682a499b..bc0d45b46 100644
--- a/packages/webapp/src/components/Dropzone/Dropzone.tsx
+++ b/packages/webapp/src/components/Dropzone/Dropzone.tsx
@@ -235,7 +235,14 @@ export const Dropzone = (_props: DropzoneProps) => {
>
{
{children}
@@ -268,8 +275,6 @@ Dropzone.Idle = DropzoneIdle;
Dropzone.Reject = DropzoneReject;
-
-
type PossibleRef = Ref | undefined;
export function assignRef(ref: PossibleRef, value: T) {
diff --git a/packages/webapp/src/containers/Attachments/UploadAttachmentsPopoverContent.tsx b/packages/webapp/src/containers/Attachments/UploadAttachmentsPopoverContent.tsx
index d7a5b8c1e..f755b42b4 100644
--- a/packages/webapp/src/containers/Attachments/UploadAttachmentsPopoverContent.tsx
+++ b/packages/webapp/src/containers/Attachments/UploadAttachmentsPopoverContent.tsx
@@ -120,6 +120,7 @@ export function UploadAttachmentsPopoverContent({
uploadIcon={null}
value={null}
title={''}
+ subtitle={'Drag and drop file here or choose file'}
classNames={{ root: styles.dropzoneRoot }}
onChange={handleChangeDropzone}
dropzoneProps={{
diff --git a/packages/webapp/src/containers/Import/ImportDropzone.tsx b/packages/webapp/src/containers/Import/ImportDropzone.tsx
index 5dae46d06..43ff50b8c 100644
--- a/packages/webapp/src/containers/Import/ImportDropzone.tsx
+++ b/packages/webapp/src/containers/Import/ImportDropzone.tsx
@@ -13,6 +13,8 @@ export function ImportDropzone() {
{({ form }) => (
{
hideAlerts();