diff --git a/database/mssql/scripts/versions/revert/v_51_ddl_revert.sql b/database/mssql/scripts/versions/revert/v_51_ddl_revert.sql new file mode 100644 index 000000000..d5e0f4b2f --- /dev/null +++ b/database/mssql/scripts/versions/revert/v_51_ddl_revert.sql @@ -0,0 +1,237 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO + + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_RECEIPT] ADD [TRANSACTION_ID] [bigint] NULL + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_RECEIPT] WITH CHECK ADD CONSTRAINT [ORBC_RECEIPT_TRANSACTION_ID_FK] FOREIGN KEY([TRANSACTION_ID]) +REFERENCES [permit].[ORBC_TRANSACTION] ([TRANSACTION_ID]) + +-- Alter trigger permit.ORBC_RCPT_A_S_IUD_TR +PRINT N'Alter trigger permit.ORBC_RCPT_A_S_IUD_TR' +GO +ALTER TRIGGER [permit].[ORBC_RCPT_A_S_IUD_TR] ON permit.[ORBC_RECEIPT] FOR INSERT, UPDATE, DELETE AS +SET NOCOUNT ON +BEGIN TRY +DECLARE @curr_date datetime; +SET @curr_date = getutcdate(); + IF NOT EXISTS(SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- historical + IF EXISTS(SELECT * FROM deleted) + update [permit].[ORBC_RECEIPT_HIST] set END_DATE_HIST = @curr_date where RECEIPT_ID in (select RECEIPT_ID from deleted) and END_DATE_HIST is null; + + IF EXISTS(SELECT * FROM inserted) + insert into [permit].[ORBC_RECEIPT_HIST] ([RECEIPT_ID], [RECEIPT_NUMBER], [TRANSACTION_ID], [RECEIPT_DOCUMENT_ID], [APP_CREATE_TIMESTAMP], [APP_CREATE_USERID], [APP_CREATE_USER_GUID], [APP_CREATE_USER_DIRECTORY], [APP_LAST_UPDATE_TIMESTAMP], [APP_LAST_UPDATE_USERID], [APP_LAST_UPDATE_USER_GUID], [APP_LAST_UPDATE_USER_DIRECTORY], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], _RECEIPT_HIST_ID, END_DATE_HIST, EFFECTIVE_DATE_HIST) + select [RECEIPT_ID], [RECEIPT_NUMBER], [TRANSACTION_ID], [RECEIPT_DOCUMENT_ID], [APP_CREATE_TIMESTAMP], [APP_CREATE_USERID], [APP_CREATE_USER_GUID], [APP_CREATE_USER_DIRECTORY], [APP_LAST_UPDATE_TIMESTAMP], [APP_LAST_UPDATE_USERID], [APP_LAST_UPDATE_USER_GUID], [APP_LAST_UPDATE_USER_DIRECTORY], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], (next value for [permit].[ORBC_RECEIPT_H_ID_SEQ]) as [_RECEIPT_HIST_ID], null as [END_DATE_HIST], @curr_date as [EFFECTIVE_DATE_HIST] from inserted; + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +-- Alter trigger permit.ORBC_RCPT_I_S_U_TR +PRINT N'Alter trigger permit.ORBC_RCPT_I_S_U_TR' +GO +ALTER TRIGGER [permit].[ORBC_RCPT_I_S_U_TR] ON permit.[ORBC_RECEIPT] INSTEAD OF UPDATE AS +SET NOCOUNT ON +BEGIN TRY + IF NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- validate concurrency control + if exists (select 1 from inserted, deleted where inserted.CONCURRENCY_CONTROL_NUMBER != deleted.CONCURRENCY_CONTROL_NUMBER+1 AND inserted.RECEIPT_ID = deleted.RECEIPT_ID) + raiserror('CONCURRENCY FAILURE.',16,1) + + + -- update statement + update [permit].[ORBC_RECEIPT] + set "RECEIPT_NUMBER" = inserted."RECEIPT_NUMBER", + "TRANSACTION_ID" = inserted."TRANSACTION_ID", + "RECEIPT_DOCUMENT_ID" = inserted."RECEIPT_DOCUMENT_ID", + "APP_LAST_UPDATE_TIMESTAMP" = inserted."APP_LAST_UPDATE_TIMESTAMP", + "APP_LAST_UPDATE_USERID" = inserted."APP_LAST_UPDATE_USERID", + "APP_LAST_UPDATE_USER_GUID" = inserted."APP_LAST_UPDATE_USER_GUID", + "APP_LAST_UPDATE_USER_DIRECTORY" = inserted."APP_LAST_UPDATE_USER_DIRECTORY", + "CONCURRENCY_CONTROL_NUMBER" = inserted."CONCURRENCY_CONTROL_NUMBER" + , DB_LAST_UPDATE_TIMESTAMP = getutcdate() + , DB_LAST_UPDATE_USERID = user_name() + from [permit].[ORBC_RECEIPT] + inner join inserted + on (ORBC_RECEIPT.RECEIPT_ID = inserted.RECEIPT_ID); + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DISABLE TRIGGER [permit].[ORBC_RCPT_I_S_U_TR] ON permit.[ORBC_RECEIPT] +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO +UPDATE r +SET TRANSACTION_ID = t.TRANSACTION_ID +FROM [permit].[ORBC_RECEIPT] r +INNER JOIN [permit].[ORBC_TRANSACTION] t ON t.RECEIPT_ID = r.RECEIPT_ID; + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_RECEIPT] ALTER COLUMN [TRANSACTION_ID] [bigint] NOT NULL + +IF @@ERROR <> 0 SET NOEXEC ON +GO +AlTER TABLE [permit].[ORBC_RECEIPT_HIST] ALTER COLUMN [TRANSACTION_ID] [bigint] NOT NULL + + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_TRANSACTION] DROP CONSTRAINT FK_ORBC_TRANSACTION_RECEIPT_ID + +IF @@ERROR <> 0 SET NOEXEC ON +GO +DROP INDEX IX_ORBC_TRANSACTION_RECEIPT_ID_FK ON [permit].[ORBC_TRANSACTION]; + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_TRANSACTION] DROP COLUMN [RECEIPT_ID] + +IF @@ERROR <> 0 SET NOEXEC ON +GO +CREATE NONCLUSTERED INDEX IX_ORBC_RECEIPT_TRANSACTION_ID_FK ON [permit].[ORBC_RECEIPT] ([TRANSACTION_ID]); + + +IF @@ERROR <> 0 SET NOEXEC ON +GO +-- Alter trigger permit.ORBC_TXN_A_S_IUD_TR +PRINT N'Alter trigger permit.ORBC_TXN_A_S_IUD_TR' +GO +ALTER TRIGGER [permit].[ORBC_TXN_A_S_IUD_TR] ON permit.[ORBC_TRANSACTION] FOR INSERT, UPDATE, DELETE AS +SET NOCOUNT ON +BEGIN TRY +DECLARE @curr_date datetime; +SET @curr_date = getutcdate(); + IF NOT EXISTS(SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- historical + IF EXISTS(SELECT * FROM deleted) + update [permit].[ORBC_TRANSACTION_HIST] set END_DATE_HIST = @curr_date where TRANSACTION_ID in (select TRANSACTION_ID from deleted) and END_DATE_HIST is null; + + IF EXISTS(SELECT * FROM inserted) + insert into [permit].[ORBC_TRANSACTION_HIST] ([TRANSACTION_ID], [TRANSACTION_TYPE], [PAYMENT_METHOD_TYPE], [PAYMENT_CARD_TYPE], [TOTAL_TRANSACTION_AMOUNT], [TRANSACTION_SUBMIT_DATE], [TRANSACTION_ORDER_NUMBER], [PG_TRANSACTION_ID], [PG_TRANSACTION_APPROVED], [PG_AUTH_CODE], [PG_TRANSACTION_CARD_TYPE], [PG_TRANSACTION_DATE], [PG_CVD_ID], [PG_PAYMENT_METHOD], [PG_MESSAGE_ID], [PG_MESSAGE_TEXT], [APP_CREATE_TIMESTAMP], [APP_CREATE_USERID], [APP_CREATE_USER_GUID], [APP_CREATE_USER_DIRECTORY], [APP_LAST_UPDATE_TIMESTAMP], [APP_LAST_UPDATE_USERID], [APP_LAST_UPDATE_USER_GUID], [APP_LAST_UPDATE_USER_DIRECTORY], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], _TRANSACTION_HIST_ID, END_DATE_HIST, EFFECTIVE_DATE_HIST) + select [TRANSACTION_ID], [TRANSACTION_TYPE], [PAYMENT_METHOD_TYPE], [PAYMENT_CARD_TYPE], [TOTAL_TRANSACTION_AMOUNT], [TRANSACTION_SUBMIT_DATE], [TRANSACTION_ORDER_NUMBER], [PG_TRANSACTION_ID], [PG_TRANSACTION_APPROVED], [PG_AUTH_CODE], [PG_TRANSACTION_CARD_TYPE], [PG_TRANSACTION_DATE], [PG_CVD_ID], [PG_PAYMENT_METHOD], [PG_MESSAGE_ID], [PG_MESSAGE_TEXT], [APP_CREATE_TIMESTAMP], [APP_CREATE_USERID], [APP_CREATE_USER_GUID], [APP_CREATE_USER_DIRECTORY], [APP_LAST_UPDATE_TIMESTAMP], [APP_LAST_UPDATE_USERID], [APP_LAST_UPDATE_USER_GUID], [APP_LAST_UPDATE_USER_DIRECTORY], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], (next value for [permit].[ORBC_TRANSACTION_H_ID_SEQ]) as [_TRANSACTION_HIST_ID], null as [END_DATE_HIST], @curr_date as [EFFECTIVE_DATE_HIST] from inserted; + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO + + +-- Alter trigger permit.ORBC_TXN_I_S_U_TR +PRINT N'Alter trigger permit.ORBC_TXN_I_S_U_TR' +GO +ALTER TRIGGER [permit].[ORBC_TXN_I_S_U_TR] ON permit.[ORBC_TRANSACTION] INSTEAD OF UPDATE AS +SET NOCOUNT ON +BEGIN TRY + IF NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- validate concurrency control + if exists (select 1 from inserted, deleted where inserted.CONCURRENCY_CONTROL_NUMBER != deleted.CONCURRENCY_CONTROL_NUMBER+1 AND inserted.TRANSACTION_ID = deleted.TRANSACTION_ID) + raiserror('CONCURRENCY FAILURE.',16,1) + + + -- update statement + update [permit].[ORBC_TRANSACTION] + set "TRANSACTION_TYPE" = inserted."TRANSACTION_TYPE", + "PAYMENT_METHOD_TYPE" = inserted."PAYMENT_METHOD_TYPE", + "PAYMENT_CARD_TYPE" = inserted."PAYMENT_CARD_TYPE", + "TOTAL_TRANSACTION_AMOUNT" = inserted."TOTAL_TRANSACTION_AMOUNT", + "TRANSACTION_SUBMIT_DATE" = inserted."TRANSACTION_SUBMIT_DATE", + "TRANSACTION_ORDER_NUMBER" = inserted."TRANSACTION_ORDER_NUMBER", + "PG_TRANSACTION_ID" = inserted."PG_TRANSACTION_ID", + "PG_TRANSACTION_APPROVED" = inserted."PG_TRANSACTION_APPROVED", + "PG_AUTH_CODE" = inserted."PG_AUTH_CODE", + "PG_TRANSACTION_CARD_TYPE" = inserted."PG_TRANSACTION_CARD_TYPE", + "PG_TRANSACTION_DATE" = inserted."PG_TRANSACTION_DATE", + "PG_CVD_ID" = inserted."PG_CVD_ID", + "PG_PAYMENT_METHOD" = inserted."PG_PAYMENT_METHOD", + "PG_MESSAGE_ID" = inserted."PG_MESSAGE_ID", + "PG_MESSAGE_TEXT" = inserted."PG_MESSAGE_TEXT", + "APP_LAST_UPDATE_TIMESTAMP" = inserted."APP_LAST_UPDATE_TIMESTAMP", + "APP_LAST_UPDATE_USERID" = inserted."APP_LAST_UPDATE_USERID", + "APP_LAST_UPDATE_USER_GUID" = inserted."APP_LAST_UPDATE_USER_GUID", + "APP_LAST_UPDATE_USER_DIRECTORY" = inserted."APP_LAST_UPDATE_USER_DIRECTORY", + "CONCURRENCY_CONTROL_NUMBER" = inserted."CONCURRENCY_CONTROL_NUMBER" + , DB_LAST_UPDATE_TIMESTAMP = getutcdate() + , DB_LAST_UPDATE_USERID = user_name() + from [permit].[ORBC_TRANSACTION] + inner join inserted + on (ORBC_TRANSACTION.TRANSACTION_ID = inserted.TRANSACTION_ID); + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DISABLE TRIGGER [permit].[ORBC_TXN_I_S_U_TR] ON permit.[ORBC_TRANSACTION] +GO + + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_TRANSACTION_HIST] DROP COLUMN [RECEIPT_ID] + +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Reverting addition of RECEIPT_ID col to ORBC_TRANSACTION and dropping TRANSACTION_ID from ORBC_RECEIPT.' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (50, @VersionDescription, getutcdate()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO +COMMIT TRANSACTION +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO \ No newline at end of file diff --git a/database/mssql/scripts/versions/v_51_ddl.sql b/database/mssql/scripts/versions/v_51_ddl.sql new file mode 100644 index 000000000..b44fa4f44 --- /dev/null +++ b/database/mssql/scripts/versions/v_51_ddl.sql @@ -0,0 +1,246 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_TRANSACTION] ADD [RECEIPT_ID] [bigint] NULL + +IF @@ERROR <> 0 SET NOEXEC ON +GO +CREATE NONCLUSTERED INDEX IX_ORBC_TRANSACTION_RECEIPT_ID_FK ON [permit].[ORBC_TRANSACTION] ([RECEIPT_ID]); + +IF @@ERROR <> 0 SET NOEXEC ON +GO +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Receipt Id of the transaction' , @level0type=N'SCHEMA',@level0name=N'permit', @level1type=N'TABLE',@level1name=N'ORBC_TRANSACTION', @level2type=N'COLUMN',@level2name=N'RECEIPT_ID' + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_TRANSACTION] WITH CHECK ADD CONSTRAINT [FK_ORBC_TRANSACTION_RECEIPT_ID] FOREIGN KEY([RECEIPT_ID]) +REFERENCES [permit].[ORBC_RECEIPT] ([RECEIPT_ID]) +GO + +ALTER TABLE [permit].[ORBC_TRANSACTION] CHECK CONSTRAINT [FK_ORBC_TRANSACTION_RECEIPT_ID] + + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_TRANSACTION_HIST] ADD [RECEIPT_ID] [bigint] NULL + +IF @@ERROR <> 0 SET NOEXEC ON +GO +-- Alter trigger permit.ORBC_TXN_A_S_IUD_TR +PRINT N'Alter trigger permit.ORBC_TXN_A_S_IUD_TR' +GO +ALTER TRIGGER [permit].[ORBC_TXN_A_S_IUD_TR] ON permit.[ORBC_TRANSACTION] FOR INSERT, UPDATE, DELETE AS +SET NOCOUNT ON +BEGIN TRY +DECLARE @curr_date datetime; +SET @curr_date = getutcdate(); + IF NOT EXISTS(SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- historical + IF EXISTS(SELECT * FROM deleted) + update [permit].[ORBC_TRANSACTION_HIST] set END_DATE_HIST = @curr_date where TRANSACTION_ID in (select TRANSACTION_ID from deleted) and END_DATE_HIST is null; + + IF EXISTS(SELECT * FROM inserted) + insert into [permit].[ORBC_TRANSACTION_HIST] ([TRANSACTION_ID], [RECEIPT_ID], [TRANSACTION_TYPE], [PAYMENT_METHOD_TYPE], [PAYMENT_CARD_TYPE], [TOTAL_TRANSACTION_AMOUNT], [TRANSACTION_SUBMIT_DATE], [TRANSACTION_ORDER_NUMBER], [PG_TRANSACTION_ID], [PG_TRANSACTION_APPROVED], [PG_AUTH_CODE], [PG_TRANSACTION_CARD_TYPE], [PG_TRANSACTION_DATE], [PG_CVD_ID], [PG_PAYMENT_METHOD], [PG_MESSAGE_ID], [PG_MESSAGE_TEXT], [APP_CREATE_TIMESTAMP], [APP_CREATE_USERID], [APP_CREATE_USER_GUID], [APP_CREATE_USER_DIRECTORY], [APP_LAST_UPDATE_TIMESTAMP], [APP_LAST_UPDATE_USERID], [APP_LAST_UPDATE_USER_GUID], [APP_LAST_UPDATE_USER_DIRECTORY], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], _TRANSACTION_HIST_ID, END_DATE_HIST, EFFECTIVE_DATE_HIST) + select [TRANSACTION_ID], [RECEIPT_ID], [TRANSACTION_TYPE], [PAYMENT_METHOD_TYPE], [PAYMENT_CARD_TYPE], [TOTAL_TRANSACTION_AMOUNT], [TRANSACTION_SUBMIT_DATE], [TRANSACTION_ORDER_NUMBER], [PG_TRANSACTION_ID], [PG_TRANSACTION_APPROVED], [PG_AUTH_CODE], [PG_TRANSACTION_CARD_TYPE], [PG_TRANSACTION_DATE], [PG_CVD_ID], [PG_PAYMENT_METHOD], [PG_MESSAGE_ID], [PG_MESSAGE_TEXT], [APP_CREATE_TIMESTAMP], [APP_CREATE_USERID], [APP_CREATE_USER_GUID], [APP_CREATE_USER_DIRECTORY], [APP_LAST_UPDATE_TIMESTAMP], [APP_LAST_UPDATE_USERID], [APP_LAST_UPDATE_USER_GUID], [APP_LAST_UPDATE_USER_DIRECTORY], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], (next value for [permit].[ORBC_TRANSACTION_H_ID_SEQ]) as [_TRANSACTION_HIST_ID], null as [END_DATE_HIST], @curr_date as [EFFECTIVE_DATE_HIST] from inserted; + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO + + +-- Alter trigger permit.ORBC_TXN_I_S_U_TR +PRINT N'Alter trigger permit.ORBC_TXN_I_S_U_TR' +GO +ALTER TRIGGER [permit].[ORBC_TXN_I_S_U_TR] ON permit.[ORBC_TRANSACTION] INSTEAD OF UPDATE AS +SET NOCOUNT ON +BEGIN TRY + IF NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- validate concurrency control + if exists (select 1 from inserted, deleted where inserted.CONCURRENCY_CONTROL_NUMBER != deleted.CONCURRENCY_CONTROL_NUMBER+1 AND inserted.TRANSACTION_ID = deleted.TRANSACTION_ID) + raiserror('CONCURRENCY FAILURE.',16,1) + + + -- update statement + update [permit].[ORBC_TRANSACTION] + set "TRANSACTION_TYPE" = inserted."TRANSACTION_TYPE", + "RECEIPT_ID" = inserted."RECEIPT_ID", + "PAYMENT_METHOD_TYPE" = inserted."PAYMENT_METHOD_TYPE", + "PAYMENT_CARD_TYPE" = inserted."PAYMENT_CARD_TYPE", + "TOTAL_TRANSACTION_AMOUNT" = inserted."TOTAL_TRANSACTION_AMOUNT", + "TRANSACTION_SUBMIT_DATE" = inserted."TRANSACTION_SUBMIT_DATE", + "TRANSACTION_ORDER_NUMBER" = inserted."TRANSACTION_ORDER_NUMBER", + "PG_TRANSACTION_ID" = inserted."PG_TRANSACTION_ID", + "PG_TRANSACTION_APPROVED" = inserted."PG_TRANSACTION_APPROVED", + "PG_AUTH_CODE" = inserted."PG_AUTH_CODE", + "PG_TRANSACTION_CARD_TYPE" = inserted."PG_TRANSACTION_CARD_TYPE", + "PG_TRANSACTION_DATE" = inserted."PG_TRANSACTION_DATE", + "PG_CVD_ID" = inserted."PG_CVD_ID", + "PG_PAYMENT_METHOD" = inserted."PG_PAYMENT_METHOD", + "PG_MESSAGE_ID" = inserted."PG_MESSAGE_ID", + "PG_MESSAGE_TEXT" = inserted."PG_MESSAGE_TEXT", + "APP_LAST_UPDATE_TIMESTAMP" = inserted."APP_LAST_UPDATE_TIMESTAMP", + "APP_LAST_UPDATE_USERID" = inserted."APP_LAST_UPDATE_USERID", + "APP_LAST_UPDATE_USER_GUID" = inserted."APP_LAST_UPDATE_USER_GUID", + "APP_LAST_UPDATE_USER_DIRECTORY" = inserted."APP_LAST_UPDATE_USER_DIRECTORY", + "CONCURRENCY_CONTROL_NUMBER" = inserted."CONCURRENCY_CONTROL_NUMBER" + , DB_LAST_UPDATE_TIMESTAMP = getutcdate() + , DB_LAST_UPDATE_USERID = user_name() + from [permit].[ORBC_TRANSACTION] + inner join inserted + on (ORBC_TRANSACTION.TRANSACTION_ID = inserted.TRANSACTION_ID); + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DISABLE TRIGGER [permit].[ORBC_TXN_I_S_U_TR] ON permit.[ORBC_TRANSACTION] +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO +UPDATE t +SET RECEIPT_ID = r.RECEIPT_ID +FROM [permit].[ORBC_TRANSACTION] t +INNER JOIN [permit].[ORBC_RECEIPT] r ON t.TRANSACTION_ID = r.TRANSACTION_ID; + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_RECEIPT] DROP CONSTRAINT ORBC_RECEIPT_TRANSACTION_ID_FK + +IF @@ERROR <> 0 SET NOEXEC ON +GO + DROP INDEX IX_ORBC_RECEIPT_TRANSACTION_ID_FK ON [permit].[ORBC_RECEIPT]; + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_RECEIPT] DROP COLUMN [TRANSACTION_ID] + + +IF @@ERROR <> 0 SET NOEXEC ON +GO +AlTER TABLE [permit].[ORBC_RECEIPT_HIST] ALTER COLUMN [TRANSACTION_ID] [bigint] NULL + +IF @@ERROR <> 0 SET NOEXEC ON +GO +-- Alter trigger permit.ORBC_RCPT_A_S_IUD_TR +PRINT N'Alter trigger permit.ORBC_RCPT_A_S_IUD_TR' +GO +ALTER TRIGGER [permit].[ORBC_RCPT_A_S_IUD_TR] ON permit.[ORBC_RECEIPT] FOR INSERT, UPDATE, DELETE AS +SET NOCOUNT ON +BEGIN TRY +DECLARE @curr_date datetime; +SET @curr_date = getutcdate(); + IF NOT EXISTS(SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- historical + IF EXISTS(SELECT * FROM deleted) + update [permit].[ORBC_RECEIPT_HIST] set END_DATE_HIST = @curr_date where RECEIPT_ID in (select RECEIPT_ID from deleted) and END_DATE_HIST is null; + + IF EXISTS(SELECT * FROM inserted) + insert into [permit].[ORBC_RECEIPT_HIST] ([RECEIPT_ID], [RECEIPT_NUMBER], [RECEIPT_DOCUMENT_ID], [APP_CREATE_TIMESTAMP], [APP_CREATE_USERID], [APP_CREATE_USER_GUID], [APP_CREATE_USER_DIRECTORY], [APP_LAST_UPDATE_TIMESTAMP], [APP_LAST_UPDATE_USERID], [APP_LAST_UPDATE_USER_GUID], [APP_LAST_UPDATE_USER_DIRECTORY], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], _RECEIPT_HIST_ID, END_DATE_HIST, EFFECTIVE_DATE_HIST) + select [RECEIPT_ID], [RECEIPT_NUMBER], [RECEIPT_DOCUMENT_ID], [APP_CREATE_TIMESTAMP], [APP_CREATE_USERID], [APP_CREATE_USER_GUID], [APP_CREATE_USER_DIRECTORY], [APP_LAST_UPDATE_TIMESTAMP], [APP_LAST_UPDATE_USERID], [APP_LAST_UPDATE_USER_GUID], [APP_LAST_UPDATE_USER_DIRECTORY], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], (next value for [permit].[ORBC_RECEIPT_H_ID_SEQ]) as [_RECEIPT_HIST_ID], null as [END_DATE_HIST], @curr_date as [EFFECTIVE_DATE_HIST] from inserted; + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +-- Alter trigger permit.ORBC_RCPT_I_S_U_TR +PRINT N'Alter trigger permit.ORBC_RCPT_I_S_U_TR' +GO +ALTER TRIGGER [permit].[ORBC_RCPT_I_S_U_TR] ON permit.[ORBC_RECEIPT] INSTEAD OF UPDATE AS +SET NOCOUNT ON +BEGIN TRY + IF NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- validate concurrency control + if exists (select 1 from inserted, deleted where inserted.CONCURRENCY_CONTROL_NUMBER != deleted.CONCURRENCY_CONTROL_NUMBER+1 AND inserted.RECEIPT_ID = deleted.RECEIPT_ID) + raiserror('CONCURRENCY FAILURE.',16,1) + + + -- update statement + update [permit].[ORBC_RECEIPT] + set "RECEIPT_NUMBER" = inserted."RECEIPT_NUMBER", + "RECEIPT_DOCUMENT_ID" = inserted."RECEIPT_DOCUMENT_ID", + "APP_LAST_UPDATE_TIMESTAMP" = inserted."APP_LAST_UPDATE_TIMESTAMP", + "APP_LAST_UPDATE_USERID" = inserted."APP_LAST_UPDATE_USERID", + "APP_LAST_UPDATE_USER_GUID" = inserted."APP_LAST_UPDATE_USER_GUID", + "APP_LAST_UPDATE_USER_DIRECTORY" = inserted."APP_LAST_UPDATE_USER_DIRECTORY", + "CONCURRENCY_CONTROL_NUMBER" = inserted."CONCURRENCY_CONTROL_NUMBER" + , DB_LAST_UPDATE_TIMESTAMP = getutcdate() + , DB_LAST_UPDATE_USERID = user_name() + from [permit].[ORBC_RECEIPT] + inner join inserted + on (ORBC_RECEIPT.RECEIPT_ID = inserted.RECEIPT_ID); + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DISABLE TRIGGER [permit].[ORBC_RCPT_I_S_U_TR] ON permit.[ORBC_RECEIPT] +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Add RECEIPT_ID col to ORBC_TRANSACTION and Drop TRANSACTION_ID from ORBC_RECEIPT' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (51, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO + diff --git a/vehicles/src/common/helper/format-template-data.helper.ts b/vehicles/src/common/helper/format-template-data.helper.ts index 8181ef3d8..f9334a727 100644 --- a/vehicles/src/common/helper/format-template-data.helper.ts +++ b/vehicles/src/common/helper/format-template-data.helper.ts @@ -87,12 +87,15 @@ export const formatTemplateData = ( template.clientNumber = companyInfo.clientNumber; template.companyAlternateName = companyInfo.alternateName; - // Format Fee Summary const transcation = permit.permitTransactions?.at(0)?.transaction; + // Format Fee Summary template.permitData.feeSummary = formatAmount( transcation.transactionTypeId, - permit.permitTransactions?.at(0)?.transactionAmount, + permit.permitTransactions?.reduce( + (accumulator, item) => accumulator + item.transactionAmount, + 0, + ), ).toString(); revisionHistory?.forEach((revision) => { diff --git a/vehicles/src/modules/permit-application-payment/payment/dto/common/payment-transaction.dto.ts b/vehicles/src/modules/permit-application-payment/payment/dto/common/payment-transaction.dto.ts new file mode 100644 index 000000000..fb9e6183f --- /dev/null +++ b/vehicles/src/modules/permit-application-payment/payment/dto/common/payment-transaction.dto.ts @@ -0,0 +1,67 @@ +import { AutoMap } from '@automapper/classes'; +import { ApiProperty } from '@nestjs/swagger'; +import { + IsEnum, + IsNumber, + IsOptional, + IsString, + Length, + MaxLength, + Min, +} from 'class-validator'; +import { PaymentCardType } from '../../../../../common/enum/payment-card-type.enum'; +import { PaymentMethodType } from '../../../../../common/enum/payment-method-type.enum'; + +export class PaymentTransactionDto { + @AutoMap() + @ApiProperty({ + example: '10000148', + required: false, + description: + 'Bambora-assigned eight-digit unique id number used to identify an individual transaction.', + }) + @IsOptional() + @IsString() + @MaxLength(20) + pgTransactionId: string; + + @AutoMap() + @ApiProperty({ + example: 'CC', + required: false, + description: 'Represents the payment method of a transaction.', + }) + @IsOptional() + @IsString() + @Length(1, 2) + pgPaymentMethod: string; + + @AutoMap() + @ApiProperty({ + example: '30.00', + description: 'Represents the amount of the transaction.', + }) + @IsNumber() + @Min(0) + transactionAmount: number; + + @AutoMap() + @ApiProperty({ + enum: PaymentMethodType, + example: PaymentMethodType.WEB, + description: 'The identifier of the user selected payment method.', + }) + @IsEnum(PaymentMethodType) + paymentMethodTypeCode: PaymentMethodType; + + @AutoMap() + @ApiProperty({ + example: PaymentCardType.VISA, + enum: PaymentCardType, + description: 'The payment types.', + required: false, + }) + @IsOptional() + @IsEnum(PaymentCardType) + paymentCardTypeCode?: PaymentCardType; +} diff --git a/vehicles/src/modules/permit-application-payment/payment/dto/request/create-refund-transaction.dto.ts b/vehicles/src/modules/permit-application-payment/payment/dto/request/create-refund-transaction.dto.ts new file mode 100644 index 000000000..a689c9714 --- /dev/null +++ b/vehicles/src/modules/permit-application-payment/payment/dto/request/create-refund-transaction.dto.ts @@ -0,0 +1,35 @@ +import { AutoMap } from '@automapper/classes'; +import { ApiProperty } from '@nestjs/swagger'; +import { + ArrayMinSize, + IsArray, + IsNumberString, + MaxLength, + ValidateNested, +} from 'class-validator'; + +import { PaymentTransactionDto } from '../common/payment-transaction.dto'; +import { Type } from 'class-transformer'; + +export class CreateRefundTransactionDto { + @AutoMap() + @ApiProperty({ + description: 'Application/Permit Id.', + example: '1', + }) + @IsNumberString() + @MaxLength(20) + applicationId: string; + + @AutoMap() + @ApiProperty({ + description: 'The transaction details specific to application/permit.', + required: true, + type: [PaymentTransactionDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @ArrayMinSize(1) + @Type(() => PaymentTransactionDto) + transactions: PaymentTransactionDto[]; +} diff --git a/vehicles/src/modules/permit-application-payment/payment/entities/receipt.entity.ts b/vehicles/src/modules/permit-application-payment/payment/entities/receipt.entity.ts index 73e2bd31c..322371022 100644 --- a/vehicles/src/modules/permit-application-payment/payment/entities/receipt.entity.ts +++ b/vehicles/src/modules/permit-application-payment/payment/entities/receipt.entity.ts @@ -1,11 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { - Entity, - Column, - PrimaryGeneratedColumn, - OneToOne, - JoinColumn, -} from 'typeorm'; +import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; import { AutoMap } from '@automapper/classes'; import { Base } from '../../../common/entities/base.entity'; import { Transaction } from './transaction.entity'; @@ -43,7 +37,6 @@ export class Receipt extends Base { }) receiptDocumentId: string; - @OneToOne(() => Transaction, (transaction) => transaction.receipt) - @JoinColumn({ name: 'TRANSACTION_ID' }) - transaction: Transaction; + @OneToMany(() => Transaction, (transaction) => transaction.receipt) + public transactions: Transaction[]; } diff --git a/vehicles/src/modules/permit-application-payment/payment/entities/transaction.entity.ts b/vehicles/src/modules/permit-application-payment/payment/entities/transaction.entity.ts index ddc8cfc5d..ebb9eec84 100644 --- a/vehicles/src/modules/permit-application-payment/payment/entities/transaction.entity.ts +++ b/vehicles/src/modules/permit-application-payment/payment/entities/transaction.entity.ts @@ -3,8 +3,9 @@ import { Entity, Column, PrimaryGeneratedColumn, - OneToOne, OneToMany, + JoinColumn, + ManyToOne, } from 'typeorm'; import { AutoMap } from '@automapper/classes'; import { Base } from '../../../common/entities/base.entity'; @@ -222,8 +223,9 @@ export class Transaction extends Base { }) pgMessageText: string; - @OneToOne(() => Receipt, (receipt) => receipt.transaction) - receipt: Receipt; + @ManyToOne(() => Receipt, (receipt) => receipt.transactions) + @JoinColumn({ name: 'RECEIPT_ID' }) + public receipt: Receipt; @OneToMany( () => PermitTransaction, diff --git a/vehicles/src/modules/permit-application-payment/payment/payment.controller.ts b/vehicles/src/modules/permit-application-payment/payment/payment.controller.ts index a57526caa..cb7e6922d 100644 --- a/vehicles/src/modules/permit-application-payment/payment/payment.controller.ts +++ b/vehicles/src/modules/permit-application-payment/payment/payment.controller.ts @@ -37,6 +37,7 @@ import { CLIENT_USER_ROLE_LIST, IDIRUserRole, } from '../../../common/enum/user-role.enum'; +import { CreateRefundTransactionDto } from './dto/request/create-refund-transaction.dto'; @ApiBearerAuth() @ApiTags('Payment') @@ -90,6 +91,34 @@ export class PaymentController { return paymentDetails; } + @ApiCreatedResponse({ + description: 'The Transaction Resource', + isArray: true, + type: ReadTransactionDto, + }) + @Permissions({ + allowedIdirRoles: [ + IDIRUserRole.PPC_CLERK, + IDIRUserRole.SYSTEM_ADMINISTRATOR, + IDIRUserRole.CTPO, + ], + }) + @Post('refund') + async createRefundTransactionDetails( + @Req() request: Request, + @Body() { applicationId, transactions }: CreateRefundTransactionDto, + ): Promise { + const currentUser = request.user as IUserJWT; + + const paymentDetails = await this.paymentService.createRefundTransactions({ + currentUser, + applicationId, + transactions, + }); + + return paymentDetails; + } + @ApiOkResponse({ description: 'The Payment Gateway Transaction Resource', type: UpdatePaymentGatewayTransactionDto, diff --git a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts index e511f1ced..bd83e524d 100644 --- a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts +++ b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts @@ -23,7 +23,10 @@ import { } from 'typeorm'; import { PermitTransaction } from './entities/permit-transaction.entity'; import { IUserJWT } from 'src/common/interface/user-jwt.interface'; -import { callDatabaseSequence } from 'src/common/helper/database.helper'; +import { + callDatabaseSequence, + setBaseEntityProperties, +} from 'src/common/helper/database.helper'; import { Permit } from '../permit/entities/permit.entity'; import { ApplicationStatus } from '../../../common/enum/application-status.enum'; import { PaymentMethodType as PaymentMethodTypeEnum } from '../../../common/enum/payment-method-type.enum'; @@ -37,6 +40,7 @@ import { PAYMENT_CURRENCY, CRYPTO_ALGORITHM_MD5, GL_PROJ_CODE_PLACEHOLDER, + PPC_FULL_TEXT, } from '../../../common/constants/api.constant'; import { convertToHash } from 'src/common/helper/crypto.helper'; import { UpdatePaymentGatewayTransactionDto } from './dto/request/update-payment-gateway-transaction.dto'; @@ -73,6 +77,8 @@ import { } from '../../../common/helper/common.helper'; import { SpecialAuth } from 'src/modules/special-auth/entities/special-auth.entity'; import { TIMEZONE_PACIFIC } from 'src/common/constants/api.constant'; +import { PaymentTransactionDto } from './dto/common/payment-transaction.dto'; +import { Nullable } from '../../../common/types/common'; @Injectable() export class PaymentService { @@ -256,11 +262,223 @@ export class PaymentService { ); } + /** + * Creates a Refund Transaction in ORBC System, ensuring that payment methods align with user roles and enabled features. + * The method verifies transactions against application status and computes transaction amounts. + * It then creates and saves new transactions and their associated records, handling any CFS payment methods. + * + * @param applicationId - The ID of the application related to the refund transactions. + * @param transactions - An array of transactions of type {@link RefundTransactionDto} to process. + * @param currentUser - The current user object of type {@link IUserJWT}. + * @param nestedQueryRunner - An optional query runner. If not provided, a new one is created. + * @returns {Promise} The created list of transactions of type {@link ReadTransactionDto}. + * @throws UnprocessableEntityException - When the payment method type is invalid for the user or feature is disabled. + * @throws BadRequestException - When the application status is not valid for the transaction. + */ + @LogAsyncMethodExecution() + async createRefundTransactions({ + applicationId, + transactions, + currentUser, + nestedQueryRunner, + }: { + applicationId: string; + transactions: PaymentTransactionDto[]; + currentUser: IUserJWT; + nestedQueryRunner?: Nullable; + }): Promise { + for (const transaction of transactions) { + if ( + !doesUserHaveRole(currentUser.orbcUserRole, IDIR_USER_ROLE_LIST) && + transaction?.paymentMethodTypeCode !== PaymentMethodTypeEnum.WEB && + transaction?.paymentMethodTypeCode !== PaymentMethodTypeEnum.ACCOUNT && + transaction?.paymentMethodTypeCode !== PaymentMethodTypeEnum.NO_PAYMENT + ) { + throwUnprocessableEntityException( + 'Invalid payment method type for the user', + ); + } else if ( + transaction?.paymentMethodTypeCode === PaymentMethodTypeEnum.ACCOUNT && + !(await isFeatureEnabled(this.cacheManager, 'CREDIT-ACCOUNT')) + ) { + throwUnprocessableEntityException('Disabled feature'); + } + + if ( + transaction?.paymentMethodTypeCode === PaymentMethodTypeEnum.POS && + !transaction?.paymentCardTypeCode + ) { + throwBadRequestException('paymentCardTypeCode', [ + `paymentCardTypeCode is required when paymentMethodTypeCode is ${transaction?.paymentMethodTypeCode}`, + ]); + } + + if ( + transaction?.paymentMethodTypeCode !== + PaymentMethodTypeEnum.NO_PAYMENT && + transaction?.transactionAmount === 0 + ) { + throwUnprocessableEntityException( + `paymentMethodTypeCode should be ${PaymentMethodTypeEnum.NO_PAYMENT} when transaction amount is 0`, + ); + } + } + + let readTransactionDto: ReadTransactionDto[]; + const queryRunner = + nestedQueryRunner || this.dataSource.createQueryRunner(); + if (!nestedQueryRunner) { + await queryRunner.connect(); + await queryRunner.startTransaction(); + } + + try { + const existingApplication: Permit = await queryRunner.manager.findOne( + Permit, + { + where: { permitId: applicationId }, + relations: { permitData: true }, + }, + ); + + if ( + !( + this.isVoidorRevoked(existingApplication.permitStatus) || + this.isApplicationInCart(existingApplication.permitStatus) || + isAmendmentApplication(existingApplication) + ) + ) { + throw new BadRequestException( + 'Application in its current status cannot be processed for payment.', + ); + } + + const totalTransactionAmount = transactions?.reduce( + (accumulator, item) => accumulator + item.transactionAmount, + 0, + ); + + await this.validateApplicationAndPayment( + totalTransactionAmount, + TransactionType.REFUND, + [existingApplication], + currentUser, + queryRunner, + ); + + let newTransactionList: Transaction[] = []; + + for (const transaction of transactions) { + const transactionOrderNumber = + await this.generateTransactionOrderNumber(); + const newTransaction = new Transaction(); + newTransaction.transactionTypeId = TransactionType.REFUND; + newTransaction.pgTransactionId = transaction.pgTransactionId; + newTransaction.totalTransactionAmount = transaction.transactionAmount; + newTransaction.paymentMethodTypeCode = + transaction.paymentMethodTypeCode; + newTransaction.paymentCardTypeCode = transaction.paymentCardTypeCode; + newTransaction.pgCardType = transaction.paymentCardTypeCode; + newTransaction.pgPaymentMethod = transaction.pgPaymentMethod; + newTransaction.transactionOrderNumber = transactionOrderNumber; + newTransaction.payerName = PPC_FULL_TEXT; + if (transaction.paymentMethodTypeCode === PaymentMethodTypeEnum.WEB) { + newTransaction.pgApproved = 1; + } + setBaseEntityProperties({ + entity: newTransaction, + currentUser, + }); + newTransactionList.push(newTransaction); + } + + newTransactionList = await queryRunner.manager.save(newTransactionList); + + const receiptNumber = await this.generateReceiptNumber(); + let receipt = new Receipt(); + receipt.receiptNumber = receiptNumber; + setBaseEntityProperties({ entity: receipt, currentUser }); + receipt = await queryRunner.manager.save(receipt); + + for (const newTransaction of newTransactionList) { + let newPermitTransactions = new PermitTransaction(); + newPermitTransactions.transaction = newTransaction; + newPermitTransactions.permit = existingApplication; + newPermitTransactions.createdDateTime = new Date(); + newPermitTransactions.createdUser = currentUser.userName; + newPermitTransactions.createdUserDirectory = + currentUser.orbcUserDirectory; + newPermitTransactions.createdUserGuid = currentUser.userGUID; + newPermitTransactions.updatedDateTime = new Date(); + newPermitTransactions.updatedUser = currentUser.userName; + newPermitTransactions.updatedUserDirectory = + currentUser.orbcUserDirectory; + newPermitTransactions.updatedUserGuid = currentUser.userGUID; + newPermitTransactions.transactionAmount = + newTransaction.totalTransactionAmount; + newPermitTransactions = await queryRunner.manager.save( + newPermitTransactions, + ); + + await queryRunner.manager.update( + Transaction, + { transactionId: newTransaction.transactionId }, + { + receipt: receipt, + updatedDateTime: new Date(), + updatedUser: currentUser.userName, + updatedUserDirectory: currentUser.orbcUserDirectory, + updatedUserGuid: currentUser.userGUID, + }, + ); + + if (isCfsPaymentMethodType(newTransaction.paymentMethodTypeCode)) { + const newCfsTransaction: CfsTransactionDetail = + new CfsTransactionDetail(); + newCfsTransaction.transaction = newTransaction; + newCfsTransaction.fileStatus = CfsFileStatus.READY; + await queryRunner.manager.save(newCfsTransaction); + } + } + + if (!nestedQueryRunner) { + await queryRunner.commitTransaction(); + } + + const createdTransaction = await queryRunner.manager.find(Transaction, { + where: { + transactionId: In( + newTransactionList?.map(({ transactionId }) => transactionId), + ), + }, + relations: ['permitTransactions', 'permitTransactions.permit'], + }); + + readTransactionDto = await this.classMapper.mapArrayAsync( + createdTransaction, + Transaction, + ReadTransactionDto, + ); + } catch (error) { + if (!nestedQueryRunner) { + await queryRunner.rollbackTransaction(); + } + this.logger.error(error); + throw error; + } finally { + if (!nestedQueryRunner) { + await queryRunner.release(); + } + } + + return readTransactionDto; + } + /** * Creates a Transaction in ORBC System. * @param currentUser - The current user object of type {@link IUserJWT} * @param createTransactionDto - The createTransactionDto object of type - * {@link CreateTransactionDto} for creating a new Transaction. + * {@link CreateTransactionDto} for creating a new Transaction. * @returns {ReadTransactionDto} The created transaction of type {@link ReadTransactionDto}. */ @LogAsyncMethodExecution() @@ -269,6 +487,10 @@ export class PaymentService { createTransactionDto: CreateTransactionDto, nestedQueryRunner?: QueryRunner, ): Promise { + if (createTransactionDto.transactionTypeId !== TransactionType.PURCHASE) { + throwUnprocessableEntityException('Invalid transaction type'); + } + if ( !doesUserHaveRole(currentUser.orbcUserRole, IDIR_USER_ROLE_LIST) && createTransactionDto?.paymentMethodTypeCode !== @@ -332,8 +554,16 @@ export class PaymentService { 'Application in its current status cannot be processed for payment.', ); } - const totalTransactionAmount = await this.validateApplicationAndPayment( - createTransactionDto, + + let totalTransactionAmount = + createTransactionDto.applicationDetails?.reduce( + (accumulator, item) => accumulator + item.transactionAmount, + 0, + ); + + totalTransactionAmount = await this.validateApplicationAndPayment( + totalTransactionAmount, + createTransactionDto.transactionTypeId, existingApplications, currentUser, queryRunner, @@ -446,9 +676,9 @@ export class PaymentService { ) ) { const receiptNumber = await this.generateReceiptNumber(); - const receipt = new Receipt(); + let receipt = new Receipt(); receipt.receiptNumber = receiptNumber; - receipt.transaction = createdTransaction; + //receipt.transaction = createdTransaction; receipt.createdDateTime = new Date(); receipt.createdUser = currentUser.userName; receipt.createdUserDirectory = currentUser.orbcUserDirectory; @@ -457,7 +687,19 @@ export class PaymentService { receipt.updatedUser = currentUser.userName; receipt.updatedUserDirectory = currentUser.orbcUserDirectory; receipt.updatedUserGuid = currentUser.userGUID; - await queryRunner.manager.save(receipt); + receipt = await queryRunner.manager.save(receipt); + + await queryRunner.manager.update( + Transaction, + { transactionId: createdTransaction.transactionId }, + { + receipt: receipt, + updatedDateTime: new Date(), + updatedUser: currentUser.userName, + updatedUserDirectory: currentUser.orbcUserDirectory, + updatedUserGuid: currentUser.userGUID, + }, + ); } readTransactionDto = await this.classMapper.mapAsync( @@ -498,15 +740,18 @@ export class PaymentService { * Additionally, for refund transactions, it checks if the total calculated transaction amount is negative as expected; * if not, it throws an error. * - * @param {CreateTransactionDto} createTransactionDto - The DTO containing the transaction details from the request. + * @param {number} totalTransactionAmount - The total transaction amount from the request. + * @param {TransactionType} transactionType - The transactionType. * @param {Permit[]} applications - A list of permits associated with the transaction. - * @param {QueryRunner} nestedQueryRunner - The query runner to use for database operations within the method. + * @param {IUserJWT} currentUser - The current user performing the transaction. + * @param {QueryRunner} queryRunner - The query runner to use for database operations within the method. * @returns {Promise} The total transaction amount calculated from the backend data. * @throws {BadRequestException} When the transaction amount in the request doesn't match with the calculated amount, * or if there's a transaction type and amount mismatch in case of refunds. */ private async validateApplicationAndPayment( - createTransactionDto: CreateTransactionDto, + totalTransactionAmount: number, + transactionType: TransactionType, applications: Permit[], currentUser: IUserJWT, queryRunner: QueryRunner, @@ -524,21 +769,18 @@ export class PaymentService { `Atleast one of the application has invalid startDate or expiryDate.`, ); } + totalTransactionAmountCalculated += await this.permitFeeCalculator( application, queryRunner, ); } - const totalTransactionAmount = - createTransactionDto.applicationDetails?.reduce( - (accumulator, item) => accumulator + item.transactionAmount, - 0, - ); + if ( !validAmount( totalTransactionAmountCalculated, totalTransactionAmount, - createTransactionDto.transactionTypeId, + transactionType, ) ) throw new BadRequestException('Transaction amount mismatch.'); @@ -695,9 +937,8 @@ export class PaymentService { if (updateTransactionTemp.pgApproved === 1) { const receiptNumber = await this.generateReceiptNumber(); - const receipt = new Receipt(); + let receipt = new Receipt(); receipt.receiptNumber = receiptNumber; - receipt.transaction = updatedTransaction; receipt.receiptNumber = receiptNumber; receipt.createdDateTime = new Date(); receipt.createdUser = currentUser.userName; @@ -707,7 +948,19 @@ export class PaymentService { receipt.updatedUser = currentUser.userName; receipt.updatedUserDirectory = currentUser.orbcUserDirectory; receipt.updatedUserGuid = currentUser.userGUID; - await queryRunner.manager.save(receipt); + receipt = await queryRunner.manager.save(receipt); + + await queryRunner.manager.update( + Transaction, + { transactionId: updatedTransaction.transactionId }, + { + receipt: receipt, + updatedDateTime: new Date(), + updatedUser: currentUser.userName, + updatedUserDirectory: currentUser.orbcUserDirectory, + updatedUserGuid: currentUser.userGUID, + }, + ); } await queryRunner.commitTransaction(); diff --git a/vehicles/src/modules/permit-application-payment/permit-receipt-document/permit-receipt-document.service.ts b/vehicles/src/modules/permit-application-payment/permit-receipt-document/permit-receipt-document.service.ts index 7e4d2d550..90181577a 100644 --- a/vehicles/src/modules/permit-application-payment/permit-receipt-document/permit-receipt-document.service.ts +++ b/vehicles/src/modules/permit-application-payment/permit-receipt-document/permit-receipt-document.service.ts @@ -40,6 +40,7 @@ import { validateEmailandFaxList, } from '../../../common/helper/notification.helper'; import { getPermitTemplateName } from '../../../common/helper/template.helper'; +import { TransactionType } from '../../../common/enum/transaction-type.enum'; @Injectable() export class PermitReceiptDocumentService { @@ -98,6 +99,14 @@ export class PermitReceiptDocumentService { permits: Permit[]; }[] = []; + // Function to check if a permitId already exists in transactionPermitList + // To avoid duplicate entries for Refund to Multiple Payment methods + function hasExistingPermit(permitId: string): boolean { + return transactionPermitList.some((item) => + item.permits.some((p) => p.permitId === permitId), + ); + } + for (const transaction of transactions) { const fetchedApplications = await this.findManyWithSuccessfulTransaction( null, @@ -106,7 +115,9 @@ export class PermitReceiptDocumentService { ); if ( - fetchedApplications?.length === transaction.permitCountPerTransactionId + fetchedApplications?.length === + transaction.permitCountPerTransactionId && + !fetchedApplications.some((app) => hasExistingPermit(app.permitId)) ) { transactionPermitList.push({ transactionId: transaction.transactionId, @@ -409,13 +420,19 @@ export class PermitReceiptDocumentService { await Promise.allSettled( fetchedPermits?.map(async (fetchedPermit) => { - const permits = fetchedPermit.permits; + let permits = fetchedPermit.permits; const permitIds = permits?.map((permit) => permit.permitId); + permits = await this.findManyWithSuccessfulTransaction( + permitIds, + companyId, + ); + if (permits?.length) { try { const permit = permits?.at(0); const company = permit?.company; const permitTransactions = permit?.permitTransactions; + const transaction = permitTransactions?.at(0)?.transaction; const receipt = transaction?.receipt; if (receipt.receiptDocumentId) { @@ -440,18 +457,70 @@ export class PermitReceiptDocumentService { permit?.permitType, ), permitNumber: permit?.permitNumber, - transactionAmount: formatAmount( + permitFee: formatAmount( transaction?.transactionTypeId, - permit?.permitTransactions?.at(0)?.transactionAmount, + permit?.permitTransactions?.reduce( + (accumulator, item) => + accumulator + item.transactionAmount, + 0, + ), ), }; }), ); + const transactionList = await Promise.all( + permits?.flatMap((permit) => + permit?.permitTransactions?.map(async (permitTransaction) => { + return { + consolidatedPaymentMethod: ( + await getPaymentCodeFromCache( + this.cacheManager, + permitTransaction?.transaction?.paymentMethodTypeCode, + permitTransaction?.transaction?.paymentCardTypeCode, + ) + ).consolidatedPaymentMethod, + pgTransactionId: + permitTransaction?.transaction?.pgTransactionId, + transactionOrderNumber: + permitTransaction?.transaction?.transactionOrderNumber, + transactionAmount: formatAmount( + transaction?.transactionTypeId, + permitTransaction?.transaction?.totalTransactionAmount, + ), + }; + }), + ), + ); + + const uniqueTransactionList = Array.from( + new Map( + transactionList.map((item) => [ + item.transactionOrderNumber, + item, + ]), + ).values(), + ); + + const totalTransactionAmount = permits?.reduce( + (accumulator, item) => + accumulator + + item.permitTransactions.reduce( + (accumulator, item) => accumulator + item.transactionAmount, + 0, + ), + 0, + ); + const dopsRequestData = { templateName: TemplateName.PAYMENT_RECEIPT, generatedDocumentFileName: `Receipt_No_${receiptNumber}`, templateData: { + transactionType: transaction.transactionTypeId, + receiptType: + transaction.transactionTypeId === TransactionType.PURCHASE + ? 'Receipt' + : 'Refund Receipt', receiptNo: receiptNumber, companyName: companyName, companyAlternateName: companyAlternateName, @@ -465,22 +534,12 @@ export class PermitReceiptDocumentService { : constants.SELF_ISSUED, totalTransactionAmount: formatAmount( transaction?.transactionTypeId, - transaction?.totalTransactionAmount, + totalTransactionAmount, ), permitDetails: permitDetails, - //Transaction Details - pgTransactionId: transaction?.pgTransactionId, - transactionOrderNumber: transaction?.transactionOrderNumber, - consolidatedPaymentMethod: ( - await getPaymentCodeFromCache( - this.cacheManager, - transaction?.paymentMethodTypeCode, - transaction?.paymentCardTypeCode, - ) - ).consolidatedPaymentMethod, + transactions: uniqueTransactionList, transactionDate: convertUtcToPt( - permit?.permitTransactions?.at(0)?.transaction - ?.transactionSubmitDate, + transaction?.transactionSubmitDate, 'MMM. D, YYYY, hh:mm a Z', ), }, diff --git a/vehicles/src/modules/permit-application-payment/permit/dto/request/void-permit.dto.ts b/vehicles/src/modules/permit-application-payment/permit/dto/request/void-permit.dto.ts index bcb70c0af..152de0d2c 100644 --- a/vehicles/src/modules/permit-application-payment/permit/dto/request/void-permit.dto.ts +++ b/vehicles/src/modules/permit-application-payment/permit/dto/request/void-permit.dto.ts @@ -1,20 +1,20 @@ import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; import { + ArrayMinSize, + IsArray, IsEmail, IsEnum, - IsNumber, IsOptional, IsString, Length, - MaxLength, - Min, MinLength, + ValidateNested, } from 'class-validator'; import { ApplicationStatus } from 'src/common/enum/application-status.enum'; -import { PaymentMethodType } from '../../../../../common/enum/payment-method-type.enum'; import { TransactionType } from '../../../../../common/enum/transaction-type.enum'; -import { PaymentCardType } from '../../../../../common/enum/payment-card-type.enum'; +import { PaymentTransactionDto } from '../../../payment/dto/common/payment-transaction.dto'; +import { Type } from 'class-transformer'; export class VoidPermitDto { @AutoMap() @@ -28,23 +28,15 @@ export class VoidPermitDto { @AutoMap() @ApiProperty({ - description: 'Provider Transaction ID.', - example: '10000148', - required: false, - }) - @IsOptional() - @IsString() - @MaxLength(20) - pgTransactionId?: string; - - @AutoMap() - @ApiProperty({ - enum: PaymentMethodType, - example: PaymentMethodType.WEB, - description: 'The identifier of the user selected payment method.', + description: 'The transaction details specific to application/permit.', + required: true, + type: [PaymentTransactionDto], }) - @IsEnum(PaymentMethodType) - paymentMethodTypeCode: PaymentMethodType; + @IsArray() + @ValidateNested({ each: true }) + @ArrayMinSize(1) + @Type(() => PaymentTransactionDto) + transactions: PaymentTransactionDto[]; @AutoMap() @ApiProperty({ @@ -56,48 +48,6 @@ export class VoidPermitDto { @IsEnum(TransactionType) transactionTypeId: TransactionType; - @AutoMap() - @ApiProperty({ - description: 'Payment Transaction Amount.', - example: 30, - }) - @IsNumber() - @Min(0) - transactionAmount: number; - - @AutoMap() - @ApiProperty({ - description: 'Payment Transaction Date.', - example: '2023-07-10T15:49:36.582Z', - required: false, - }) - @IsOptional() - @IsString() - //@MaxLength(27) // TODO Should it be Is Date? - pgTransactionDate?: string; - - @AutoMap() - @ApiProperty({ - example: 'CC', - description: 'Represents the payment method of a transaction.', - required: false, - }) - @IsOptional() - @IsString() - @Length(1, 2) - pgPaymentMethod?: string; - - @AutoMap() - @ApiProperty({ - enum: PaymentCardType, - example: PaymentCardType.VISA, - description: 'Represents the card type used for the transaction.', - required: false, - }) - @IsOptional() - @IsEnum(PaymentCardType) - pgCardType?: PaymentCardType; - @AutoMap() @ApiProperty({ example: 'This permit was voided because of so-and-so reason', diff --git a/vehicles/src/modules/permit-application-payment/permit/permit.service.ts b/vehicles/src/modules/permit-application-payment/permit/permit.service.ts index eb063ce04..6adc9fffd 100644 --- a/vehicles/src/modules/permit-application-payment/permit/permit.service.ts +++ b/vehicles/src/modules/permit-application-payment/permit/permit.service.ts @@ -60,6 +60,7 @@ import { generateFaxEmail, validateEmailandFaxList, } from '../../../common/helper/notification.helper'; +import { TransactionType } from '../../../common/enum/transaction-type.enum'; @Injectable() export class PermitService { @@ -591,33 +592,46 @@ export class PermitService { }, ); - const createTransactionDto = new CreateTransactionDto(); - createTransactionDto.transactionTypeId = voidPermitDto.transactionTypeId; - createTransactionDto.paymentMethodTypeCode = - voidPermitDto.paymentMethodTypeCode; - createTransactionDto.paymentCardTypeCode = voidPermitDto.pgCardType; - createTransactionDto.pgCardType = voidPermitDto.pgCardType; - createTransactionDto.pgTransactionId = voidPermitDto.pgTransactionId; - createTransactionDto.pgPaymentMethod = voidPermitDto.pgPaymentMethod; - - // Refund for void should automatically set this flag to approved for payment gateway payment methods - // Otherwise, the flag is not applicable - if (voidPermitDto.paymentMethodTypeCode === PaymentMethodType.WEB) { - createTransactionDto.pgApproved = 1; - } - - createTransactionDto.applicationDetails = [ - { + if (voidPermitDto.transactionTypeId === TransactionType.REFUND) { + await this.paymentService.createRefundTransactions({ + currentUser, applicationId: newPermit.permitId, - transactionAmount: voidPermitDto.transactionAmount, - }, - ]; - await this.paymentService.createTransactions( - currentUser, - createTransactionDto, - queryRunner, - ); + transactions: voidPermitDto.transactions, + nestedQueryRunner: queryRunner, + }); + } + if (voidPermitDto.transactionTypeId === TransactionType.PURCHASE) { + const transaction = voidPermitDto?.transactions?.at(0); + const createTransactionDto = new CreateTransactionDto(); + createTransactionDto.transactionTypeId = + voidPermitDto.transactionTypeId; + createTransactionDto.paymentMethodTypeCode = + transaction.paymentMethodTypeCode; + createTransactionDto.paymentCardTypeCode = + transaction.paymentCardTypeCode; + createTransactionDto.pgCardType = transaction.paymentCardTypeCode; + createTransactionDto.pgTransactionId = transaction.pgTransactionId; + createTransactionDto.pgPaymentMethod = transaction.pgPaymentMethod; + + // Refund for void should automatically set this flag to approved for payment gateway payment methods + // Otherwise, the flag is not applicable + if (transaction.paymentMethodTypeCode === PaymentMethodType.WEB) { + createTransactionDto.pgApproved = 1; + } + + createTransactionDto.applicationDetails = [ + { + applicationId: newPermit.permitId, + transactionAmount: transaction.transactionAmount, + }, + ]; + await this.paymentService.createTransactions( + currentUser, + createTransactionDto, + queryRunner, + ); + } await queryRunner.commitTransaction(); success = permitId; voidRevokedPermitId = newPermit.permitId;