From cccdcf013cd03fd76eb8b5dcd2b9b8bbfbe6d88e Mon Sep 17 00:00:00 2001 From: Marek Biczysko <34810846+MarekBiczysko@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:26:44 +0100 Subject: [PATCH] 2215787_drop_cash_assist_part_2 (#4521) * 2215787_drop_cash_assist_part_2 * fixture fix * ut * ut * ut * fix schnappy * migration fix * ut * ut * upd unit tests :star2: * upd object_id_filter :star: * fix generate_report_service :100: * fix filters & update tests :100: :star: * upd migrations * upd migrations --------- Co-authored-by: marekbiczysko Co-authored-by: Maciej Szewczyk Co-authored-by: pavlo-mk --- src/frontend/data/schema.graphql | 46 +- .../fakeGrievanceTicketPaymentVerification.ts | 253 +++++++-- src/frontend/src/__generated__/graphql.tsx | 155 ++---- .../AllPaymentVerificationLogEntries.ts | 2 - .../PaymentGrievanceDetails.test.tsx.snap | 17 +- .../PaymentPlanVerificationDetailsPage.tsx | 1 - ...salActivityLogTablePaymentVerification.tsx | 3 - .../apps/core/exchange_rates/utils.py | 21 - src/hct_mis_api/apps/dashboard/services.py | 50 +- src/hct_mis_api/apps/grievance/fixtures.py | 7 +- .../grievance/migrations/0005_migration.py | 66 +++ .../grievance/migrations/0006_migration.py | 29 ++ src/hct_mis_api/apps/grievance/models.py | 21 +- ...ticket_based_on_payment_record_services.py | 3 +- .../household/migrations/0005_migration.py | 23 + src/hct_mis_api/apps/payment/admin.py | 81 +-- src/hct_mis_api/apps/payment/filters.py | 27 +- src/hct_mis_api/apps/payment/fixtures.py | 3 +- .../apps/payment/migrations/0008_migration.py | 87 ++++ .../apps/payment/migrations/0009_migration.py | 69 +++ .../apps/payment/models/__init__.py | 12 - .../apps/payment/models/cash_assist.py | 482 ------------------ .../apps/payment/models/payment.py | 6 +- .../apps/payment/models/verification.py | 58 +-- .../pdf/payment_plan_export_pdf_service.py | 14 +- src/hct_mis_api/apps/payment/schema.py | 26 +- .../services/create_payment_verifications.py | 6 +- .../payment/services/dashboard_service.py | 36 +- .../apps/payment/services/mark_as_failed.py | 10 +- .../tasks/CheckRapidProVerificationTask.py | 4 +- ...lsx_payment_plan_per_fsp_import_service.py | 4 +- .../services/generate_report_service.py | 156 ++---- src/hct_mis_api/apps/utils/models.py | 2 +- src/hct_mis_api/migrations_script/main.py | 3 + .../migrate_cash_assist_models.py | 309 ----------- .../migrate_data_to_representations.py | 44 +- tests/selenium/filters/test_filters.py | 4 +- .../test_payment_verification.py | 14 +- tests/selenium/people/test_people.py | 8 +- .../test_people_periodic_data_update.py | 4 +- .../test_filter_already_existing_tickets.py | 4 +- .../services/test_dashboard_service.py | 14 +- .../snap_test_all_payment_plan_queries.py | 28 +- .../payment/test_all_payment_plan_queries.py | 67 ++- .../apps/payment/test_dashboard_queries.py | 20 +- .../apps/payment/test_fsp_in_payment_plan.py | 18 +- tests/unit/apps/payment/test_models1.py | 0 .../test_payment_verification_mutations.py | 4 +- ...erification_plan_status_change_services.py | 2 +- .../apps/reporting/test_report_service.py | 40 +- .../test_migrate_cash_assist_models.py | 451 ---------------- 51 files changed, 802 insertions(+), 2012 deletions(-) delete mode 100644 src/hct_mis_api/apps/core/exchange_rates/utils.py create mode 100644 src/hct_mis_api/apps/grievance/migrations/0005_migration.py create mode 100644 src/hct_mis_api/apps/grievance/migrations/0006_migration.py create mode 100644 src/hct_mis_api/apps/household/migrations/0005_migration.py create mode 100644 src/hct_mis_api/apps/payment/migrations/0008_migration.py create mode 100644 src/hct_mis_api/apps/payment/migrations/0009_migration.py delete mode 100644 src/hct_mis_api/apps/payment/models/cash_assist.py delete mode 100644 src/hct_mis_api/one_time_scripts/migrate_cash_assist_models.py delete mode 100644 tests/unit/apps/payment/test_models1.py delete mode 100644 tests/unit/one_time_scripts/test_migrate_cash_assist_models.py diff --git a/src/frontend/data/schema.graphql b/src/frontend/data/schema.graphql index 9cf6a765c4..a4d8f3a996 100644 --- a/src/frontend/data/schema.graphql +++ b/src/frontend/data/schema.graphql @@ -533,11 +533,6 @@ type ContentTypeObjectType { id: ID! appLabel: String! model: String! - paymentverificationplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationPlanNodeConnection! - paymentverificationSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationNodeConnection! - paymentverificationsummarySet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationSummaryNodeConnection! - ticketcomplaintdetailsSet(offset: Int, before: String, after: String, first: Int, last: Int): TicketComplaintDetailsNodeConnection! - ticketsensitivedetailsSet(offset: Int, before: String, after: String, first: Int, last: Int): TicketSensitiveDetailsNodeConnection! logEntries(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! name: String } @@ -1286,6 +1281,7 @@ type FinancialServiceProviderXlsxTemplateNode implements Node { columns: [String] coreFields: [String!]! flexFields: [String!]! + documentTypes: [String!]! financialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! } @@ -2143,6 +2139,18 @@ input IndividualUpdateDataObjectType { deliveryMechanismData: [DeliveryMechanismDataObjectType] deliveryMechanismDataToEdit: [EditDeliveryMechanismDataObjectType] deliveryMechanismDataToRemove: [ID] + consent: Boolean + residenceStatus: String + countryOrigin: String + country: String + address: String + village: String + currency: String + unhcrId: String + nameEnumerator: String + orgEnumerator: String + orgNameEnumerator: String + registrationMethod: String } type InvalidPaymentVerificationPlan { @@ -2529,8 +2537,8 @@ type PaymentNode implements Node { followUps(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! householdSnapshot: PaymentHouseholdSnapshotNode paymentVerification: PaymentVerificationNode - ticketComplaintDetails: TicketComplaintDetailsNode - ticketSensitiveDetails: TicketSensitiveDetailsNode + ticketComplaintDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketComplaintDetailsNodeConnection! + ticketSensitiveDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketSensitiveDetailsNodeConnection! adminUrl: String paymentPlanHardConflicted: Boolean paymentPlanHardConflictedData: [PaymentConflictDataNode] @@ -2762,8 +2770,6 @@ type PaymentVerificationNode implements Node { updatedAt: DateTime! version: BigInt! paymentVerificationPlan: PaymentVerificationPlanNode! - paymentContentType: ContentTypeObjectType - paymentObjectId: UUID payment: GenericPaymentNode status: PaymentVerificationStatus! statusDate: DateTime @@ -2794,8 +2800,6 @@ type PaymentVerificationPlanNode implements Node { version: BigInt! unicefId: String status: PaymentVerificationPlanStatus! - paymentPlanContentType: ContentTypeObjectType - paymentPlanObjectId: UUID paymentPlan: PaymentPlanNode sampling: PaymentVerificationPlanSampling! verificationChannel: PaymentVerificationPlanVerificationChannel! @@ -2875,20 +2879,6 @@ type PaymentVerificationSummaryNode implements Node { activationDate: DateTime completionDate: DateTime paymentPlan: PaymentPlanNode - paymentPlanContentType: ContentTypeObjectType - paymentPlanObjectId: UUID -} - -type PaymentVerificationSummaryNodeConnection { - pageInfo: PageInfo! - edges: [PaymentVerificationSummaryNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type PaymentVerificationSummaryNodeEdge { - node: PaymentVerificationSummaryNode - cursor: String! } enum PaymentVerificationSummaryStatus { @@ -3149,7 +3139,7 @@ type Query { paymentVerificationStatusChoices: [ChoiceObject] allRapidProFlows(businessAreaSlug: String!): [RapidProFlow] sampleSize(input: GetCashplanVerificationSampleSizeInput): GetCashplanVerificationSampleSizeObject - allPaymentVerificationLogEntries(offset: Int, before: String, after: String, first: Int, last: Int, objectId: UUID, user: ID, businessArea: String!, search: String, module: String, userId: String, programId: String, objectType: String): PaymentVerificationLogEntryNodeConnection + allPaymentVerificationLogEntries(offset: Int, before: String, after: String, first: Int, last: Int, objectId: UUID, user: ID, businessArea: String!, search: String, module: String, userId: String, programId: String): PaymentVerificationLogEntryNodeConnection paymentPlan(id: ID!): PaymentPlanNode allPaymentPlans(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, search: String, status: [String], totalEntitledQuantityFrom: Float, totalEntitledQuantityTo: Float, dispersionStartDate: Date, dispersionEndDate: Date, isFollowUp: Boolean, sourcePaymentPlanId: String, program: String, programCycle: String, orderBy: String): PaymentPlanNodeConnection paymentPlanStatusChoices: [ChoiceObject] @@ -4141,8 +4131,6 @@ type TicketComplaintDetailsNode implements Node { id: ID! createdAt: DateTime! updatedAt: DateTime! - paymentContentType: ContentTypeObjectType - paymentObjectId: UUID household: HouseholdNode individual: IndividualNode payment: PaymentNode @@ -4414,8 +4402,6 @@ type TicketSensitiveDetailsNode implements Node { id: ID! createdAt: DateTime! updatedAt: DateTime! - paymentContentType: ContentTypeObjectType - paymentObjectId: UUID household: HouseholdNode individual: IndividualNode payment: PaymentNode diff --git a/src/frontend/fixtures/grievances/fakeGrievanceTicketPaymentVerification.ts b/src/frontend/fixtures/grievances/fakeGrievanceTicketPaymentVerification.ts index d1bf8077d1..56a39ddac2 100644 --- a/src/frontend/fixtures/grievances/fakeGrievanceTicketPaymentVerification.ts +++ b/src/frontend/fixtures/grievances/fakeGrievanceTicketPaymentVerification.ts @@ -1,50 +1,243 @@ import { GrievanceTicketNode } from '../../src/__generated__/graphql'; export const fakeGrievanceTicketPaymentVerification = { - id: - 'R3JpZXZhbmNlVGlja2V0Tm9kZTo3ZTY1N2JiZC1hNzM4LTQ0MTktYjlmOS04YTIyOWI2MGUwNzU=', - unicefId: 'GRV-000004', - status: 5, - category: 1, + id: 'R3JpZXZhbmNlVGlja2V0Tm9kZTo2Zjc5M2RhZi1jZDRlLTRiYTMtYjg0OS0wYTljODg0ZWNhNTY=', + unicefId: 'GRV-0000002', + status: 1, + category: 4, consent: true, - createdBy: null, - createdAt: '2022-04-08T09:22:18.806856', - updatedAt: '2022-04-11T09:14:45.395754', - description: '', + partner: null, + targetId: 'HH-23-0000.0003', + businessArea: { + postponeDeduplication: false, + __typename: 'UserBusinessAreaNode', + }, + createdBy: { + id: 'VXNlck5vZGU6NDE5NmMyYzUtYzJkZC00OGQyLTg4N2YtM2E5ZDM5ZTc4OTE2', + firstName: 'Root', + lastName: 'Rootkowski', + email: 'root@root.com', + __typename: 'UserNode', + }, + createdAt: '2024-12-11T21:09:18.393442+00:00', + updatedAt: '2024-12-11T21:09:18.393452+00:00', + description: 'feijifjief', language: '', admin: null, admin2: null, area: '', assignedTo: null, + adminUrl: + '/api/unicorn/grievance/grievanceticket/6f793daf-cd4e-4ba3-b849-0a9c884eca56/change/', individual: null, - household: null, - paymentRecord: null, + household: { + id: 'SG91c2Vob2xkTm9kZTphYTAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDE=', + status: 'ACTIVE', + adminUrl: + '/api/unicorn/household/household/aa000000-0000-0000-0000-000000000001/change/', + createdAt: '2024-12-09T20:40:33.645624+00:00', + rdiMergeStatus: 'PENDING', + residenceStatus: '', + maleChildrenCount: null, + femaleChildrenCount: null, + childrenDisabledCount: null, + size: 4, + totalCashReceived: null, + totalCashReceivedUsd: null, + currency: '', + firstRegistrationDate: '2023-12-10T20:40:33.544366+00:00', + lastRegistrationDate: '2024-12-09T20:40:33.544366+00:00', + sanctionListPossibleMatch: false, + sanctionListConfirmedMatch: false, + hasDuplicates: false, + unicefId: 'HH-23-0000.0003', + flexFields: {}, + unhcrId: '', + geopoint: null, + village: '', + adminAreaTitle: '', + admin1: null, + admin2: null, + admin3: null, + admin4: null, + headOfHousehold: { + id: 'SW5kaXZpZHVhbE5vZGU6Y2MwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAx', + fullName: 'Jan Kowalski', + givenName: '', + familyName: '', + __typename: 'IndividualNode', + }, + address: 'Ohio', + individuals: { + totalCount: 1, + __typename: 'IndividualNodeConnection', + edges: [ + { + node: { + id: 'SW5kaXZpZHVhbE5vZGU6Y2MwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAx', + age: 29, + lastRegistrationDate: '2024-12-09', + rdiMergeStatus: 'PENDING', + adminUrl: + '/api/unicorn/household/individual/cc000000-0000-0000-0000-000000000001/change/', + createdAt: '2024-12-09T20:40:33.638304+00:00', + updatedAt: '2024-12-09T20:40:33.648548+00:00', + fullName: 'Jan Kowalski', + sex: 'MALE', + unicefId: 'IND-23-0000.0008', + birthDate: '1994-12-17', + maritalStatus: 'A_', + phoneNo: '', + phoneNoValid: false, + email: '', + sanctionListPossibleMatch: false, + sanctionListConfirmedMatch: false, + deduplicationGoldenRecordStatus: 'UNIQUE', + sanctionListLastCheck: null, + role: 'PRIMARY', + relationship: null, + status: 'ACTIVE', + documents: { + edges: [], + __typename: 'DocumentNodeConnection', + }, + identities: { + edges: [], + __typename: 'IndividualIdentityNodeConnection', + }, + household: { + id: 'SG91c2Vob2xkTm9kZTphYTAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDE=', + unicefId: 'HH-23-0000.0003', + status: 'ACTIVE', + importId: 'HH-23-0000.0003', + admin1: null, + admin2: null, + admin3: null, + admin4: null, + programs: { + edges: [], + __typename: 'ProgramNodeConnection', + }, + totalCashReceivedUsd: null, + lastRegistrationDate: '2024-12-09T20:40:33.544366+00:00', + start: null, + firstRegistrationDate: '2023-12-10T20:40:33.544366+00:00', + countryOrigin: '', + village: '', + __typename: 'HouseholdNode', + }, + __typename: 'IndividualNode', + }, + __typename: 'IndividualNodeEdge', + }, + ], + }, + programs: { + edges: [], + __typename: 'ProgramNodeConnection', + }, + __typename: 'HouseholdNode', + activeIndividualsCount: 1, + countryOrigin: '', + country: '', + zipCode: null, + femaleAgeGroup05Count: null, + femaleAgeGroup611Count: null, + femaleAgeGroup1217Count: null, + femaleAgeGroup1859Count: null, + femaleAgeGroup60Count: null, + pregnantCount: null, + maleAgeGroup05Count: null, + maleAgeGroup611Count: null, + maleAgeGroup1217Count: null, + maleAgeGroup1859Count: null, + maleAgeGroup60Count: null, + femaleAgeGroup05DisabledCount: null, + femaleAgeGroup611DisabledCount: null, + femaleAgeGroup1217DisabledCount: null, + femaleAgeGroup1859DisabledCount: null, + femaleAgeGroup60DisabledCount: null, + maleAgeGroup05DisabledCount: null, + maleAgeGroup611DisabledCount: null, + maleAgeGroup1217DisabledCount: null, + maleAgeGroup1859DisabledCount: null, + maleAgeGroup60DisabledCount: null, + fchildHoh: null, + childHoh: null, + start: null, + deviceid: '', + orgNameEnumerator: '', + returnee: null, + nameEnumerator: '', + lastSyncAt: null, + consentSharing: [], + orgEnumerator: 'A_', + updatedAt: '2024-12-09T20:40:33.645629+00:00', + consent: null, + collectIndividualData: 'A_', + registrationDataImport: { + name: 'Test Import', + dataSource: 'API', + importDate: '2024-12-09T20:40:33.635509+00:00', + importedBy: { + firstName: 'Andrea', + lastName: 'Brock', + email: 'andrea.brock_1733776833579200297@unicef.com', + username: 'AndreaBrock_1733776833579203297528', + __typename: 'UserNode', + }, + __typename: 'RegistrationDataImportNode', + }, + deliveredQuantities: [ + { + totalDeliveredQuantity: '0', + currency: 'USD', + __typename: 'DeliveredQuantityNode', + }, + ], + programRegistrationId: null, + }, + paymentRecord: { + id: 'UGF5bWVudE5vZGU6MTAwMDAwMDAtZmVlZC1iZWVmLTAwMDAtMDAwMDBiYWRmMDBk', + caId: 'RCPT-0060-24-0.000.078', + deliveredQuantity: null, + entitlementQuantity: null, + objType: 'Payment', + parent: { + id: 'UGF5bWVudFBsYW5Ob2RlOjAwMDAwMDAwLWZlZWQtYmVlZi0wMDAwLTAwMDAwYmFkZjAwZA==', + unicefId: 'PP-0060-24-00000023', + objType: 'PaymentPlan', + __typename: 'CashPlanAndPaymentPlanNode', + }, + verification: null, + __typename: 'PaymentRecordAndPaymentNode', + }, relatedTickets: [], + linkedTickets: [], + existingTickets: [], addIndividualTicketDetails: null, individualDataUpdateTicketDetails: null, householdDataUpdateTicketDetails: null, deleteIndividualTicketDetails: null, deleteHouseholdTicketDetails: null, systemFlaggingTicketDetails: null, - paymentVerificationTicketDetails: { - id: - 'VGlja2V0UGF5bWVudFZlcmlmaWNhdGlvbkRldGFpbHNOb2RlOmQ5NWFlNzA2LWRmNTQtNDYyMi1hODVmLTRiOGExZTg2Y2VhMQ==', - newStatus: null, - newReceivedAmount: 45, - approveStatus: false, - paymentVerificationStatus: 'NOT_RECEIVED', - hasMultiplePaymentVerifications: false, - paymentVerification: { - id: - 'UGF5bWVudFZlcmlmaWNhdGlvbk5vZGU6NDhmZjMwODEtNTVhMy00Zjg4LWJjNTgtNTE0YWM0MGI0MzQ2', - receivedAmount: 0, - paymentObjectId: null, - paymentContentType: null, - }, - __typename: 'TicketPaymentVerificationDetailsNode', - }, + paymentVerificationTicketDetails: null, needsAdjudicationTicketDetails: null, - issueType: null, - ticketNotes: { edges: [], __typename: 'TicketNoteNodeConnection' }, + issueType: 18, + ticketNotes: { + edges: [], + __typename: 'TicketNoteNodeConnection', + }, + priority: 0, + urgency: 0, + programs: [ + { + name: 'Test Program', + id: 'UHJvZ3JhbU5vZGU6MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtZmFjZWIwMGMwMDAw', + __typename: 'ProgramNode', + }, + ], + comments: null, + documentation: [], __typename: 'GrievanceTicketNode', } as GrievanceTicketNode; diff --git a/src/frontend/src/__generated__/graphql.tsx b/src/frontend/src/__generated__/graphql.tsx index e29a656a6e..c69fe81fe5 100644 --- a/src/frontend/src/__generated__/graphql.tsx +++ b/src/frontend/src/__generated__/graphql.tsx @@ -891,11 +891,6 @@ export type ContentTypeObjectType = { logEntries: PaymentVerificationLogEntryNodeConnection; model: Scalars['String']['output']; name?: Maybe; - paymentverificationSet: PaymentVerificationNodeConnection; - paymentverificationplanSet: PaymentVerificationPlanNodeConnection; - paymentverificationsummarySet: PaymentVerificationSummaryNodeConnection; - ticketcomplaintdetailsSet: TicketComplaintDetailsNodeConnection; - ticketsensitivedetailsSet: TicketSensitiveDetailsNodeConnection; }; @@ -907,51 +902,6 @@ export type ContentTypeObjectTypeLogEntriesArgs = { offset?: InputMaybe; }; - -export type ContentTypeObjectTypePaymentverificationSetArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - offset?: InputMaybe; -}; - - -export type ContentTypeObjectTypePaymentverificationplanSetArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - offset?: InputMaybe; -}; - - -export type ContentTypeObjectTypePaymentverificationsummarySetArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - offset?: InputMaybe; -}; - - -export type ContentTypeObjectTypeTicketcomplaintdetailsSetArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - offset?: InputMaybe; -}; - - -export type ContentTypeObjectTypeTicketsensitivedetailsSetArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - offset?: InputMaybe; -}; - export type CopyProgram = { __typename?: 'CopyProgram'; program?: Maybe; @@ -1889,6 +1839,7 @@ export type FinancialServiceProviderXlsxTemplateNode = Node & { coreFields: Array; createdAt: Scalars['DateTime']['output']; createdBy?: Maybe; + documentTypes: Array; financialServiceProviders: FinancialServiceProviderNodeConnection; flexFields: Array; id: Scalars['ID']['output']; @@ -3178,10 +3129,15 @@ export enum IndividualSex { } export type IndividualUpdateDataObjectType = { + address?: InputMaybe; administrationOfRutf?: InputMaybe; birthDate?: InputMaybe; blockchainName?: InputMaybe; commsDisability?: InputMaybe; + consent?: InputMaybe; + country?: InputMaybe; + countryOrigin?: InputMaybe; + currency?: InputMaybe; deliveryMechanismData?: InputMaybe>>; deliveryMechanismDataToEdit?: InputMaybe>>; deliveryMechanismDataToRemove?: InputMaybe>>; @@ -3203,7 +3159,10 @@ export type IndividualUpdateDataObjectType = { maritalStatus?: InputMaybe; memoryDisability?: InputMaybe; middleName?: InputMaybe; + nameEnumerator?: InputMaybe; observedDisability?: InputMaybe>>; + orgEnumerator?: InputMaybe; + orgNameEnumerator?: InputMaybe; paymentChannels?: InputMaybe>>; paymentChannelsToEdit?: InputMaybe>>; paymentChannelsToRemove?: InputMaybe>>; @@ -3213,12 +3172,16 @@ export type IndividualUpdateDataObjectType = { physicalDisability?: InputMaybe; preferredLanguage?: InputMaybe; pregnant?: InputMaybe; + registrationMethod?: InputMaybe; relationship?: InputMaybe; + residenceStatus?: InputMaybe; role?: InputMaybe; seeingDisability?: InputMaybe; selfcareDisability?: InputMaybe; sex?: InputMaybe; status?: InputMaybe; + unhcrId?: InputMaybe; + village?: InputMaybe; walletAddress?: InputMaybe; walletName?: InputMaybe; whoAnswersAltPhone?: InputMaybe; @@ -4257,8 +4220,8 @@ export type PaymentNode = Node & { status: PaymentStatus; statusDate: Scalars['DateTime']['output']; targetPopulation?: Maybe; - ticketComplaintDetails?: Maybe; - ticketSensitiveDetails?: Maybe; + ticketComplaintDetails: TicketComplaintDetailsNodeConnection; + ticketSensitiveDetails: TicketSensitiveDetailsNodeConnection; tokenNumber?: Maybe; totalPersonsCovered?: Maybe; transactionReferenceId?: Maybe; @@ -4277,6 +4240,24 @@ export type PaymentNodeFollowUpsArgs = { offset?: InputMaybe; }; + +export type PaymentNodeTicketComplaintDetailsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + offset?: InputMaybe; +}; + + +export type PaymentNodeTicketSensitiveDetailsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + offset?: InputMaybe; +}; + export type PaymentNodeConnection = { __typename?: 'PaymentNodeConnection'; edgeCount?: Maybe; @@ -4567,8 +4548,6 @@ export type PaymentVerificationNode = Node & { id: Scalars['ID']['output']; isManuallyEditable?: Maybe; payment?: Maybe; - paymentContentType?: Maybe; - paymentObjectId?: Maybe; paymentVerificationPlan: PaymentVerificationPlanNode; receivedAmount?: Maybe; sentToRapidPro: Scalars['Boolean']['output']; @@ -4627,8 +4606,6 @@ export type PaymentVerificationPlanNode = Node & { marginOfError?: Maybe; notReceivedCount?: Maybe; paymentPlan?: Maybe; - paymentPlanContentType?: Maybe; - paymentPlanObjectId?: Maybe; paymentRecordVerifications: PaymentVerificationNodeConnection; rapidProFlowId: Scalars['String']['output']; rapidProFlowStartUuids: Array; @@ -4711,26 +4688,10 @@ export type PaymentVerificationSummaryNode = Node & { createdAt: Scalars['DateTime']['output']; id: Scalars['ID']['output']; paymentPlan?: Maybe; - paymentPlanContentType?: Maybe; - paymentPlanObjectId?: Maybe; status: PaymentVerificationSummaryStatus; updatedAt: Scalars['DateTime']['output']; }; -export type PaymentVerificationSummaryNodeConnection = { - __typename?: 'PaymentVerificationSummaryNodeConnection'; - edgeCount?: Maybe; - edges: Array>; - pageInfo: PageInfo; - totalCount?: Maybe; -}; - -export type PaymentVerificationSummaryNodeEdge = { - __typename?: 'PaymentVerificationSummaryNodeEdge'; - cursor: Scalars['String']['output']; - node?: Maybe; -}; - export enum PaymentVerificationSummaryStatus { Active = 'ACTIVE', Finished = 'FINISHED', @@ -5715,7 +5676,6 @@ export type QueryAllPaymentVerificationLogEntriesArgs = { last?: InputMaybe; module?: InputMaybe; objectId?: InputMaybe; - objectType?: InputMaybe; offset?: InputMaybe; programId?: InputMaybe; search?: InputMaybe; @@ -7509,8 +7469,6 @@ export type TicketComplaintDetailsNode = Node & { id: Scalars['ID']['output']; individual?: Maybe; payment?: Maybe; - paymentContentType?: Maybe; - paymentObjectId?: Maybe; paymentRecord?: Maybe; updatedAt: Scalars['DateTime']['output']; }; @@ -7834,8 +7792,6 @@ export type TicketSensitiveDetailsNode = Node & { id: Scalars['ID']['output']; individual?: Maybe; payment?: Maybe; - paymentContentType?: Maybe; - paymentObjectId?: Maybe; paymentRecord?: Maybe; updatedAt: Scalars['DateTime']['output']; }; @@ -9774,7 +9730,6 @@ export type IndividualPhotosQuery = { __typename?: 'Query', individual?: { __typ export type AllPaymentVerificationLogEntriesQueryVariables = Exact<{ businessArea: Scalars['String']['input']; objectId?: InputMaybe; - objectType?: InputMaybe; after?: InputMaybe; before?: InputMaybe; first?: InputMaybe; @@ -18538,14 +18493,13 @@ export type IndividualPhotosLazyQueryHookResult = ReturnType; export type IndividualPhotosQueryResult = Apollo.QueryResult; export const AllPaymentVerificationLogEntriesDocument = gql` - query AllPaymentVerificationLogEntries($businessArea: String!, $objectId: UUID, $objectType: String, $after: String, $before: String, $first: Int, $last: Int, $search: String, $module: String) { + query AllPaymentVerificationLogEntries($businessArea: String!, $objectId: UUID, $after: String, $before: String, $first: Int, $last: Int, $search: String, $module: String) { allPaymentVerificationLogEntries( after: $after before: $before first: $first last: $last objectId: $objectId - objectType: $objectType businessArea: $businessArea search: $search module: $module @@ -18605,7 +18559,6 @@ export const AllPaymentVerificationLogEntriesDocument = gql` * variables: { * businessArea: // value for 'businessArea' * objectId: // value for 'objectId' - * objectType: // value for 'objectType' * after: // value for 'after' * before: // value for 'before' * first: // value for 'first' @@ -22663,8 +22616,6 @@ export type ResolversTypes = { PaymentVerificationStatus: PaymentVerificationStatus; PaymentVerificationStatusForUpdate: PaymentVerificationStatusForUpdate; PaymentVerificationSummaryNode: ResolverTypeWrapper; - PaymentVerificationSummaryNodeConnection: ResolverTypeWrapper; - PaymentVerificationSummaryNodeEdge: ResolverTypeWrapper; PaymentVerificationSummaryStatus: PaymentVerificationSummaryStatus; PeriodicFieldDataInput: PeriodicFieldDataInput; PeriodicFieldDataNode: ResolverTypeWrapper; @@ -23142,8 +23093,6 @@ export type ResolversParentTypes = { PaymentVerificationPlanNodeConnection: PaymentVerificationPlanNodeConnection; PaymentVerificationPlanNodeEdge: PaymentVerificationPlanNodeEdge; PaymentVerificationSummaryNode: PaymentVerificationSummaryNode; - PaymentVerificationSummaryNodeConnection: PaymentVerificationSummaryNodeConnection; - PaymentVerificationSummaryNodeEdge: PaymentVerificationSummaryNodeEdge; PeriodicFieldDataInput: PeriodicFieldDataInput; PeriodicFieldDataNode: PeriodicFieldDataNode; PeriodicFieldNode: PeriodicFieldNode; @@ -23774,11 +23723,6 @@ export type ContentTypeObjectTypeResolvers>; model?: Resolver; name?: Resolver, ParentType, ContextType>; - paymentverificationSet?: Resolver>; - paymentverificationplanSet?: Resolver>; - paymentverificationsummarySet?: Resolver>; - ticketcomplaintdetailsSet?: Resolver>; - ticketsensitivedetailsSet?: Resolver>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -24329,6 +24273,7 @@ export type FinancialServiceProviderXlsxTemplateNodeResolvers, ParentType, ContextType>; createdAt?: Resolver; createdBy?: Resolver, ParentType, ContextType>; + documentTypes?: Resolver, ParentType, ContextType>; financialServiceProviders?: Resolver>; flexFields?: Resolver, ParentType, ContextType>; id?: Resolver; @@ -25330,8 +25275,8 @@ export type PaymentNodeResolvers; statusDate?: Resolver; targetPopulation?: Resolver, ParentType, ContextType>; - ticketComplaintDetails?: Resolver, ParentType, ContextType>; - ticketSensitiveDetails?: Resolver, ParentType, ContextType>; + ticketComplaintDetails?: Resolver>; + ticketSensitiveDetails?: Resolver>; tokenNumber?: Resolver, ParentType, ContextType>; totalPersonsCovered?: Resolver, ParentType, ContextType>; transactionReferenceId?: Resolver, ParentType, ContextType>; @@ -25527,8 +25472,6 @@ export type PaymentVerificationNodeResolvers; isManuallyEditable?: Resolver, ParentType, ContextType>; payment?: Resolver, ParentType, ContextType>; - paymentContentType?: Resolver, ParentType, ContextType>; - paymentObjectId?: Resolver, ParentType, ContextType>; paymentVerificationPlan?: Resolver; receivedAmount?: Resolver, ParentType, ContextType>; sentToRapidPro?: Resolver; @@ -25569,8 +25512,6 @@ export type PaymentVerificationPlanNodeResolvers, ParentType, ContextType>; notReceivedCount?: Resolver, ParentType, ContextType>; paymentPlan?: Resolver, ParentType, ContextType>; - paymentPlanContentType?: Resolver, ParentType, ContextType>; - paymentPlanObjectId?: Resolver, ParentType, ContextType>; paymentRecordVerifications?: Resolver>; rapidProFlowId?: Resolver; rapidProFlowStartUuids?: Resolver, ParentType, ContextType>; @@ -25611,27 +25552,11 @@ export type PaymentVerificationSummaryNodeResolvers; id?: Resolver; paymentPlan?: Resolver, ParentType, ContextType>; - paymentPlanContentType?: Resolver, ParentType, ContextType>; - paymentPlanObjectId?: Resolver, ParentType, ContextType>; status?: Resolver; updatedAt?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; -export type PaymentVerificationSummaryNodeConnectionResolvers = { - edgeCount?: Resolver, ParentType, ContextType>; - edges?: Resolver>, ParentType, ContextType>; - pageInfo?: Resolver; - totalCount?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type PaymentVerificationSummaryNodeEdgeResolvers = { - cursor?: Resolver; - node?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type PeriodicFieldDataNodeResolvers = { id?: Resolver; numberOfRounds?: Resolver; @@ -26663,8 +26588,6 @@ export type TicketComplaintDetailsNodeResolvers; individual?: Resolver, ParentType, ContextType>; payment?: Resolver, ParentType, ContextType>; - paymentContentType?: Resolver, ParentType, ContextType>; - paymentObjectId?: Resolver, ParentType, ContextType>; paymentRecord?: Resolver, ParentType, ContextType>; updatedAt?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -26951,8 +26874,6 @@ export type TicketSensitiveDetailsNodeResolvers; individual?: Resolver, ParentType, ContextType>; payment?: Resolver, ParentType, ContextType>; - paymentContentType?: Resolver, ParentType, ContextType>; - paymentObjectId?: Resolver, ParentType, ContextType>; paymentRecord?: Resolver, ParentType, ContextType>; updatedAt?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -27458,8 +27379,6 @@ export type Resolvers = { PaymentVerificationPlanNodeConnection?: PaymentVerificationPlanNodeConnectionResolvers; PaymentVerificationPlanNodeEdge?: PaymentVerificationPlanNodeEdgeResolvers; PaymentVerificationSummaryNode?: PaymentVerificationSummaryNodeResolvers; - PaymentVerificationSummaryNodeConnection?: PaymentVerificationSummaryNodeConnectionResolvers; - PaymentVerificationSummaryNodeEdge?: PaymentVerificationSummaryNodeEdgeResolvers; PeriodicFieldDataNode?: PeriodicFieldDataNodeResolvers; PeriodicFieldNode?: PeriodicFieldNodeResolvers; ProgramCycleNode?: ProgramCycleNodeResolvers; diff --git a/src/frontend/src/apollo/queries/payments/verification/AllPaymentVerificationLogEntries.ts b/src/frontend/src/apollo/queries/payments/verification/AllPaymentVerificationLogEntries.ts index 30ebf54d5f..72476bcfe1 100644 --- a/src/frontend/src/apollo/queries/payments/verification/AllPaymentVerificationLogEntries.ts +++ b/src/frontend/src/apollo/queries/payments/verification/AllPaymentVerificationLogEntries.ts @@ -4,7 +4,6 @@ export const ALL_PAYMENT_VERIFICATION_LOG_ENTRIES_QUERY = gql` query AllPaymentVerificationLogEntries( $businessArea: String! $objectId: UUID - $objectType: String $after: String $before: String $first: Int @@ -18,7 +17,6 @@ export const ALL_PAYMENT_VERIFICATION_LOG_ENTRIES_QUERY = gql` first: $first last: $last objectId: $objectId - objectType: $objectType businessArea: $businessArea search: $search module: $module diff --git a/src/frontend/src/components/grievances/PaymentGrievance/PaymentGrievanceDetails/__snapshots__/PaymentGrievanceDetails.test.tsx.snap b/src/frontend/src/components/grievances/PaymentGrievance/PaymentGrievanceDetails/__snapshots__/PaymentGrievanceDetails.test.tsx.snap index 6dd0d1161e..736b7f46eb 100644 --- a/src/frontend/src/components/grievances/PaymentGrievance/PaymentGrievanceDetails/__snapshots__/PaymentGrievanceDetails.test.tsx.snap +++ b/src/frontend/src/components/grievances/PaymentGrievance/PaymentGrievanceDetails/__snapshots__/PaymentGrievanceDetails.test.tsx.snap @@ -16,17 +16,6 @@ exports[`components/grievances/PaymentGrievanceDetails should render with data 1 > Payment Details - + /> diff --git a/src/frontend/src/containers/pages/payments/PaymentPlanVerificationDetailsPage.tsx b/src/frontend/src/containers/pages/payments/PaymentPlanVerificationDetailsPage.tsx index b96b63bec4..08d294500c 100644 --- a/src/frontend/src/containers/pages/payments/PaymentPlanVerificationDetailsPage.tsx +++ b/src/frontend/src/containers/pages/payments/PaymentPlanVerificationDetailsPage.tsx @@ -251,7 +251,6 @@ export function PaymentPlanVerificationDetailsPage(): ReactElement { hasPermissions(PERMISSIONS.ACTIVITY_LOG_VIEW, permissions) && ( )} diff --git a/src/frontend/src/containers/tables/UniversalActivityLogTablePaymentVerification.tsx b/src/frontend/src/containers/tables/UniversalActivityLogTablePaymentVerification.tsx index eb5218ba38..35ea66a75c 100644 --- a/src/frontend/src/containers/tables/UniversalActivityLogTablePaymentVerification.tsx +++ b/src/frontend/src/containers/tables/UniversalActivityLogTablePaymentVerification.tsx @@ -9,11 +9,9 @@ import { useBaseUrl } from '@hooks/useBaseUrl'; interface UniversalActivityLogTablePaymentVerificationProps { objectId: string; - objectType?: string; } export function UniversalActivityLogTablePaymentVerification({ objectId, - objectType, }: UniversalActivityLogTablePaymentVerificationProps): ReactElement { const [page, setPage] = useState(0); const { businessArea } = useBaseUrl(); @@ -22,7 +20,6 @@ export function UniversalActivityLogTablePaymentVerification({ variables: { businessArea, objectId: decodeIdString(objectId), - objectType, first: rowsPerPage, }, fetchPolicy: 'network-only', diff --git a/src/hct_mis_api/apps/core/exchange_rates/utils.py b/src/hct_mis_api/apps/core/exchange_rates/utils.py deleted file mode 100644 index 1d67b87654..0000000000 --- a/src/hct_mis_api/apps/core/exchange_rates/utils.py +++ /dev/null @@ -1,21 +0,0 @@ -import logging -from decimal import Decimal - -from hct_mis_api.apps.core.exchange_rates import ExchangeRates -from hct_mis_api.apps.payment.models import PaymentRecord - -logger = logging.getLogger(__name__) - - -def calculate_delivery_quantity_in_usd(exchange_rates_client: ExchangeRates, payment_record: PaymentRecord) -> None: - exchange_rate = exchange_rates_client.get_exchange_rate_for_currency_code( - payment_record.currency, payment_record.parent.dispersion_date - ) - - if exchange_rate is None: - logger.info(f"exchange_rate not found for {payment_record.ca_id}") - return - exchange_rate = Decimal(exchange_rate) - payment_record.delivered_quantity_usd = Decimal(payment_record.delivered_quantity / exchange_rate).quantize( - Decimal(".01") - ) diff --git a/src/hct_mis_api/apps/dashboard/services.py b/src/hct_mis_api/apps/dashboard/services.py index 748e93f9c4..e2b36afe34 100644 --- a/src/hct_mis_api/apps/dashboard/services.py +++ b/src/hct_mis_api/apps/dashboard/services.py @@ -11,7 +11,7 @@ from hct_mis_api.apps.core.models import BusinessArea from hct_mis_api.apps.dashboard.serializers import DashboardHouseholdSerializer -from hct_mis_api.apps.payment.models import Payment, PaymentRecord +from hct_mis_api.apps.payment.models import Payment CACHE_TIMEOUT = 60 * 60 * 24 # 24 hours @@ -120,52 +120,6 @@ def refresh_data(cls, business_area_slug: str) -> ReturnDict: ) ) - payment_records_aggregated = ( - PaymentRecord.objects.using("read_only") - .select_related( - "business_area", "household", "program", "household__admin1", "service_provider", "delivery_type" - ) - .filter(business_area=area, household__is_removed=False) - .exclude(status__in=["Transaction Erroneous", "Not Distributed", "Force failed", "Manually Cancelled"]) - .annotate( - year=ExtractYear(Coalesce("delivery_date", "status_date")), - month=ExtractMonth(Coalesce("delivery_date", "status_date")), - programs=Coalesce(F("household__program__name"), Value("Unknown program")), - sectors=Coalesce(F("household__program__sector"), Value("Unknown sector")), - admin1=Coalesce(F("household__admin1__name"), Value("Unknown admin1")), - fsp=Coalesce(F("service_provider__short_name"), Value("Unknown fsp")), - delivery_types=F("delivery_type__name"), - ) - .distinct() - .values( - "currency", - "year", - "month", - "status", - "programs", - "sectors", - "admin1", - "fsp", - "delivery_types", - ) - .annotate( - total_usd=Sum( - Coalesce("delivered_quantity_usd", "entitlement_quantity_usd", Value(0.0)), - output_field=DecimalField(), - ), - total_quantity=Sum( - Coalesce("delivered_quantity", "entitlement_quantity", Value(0.0)), output_field=DecimalField() - ), - total_payments=Count("id", distinct=True), - individuals=Sum("household__size"), - households=Count("household", distinct=True), - children_counts=Sum("household__children_count"), - pwd_counts=pwdSum, - ) - ) - - combined_list = list(payments_aggregated) + list(payment_records_aggregated) - summary = defaultdict( lambda: { "total_usd": 0, @@ -178,7 +132,7 @@ def refresh_data(cls, business_area_slug: str) -> ReturnDict: "pwd_counts": 0, } ) - for item in combined_list: + for item in list(payments_aggregated): key = ( item["currency"], item["year"], diff --git a/src/hct_mis_api/apps/grievance/fixtures.py b/src/hct_mis_api/apps/grievance/fixtures.py index f2add77bea..60cf4c7fff 100644 --- a/src/hct_mis_api/apps/grievance/fixtures.py +++ b/src/hct_mis_api/apps/grievance/fixtures.py @@ -79,7 +79,7 @@ class Meta: ) household = None individual = None - payment_obj = None + payment = None @factory.post_generation def create_extras(obj, create: bool, extracted: bool, **kwargs: Any) -> None: @@ -105,8 +105,7 @@ class Meta: ) household = None individual = None - payment_object_id = None - payment_content_type_id = None + payment = None @factory.post_generation def create_extras(obj, create: bool, extracted: bool, **kwargs: Any) -> None: @@ -133,7 +132,7 @@ class Meta: ) household = None individual = None - payment_obj = None + payment = None class GrievanceComplaintTicketWithoutExtrasFactory(DjangoModelFactory): diff --git a/src/hct_mis_api/apps/grievance/migrations/0005_migration.py b/src/hct_mis_api/apps/grievance/migrations/0005_migration.py new file mode 100644 index 0000000000..f6ae054f09 --- /dev/null +++ b/src/hct_mis_api/apps/grievance/migrations/0005_migration.py @@ -0,0 +1,66 @@ +# Generated by Django 3.2.25 on 2024-12-11 13:11 +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_onetoone_to_foreignkey(apps, schema_editor): + TicketSensitiveDetails = apps.get_model("grievance", "TicketSensitiveDetails") + TicketComplaintDetails = apps.get_model("grievance", "TicketComplaintDetails") + TicketSensitiveDetails.objects.filter( + payment_object_id__isnull=False + ).update(payment_fk=models.F("payment_object_id")) + TicketComplaintDetails.objects.filter( + payment_object_id__isnull=False + ).update(payment_fk=models.F("payment_object_id")) + + +class Migration(migrations.Migration): + + + dependencies = [ + ('grievance', '0004_migration'), + ] + + operations = [ + migrations.AddField( + model_name='ticketcomplaintdetails', + name='payment_fk', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='payment.payment'), + ), + migrations.AddField( + model_name='ticketsensitivedetails', + name='payment_fk', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='payment.payment'), + ), + migrations.RunPython(migrate_onetoone_to_foreignkey), + migrations.RemoveField( + model_name='ticketcomplaintdetails', + name='payment', + ), + migrations.RemoveField( + model_name='ticketsensitivedetails', + name='payment', + ), + migrations.RenameField( + model_name='ticketcomplaintdetails', + old_name='payment_fk', + new_name='payment', + ), + migrations.RenameField( + model_name='ticketsensitivedetails', + old_name='payment_fk', + new_name='payment', + ), + migrations.AlterField( + model_name='ticketcomplaintdetails', + name='payment', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='ticket_complaint_details', to='payment.payment'), + ), + migrations.AlterField( + model_name='ticketsensitivedetails', + name='payment', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='ticket_sensitive_details', to='payment.payment'), + ), + ] diff --git a/src/hct_mis_api/apps/grievance/migrations/0006_migration.py b/src/hct_mis_api/apps/grievance/migrations/0006_migration.py new file mode 100644 index 0000000000..384e90342c --- /dev/null +++ b/src/hct_mis_api/apps/grievance/migrations/0006_migration.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.25 on 2024-12-11 13:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('grievance', '0005_migration'), + ] + + operations = [ + migrations.RemoveField( + model_name='ticketcomplaintdetails', + name='payment_content_type', + ), + migrations.RemoveField( + model_name='ticketcomplaintdetails', + name='payment_object_id', + ), + migrations.RemoveField( + model_name='ticketsensitivedetails', + name='payment_content_type', + ), + migrations.RemoveField( + model_name='ticketsensitivedetails', + name='payment_object_id', + ), + ] diff --git a/src/hct_mis_api/apps/grievance/models.py b/src/hct_mis_api/apps/grievance/models.py index 67acda98ee..a43a7d477c 100644 --- a/src/hct_mis_api/apps/grievance/models.py +++ b/src/hct_mis_api/apps/grievance/models.py @@ -5,14 +5,12 @@ from django.conf import settings from django.contrib.auth import get_user_model -from django.contrib.contenttypes.fields import GenericForeignKey -from django.contrib.contenttypes.models import ContentType from django.core.cache import cache from django.core.exceptions import ValidationError from django.core.files.storage import default_storage from django.core.validators import MinValueValidator from django.db import models -from django.db.models import JSONField, Q, QuerySet, UniqueConstraint, UUIDField +from django.db.models import JSONField, Q, QuerySet, UniqueConstraint from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.functional import cached_property @@ -101,15 +99,6 @@ def get_queryset(self) -> "QuerySet": return super().get_queryset().filter(is_original=True) -class GenericPaymentTicket(TimeStampedUUIDModel): - payment_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True) - payment_object_id = UUIDField(null=True) - payment_obj = GenericForeignKey("payment_content_type", "payment_object_id") - - class Meta: - abstract = True - - class GrievanceTicket(TimeStampedUUIDModel, AdminUrlMixin, ConcurrencyModel, UnicefIdentifiedModel): ACTIVITY_LOG_MAPPING = create_mapping_dict( [ @@ -628,7 +617,7 @@ class TicketNote(TimeStampedUUIDModel): } -class TicketComplaintDetails(GenericPaymentTicket): # TODO TP drop GenericPaymentTicket +class TicketComplaintDetails(TimeStampedUUIDModel): STATUS_FLOW = GENERAL_STATUS_FLOW ticket = models.OneToOneField( @@ -648,7 +637,7 @@ class TicketComplaintDetails(GenericPaymentTicket): # TODO TP drop GenericPayme on_delete=models.CASCADE, null=True, ) - payment = models.OneToOneField( + payment = models.ForeignKey( Payment, on_delete=models.CASCADE, null=True, @@ -659,7 +648,7 @@ class Meta: verbose_name_plural = "Ticket Complaint Details" -class TicketSensitiveDetails(GenericPaymentTicket): # TODO TP drop GenericPaymentTicket +class TicketSensitiveDetails(TimeStampedUUIDModel): STATUS_FLOW = GENERAL_STATUS_FLOW ticket = models.OneToOneField( @@ -679,7 +668,7 @@ class TicketSensitiveDetails(GenericPaymentTicket): # TODO TP drop GenericPayme on_delete=models.CASCADE, null=True, ) - payment = models.OneToOneField( + payment = models.ForeignKey( Payment, on_delete=models.CASCADE, null=True, diff --git a/src/hct_mis_api/apps/grievance/services/ticket_based_on_payment_record_services.py b/src/hct_mis_api/apps/grievance/services/ticket_based_on_payment_record_services.py index b29654ab34..5eb455eef7 100644 --- a/src/hct_mis_api/apps/grievance/services/ticket_based_on_payment_record_services.py +++ b/src/hct_mis_api/apps/grievance/services/ticket_based_on_payment_record_services.py @@ -29,8 +29,7 @@ def create_tickets_based_on_payment_records_service( model.objects.create( individual=individual, household=household, - payment_content_type=None, - payment_object_id=None, + payment=None, ticket=grievance_ticket, ) grievance_ticket.refresh_from_db() diff --git a/src/hct_mis_api/apps/household/migrations/0005_migration.py b/src/hct_mis_api/apps/household/migrations/0005_migration.py new file mode 100644 index 0000000000..daea327c0b --- /dev/null +++ b/src/hct_mis_api/apps/household/migrations/0005_migration.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.25 on 2024-12-16 11:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('household', '0004_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='household', + name='internal_data', + field=models.JSONField(blank=True, default=dict), + ), + migrations.AlterField( + model_name='individual', + name='internal_data', + field=models.JSONField(blank=True, default=dict), + ), + ] diff --git a/src/hct_mis_api/apps/payment/admin.py b/src/hct_mis_api/apps/payment/admin.py index ab82df38ba..6f9ff7d9d1 100644 --- a/src/hct_mis_api/apps/payment/admin.py +++ b/src/hct_mis_api/apps/payment/admin.py @@ -12,7 +12,7 @@ from admin_extra_buttons.decorators import button from admin_extra_buttons.mixins import confirm_action -from adminfilters.autocomplete import AutoCompleteFilter, LinkedAutoCompleteFilter +from adminfilters.autocomplete import AutoCompleteFilter from adminfilters.depot.widget import DepotManager from adminfilters.filters import ChoicesFieldComboFilter, ValueFilter from adminfilters.querystring import QueryStringFilter @@ -20,7 +20,6 @@ from smart_admin.mixins import LinkedObjectsMixin from hct_mis_api.apps.payment.models import ( - CashPlan, DeliveryMechanism, DeliveryMechanismData, DeliveryMechanismPerPaymentPlan, @@ -31,10 +30,8 @@ PaymentHouseholdSnapshot, PaymentPlan, PaymentPlanSupportingDocument, - PaymentRecord, PaymentVerification, PaymentVerificationPlan, - ServiceProvider, ) from hct_mis_api.apps.payment.services.verification_plan_status_change_services import ( VerificationPlanStatusChangeServices, @@ -48,49 +45,6 @@ from django.forms import Form -@admin.register(PaymentRecord) -class PaymentRecordAdmin(AdminAdvancedFiltersMixin, LinkedObjectsMixin, HOPEModelAdminBase): - list_display = ( - "unicef_id", - "household", - "status", - "cash_plan_name", - "target_population", - "business_area", - ) - list_filter = ( - DepotManager, - QueryStringFilter, - ("status", ChoicesFieldComboFilter), - ("business_area", LinkedAutoCompleteFilter.factory(parent=None)), - ("target_population", LinkedAutoCompleteFilter.factory(parent="business_area")), - ("parent", LinkedAutoCompleteFilter.factory(parent="business_area")), - ("service_provider", LinkedAutoCompleteFilter.factory(parent="business_area")), - ) - advanced_filter_fields = ( - "status", - "delivery_date", - ("service_provider__name", "Service Provider"), - ("parent__name", "CashPlan"), - ("target_population__name", "TargetPopulation"), - ) - date_hierarchy = "updated_at" - raw_id_fields = ( - "business_area", - "parent", - "household", - "head_of_household", - "target_population", - "service_provider", - ) - - def cash_plan_name(self, obj: Any) -> str: - return obj.parent.name or "" - - def get_queryset(self, request: HttpRequest) -> QuerySet: - return super().get_queryset(request).select_related("household", "parent", "target_population", "business_area") - - @admin.register(PaymentVerificationPlan) class PaymentVerificationPlanAdmin(LinkedObjectsMixin, HOPEModelAdminBase): list_display = ("payment_plan", "status", "verification_channel") @@ -100,7 +54,7 @@ class PaymentVerificationPlanAdmin(LinkedObjectsMixin, HOPEModelAdminBase): ) date_hierarchy = "updated_at" search_fields = ("payment_plan__name",) - raw_id_fields = ("payment_plan_content_type",) + raw_id_fields = ("payment_plan",) @button() def verifications(self, request: HttpRequest, pk: "UUID") -> HttpResponseRedirect: @@ -154,7 +108,7 @@ class PaymentVerificationAdmin(HOPEModelAdminBase): ("payment__household__unicef_id", ValueFilter), ) date_hierarchy = "updated_at" - raw_id_fields = ("payment_verification_plan", "payment_content_type") + raw_id_fields = ("payment_verification_plan", "payment") def payment_plan_name(self, obj: PaymentVerification) -> str: # pragma: no cover payment_plan = obj.payment_verification_plan.payment_plan @@ -178,35 +132,6 @@ def get_queryset(self, request: HttpRequest) -> QuerySet: ) -@admin.register(ServiceProvider) -class ServiceProviderAdmin(HOPEModelAdminBase): - list_display = ("full_name", "short_name", "country", "vision_id") - search_fields = ("full_name", "vision_id", "short_name") - list_filter = (("business_area", AutoCompleteFilter),) - autocomplete_fields = ("business_area",) - - -@admin.register(CashPlan) -class CashPlanAdmin(HOPEModelAdminBase): - list_display = ("name", "program", "delivery_type", "status", "verification_status", "ca_id") - list_filter = ( - ("status", ChoicesFieldComboFilter), - ("business_area", AutoCompleteFilter), - ("delivery_type", ChoicesFieldComboFilter), - ("payment_verification_summary__status", ChoicesFieldComboFilter), - ("program__id", ValueFilter), - ("vision_id", ValueFilter), - ) - raw_id_fields = ("business_area", "program", "service_provider") - search_fields = ("name",) - - @button() - def payments(self, request: HttpRequest, pk: str) -> TemplateResponse: - context = self.get_common_context(request, pk, aeu_groups=[None], action="payments") - - return TemplateResponse(request, "admin/cashplan/payments.html", context) - - @admin.register(PaymentPlan) class PaymentPlanAdmin(HOPEModelAdminBase, PaymentPlanCeleryTasksMixin): list_display = ("unicef_id", "program_cycle", "status", "target_population") diff --git a/src/hct_mis_api/apps/payment/filters.py b/src/hct_mis_api/apps/payment/filters.py index 1c9b83d876..28ef1774ba 100644 --- a/src/hct_mis_api/apps/payment/filters.py +++ b/src/hct_mis_api/apps/payment/filters.py @@ -5,12 +5,10 @@ from django.db.models import Case, Count, IntegerField, Q, QuerySet, Value, When from django.db.models.functions import Lower from django.shortcuts import get_object_or_404 -from django.utils.translation import gettext_lazy as _ from django_filters import ( BooleanFilter, CharFilter, - ChoiceFilter, DateFilter, FilterSet, MultipleChoiceFilter, @@ -26,7 +24,6 @@ decode_id_string_required, ) from hct_mis_api.apps.payment.models import ( - CashPlan, DeliveryMechanism, FinancialServiceProvider, FinancialServiceProviderXlsxTemplate, @@ -99,10 +96,7 @@ def payment_plan_filter(self, qs: QuerySet, name: str, value: str) -> QuerySet: ) def business_area_filter(self, qs: QuerySet, name: str, value: str) -> QuerySet: - return qs.filter( - Q(payment_verification_plan__payment_plan__business_area__slug=value) - | Q(payment_verification_plan__cash_plan__business_area__slug=value) - ) + return qs.filter(payment_verification_plan__payment_plan__business_area__slug=value) class PaymentVerificationPlanFilter(FilterSet): @@ -124,26 +118,11 @@ class Meta: class PaymentVerificationLogEntryFilter(LogEntryFilter): - PLAN_TYPE_CASH = "CashPlan" - PLAN_TYPE_PAYMENT = "PaymentPlan" - PLAN_TYPE_CHOICES = ( - (PLAN_TYPE_CASH, _("CashPlan")), - (PLAN_TYPE_PAYMENT, _("PaymentPlan")), - ) object_id = UUIDFilter(method="object_id_filter") - object_type = ChoiceFilter(method="object_type_filter", choices=PLAN_TYPE_CHOICES) - - def filter_queryset(self, queryset: QuerySet) -> QuerySet: - cleaned_data = self.form.cleaned_data - object_type = cleaned_data.get("object_type") - object_id = cleaned_data.get("object_id") - plan_object = (PaymentPlan if object_type == self.PLAN_TYPE_PAYMENT else CashPlan).objects.get(pk=object_id) - verifications_ids = plan_object.payment_verification_plans.all().values_list("pk", flat=True) - return queryset.filter(object_id__in=verifications_ids) def object_id_filter(self, qs: QuerySet, name: str, value: UUID) -> QuerySet: - cash_plan = CashPlan.objects.get(pk=value) - verifications_ids = cash_plan.verifications.all().values_list("pk", flat=True) + payment_plan = PaymentPlan.objects.get(pk=value) + verifications_ids = payment_plan.payment_verification_plans.all().values_list("pk", flat=True) return qs.filter(object_id__in=verifications_ids) diff --git a/src/hct_mis_api/apps/payment/fixtures.py b/src/hct_mis_api/apps/payment/fixtures.py index d4fd7f32ad..77cb076a97 100644 --- a/src/hct_mis_api/apps/payment/fixtures.py +++ b/src/hct_mis_api/apps/payment/fixtures.py @@ -38,7 +38,6 @@ FinancialServiceProvider, FinancialServiceProviderXlsxTemplate, FspXlsxTemplatePerDeliveryMechanism, - GenericPayment, Payment, PaymentPlan, PaymentPlanSplit, @@ -288,7 +287,7 @@ class Meta: parent = factory.SubFactory(PaymentPlanFactory) business_area = factory.LazyAttribute(lambda o: BusinessArea.objects.first()) - status = GenericPayment.STATUS_PENDING + status = Payment.STATUS_PENDING status_date = factory.Faker( "date_time_this_decade", before_now=True, diff --git a/src/hct_mis_api/apps/payment/migrations/0008_migration.py b/src/hct_mis_api/apps/payment/migrations/0008_migration.py new file mode 100644 index 0000000000..24816b1caf --- /dev/null +++ b/src/hct_mis_api/apps/payment/migrations/0008_migration.py @@ -0,0 +1,87 @@ +# Generated by Django 3.2.25 on 2024-12-11 13:41 + +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_onetoone_to_foreignkey(apps, schema_editor): + PaymentVerification = apps.get_model("payment", "PaymentVerification") + PaymentVerification.objects.filter( + payment_id__isnull=False + ).update(payment_fk=models.F("payment_id")) + + +class Migration(migrations.Migration): + + dependencies = [ + ('grievance', '0006_migration'), + ('payment', '0007_migration'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='paymentverification', + name='payment_content_type_and_payment_id', + ), + migrations.RemoveConstraint( + model_name='paymentverificationsummary', + name='payment_plan_content_type_and_payment_plan_id', + ), + migrations.RemoveIndex( + model_name='paymentverification', + name='payment_pay_payment_ec4a29_idx', + ), + migrations.RemoveIndex( + model_name='paymentverificationplan', + name='payment_pay_payment_3ba67e_idx', + ), + migrations.RemoveIndex( + model_name='paymentverificationsummary', + name='payment_pay_payment_8b7d61_idx', + ), + migrations.RemoveField( + model_name='paymentverification', + name='payment_content_type', + ), + migrations.RemoveField( + model_name='paymentverification', + name='payment_object_id', + ), + migrations.RemoveField( + model_name='paymentverificationplan', + name='payment_plan_content_type', + ), + migrations.RemoveField( + model_name='paymentverificationplan', + name='payment_plan_object_id', + ), + migrations.RemoveField( + model_name='paymentverificationsummary', + name='payment_plan_content_type', + ), + migrations.RemoveField( + model_name='paymentverificationsummary', + name='payment_plan_object_id', + ), + migrations.AddField( + model_name='paymentverification', + name='payment_fk', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='payment.payment'), + ), + migrations.RunPython(migrate_onetoone_to_foreignkey), + migrations.RemoveField( + model_name='paymentverification', + name='payment', + ), + migrations.RenameField( + model_name='paymentverification', + old_name='payment_fk', + new_name='payment', + ), + migrations.AlterField( + model_name='paymentverification', + name='payment', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='payment_verifications', to='payment.payment'), + ), + ] diff --git a/src/hct_mis_api/apps/payment/migrations/0009_migration.py b/src/hct_mis_api/apps/payment/migrations/0009_migration.py new file mode 100644 index 0000000000..5c58af088d --- /dev/null +++ b/src/hct_mis_api/apps/payment/migrations/0009_migration.py @@ -0,0 +1,69 @@ +# Generated by Django 3.2.25 on 2024-12-16 11:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('payment', '0008_migration'), + ] + + operations = [ + migrations.RemoveField( + model_name='paymentrecord', + name='business_area', + ), + migrations.RemoveField( + model_name='paymentrecord', + name='delivery_type', + ), + migrations.RemoveField( + model_name='paymentrecord', + name='head_of_household', + ), + migrations.RemoveField( + model_name='paymentrecord', + name='household', + ), + migrations.RemoveField( + model_name='paymentrecord', + name='parent', + ), + migrations.RemoveField( + model_name='paymentrecord', + name='service_provider', + ), + migrations.RemoveField( + model_name='paymentrecord', + name='target_population', + ), + migrations.RemoveField( + model_name='serviceprovider', + name='business_area', + ), + migrations.AlterField( + model_name='financialserviceprovider', + name='internal_data', + field=models.JSONField(blank=True, default=dict), + ), + migrations.AlterField( + model_name='payment', + name='internal_data', + field=models.JSONField(blank=True, default=dict), + ), + migrations.AlterField( + model_name='paymentplan', + name='internal_data', + field=models.JSONField(blank=True, default=dict), + ), + migrations.DeleteModel( + name='CashPlan', + ), + migrations.DeleteModel( + name='PaymentRecord', + ), + migrations.DeleteModel( + name='ServiceProvider', + ), + ] diff --git a/src/hct_mis_api/apps/payment/models/__init__.py b/src/hct_mis_api/apps/payment/models/__init__.py index a26573da4a..64a34571ff 100644 --- a/src/hct_mis_api/apps/payment/models/__init__.py +++ b/src/hct_mis_api/apps/payment/models/__init__.py @@ -3,13 +3,6 @@ Approval, ApprovalProcess, ) -from hct_mis_api.apps.payment.models.cash_assist import ( - CashPlan, - GenericPayment, - GenericPaymentPlan, - PaymentRecord, - ServiceProvider, -) from hct_mis_api.apps.payment.models.payment import ( DeliveryMechanism, DeliveryMechanismData, @@ -37,11 +30,6 @@ "AcceptanceProcessThreshold", "Approval", "ApprovalProcess", - "CashPlan", - "GenericPayment", - "GenericPaymentPlan", - "PaymentRecord", - "ServiceProvider", "DeliveryMechanism", "DeliveryMechanismData", "DeliveryMechanismPerPaymentPlan", diff --git a/src/hct_mis_api/apps/payment/models/cash_assist.py b/src/hct_mis_api/apps/payment/models/cash_assist.py deleted file mode 100644 index 0f2a092af9..0000000000 --- a/src/hct_mis_api/apps/payment/models/cash_assist.py +++ /dev/null @@ -1,482 +0,0 @@ -import logging -from datetime import datetime -from decimal import Decimal -from functools import cached_property -from typing import TYPE_CHECKING, Any, Callable, Optional - -from django.contrib.contenttypes.fields import GenericRelation -from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ValidationError -from django.core.validators import MinValueValidator -from django.db import models -from django.db.models import Q, QuerySet -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ - -from model_utils import Choices - -from hct_mis_api.apps.core.currencies import USDC -from hct_mis_api.apps.core.exchange_rates import ExchangeRates -from hct_mis_api.apps.payment.delivery_mechanisms import DeliveryMechanismChoices -from hct_mis_api.apps.utils.models import ( - AdminUrlMixin, - ConcurrencyModel, - TimeStampedUUIDModel, -) - -if TYPE_CHECKING: - from hct_mis_api.apps.core.exchange_rates.api import ExchangeRateClient - - -logger = logging.getLogger(__name__) - - -class GenericPaymentPlan(TimeStampedUUIDModel): - usd_fields = [ - "total_entitled_quantity_usd", - "total_entitled_quantity_revised_usd", - "total_delivered_quantity_usd", - "total_undelivered_quantity_usd", - ] - - business_area = models.ForeignKey("core.BusinessArea", on_delete=models.CASCADE) - status_date = models.DateTimeField() - start_date = models.DateTimeField( - db_index=True, - blank=True, - null=True, - ) - end_date = models.DateTimeField( - db_index=True, - blank=True, - null=True, - ) - program = models.ForeignKey("program.Program", on_delete=models.CASCADE) - exchange_rate = models.DecimalField(decimal_places=8, blank=True, null=True, max_digits=14) - - total_entitled_quantity = models.DecimalField( - decimal_places=2, - max_digits=12, - validators=[MinValueValidator(Decimal("0"))], - db_index=True, - null=True, - ) - total_entitled_quantity_usd = models.DecimalField( - decimal_places=2, max_digits=12, validators=[MinValueValidator(Decimal("0"))], null=True, blank=True - ) - total_entitled_quantity_revised = models.DecimalField( - decimal_places=2, - max_digits=12, - validators=[MinValueValidator(Decimal("0"))], - db_index=True, - null=True, - blank=True, - ) - total_entitled_quantity_revised_usd = models.DecimalField( - decimal_places=2, max_digits=12, validators=[MinValueValidator(Decimal("0"))], null=True, blank=True - ) - total_delivered_quantity = models.DecimalField( - decimal_places=2, - max_digits=12, - validators=[MinValueValidator(Decimal("0"))], - db_index=True, - null=True, - blank=True, - ) - total_delivered_quantity_usd = models.DecimalField( - decimal_places=2, max_digits=12, validators=[MinValueValidator(Decimal("0"))], null=True, blank=True - ) - total_undelivered_quantity = models.DecimalField( - decimal_places=2, - max_digits=12, - validators=[MinValueValidator(Decimal("0"))], - db_index=True, - null=True, - blank=True, - ) - total_undelivered_quantity_usd = models.DecimalField( - decimal_places=2, max_digits=12, validators=[MinValueValidator(Decimal("0"))], null=True, blank=True - ) - - class Meta: - abstract = True - - @property - def get_unicef_id(self) -> str: - return self.ca_id if isinstance(self, CashPlan) else self.unicef_id - - def get_exchange_rate(self, exchange_rates_client: Optional["ExchangeRateClient"] = None) -> float: - if self.currency == USDC: - # exchange rate for Digital currency - return 1.0 - - if exchange_rates_client is None: - exchange_rates_client = ExchangeRates() - - return exchange_rates_client.get_exchange_rate_for_currency_code(self.currency, self.currency_exchange_date) - - @property - def get_payment_verification_summary(self) -> Any: - """PaymentPlan has only one payment_verification_summary""" - from hct_mis_api.apps.payment.models import PaymentVerificationSummary - - c_type = ContentType.objects.get_for_model(self.__class__) - try: - verification_summary = PaymentVerificationSummary.objects.get( - payment_plan_content_type_id=c_type.pk, payment_plan_object_id=self.pk - ) - except PaymentVerificationSummary.DoesNotExist: - return None - return verification_summary - - @property - def get_payment_verification_plans(self) -> Any: - from hct_mis_api.apps.payment.models import PaymentVerificationPlan - - c_type = ContentType.objects.get_for_model(self.__class__) - payment_verification_plans = PaymentVerificationPlan.objects.filter( - payment_plan_content_type_id=c_type.pk, payment_plan_object_id=self.pk - ) - return payment_verification_plans - - def available_payment_records( - self, - payment_verification_plan: Any = None, - extra_validation: Optional[Callable] = None, - ) -> QuerySet: - params = Q(status__in=GenericPayment.ALLOW_CREATE_VERIFICATION, delivered_quantity__gt=0) - - if payment_verification_plan: - params &= Q( - Q(payment_verification__isnull=True) - | Q(payment_verification__payment_verification_plan=payment_verification_plan) - ) - else: - params &= Q(payment_verification__isnull=True) - - payment_records = self.payment_items.select_related("head_of_household").filter(params).distinct() - - if extra_validation: - payment_records = list(map(lambda pr: pr.pk, filter(extra_validation, payment_records))) - - qs = PaymentRecord.objects.filter(pk__in=payment_records) - - return qs - - @property - def can_create_payment_verification_plan(self) -> int: - return self.available_payment_records().count() > 0 - - -class GenericPayment(TimeStampedUUIDModel): - usd_fields = ["delivered_quantity_usd", "entitlement_quantity_usd"] - - STATUS_SUCCESS = "Transaction Successful" - STATUS_ERROR = "Transaction Erroneous" - STATUS_DISTRIBUTION_SUCCESS = "Distribution Successful" - STATUS_NOT_DISTRIBUTED = "Not Distributed" - STATUS_FORCE_FAILED = "Force failed" - STATUS_DISTRIBUTION_PARTIAL = "Partially Distributed" - STATUS_PENDING = "Pending" - # Payment Gateway statuses - STATUS_SENT_TO_PG = "Sent to Payment Gateway" - STATUS_SENT_TO_FSP = "Sent to FSP" - STATUS_MANUALLY_CANCELLED = "Manually Cancelled" - - STATUS_CHOICE = ( - (STATUS_DISTRIBUTION_SUCCESS, _("Distribution Successful")), # Delivered Fully - (STATUS_NOT_DISTRIBUTED, _("Not Distributed")), # Not Delivered - (STATUS_SUCCESS, _("Transaction Successful")), # Delivered Fully - (STATUS_ERROR, _("Transaction Erroneous")), # Unsuccessful - (STATUS_FORCE_FAILED, _("Force failed")), # Force Failed - (STATUS_DISTRIBUTION_PARTIAL, _("Partially Distributed")), # Delivered Partially - (STATUS_PENDING, _("Pending")), # Pending - (STATUS_SENT_TO_PG, _("Sent to Payment Gateway")), - (STATUS_SENT_TO_FSP, _("Sent to FSP")), - (STATUS_MANUALLY_CANCELLED, _("Manually Cancelled")), - ) - - ALLOW_CREATE_VERIFICATION = (STATUS_SUCCESS, STATUS_DISTRIBUTION_SUCCESS, STATUS_DISTRIBUTION_PARTIAL) - PENDING_STATUSES = (STATUS_PENDING, STATUS_SENT_TO_PG, STATUS_SENT_TO_FSP) - - ENTITLEMENT_CARD_STATUS_ACTIVE = "ACTIVE" - ENTITLEMENT_CARD_STATUS_INACTIVE = "INACTIVE" - ENTITLEMENT_CARD_STATUS_CHOICE = Choices( - (ENTITLEMENT_CARD_STATUS_ACTIVE, _("Active")), - (ENTITLEMENT_CARD_STATUS_INACTIVE, _("Inactive")), - ) - - business_area = models.ForeignKey("core.BusinessArea", on_delete=models.CASCADE) - status = models.CharField( - max_length=255, - choices=STATUS_CHOICE, - default=STATUS_PENDING, - ) - status_date = models.DateTimeField() - household = models.ForeignKey("household.Household", on_delete=models.CASCADE) - head_of_household = models.ForeignKey("household.Individual", on_delete=models.CASCADE, null=True) - delivery_type = models.ForeignKey("payment.DeliveryMechanism", on_delete=models.SET_NULL, null=True) - currency = models.CharField( - max_length=4, - ) - entitlement_quantity = models.DecimalField( - decimal_places=2, max_digits=12, validators=[MinValueValidator(Decimal("0.00"))], null=True, blank=True - ) - entitlement_quantity_usd = models.DecimalField( - decimal_places=2, max_digits=12, validators=[MinValueValidator(Decimal("0.00"))], null=True, blank=True - ) - delivered_quantity = models.DecimalField( - decimal_places=2, max_digits=12, validators=[MinValueValidator(Decimal("0.00"))], null=True, blank=True - ) - delivered_quantity_usd = models.DecimalField( - decimal_places=2, max_digits=12, validators=[MinValueValidator(Decimal("0.00"))], null=True, blank=True - ) - delivery_date = models.DateTimeField(null=True, blank=True) - transaction_reference_id = models.CharField(max_length=255, null=True, blank=True) # transaction_id - transaction_status_blockchain_link = models.CharField(max_length=255, null=True, blank=True) - - class Meta: - abstract = True - - @property - def verification(self) -> Any: - from hct_mis_api.apps.payment.models import PaymentVerification - - c_type = ContentType.objects.get_for_model(self.__class__) - try: - verification = PaymentVerification.objects.get(payment_content_type_id=c_type.pk, payment_object_id=self.pk) - except PaymentVerification.DoesNotExist: - return None - return verification - - def get_revert_mark_as_failed_status(self, delivered_quantity: Decimal) -> str: - raise NotImplementedError() - - def mark_as_failed(self) -> None: - if self.status is self.STATUS_FORCE_FAILED: - raise ValidationError("Status shouldn't be failed") - self.status = self.STATUS_FORCE_FAILED - self.status_date = timezone.now() - self.delivered_quantity = 0 - self.delivered_quantity_usd = 0 - self.delivery_date = None - - def revert_mark_as_failed(self, delivered_quantity: Decimal, delivery_date: datetime) -> None: - if self.status != self.STATUS_FORCE_FAILED: - raise ValidationError("Only payment marked as force failed can be reverted") - if self.entitlement_quantity is None: - raise ValidationError("Entitlement quantity need to be set in order to revert") - - self.status = self.get_revert_mark_as_failed_status(delivered_quantity) - self.status_date = timezone.now() - self.delivered_quantity = delivered_quantity - self.delivery_date = delivery_date - - @property - def get_unicef_id(self) -> str: - return self.ca_id if isinstance(self, PaymentRecord) else self.unicef_id - - @property - def payment_status(self) -> str: - status = "-" - if self.status == GenericPayment.STATUS_PENDING: - status = "Pending" - - elif self.status in (GenericPayment.STATUS_DISTRIBUTION_SUCCESS, GenericPayment.STATUS_SUCCESS): - status = "Delivered Fully" - - elif self.status == GenericPayment.STATUS_DISTRIBUTION_PARTIAL: - status = "Delivered Partially" - - elif self.status == GenericPayment.STATUS_NOT_DISTRIBUTED: - status = "Not Delivered" - - elif self.status == GenericPayment.STATUS_ERROR: - status = "Unsuccessful" - - elif self.status == GenericPayment.STATUS_FORCE_FAILED: - status = "Force Failed" - - return status - - -class CashPlan(ConcurrencyModel, AdminUrlMixin, GenericPaymentPlan): - DISTRIBUTION_COMPLETED = "Distribution Completed" - DISTRIBUTION_COMPLETED_WITH_ERRORS = "Distribution Completed with Errors" - TRANSACTION_COMPLETED = "Transaction Completed" - TRANSACTION_COMPLETED_WITH_ERRORS = "Transaction Completed with Errors" - - STATUS_CHOICE = ( - (DISTRIBUTION_COMPLETED, _("Distribution Completed")), - ( - DISTRIBUTION_COMPLETED_WITH_ERRORS, - _("Distribution Completed with Errors"), - ), - (TRANSACTION_COMPLETED, _("Transaction Completed")), - ( - TRANSACTION_COMPLETED_WITH_ERRORS, - _("Transaction Completed with Errors"), - ), - ) - name = models.CharField(max_length=255, db_index=True) - ca_id = models.CharField(max_length=255, null=True, db_index=True) - ca_hash_id = models.UUIDField(unique=True, null=True) - status = models.CharField(max_length=255, choices=STATUS_CHOICE, db_index=True) - distribution_level = models.CharField(max_length=255) - dispersion_date = models.DateTimeField() - coverage_duration = models.PositiveIntegerField() - coverage_unit = models.CharField(max_length=255) - comments = models.CharField(max_length=255, null=True) - delivery_type = models.CharField( - choices=DeliveryMechanismChoices.DELIVERY_TYPE_CHOICES, - max_length=32, - null=True, - db_index=True, - ) - assistance_measurement = models.CharField(max_length=255, db_index=True) - assistance_through = models.CharField(max_length=255, db_index=True) - service_provider = models.ForeignKey( - "payment.ServiceProvider", - null=True, - related_name="cash_plans", - on_delete=models.CASCADE, - ) - vision_id = models.CharField(max_length=255, null=True) - funds_commitment = models.CharField(max_length=255, null=True) - down_payment = models.CharField(max_length=255, null=True) - validation_alerts_count = models.IntegerField() - total_persons_covered = models.IntegerField(db_index=True) - total_persons_covered_revised = models.IntegerField(db_index=True) - payment_verification_summary = GenericRelation( - "payment.PaymentVerificationSummary", - content_type_field="payment_plan_content_type", - object_id_field="payment_plan_object_id", - related_query_name="cash_plan", - ) - payment_verification_plan = GenericRelation( - "payment.PaymentVerificationPlan", - content_type_field="payment_plan_content_type", - object_id_field="payment_plan_object_id", - related_query_name="cash_plan", - ) - is_migrated_to_payment_plan = models.BooleanField(default=False) - - def __str__(self) -> str: - return self.name or "" - - @property - def payment_records_count(self) -> int: - return self.payment_items.count() - - @property - def bank_reconciliation_success(self) -> int: - return self.payment_items.filter(status__in=PaymentRecord.ALLOW_CREATE_VERIFICATION).count() - - @property - def bank_reconciliation_error(self) -> int: - return self.payment_items.filter(status=PaymentRecord.STATUS_ERROR).count() - - @cached_property - def total_number_of_households(self) -> int: - # https://unicef.visualstudio.com/ICTD-HCT-MIS/_workitems/edit/84040 - return self.payment_items.count() - - @property - def currency(self) -> Optional[str]: - payment_record = self.payment_items.first() - return payment_record.currency if payment_record else None - - @property - def currency_exchange_date(self) -> datetime: - return self.dispersion_date - - def unicef_id(self) -> str: - return self.ca_id - - @property - def verification_status(self) -> Optional[str]: - summary = self.payment_verification_summary.first() - return getattr(summary, "status", None) - - class Meta: - verbose_name = "Cash Plan" - ordering = ["created_at"] - - -class PaymentRecord(ConcurrencyModel, AdminUrlMixin, GenericPayment): - ENTITLEMENT_CARD_STATUS_ACTIVE = "ACTIVE" - ENTITLEMENT_CARD_STATUS_INACTIVE = "INACTIVE" - ENTITLEMENT_CARD_STATUS_CHOICE = Choices( - (ENTITLEMENT_CARD_STATUS_ACTIVE, _("Active")), - (ENTITLEMENT_CARD_STATUS_INACTIVE, _("Inactive")), - ) - - ca_id = models.CharField(max_length=255, null=True, db_index=True) - ca_hash_id = models.UUIDField(unique=True, null=True) - parent = models.ForeignKey( - "payment.CashPlan", - on_delete=models.CASCADE, - related_name="payment_items", - null=True, - ) - - full_name = models.CharField(max_length=255) - total_persons_covered = models.IntegerField() - distribution_modality = models.CharField( - max_length=255, - ) - target_population = models.ForeignKey( - "targeting.TargetPopulation", - on_delete=models.CASCADE, - related_name="payment_records", - ) - target_population_cash_assist_id = models.CharField(max_length=255) - entitlement_card_number = models.CharField(max_length=255, null=True) - entitlement_card_status = models.CharField( - choices=ENTITLEMENT_CARD_STATUS_CHOICE, default="ACTIVE", max_length=20, null=True - ) - entitlement_card_issue_date = models.DateField(null=True) - vision_id = models.CharField(max_length=255, null=True) - registration_ca_id = models.CharField(max_length=255, null=True) - service_provider = models.ForeignKey( - "payment.ServiceProvider", - on_delete=models.CASCADE, - ) - payment_verification = GenericRelation( - "payment.PaymentVerification", - content_type_field="payment_content_type", - object_id_field="payment_object_id", - related_query_name="payment_record", - ) - ticket_complaint_details = GenericRelation( - "grievance.TicketComplaintDetails", - content_type_field="payment_content_type", - object_id_field="payment_object_id", - related_query_name="payment_record", - ) - ticket_sensitive_details = GenericRelation( - "grievance.TicketSensitiveDetails", - content_type_field="payment_content_type", - object_id_field="payment_object_id", - related_query_name="payment_record", - ) - - @property - def unicef_id(self) -> str: - return self.ca_id - - def get_revert_mark_as_failed_status(self, delivered_quantity: Decimal) -> str: - return self.STATUS_SUCCESS - - -class ServiceProvider(TimeStampedUUIDModel): - business_area = models.ForeignKey("core.BusinessArea", on_delete=models.CASCADE) - ca_id = models.CharField(max_length=255, unique=True) - full_name = models.CharField(max_length=255, null=True) - short_name = models.CharField(max_length=100, null=True) - country = models.CharField(max_length=3) - vision_id = models.CharField(max_length=255, null=True) - is_migrated_to_payment_plan = models.BooleanField(default=False) - - def __str__(self) -> str: - return self.full_name or "" diff --git a/src/hct_mis_api/apps/payment/models/payment.py b/src/hct_mis_api/apps/payment/models/payment.py index 1b6e4874ae..ab890e285a 100644 --- a/src/hct_mis_api/apps/payment/models/payment.py +++ b/src/hct_mis_api/apps/payment/models/payment.py @@ -889,11 +889,11 @@ def available_payment_records( if payment_verification_plan: params &= Q( - Q(payment_verification__isnull=True) - | Q(payment_verification__payment_verification_plan=payment_verification_plan) + Q(payment_verifications__isnull=True) + | Q(payment_verifications__payment_verification_plan=payment_verification_plan) ) else: - params &= Q(payment_verification__isnull=True) + params &= Q(payment_verifications__isnull=True) payment_records = self.payment_items.select_related("head_of_household").filter(params).distinct() diff --git a/src/hct_mis_api/apps/payment/models/verification.py b/src/hct_mis_api/apps/payment/models/verification.py index 38dadc67d3..64aa5537e9 100644 --- a/src/hct_mis_api/apps/payment/models/verification.py +++ b/src/hct_mis_api/apps/payment/models/verification.py @@ -3,12 +3,10 @@ from typing import TYPE_CHECKING, Any, Optional from django.contrib.admin.options import get_content_type_for_model -from django.contrib.contenttypes.fields import GenericForeignKey -from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField from django.core.validators import MinValueValidator from django.db import models -from django.db.models import Count, JSONField, Q, UniqueConstraint, UUIDField +from django.db.models import Count, JSONField, Q from django.db.models.signals import post_delete, post_save from django.dispatch import receiver from django.utils import timezone @@ -24,8 +22,9 @@ UnicefIdentifiedModel, ) -if TYPE_CHECKING: - from hct_mis_api.apps.program.models import Program # pragma: no cover +if TYPE_CHECKING: # pragma: no cover + from hct_mis_api.apps.payment.models import PaymentPlan + from hct_mis_api.apps.program.models import Program logger = logging.getLogger(__name__) @@ -81,11 +80,6 @@ class PaymentVerificationPlan(TimeStampedUUIDModel, ConcurrencyModel, UnicefIden ) status = models.CharField(max_length=50, choices=STATUS_CHOICES, default=STATUS_PENDING, db_index=True) - # TODO TP DROP - payment_plan_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True) - payment_plan_object_id = UUIDField(null=True) - payment_plan_obj = GenericForeignKey("payment_plan_content_type", "payment_plan_object_id") - payment_plan = models.ForeignKey( "payment.PaymentPlan", on_delete=models.CASCADE, related_name="payment_verification_plans", null=True ) @@ -112,10 +106,6 @@ class PaymentVerificationPlan(TimeStampedUUIDModel, ConcurrencyModel, UnicefIden class Meta: ordering = ("created_at",) - # TODO TP DROP - indexes = [ - models.Index(fields=["payment_plan_content_type", "payment_plan_object_id"]), - ] @property def business_area(self) -> BusinessArea: @@ -173,7 +163,7 @@ def get_program(self) -> Optional["Program"]: return self.payment_plan.program_cycle.program -def build_summary(payment_plan: Optional[Any]) -> None: +def build_summary(payment_plan: Optional["PaymentPlan"]) -> None: if not payment_plan: return @@ -240,16 +230,10 @@ class PaymentVerification(TimeStampedUUIDModel, ConcurrencyModel, AdminUrlMixin) related_name="payment_record_verifications", ) - # TODO TP DROP - payment_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True) - payment_object_id = UUIDField(null=True) - payment_obj = GenericForeignKey("payment_content_type", "payment_object_id") - - payment = models.OneToOneField( + payment = models.ForeignKey( "payment.Payment", on_delete=models.CASCADE, - related_name="payment_verification", - null=True, + related_name="payment_verifications", ) status = models.CharField(max_length=50, choices=STATUS_CHOICES, default=STATUS_PENDING) @@ -262,17 +246,6 @@ class PaymentVerification(TimeStampedUUIDModel, ConcurrencyModel, AdminUrlMixin) ) sent_to_rapid_pro = models.BooleanField(default=False) - class Meta: - indexes = [ - models.Index(fields=["payment_content_type", "payment_object_id"]), - ] - constraints = [ - UniqueConstraint( - fields=["payment_content_type", "payment_object_id"], - name="payment_content_type_and_payment_id", - ) - ] - @property def is_manually_editable(self) -> bool: if self.payment_verification_plan.verification_channel != PaymentVerificationPlan.VERIFICATION_CHANNEL_MANUAL: @@ -312,23 +285,6 @@ class PaymentVerificationSummary(TimeStampedUUIDModel): null=True, ) - # TODO TP drop - payment_plan_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True) - payment_plan_object_id = UUIDField(null=True) - payment_plan_obj = GenericForeignKey("payment_plan_content_type", "payment_plan_object_id") - - class Meta: - # TODO TP DROP - indexes = [ - models.Index(fields=["payment_plan_content_type", "payment_plan_object_id"]), - ] - constraints = [ - UniqueConstraint( - fields=["payment_plan_content_type", "payment_plan_object_id"], - name="payment_plan_content_type_and_payment_plan_id", - ) - ] - def mark_as_active(self) -> None: self.status = self.STATUS_ACTIVE self.completion_date = None diff --git a/src/hct_mis_api/apps/payment/pdf/payment_plan_export_pdf_service.py b/src/hct_mis_api/apps/payment/pdf/payment_plan_export_pdf_service.py index ab5112cac8..c8e6bf2fc6 100644 --- a/src/hct_mis_api/apps/payment/pdf/payment_plan_export_pdf_service.py +++ b/src/hct_mis_api/apps/payment/pdf/payment_plan_export_pdf_service.py @@ -6,7 +6,7 @@ from django.urls import reverse from hct_mis_api.apps.core.utils import encode_id_base64 -from hct_mis_api.apps.payment.models import Approval, GenericPayment, PaymentPlan +from hct_mis_api.apps.payment.models import Approval, Payment, PaymentPlan from hct_mis_api.apps.utils.pdf_generator import generate_pdf_from_html if TYPE_CHECKING: @@ -75,12 +75,12 @@ def generate_pdf_summary(self) -> Any: release = approval_process.approvals.filter(type=Approval.FINANCE_RELEASE).first() reconciliation_qs = self.payment_plan.eligible_payments.aggregate( - pending=Count("id", filter=Q(status__in=GenericPayment.PENDING_STATUSES)), - pending_usd=Sum("entitlement_quantity_usd", filter=Q(status__in=GenericPayment.PENDING_STATUSES)), - pending_local=Sum("entitlement_quantity", filter=Q(status__in=GenericPayment.PENDING_STATUSES)), - reconciled=Count("id", filter=~Q(status__in=GenericPayment.PENDING_STATUSES)), - reconciled_usd=Sum("delivered_quantity_usd", filter=~Q(status__in=GenericPayment.PENDING_STATUSES)), - reconciled_local=Sum("delivered_quantity", filter=~Q(status__in=GenericPayment.PENDING_STATUSES)), + pending=Count("id", filter=Q(status__in=Payment.PENDING_STATUSES)), + pending_usd=Sum("entitlement_quantity_usd", filter=Q(status__in=Payment.PENDING_STATUSES)), + pending_local=Sum("entitlement_quantity", filter=Q(status__in=Payment.PENDING_STATUSES)), + reconciled=Count("id", filter=~Q(status__in=Payment.PENDING_STATUSES)), + reconciled_usd=Sum("delivered_quantity_usd", filter=~Q(status__in=Payment.PENDING_STATUSES)), + reconciled_local=Sum("delivered_quantity", filter=~Q(status__in=Payment.PENDING_STATUSES)), ) pdf_context_data = { diff --git a/src/hct_mis_api/apps/payment/schema.py b/src/hct_mis_api/apps/payment/schema.py index ade4033e12..03b3ea2977 100644 --- a/src/hct_mis_api/apps/payment/schema.py +++ b/src/hct_mis_api/apps/payment/schema.py @@ -84,7 +84,6 @@ DeliveryMechanismPerPaymentPlan, FinancialServiceProvider, FinancialServiceProviderXlsxTemplate, - GenericPayment, Payment, PaymentHouseholdSnapshot, PaymentPlan, @@ -366,10 +365,11 @@ def resolve_target_population(self, info: Any) -> TargetPopulation: return self.parent.target_population def resolve_full_name(self, info: Any) -> str: + # TODO: add to test this one return self.head_of_household.full_name if self.head_of_household else "" def resolve_verification(self, info: Any) -> Optional[Any]: - return getattr(self, "payment_verification", None) + return self.payment_verifications.first() def resolve_distribution_modality(self, info: Any) -> str: return self.parent.unicef_id @@ -654,21 +654,21 @@ def resolve_total_withdrawn_households_count(self, info: Any) -> graphene.Int: @staticmethod def resolve_reconciliation_summary(parent: PaymentPlan, info: Any) -> Dict[str, int]: return parent.eligible_payments.aggregate( - delivered_fully=Count("id", filter=Q(status=GenericPayment.STATUS_DISTRIBUTION_SUCCESS)), - delivered_partially=Count("id", filter=Q(status=GenericPayment.STATUS_DISTRIBUTION_PARTIAL)), - not_delivered=Count("id", filter=Q(status=GenericPayment.STATUS_NOT_DISTRIBUTED)), + delivered_fully=Count("id", filter=Q(status=Payment.STATUS_DISTRIBUTION_SUCCESS)), + delivered_partially=Count("id", filter=Q(status=Payment.STATUS_DISTRIBUTION_PARTIAL)), + not_delivered=Count("id", filter=Q(status=Payment.STATUS_NOT_DISTRIBUTED)), unsuccessful=Count( "id", filter=Q( status__in=[ - GenericPayment.STATUS_ERROR, - GenericPayment.STATUS_FORCE_FAILED, - GenericPayment.STATUS_MANUALLY_CANCELLED, + Payment.STATUS_ERROR, + Payment.STATUS_FORCE_FAILED, + Payment.STATUS_MANUALLY_CANCELLED, ] ), ), - pending=Count("id", filter=Q(status__in=GenericPayment.PENDING_STATUSES)), - reconciled=Count("id", filter=~Q(status__in=GenericPayment.PENDING_STATUSES)), + pending=Count("id", filter=Q(status__in=Payment.PENDING_STATUSES)), + reconciled=Count("id", filter=~Q(status__in=Payment.PENDING_STATUSES)), number_of_payments=Count("id"), ) @@ -852,7 +852,7 @@ def resolve_status(self, info: Any, **kwargs: Any) -> str: return self.status.replace(" ", "_").upper() def resolve_verification(self, info: Any, **kwargs: Any) -> Any: - return getattr(self, "payment_verification", None) + return self.payment_verifications.first() class PageInfoNode(graphene.ObjectType): @@ -1288,8 +1288,8 @@ def resolve_chart_payment(self, info: Any, business_area_slug: str, year: int, * year, business_area_slug, chart_filters_decoder(kwargs) ) payment_items_dict = payment_items_qs.aggregate( - successful=Count("id", filter=~Q(status=GenericPayment.STATUS_ERROR)), - unsuccessful=Count("id", filter=Q(status=GenericPayment.STATUS_ERROR)), + successful=Count("id", filter=~Q(status=Payment.STATUS_ERROR)), + unsuccessful=Count("id", filter=Q(status=Payment.STATUS_ERROR)), ) dataset = [ diff --git a/src/hct_mis_api/apps/payment/services/create_payment_verifications.py b/src/hct_mis_api/apps/payment/services/create_payment_verifications.py index 32c597ff56..4873b22a25 100644 --- a/src/hct_mis_api/apps/payment/services/create_payment_verifications.py +++ b/src/hct_mis_api/apps/payment/services/create_payment_verifications.py @@ -3,16 +3,14 @@ from django.utils import timezone from hct_mis_api.apps.payment.models import ( - PaymentRecord, + Payment, PaymentVerification, PaymentVerificationPlan, ) class CreatePaymentVerifications: - def __init__( - self, payment_verification_plan: PaymentVerificationPlan, payment_records: Iterable[PaymentRecord] - ) -> None: + def __init__(self, payment_verification_plan: PaymentVerificationPlan, payment_records: Iterable[Payment]) -> None: self.payment_verification_plan = payment_verification_plan self.payment_records = payment_records diff --git a/src/hct_mis_api/apps/payment/services/dashboard_service.py b/src/hct_mis_api/apps/payment/services/dashboard_service.py index 6fc194460c..e11d5939bc 100644 --- a/src/hct_mis_api/apps/payment/services/dashboard_service.py +++ b/src/hct_mis_api/apps/payment/services/dashboard_service.py @@ -27,18 +27,12 @@ def payment_verification_chart_query( status_choices_mapping = dict(PaymentVerification.STATUS_CHOICES) params = Q() - params &= Q(Q(payment__delivery_date__year=year) | Q(payment_record__delivery_date__year=year)) - params &= Q( - Q(payment__business_area__slug=business_area_slug) | Q(payment_record__business_area__slug=business_area_slug) - ) - params &= Q( - Q(payment__household__collect_type=collect_type) | Q(payment_record__household__collect_type=collect_type) - ) + params &= Q(payment__delivery_date__year=year) + params &= Q(payment__business_area__slug=business_area_slug) + params &= Q(payment__household__collect_type=collect_type) if program: - params &= Q( - Q(payment__parent__program_cycle__program__id=program) | Q(payment_record__parent__program__id=program) - ) + params &= Q(payment__parent__program_cycle__program__id=program) if administrative_area: inner_params = Q() @@ -46,10 +40,6 @@ def payment_verification_chart_query( Q(payment__household__admin_area__id=administrative_area) & Q(payment__household__admin_area__area_type__area_level=2) ) - inner_params |= Q( - Q(payment_record__household__admin_area__id=administrative_area) - & Q(payment_record__household__admin_area__area_type__area_level=2) - ) params &= inner_params payment_verifications = PaymentVerification.objects.filter(params).distinct() @@ -69,9 +59,7 @@ def payment_verification_chart_query( for (dataset_percentage_value, status) in zip(dataset_percentage, status_choices_mapping.values()) ] - samples_count = payment_verifications.aggregate(payments_count=Count("payment") + Count("payment_record"))[ - "payments_count" - ] + samples_count = payment_verifications.aggregate(payments_count=Count("payment"))["payments_count"] all_payment_records_for_created_verifications = ( Payment.objects.filter(excluded=False, conflicted=False) .filter( @@ -90,12 +78,7 @@ def payment_verification_chart_query( ) households_number = ( - Household.objects.filter( - Q(pk__in=payment_verifications.values("payment__household")) - | Q(pk__in=payment_verifications.values("payment_record__household")) - ) - .distinct() - .count() + Household.objects.filter(Q(pk__in=payment_verifications.values("payment__household"))).distinct().count() ) return { @@ -117,20 +100,17 @@ def total_cash_transferred_by_administrative_area_table_query( return ( Area.objects.filter( - Q(household__paymentrecord__id__in=payment_items_ids) | Q(household__payment__id__in=payment_items_ids), + household__payment__id__in=payment_items_ids, area_type__area_level=2, ) .distinct() .annotate( - total_transferred_payment_records=Coalesce( - Sum("household__paymentrecord__delivered_quantity_usd", output_field=DecimalField()), Decimal(0.0) - ), total_transferred_payments=Coalesce( Sum("household__payment__delivered_quantity_usd", output_field=DecimalField()), Decimal(0.0) ), ) .annotate( num_households=Count("household", distinct=True), - total_transferred=F("total_transferred_payments") + F("total_transferred_payment_records"), + total_transferred=F("total_transferred_payments"), ) ) diff --git a/src/hct_mis_api/apps/payment/services/mark_as_failed.py b/src/hct_mis_api/apps/payment/services/mark_as_failed.py index 6bd43600d6..6e4fbf98de 100644 --- a/src/hct_mis_api/apps/payment/services/mark_as_failed.py +++ b/src/hct_mis_api/apps/payment/services/mark_as_failed.py @@ -1,26 +1,24 @@ import datetime from decimal import Decimal -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING from django.db.models import Sum -from hct_mis_api.apps.payment.models import Payment, PaymentRecord +from hct_mis_api.apps.payment.models import Payment from hct_mis_api.apps.payment.utils import get_quantity_in_usd if TYPE_CHECKING: from hct_mis_api.apps.household.models import Household -def mark_as_failed(payment_item: Union[PaymentRecord, Payment]) -> None: +def mark_as_failed(payment_item: Payment) -> None: payment_item.mark_as_failed() payment_item.save() recalculate_cash_received(payment_item.household) -def revert_mark_as_failed( - payment_item: Union[PaymentRecord, Payment], delivered_quantity: Decimal, delivery_date: datetime.datetime -) -> None: +def revert_mark_as_failed(payment_item: Payment, delivered_quantity: Decimal, delivery_date: datetime.datetime) -> None: payment_item.revert_mark_as_failed(delivered_quantity, delivery_date) payment_item.delivered_quantity_usd = get_quantity_in_usd( amount=delivered_quantity, diff --git a/src/hct_mis_api/apps/payment/tasks/CheckRapidProVerificationTask.py b/src/hct_mis_api/apps/payment/tasks/CheckRapidProVerificationTask.py index a30ce0df0e..1bf6967d0c 100644 --- a/src/hct_mis_api/apps/payment/tasks/CheckRapidProVerificationTask.py +++ b/src/hct_mis_api/apps/payment/tasks/CheckRapidProVerificationTask.py @@ -3,7 +3,7 @@ from hct_mis_api.apps.core.services.rapid_pro.api import RapidProAPI from hct_mis_api.apps.payment.models import ( - PaymentRecord, + Payment, PaymentVerification, PaymentVerificationPlan, ) @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) -def does_payment_record_have_right_hoh_phone_number(record: PaymentRecord) -> bool: +def does_payment_record_have_right_hoh_phone_number(record: Payment) -> bool: hoh = record.head_of_household if not hoh: logging.warning("Payment record has no head of household") diff --git a/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_per_fsp_import_service.py b/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_per_fsp_import_service.py index c7d0306249..b34460b5c1 100644 --- a/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_per_fsp_import_service.py +++ b/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_per_fsp_import_service.py @@ -347,9 +347,7 @@ def _import_row(self, row: Row, exchange_rate: float) -> None: self.payments_to_save.append(payment) # update PaymentVerification status - if hasattr(payment, "payment_verification"): - payment_verification = payment.payment_verification - + if payment_verification := payment.payment_verifications.first(): if payment_verification.status != PaymentVerification.STATUS_PENDING: if payment_verification.received_amount == delivered_quantity: pv_status = PaymentVerification.STATUS_RECEIVED diff --git a/src/hct_mis_api/apps/reporting/services/generate_report_service.py b/src/hct_mis_api/apps/reporting/services/generate_report_service.py index 0505ba7551..8741e869be 100644 --- a/src/hct_mis_api/apps/reporting/services/generate_report_service.py +++ b/src/hct_mis_api/apps/reporting/services/generate_report_service.py @@ -5,10 +5,7 @@ from typing import TYPE_CHECKING, List, Tuple from django.conf import settings -from django.contrib.postgres.aggregates.general import ArrayAgg -from django.contrib.postgres.fields import ArrayField from django.core.files import File -from django.db import models from django.db.models import ( Case, Count, @@ -24,7 +21,7 @@ Value, When, ) -from django.db.models.functions import Coalesce, Concat, Greatest, Least +from django.db.models.functions import Coalesce, Greatest, Least from django.template.loader import render_to_string import openpyxl @@ -46,9 +43,8 @@ ) from hct_mis_api.apps.payment.delivery_mechanisms import DeliveryMechanismChoices from hct_mis_api.apps.payment.models import ( - CashPlan, + Payment, PaymentPlan, - PaymentRecord, PaymentVerification, PaymentVerificationPlan, ) @@ -78,7 +74,7 @@ def get_individuals(report: Report) -> QuerySet[Individual]: return Individual.objects.filter(**filter_vars) @classmethod - def format_individual_row(self, individual: Individual) -> tuple: + def format_individual_row(cls, individual: Individual) -> tuple: return ( individual.household.id, individual.household.country_origin.name if individual.household.country_origin else "", @@ -97,7 +93,7 @@ def format_individual_row(self, individual: Individual) -> tuple: individual.selfcare_disability, individual.pregnant, individual.relationship, - self._to_values_list(individual.households_and_roles.all(), "role"), + cls._to_values_list(individual.households_and_roles.all(), "role"), dict(WORK_STATUS_CHOICE).get(individual.work_status, ""), individual.sanction_list_possible_match, individual.deduplication_batch_status, @@ -112,8 +108,8 @@ def format_individual_row(self, individual: Individual) -> tuple: if individual.deduplication_golden_record_results else "" ), - self._format_date(individual.first_registration_date), - self._format_date(individual.last_registration_date), + cls._format_date(individual.first_registration_date), + cls._format_date(individual.last_registration_date), ) @staticmethod @@ -131,7 +127,7 @@ def get_households(report: Report) -> QuerySet: return Household.objects.filter(**filter_vars) @classmethod - def format_household_row(self, household: Household) -> tuple: + def format_household_row(cls, household: Household) -> tuple: row = [ household.id, household.country_origin.name if household.country_origin else "", @@ -164,8 +160,8 @@ def format_household_row(self, household: Household) -> tuple: household.male_age_group_18_59_disabled_count, household.male_age_group_60_count, household.male_age_group_60_disabled_count, - self._format_date(household.first_registration_date), - self._format_date(household.last_registration_date), + cls._format_date(household.first_registration_date), + cls._format_date(household.last_registration_date), household.org_name_enumerator, ] for program in household.programs.all(): @@ -175,17 +171,19 @@ def format_household_row(self, household: Household) -> tuple: @staticmethod def get_cash_plan_verifications(report: Report) -> QuerySet: pp_business_area_ids = list( - CashPlan.objects.filter(business_area=report.business_area).values_list("id", flat=True) + PaymentPlan.objects.filter(business_area=report.business_area).values_list("id", flat=True) ) filter_vars = { - "payment_plan_object_id__in": pp_business_area_ids, + "payment_plan_id__in": pp_business_area_ids, "completion_date__isnull": False, "completion_date__gte": report.date_from, "completion_date__lte": report.date_to, } if report.program: - pp_program_ids = list(CashPlan.objects.filter(program=report.program).values_list("id", flat=True)) - filter_vars["payment_plan_object_id__in"] = pp_program_ids + pp_program_ids = list( + PaymentPlan.objects.filter(program_cycle__program=report.program).values_list("id", flat=True) + ) + filter_vars["payment_plan_id__in"] = pp_program_ids return PaymentVerificationPlan.objects.filter(**filter_vars) @staticmethod @@ -225,14 +223,14 @@ def get_payments(report: Report) -> QuerySet: filter_vars = { "business_area": report.business_area, "delivery_date__date__range": (report.date_from, report.date_to), - "parent__program": report.program, + "parent__program_cycle__program": report.program, } if report.admin_area.all().exists(): filter_vars["household__admin_area__in"] = report.admin_area.all() - return PaymentRecord.objects.filter(**filter_vars) + return Payment.objects.filter(**filter_vars) @classmethod - def format_payment_row(cls, payment: PaymentRecord) -> tuple: + def format_payment_row(cls, payment: Payment) -> tuple: cash_or_voucher = "" if payment.delivery_type: if payment.delivery_type in [ @@ -274,7 +272,9 @@ def get_payment_verifications(report: Report) -> QuerySet: "payment_verification_plan__completion_date__date__range": (report.date_from, report.date_to), } if report.program: - pp_program_ids = list(PaymentPlan.objects.filter(program=report.program).values_list("id", flat=True)) + pp_program_ids = list( + PaymentPlan.objects.filter(program_cycle__program=report.program).values_list("id", flat=True) + ) filter_vars["payment_verification_plan__payment_plan_id__in"] = pp_program_ids return PaymentVerification.objects.filter(**filter_vars) @@ -315,44 +315,6 @@ def format_payment_plan_row(cls, payment_plan: PaymentPlan) -> tuple: cls._format_date(payment_plan.dispersion_end_date), ) - @staticmethod - def get_cash_plans(report: Report) -> QuerySet: - filter_vars = { - "business_area": report.business_area, - "end_date__gte": report.date_from, - "end_date__lte": report.date_to, - } - if report.program: - filter_vars["program"] = report.program - return CashPlan.objects.filter(**filter_vars) - - @classmethod - def format_cash_plan_row(cls, cash_plan: CashPlan) -> tuple: - return ( - cash_plan.ca_id, - cash_plan.name, - cls._format_date(cash_plan.start_date), - cls._format_date(cash_plan.end_date), - cash_plan.program.name, - cash_plan.funds_commitment, - cash_plan.assistance_measurement, - cash_plan.assistance_through, - cash_plan.delivery_type, - cls._format_date(cash_plan.dispersion_date), - cash_plan.down_payment, - cash_plan.total_delivered_quantity, - cash_plan.total_undelivered_quantity, - cash_plan.total_entitled_quantity, - cash_plan.total_entitled_quantity_revised, - cash_plan.total_persons_covered, - cash_plan.total_persons_covered_revised, - cash_plan.status, - cls._format_date(cash_plan.status_date), - cash_plan.vision_id, - cash_plan.validation_alerts_count, - # cash_plan.verification_status, - ) - @staticmethod def get_payments_for_individuals(report: Report) -> QuerySet: if isinstance(report.date_to, str): @@ -362,54 +324,36 @@ def get_payments_for_individuals(report: Report) -> QuerySet: date_to_time += timedelta(days=1) filter_q = Q( # Q(household__program=report.program) & # TODO Uncomment after add program to household - Q( - Q(household__paymentrecord__business_area=report.business_area) - | Q(household__payment__business_area=report.business_area) - ) - & Q( - Q(household__paymentrecord__delivery_date__gte=report.date_from) - | Q(household__payment__delivery_date__gte=report.date_from) - ) - & Q( - Q(household__paymentrecord__delivery_date__lt=date_to_time) - | Q(household__payment__delivery_date__lt=date_to_time) - ) + Q(household__payment__business_area=report.business_area) + & Q(household__payment__delivery_date__gte=report.date_from) + & Q(household__payment__delivery_date__lt=date_to_time) ) if report.admin_area.all().exists(): filter_q &= Q(household__admin_area__in=report.admin_area.all()) if report.program: - filter_q &= Q( - Q(household__paymentrecord__parent__program=report.program) - | Q(household__payment__parent__program=report.program) - ) + filter_q &= Q(household__payment__parent__program_cycle__program=report.program) return ( Individual.objects.filter(filter_q) - .annotate(first_delivery_date_paymentrecord=Min("household__paymentrecord__delivery_date")) .annotate(first_delivery_date_payment=Min("household__payment__delivery_date")) .annotate( first_delivery_date=Least( - F("first_delivery_date_paymentrecord"), F("first_delivery_date_payment"), + Value("2099-12-31"), output_field=DateTimeField(), ) ) - .annotate(last_delivery_date_paymentrecord=Max("household__paymentrecord__delivery_date")) .annotate(last_delivery_date_payment=Max("household__payment__delivery_date")) .annotate( last_delivery_date=Greatest( - F("last_delivery_date_paymentrecord"), F("last_delivery_date_payment"), + Value("1990-01-01"), output_field=DateTimeField(), ) ) .annotate( payments_made=Count( Case( - When( - Q(household__paymentrecord__delivered_quantity__gte=0), - then=F("household__paymentrecord__id"), - ), When( Q(household__payment__delivered_quantity__gte=0), then=F("household__payment__id"), @@ -418,27 +362,16 @@ def get_payments_for_individuals(report: Report) -> QuerySet: ) ) ) - .annotate( - payment_currency=ArrayAgg( - Concat( - "household__paymentrecord__currency", - Value(" "), - "household__payment__currency", - output_field=ArrayField(models.CharField()), - ) - ) - ) + .annotate(payment_currency=F("household__payment__currency")) .annotate( total_delivered_quantity_local=Coalesce( - Sum("household__paymentrecord__delivered_quantity"), Value(0), output_field=DecimalField() + Sum("household__payment__delivered_quantity"), Value(0), output_field=DecimalField() ) - + Coalesce(Sum("household__payment__delivered_quantity"), Value(0), output_field=DecimalField()) ) .annotate( total_delivered_quantity_usd=Coalesce( - Sum("household__paymentrecord__delivered_quantity_usd"), Value(0), output_field=DecimalField() + Sum("household__payment__delivered_quantity_usd"), Value(0), output_field=DecimalField() ) - + Coalesce(Sum("household__payment__delivered_quantity_usd"), Value(0), output_field=DecimalField()) ) .order_by("household__id") .distinct() @@ -662,30 +595,6 @@ class GenerateReportService: "dispersion start date", "dispersion end date", ), - Report.CASH_PLAN: ( - "cash plan ID", # ANT-21-CSH-00001 - "cash plan name", - "start date", - "end date", - "programme", - "funds commitment", # 234567 - "assistance measurement", # Euro - "assistance through", # Cairo Amman Bank - "delivery type", # DEPOSIT_TO_CARD - "dispersion date", - "down payment", - "total delivered quantity", # 220,00 - "total undelivered quantity", # 10,00 - "total entitled quantity", # 230,00 - "total entitled quantity revised", # 230,00 - "total persons covered", # 12 - "total persons covered revised", # 12 - "status", # DISTRIBUTION_COMPLETED_WITH_ERRORS - "status date", - "VISION ID", # 2345253423 - "validation alerts count", # 2 - # "cash plan verification status", # FINISHED - ), Report.INDIVIDUALS_AND_PAYMENT: ( "household id", "country of origin", @@ -747,7 +656,6 @@ class GenerateReportService: Report.PAYMENTS: ("Delivery Date From", "Delivery Date To"), Report.PAYMENT_PLAN: ("Dispersion Start Date", "Dispersion End Date"), Report.INDIVIDUALS_AND_PAYMENT: ("Delivery Date From", "Delivery Date To"), - Report.CASH_PLAN: ("End Date From", "End Date To"), Report.GRIEVANCES: ("End Date From", "End Date To"), } ROW_CONTENT_METHODS = { @@ -772,10 +680,6 @@ class GenerateReportService: GenerateReportContentHelpers.get_payment_plans, GenerateReportContentHelpers.format_payment_plan_row, ), - Report.CASH_PLAN: ( - GenerateReportContentHelpers.get_cash_plans, - GenerateReportContentHelpers.format_cash_plan_row, - ), Report.INDIVIDUALS_AND_PAYMENT: ( GenerateReportContentHelpers.get_payments_for_individuals, GenerateReportContentHelpers.format_payments_for_individuals_row, diff --git a/src/hct_mis_api/apps/utils/models.py b/src/hct_mis_api/apps/utils/models.py index b9d42133b0..981aa3390d 100644 --- a/src/hct_mis_api/apps/utils/models.py +++ b/src/hct_mis_api/apps/utils/models.py @@ -636,7 +636,7 @@ def purge(cls) -> None: class InternalDataFieldModel(models.Model): - internal_data = models.JSONField(default=dict) + internal_data = models.JSONField(default=dict, blank=True) class Meta: abstract = True diff --git a/src/hct_mis_api/migrations_script/main.py b/src/hct_mis_api/migrations_script/main.py index be92686ad1..6a2c7c9038 100644 --- a/src/hct_mis_api/migrations_script/main.py +++ b/src/hct_mis_api/migrations_script/main.py @@ -50,6 +50,7 @@ def apply_migrations(): ("program", "0002_migration"), ("household", "0003_migration"), ("household", "0004_migration"), + ("household", "0005_migration"), ("grievance", "0004_migration"), ("payment", "0002_migration"), ("payment", "0003_migration"), @@ -57,6 +58,8 @@ def apply_migrations(): ("payment", "0005_migration"), ("payment", "0006_migration"), ("payment", "0007_migration"), + ("payment", "0008_migration"), + ("payment", "0009_migration"), ("aurora", "0003_migration"), ] fake_migrations(excluded_migrations) diff --git a/src/hct_mis_api/one_time_scripts/migrate_cash_assist_models.py b/src/hct_mis_api/one_time_scripts/migrate_cash_assist_models.py deleted file mode 100644 index 430d14baf6..0000000000 --- a/src/hct_mis_api/one_time_scripts/migrate_cash_assist_models.py +++ /dev/null @@ -1,309 +0,0 @@ -from decimal import Decimal - -from django.contrib.contenttypes.models import ContentType -from django.db import transaction -from django.db.models import Sum -from django.db.models.functions import Coalesce - -from hct_mis_api.apps.grievance.models import ( - TicketComplaintDetails, - TicketSensitiveDetails, -) -from hct_mis_api.apps.payment.models import ( - CashPlan, - DeliveryMechanism, - DeliveryMechanismPerPaymentPlan, - FinancialServiceProvider, - Payment, - PaymentPlan, - PaymentRecord, - PaymentVerification, - PaymentVerificationPlan, - PaymentVerificationSummary, - ServiceProvider, -) -from hct_mis_api.apps.payment.services.payment_household_snapshot_service import ( - create_payment_plan_snapshot_data, -) -from hct_mis_api.apps.targeting.models import TargetPopulation - - -def get_status(status: str) -> str: - mapping = {"Transaction Successful": "Distribution Successful"} - return mapping.get(status, status) - - -def migrate_cash_plan_to_payment_plan() -> None: - content_type_for_payment_plan = ContentType.objects.get_for_model(PaymentPlan) - content_type_for_cash_plan = ContentType.objects.get_for_model(CashPlan) - content_type_for_payment_record = ContentType.objects.get_for_model(PaymentRecord) - - print("**Migrating Cash Plan to Payment Plan**") - delivery_type_to_obj = {obj.name: obj for obj in DeliveryMechanism.objects.all()} - - print("Creating FinancialServiceProviders") - for sp in ServiceProvider.objects.filter(is_migrated_to_payment_plan=False): - print(f"\nProcessing Service Provider {sp}") - if not sp.cash_plans.exists(): - print(f"Service provider {sp} has no cash plans") - continue - - if FinancialServiceProvider.objects.filter(vision_vendor_number=sp.vision_id).exists(): - print(f"FinancialServiceProvider with vision_id {sp.vision_id} already exists") - continue - - if not sp.vision_id: - raise ValueError(f"Service Provider {sp} does not have vision_id") - - delivery_mechanisms = set(sp.cash_plans.all().values_list("delivery_type", flat=True)) - - fsp = FinancialServiceProvider.objects.create( - name=sp.full_name, - vision_vendor_number=sp.vision_id, - communication_channel="API", - internal_data={ - "is_cash_assist": True, - "business_area": sp.business_area.slug, - "country": sp.country, - "ca_id": sp.ca_id, - "short_name": sp.short_name, - }, - ) - fsp.delivery_mechanisms.set([delivery_type_to_obj[dt] for dt in delivery_mechanisms]) - sp.is_migrated_to_payment_plan = True - sp.save(update_fields=["is_migrated_to_payment_plan"]) - - fsp_vision_vendor_number_to_obj = {obj.vision_vendor_number: obj for obj in FinancialServiceProvider.objects.all()} - - dm_cash = delivery_type_to_obj["Cash"] - - cash_plans = CashPlan.objects.filter(is_migrated_to_payment_plan=False) - print(f"Total Cash Plans to migrate: {cash_plans.count()}") - cp_count = cash_plans.count() - cp_i = 0 - for cp in cash_plans.iterator(chunk_size=50): - if cp_i % 50 == 0: - print(f"Processing cash plan {cp_i}/{cp_count}") - cp_i += 1 - with transaction.atomic(): - if not cp.payment_items.exists(): - continue - - # get target populations from payment records - target_populations = cp.payment_items.values_list("target_population", flat=True).distinct() - tp_counter = 0 - # for each target population create a payment plan within tp.payment_cycle - for tp_id in target_populations: - tp = TargetPopulation.objects.get(id=tp_id) - payment_records = cp.payment_items.filter(target_population=tp) - first_record = payment_records.first() - if first_record.delivery_type: - delivery_mechanism = delivery_type_to_obj[first_record.delivery_type.name] - else: - delivery_mechanism = dm_cash - currency = first_record.currency - - # create payment plan - pp = PaymentPlan.objects.create( - status="FINISHED", - name=tp.name, - business_area_id=tp.business_area.id, - created_by_id=tp.created_by.id, - target_population_id=tp.id, - program_cycle_id=tp.program_cycle.id, - currency=currency, - dispersion_start_date=cp.start_date or tp.program_cycle.start_date, - dispersion_end_date=cp.dispersion_date or tp.program_cycle.end_date, - start_date=cp.start_date or tp.program_cycle.start_date, - end_date=cp.end_date or tp.program_cycle.end_date, - status_date=cp.status_date, - exchange_rate=cp.exchange_rate, - total_entitled_quantity=cp.total_entitled_quantity, - total_entitled_quantity_usd=cp.total_entitled_quantity_usd, - total_entitled_quantity_revised=cp.total_entitled_quantity_revised, - total_entitled_quantity_revised_usd=cp.total_entitled_quantity_revised_usd, - total_delivered_quantity=cp.total_delivered_quantity, - total_delivered_quantity_usd=cp.total_delivered_quantity_usd, - total_undelivered_quantity=cp.total_undelivered_quantity, - total_undelivered_quantity_usd=cp.total_undelivered_quantity_usd, - is_cash_assist=True, - internal_data={ - "name": cp.name, - "ca_hash_id": str(cp.ca_hash_id), - "distribution_level": cp.distribution_level, - "coverage_duration": cp.coverage_duration, - "coverage_unit": cp.coverage_unit, - "comments": cp.comments, - "assistance_measurement": cp.assistance_measurement, - "assistance_through": cp.assistance_through, - "vision_id": cp.vision_id, - "funds_commitment": cp.funds_commitment, - "down_payment": cp.down_payment, - "validation_alerts_count": cp.validation_alerts_count, - "total_persons_covered": cp.total_persons_covered, - "total_persons_covered_revised": cp.total_persons_covered_revised, - }, - ) - pp.created_at = cp.created_at - pp.unicef_id = cp.ca_id - pp.save(update_fields=["unicef_id", "created_at"]) - pp.update_population_count_fields() - - financial_service_provider = fsp_vision_vendor_number_to_obj.get(cp.service_provider.vision_id) - if not financial_service_provider: - raise ValueError( - f"FinancialServiceProvider not found for vision_id: {first_record.service_provider.vision_id}" - f"Cash Plan: {cp}" - f"Record: {first_record}" - f"Service Provider: {first_record.service_provider}" - ) - - dmppp = DeliveryMechanismPerPaymentPlan.objects.create( - payment_plan_id=pp.id, - delivery_mechanism_id=delivery_mechanism.id, - sent_date=cp.status_date, - delivery_mechanism_order=1, - created_by_id=tp.created_by.id, - financial_service_provider_id=financial_service_provider.id, - ) - dmppp.created_at = tp.created_at - dmppp.save(update_fields=["created_at"]) - - payment_verification_summary = PaymentVerificationSummary.objects.filter( - payment_plan_content_type_id=content_type_for_cash_plan.pk, payment_plan_object_id=cp.pk - ).first() - if payment_verification_summary: - if tp_counter > 0: - # create a copy of the summary for each target population - payment_verification_summary.pk = None - payment_verification_summary.payment_plan_content_type_id = content_type_for_payment_plan.id - payment_verification_summary.payment_plan_object_id = pp.id - payment_verification_summary.payment_plan = pp - payment_verification_summary.save() - else: - payment_verification_summary.payment_plan = pp - payment_verification_summary.save() - - payment_verification_plan = PaymentVerificationPlan.objects.filter( - payment_plan_content_type_id=content_type_for_cash_plan.pk, payment_plan_object_id=cp.pk - ).first() - if payment_verification_plan: - if tp_counter > 0: - # create a copy of the summary for each target population - _unicef_id = payment_verification_plan.unicef_id - payment_verification_plan.pk = None - payment_verification_plan.payment_plan_content_type_id = content_type_for_payment_plan.id - payment_verification_plan.payment_plan_object_id = pp.id - payment_verification_plan.payment_plan = pp - payment_verification_plan.save() - payment_verification_plan.unicef_id = _unicef_id - payment_verification_plan.save(update_fields=["unicef_id"]) - - else: - payment_verification_plan.payment_plan = pp - payment_verification_plan.save() - - with transaction.atomic(): - for record in cp.payment_items.filter(target_population=tp).prefetch_related("service_provider"): - financial_service_provider = fsp_vision_vendor_number_to_obj.get( - record.service_provider.vision_id - ) - payment = Payment.objects.create( - parent_id=pp.id, - business_area_id=pp.business_area.id, - status=get_status(record.status), - status_date=record.status_date, - household_id=record.household_id, - head_of_household_id=record.head_of_household_id or record.household.head_of_household_id, - collector_id=record.head_of_household_id or record.household.head_of_household_id, - delivery_type_id=record.delivery_type_id, - currency=record.currency, - entitlement_quantity=record.entitlement_quantity, - entitlement_quantity_usd=record.entitlement_quantity_usd, - delivered_quantity=record.delivered_quantity, - delivered_quantity_usd=record.delivered_quantity_usd, - delivery_date=record.delivery_date, - transaction_reference_id=record.transaction_reference_id, - transaction_status_blockchain_link=record.transaction_status_blockchain_link, - financial_service_provider=financial_service_provider, - program_id=tp.program_cycle.program_id, - is_cash_assist=True, - internal_data={ - "ca_hash_id": str(record.ca_hash_id), - "full_name": record.full_name, - "total_persons_covered": record.total_persons_covered, - "distribution_modality": record.distribution_modality, - "target_population_cash_assist_id": record.target_population_cash_assist_id, - "target_population": str(record.target_population_id), - "entitlement_card_number": record.entitlement_card_number, - "entitlement_card_status": record.entitlement_card_status, - "entitlement_card_issue_date": str(record.entitlement_card_issue_date), - "vision_id": record.vision_id, - "registration_ca_id": record.registration_ca_id, - "service_provider": str(record.service_provider_id), - }, - ) - payment.unicef_id = record.ca_id - payment.save(update_fields=["unicef_id"]) - - payment_record_verification = PaymentVerification.objects.filter( - payment_content_type_id=content_type_for_payment_record.pk, payment_object_id=record.pk - ).first() - if payment_record_verification: - payment_record_verification.payment_verification_plan = payment_verification_plan - payment_record_verification.payment = payment - payment_record_verification.save() - - ticket_complaint_details = TicketComplaintDetails.objects.filter( - payment_content_type_id=content_type_for_payment_record.pk, payment_object_id=record.pk - ).first() - if ticket_complaint_details: - ticket_complaint_details.payment = payment - ticket_complaint_details.save() - - ticket_sensitive_details = TicketSensitiveDetails.objects.filter( - payment_content_type_id=content_type_for_payment_record.pk, payment_object_id=record.pk - ).first() - if ticket_sensitive_details: - ticket_sensitive_details.payment = payment - ticket_sensitive_details.save() - - create_payment_plan_snapshot_data(pp) - - pp.update_population_count_fields() - - payments = pp.eligible_payments.aggregate( - total_entitled_quantity=Coalesce(Sum("entitlement_quantity"), Decimal(0.0)), - total_entitled_quantity_usd=Coalesce(Sum("entitlement_quantity_usd"), Decimal(0.0)), - total_delivered_quantity=Coalesce(Sum("delivered_quantity"), Decimal(0.0)), - total_delivered_quantity_usd=Coalesce(Sum("delivered_quantity_usd"), Decimal(0.0)), - ) - - pp.total_entitled_quantity = payments.get("total_entitled_quantity", 0.00) - pp.total_entitled_quantity_usd = payments.get("total_entitled_quantity_usd", 0.00) - pp.total_delivered_quantity = payments.get("total_delivered_quantity", 0.00) - pp.total_delivered_quantity_usd = payments.get("total_delivered_quantity_usd", 0.00) - - pp.total_undelivered_quantity = pp.total_entitled_quantity - pp.total_delivered_quantity - pp.total_undelivered_quantity_usd = pp.total_entitled_quantity_usd - pp.total_delivered_quantity_usd - - pp.save( - update_fields=[ - "total_entitled_quantity", - "total_entitled_quantity_usd", - "total_delivered_quantity", - "total_delivered_quantity_usd", - "total_undelivered_quantity", - "total_undelivered_quantity_usd", - ] - ) - - tp_counter += 1 - - cp.is_migrated_to_payment_plan = True - cp.save(update_fields=["is_migrated_to_payment_plan"]) - - -def migrate_cash_assist_models() -> None: - print("***Migrating Cash Assist models to Payment models***") - migrate_cash_plan_to_payment_plan() diff --git a/src/hct_mis_api/one_time_scripts/migrate_data_to_representations.py b/src/hct_mis_api/one_time_scripts/migrate_data_to_representations.py index 84eb814546..5d247a078c 100644 --- a/src/hct_mis_api/one_time_scripts/migrate_data_to_representations.py +++ b/src/hct_mis_api/one_time_scripts/migrate_data_to_representations.py @@ -23,7 +23,7 @@ IndividualIdentity, IndividualRoleInHousehold, ) -from hct_mis_api.apps.payment.models import Payment, PaymentRecord +from hct_mis_api.apps.payment.models import Payment from hct_mis_api.apps.program.models import Program from hct_mis_api.apps.registration_data.models import RegistrationDataImport from hct_mis_api.apps.targeting.models import HouseholdSelection, TargetPopulation @@ -598,8 +598,6 @@ def adjust_payment_objects(business_area: Optional[BusinessArea] = None) -> None for business_area in business_areas: logger.info(f"Adjusting payments for business area {business_area.name}") adjust_payments(business_area) # type: ignore - logger.info(f"Adjusting payment records for business area {business_area.name}") - adjust_payment_records(business_area) # type: ignore def adjust_payments(business_area: BusinessArea) -> None: @@ -654,46 +652,6 @@ def adjust_payments(business_area: BusinessArea) -> None: del payment_updates -def adjust_payment_records(business_area: BusinessArea) -> None: - """ - Adjust PaymentRecord individuals and households to their representations. - PaymentRecord is already related to program through TargetPopulation. - """ - payment_records_ids = list( - PaymentRecord.objects.filter( - target_population__program__business_area=business_area, household__is_original=True - ).values_list("pk", flat=True) - ) - payment_records_count = len(payment_records_ids) - for batch_start in range(0, payment_records_count, BATCH_SIZE): - batch_end = batch_start + BATCH_SIZE - logger.info(f"Adjusting payment records {batch_start} - {batch_end}/{payment_records_count}") - payment_record_updates = [] - - payment_records_batch = PaymentRecord.objects.filter(id__in=payment_records_ids[batch_start:batch_end]) - for payment_record in payment_records_batch: - payment_record_program = payment_record.target_population.program - if payment_record.head_of_household: - representation_head_of_household = get_individual_representation_per_program_by_old_individual_id( - program=payment_record_program, - old_individual_id=payment_record.head_of_household_id, - ) - else: - representation_head_of_household = None - representation_household = get_household_representation_per_program_by_old_household_id( - program=payment_record_program, - old_household_id=payment_record.household_id, - ) - payment_record.refresh_from_db() - if representation_household: - payment_record.head_of_household = representation_head_of_household - payment_record.household = representation_household - payment_record_updates.append(payment_record) - - PaymentRecord.objects.bulk_update(payment_record_updates, fields=["head_of_household_id", "household_id"]) - del payment_record_updates - - def handle_rdis(rdis: QuerySet, program: Program, hhs_to_ignore: Optional[QuerySet] = None) -> None: rdis_count = rdis.count() for i, rdi in enumerate(rdis): diff --git a/tests/selenium/filters/test_filters.py b/tests/selenium/filters/test_filters.py index 8ba36abb3c..8b78fb29ff 100644 --- a/tests/selenium/filters/test_filters.py +++ b/tests/selenium/filters/test_filters.py @@ -17,7 +17,7 @@ PaymentVerificationPlanFactory, PaymentVerificationSummaryFactory, ) -from hct_mis_api.apps.payment.models import GenericPayment, PaymentPlan +from hct_mis_api.apps.payment.models import Payment, PaymentPlan from hct_mis_api.apps.payment.models import PaymentVerification as PV from hct_mis_api.apps.payment.models import PaymentVerificationPlan from hct_mis_api.apps.program.fixtures import ProgramFactory @@ -195,7 +195,7 @@ def payment_verification_creator( entitlement_quantity=21.36, delivered_quantity=21.36, currency="PLN", - status=GenericPayment.STATUS_DISTRIBUTION_SUCCESS, + status=Payment.STATUS_DISTRIBUTION_SUCCESS, ) pv_summary = PaymentVerificationSummaryFactory(payment_plan=payment_plan) pv_summary.activation_date = datetime.now() - relativedelta(months=1) diff --git a/tests/selenium/payment_verification/test_payment_verification.py b/tests/selenium/payment_verification/test_payment_verification.py index 31505e9db8..ba88b48bc2 100644 --- a/tests/selenium/payment_verification/test_payment_verification.py +++ b/tests/selenium/payment_verification/test_payment_verification.py @@ -20,7 +20,7 @@ PaymentVerificationPlanFactory, PaymentVerificationSummaryFactory, ) -from hct_mis_api.apps.payment.models import GenericPayment, Payment, PaymentPlan +from hct_mis_api.apps.payment.models import Payment, PaymentPlan from hct_mis_api.apps.payment.models import PaymentVerification as PV from hct_mis_api.apps.payment.models import PaymentVerificationPlan from hct_mis_api.apps.program.fixtures import ProgramFactory @@ -131,7 +131,7 @@ def payment_verification_multiple_verification_plans(number_verification_plans: entitlement_quantity=Decimal(21.36), delivered_quantity=Decimal(21.36), currency="PLN", - status=GenericPayment.STATUS_DISTRIBUTION_SUCCESS, + status=Payment.STATUS_DISTRIBUTION_SUCCESS, ) ) @@ -178,7 +178,7 @@ def empty_payment_verification(social_worker_program: Program) -> None: entitlement_quantity=Decimal(21.36), delivered_quantity=Decimal(21.36), currency="PLN", - status=GenericPayment.STATUS_DISTRIBUTION_SUCCESS, + status=Payment.STATUS_DISTRIBUTION_SUCCESS, ) PaymentVerificationSummaryFactory(payment_plan=payment_plan) @@ -194,8 +194,9 @@ def add_payment_verification_xlsx() -> PV: def payment_verification_creator(channel: str = PaymentVerificationPlan.VERIFICATION_CHANNEL_MANUAL) -> PV: + user = User.objects.first() registration_data_import = RegistrationDataImportFactory( - imported_by=User.objects.first(), business_area=BusinessArea.objects.first() + imported_by=user, business_area=BusinessArea.objects.first() ) program = Program.objects.filter(name="Active Program").first() household, individuals = create_household( @@ -214,6 +215,7 @@ def payment_verification_creator(channel: str = PaymentVerificationPlan.VERIFICA business_area=BusinessArea.objects.first(), start_date=datetime.now() - relativedelta(months=1), end_date=datetime.now() + relativedelta(months=1), + created_by=user, ) payment_plan.unicef_id = "PP-0000-00-1122334" @@ -229,7 +231,7 @@ def payment_verification_creator(channel: str = PaymentVerificationPlan.VERIFICA entitlement_quantity=21.36, delivered_quantity=21.36, currency="PLN", - status=GenericPayment.STATUS_DISTRIBUTION_SUCCESS, + status=Payment.STATUS_DISTRIBUTION_SUCCESS, ) payment_verification_plan = PaymentVerificationPlanFactory( payment_plan=payment_plan, @@ -363,7 +365,7 @@ def test_happy_path_payment_verification( assert payment_record.household.unicef_id in pagePaymentRecord.getLabelHousehold().text assert payment_record.parent.target_population.name in pagePaymentRecord.getLabelTargetPopulation().text assert payment_record.parent.unicef_id in pagePaymentRecord.getLabelDistributionModality().text - assert payment_record.payment_verification.status in pagePaymentRecord.getLabelStatus()[1].text + assert payment_record.payment_verifications.first().status in pagePaymentRecord.getLabelStatus()[1].text assert "PLN 0.00" in pagePaymentRecord.getLabelAmountReceived().text assert payment_record.household.unicef_id in pagePaymentRecord.getLabelHouseholdId().text assert "21.36" in pagePaymentRecord.getLabelEntitlementQuantity().text diff --git a/tests/selenium/people/test_people.py b/tests/selenium/people/test_people.py index da78b48eaa..748fb297fb 100644 --- a/tests/selenium/people/test_people.py +++ b/tests/selenium/people/test_people.py @@ -15,7 +15,7 @@ ) from hct_mis_api.apps.household.models import HOST, SEEING, Individual from hct_mis_api.apps.payment.fixtures import PaymentFactory, PaymentPlanFactory -from hct_mis_api.apps.payment.models import GenericPayment, PaymentRecord +from hct_mis_api.apps.payment.models import Payment from hct_mis_api.apps.program.fixtures import ProgramFactory from hct_mis_api.apps.program.models import BeneficiaryGroup, Program from tests.selenium.page_object.filters import Filters @@ -58,7 +58,7 @@ def add_people(social_worker_program: Program) -> List: @pytest.fixture -def add_people_with_payment_record(add_people: List) -> PaymentRecord: +def add_people_with_payment_record(add_people: List) -> Payment: program = Program.objects.filter(name="Worker Program").first() payment_plan = PaymentPlanFactory( @@ -74,7 +74,7 @@ def add_people_with_payment_record(add_people: List) -> PaymentRecord: entitlement_quantity=21.36, delivered_quantity=21.36, currency="PLN", - status=GenericPayment.STATUS_DISTRIBUTION_SUCCESS, + status=Payment.STATUS_DISTRIBUTION_SUCCESS, ) add_people[1].total_cash_received_usd = 21.36 add_people[1].save() @@ -239,7 +239,7 @@ def test_smoke_page_details_people( @pytest.mark.xfail(reason="UNSTABLE") def test_people_happy_path( self, - add_people_with_payment_record: PaymentRecord, + add_people_with_payment_record: Payment, pagePeople: People, pagePeopleDetails: PeopleDetails, ) -> None: diff --git a/tests/selenium/people/test_people_periodic_data_update.py b/tests/selenium/people/test_people_periodic_data_update.py index 0be5335452..6ce4586840 100644 --- a/tests/selenium/people/test_people_periodic_data_update.py +++ b/tests/selenium/people/test_people_periodic_data_update.py @@ -15,7 +15,7 @@ from hct_mis_api.apps.household.fixtures import create_household_and_individuals from hct_mis_api.apps.household.models import HOST, SEEING, Individual from hct_mis_api.apps.payment.fixtures import PaymentFactory, PaymentPlanFactory -from hct_mis_api.apps.payment.models import GenericPayment +from hct_mis_api.apps.payment.models import Payment from hct_mis_api.apps.periodic_data_update.fixtures import ( PeriodicDataUpdateTemplateFactory, PeriodicDataUpdateUploadFactory, @@ -97,7 +97,7 @@ def individual(add_people: Individual) -> Individual: entitlement_quantity=21.36, delivered_quantity=21.36, currency="PLN", - status=GenericPayment.STATUS_DISTRIBUTION_SUCCESS, + status=Payment.STATUS_DISTRIBUTION_SUCCESS, ) add_people.total_cash_received_usd = 21.36 add_people.save() diff --git a/tests/unit/apps/grievance/test_filter_already_existing_tickets.py b/tests/unit/apps/grievance/test_filter_already_existing_tickets.py index ceb871822b..16136a7631 100644 --- a/tests/unit/apps/grievance/test_filter_already_existing_tickets.py +++ b/tests/unit/apps/grievance/test_filter_already_existing_tickets.py @@ -130,7 +130,7 @@ def setUpTestData(cls) -> None: cls.ticket = SensitiveGrievanceTicketWithoutExtrasFactory( household=cls.household_1, individual=cls.individuals_1[0], - payment_obj=cls.payment, + payment=cls.payment, ticket=grievance_1, ) SensitiveGrievanceTicketWithoutExtrasFactory( @@ -141,7 +141,7 @@ def setUpTestData(cls) -> None: SensitiveGrievanceTicketWithoutExtrasFactory( household=cls.household_3, individual=cls.individuals_3[0], - payment_obj=cls.payment_record2, + payment=cls.payment_record2, ticket=grievance_3, ) GrievanceComplaintTicketFactory.create_batch(5) diff --git a/tests/unit/apps/payment/services/test_dashboard_service.py b/tests/unit/apps/payment/services/test_dashboard_service.py index 295cf562b6..a569aeb525 100644 --- a/tests/unit/apps/payment/services/test_dashboard_service.py +++ b/tests/unit/apps/payment/services/test_dashboard_service.py @@ -15,7 +15,7 @@ PaymentPlanFactory, generate_delivery_mechanisms, ) -from hct_mis_api.apps.payment.models import DeliveryMechanism, GenericPayment +from hct_mis_api.apps.payment.models import DeliveryMechanism, Payment from hct_mis_api.apps.payment.services.dashboard_service import ( payment_verification_chart_query, ) @@ -87,7 +87,7 @@ def setUp(self) -> None: delivery_type=self.dm_cash, delivered_quantity=10 + num, delivered_quantity_usd=10 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, currency="PLN", ) @@ -98,7 +98,7 @@ def setUp(self) -> None: delivery_type=self.dm_voucher, delivered_quantity=20 + num, delivered_quantity_usd=20 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, currency="PLN", ) @@ -109,7 +109,7 @@ def setUp(self) -> None: delivery_type=self.dm_cash, delivered_quantity=30 + num, delivered_quantity_usd=30 + num, - status=GenericPayment.STATUS_ERROR, + status=Payment.STATUS_ERROR, business_area=business_area, currency="PLN", ) @@ -121,7 +121,7 @@ def setUp(self) -> None: delivery_type=self.dm_cash, delivered_quantity=10 + num, delivered_quantity_usd=10 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, household=household4, currency="PLN", @@ -133,7 +133,7 @@ def setUp(self) -> None: delivery_type=self.dm_voucher, delivered_quantity=20 + num, delivered_quantity_usd=20 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, household=household5, currency="PLN", @@ -145,7 +145,7 @@ def setUp(self) -> None: delivery_type=self.dm_cash, delivered_quantity=30 + num, delivered_quantity_usd=30 + num, - status=GenericPayment.STATUS_ERROR, + status=Payment.STATUS_ERROR, business_area=business_area, household=household6, currency="PLN", diff --git a/tests/unit/apps/payment/snapshots/snap_test_all_payment_plan_queries.py b/tests/unit/apps/payment/snapshots/snap_test_all_payment_plan_queries.py index 7022088f32..7d078d975b 100644 --- a/tests/unit/apps/payment/snapshots/snap_test_all_payment_plan_queries.py +++ b/tests/unit/apps/payment/snapshots/snap_test_all_payment_plan_queries.py @@ -7,6 +7,22 @@ snapshots = Snapshot() +snapshots['TestPaymentPlanQueries::test_all_payment_verification_log_entries 1'] = { + 'data': { + 'allPaymentVerificationLogEntries': { + 'edges': [ + { + 'node': { + 'action': 'CREATE', + 'isUserGenerated': None + } + } + ], + 'totalCount': 1 + } + } +} + snapshots['TestPaymentPlanQueries::test_fetch_all_payment_plans 1'] = { 'data': { 'allPaymentPlans': { @@ -404,13 +420,15 @@ 'data': { 'payment': { 'additionalCollectorName': None, + 'fullName': 'First1 Mid1 Last1', 'reasonForUnsuccessfulPayment': 'reason 123', 'snapshotCollectorBankAccountNumber': None, 'snapshotCollectorBankName': None, 'snapshotCollectorDebitCardNumber': None, 'snapshotCollectorDeliveryPhoneNo': None, 'snapshotCollectorFullName': None, - 'totalPersonsCovered': 5 + 'totalPersonsCovered': 5, + 'verification': None } } } @@ -419,13 +437,15 @@ 'data': { 'payment': { 'additionalCollectorName': 'AddCollectorName11', + 'fullName': 'First2 Mid2 Last3', 'reasonForUnsuccessfulPayment': 'reason 222', 'snapshotCollectorBankAccountNumber': 'PrimaryCollBankNumber', 'snapshotCollectorBankName': 'PrimaryCollBankName', 'snapshotCollectorDebitCardNumber': 'PrimaryCollDebitCardNumber', 'snapshotCollectorDeliveryPhoneNo': '1111111', 'snapshotCollectorFullName': 'PrimaryCollectorFullName', - 'totalPersonsCovered': 99 + 'totalPersonsCovered': 99, + 'verification': None } } } @@ -434,13 +454,15 @@ 'data': { 'payment': { 'additionalCollectorName': 'AddCollectorName22', + 'fullName': 'First3 Mid3 Last3', 'reasonForUnsuccessfulPayment': 'reason 333', 'snapshotCollectorBankAccountNumber': 'AlternateCollBankNumber', 'snapshotCollectorBankName': 'AlternateCollBankName', 'snapshotCollectorDebitCardNumber': 'AlternateCollDebitCardNumber', 'snapshotCollectorDeliveryPhoneNo': '222222222', 'snapshotCollectorFullName': 'AlternateCollectorFullName', - 'totalPersonsCovered': 55 + 'totalPersonsCovered': 55, + 'verification': None } } } diff --git a/tests/unit/apps/payment/test_all_payment_plan_queries.py b/tests/unit/apps/payment/test_all_payment_plan_queries.py index f9274d3b5b..549ca2d9f8 100644 --- a/tests/unit/apps/payment/test_all_payment_plan_queries.py +++ b/tests/unit/apps/payment/test_all_payment_plan_queries.py @@ -11,15 +11,18 @@ from hct_mis_api.apps.account.fixtures import UserFactory from hct_mis_api.apps.account.permissions import Permissions +from hct_mis_api.apps.activity_log.models import LogEntry +from hct_mis_api.apps.activity_log.utils import create_diff from hct_mis_api.apps.core.base_test_case import APITestCase from hct_mis_api.apps.core.fixtures import create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea from hct_mis_api.apps.core.utils import encode_id_base64 from hct_mis_api.apps.household.fixtures import HouseholdFactory, IndividualFactory from hct_mis_api.apps.payment.fixtures import ( FinancialServiceProviderFactory, PaymentFactory, PaymentPlanFactory, + PaymentVerificationPlanFactory, + PaymentVerificationSummaryFactory, RealProgramFactory, ) from hct_mis_api.apps.payment.models import ( @@ -28,6 +31,7 @@ PaymentHouseholdSnapshot, PaymentPlan, PaymentPlanSupportingDocument, + PaymentVerificationPlan, ) from hct_mis_api.apps.program.fixtures import ProgramCycleFactory @@ -203,6 +207,7 @@ class TestPaymentPlanQueries(APITestCase): query Payment($id: ID!) { payment(id: $id) { totalPersonsCovered + fullName snapshotCollectorFullName snapshotCollectorDeliveryPhoneNo snapshotCollectorBankName @@ -210,6 +215,9 @@ class TestPaymentPlanQueries(APITestCase): snapshotCollectorDebitCardNumber additionalCollectorName reasonForUnsuccessfulPayment + verification { + status + } } } """ @@ -217,12 +225,12 @@ class TestPaymentPlanQueries(APITestCase): @classmethod def setUpTestData(cls) -> None: super().setUpTestData() - create_afghanistan() + cls.business_area = create_afghanistan() cls.user = UserFactory.create(username="qazxsw321") cls.create_user_role_with_permissions( cls.user, - [Permissions.PM_VIEW_LIST, Permissions.PM_VIEW_DETAILS], - BusinessArea.objects.get(slug="afghanistan"), + [Permissions.PM_VIEW_LIST, Permissions.PM_VIEW_DETAILS, Permissions.ACTIVITY_LOG_VIEW], + cls.business_area, ) with freeze_time("2020-10-10"): @@ -236,6 +244,7 @@ def setUpTestData(cls) -> None: dispersion_start_date=datetime(2020, 8, 10), dispersion_end_date=datetime(2020, 12, 10), is_follow_up=False, + created_by=cls.user, ) cls.pp.unicef_id = "PP-01" cls.pp.save() @@ -309,7 +318,7 @@ def setUpTestData(cls) -> None: IndividualFactory(household=hh2, sex="MALE", birth_date=datetime.now().date() - relativedelta(years=20)) AcceptanceProcessThreshold.objects.create( - business_area=BusinessArea.objects.first(), + business_area=cls.business_area, approval_number_required=2, authorization_number_required=2, finance_release_number_required=3, @@ -467,9 +476,9 @@ def test_payment_node_with_legacy_data(self) -> None: dispersion_end_date=datetime(2023, 12, 10), is_follow_up=False, ) - hoh_1 = IndividualFactory(household=None) - hoh_2 = IndividualFactory(household=None) - hoh_3 = IndividualFactory(household=None) + hoh_1 = IndividualFactory(household=None, given_name="First1", middle_name="Mid1", family_name="Last1") + hoh_2 = IndividualFactory(household=None, given_name="First2", middle_name="Mid2", family_name="Last3") + hoh_3 = IndividualFactory(household=None, given_name="First3", middle_name="Mid3", family_name="Last3") household_1 = HouseholdFactory(head_of_household=hoh_1, size=5) household_2 = HouseholdFactory(head_of_household=hoh_2, size=10) household_3 = HouseholdFactory(head_of_household=hoh_3, size=15) @@ -538,3 +547,45 @@ def test_payment_node_with_legacy_data(self) -> None: context={"user": self.user}, variables={"id": encode_id_base64(payment_id, "Payment")}, ) + + def test_all_payment_verification_log_entries(self) -> None: + query = """ + query allPaymentVerificationLogEntries($objectId: UUID, $businessArea: String!) { + allPaymentVerificationLogEntries(objectId: $objectId, businessArea: $businessArea) { + totalCount + edges { + node { + isUserGenerated + action + } + } + } + } + """ + payment_plan_id = str(self.pp.id) + PaymentVerificationSummaryFactory(payment_plan=self.pp) + PaymentVerificationSummaryFactory(payment_plan=self.pp_conflicted) + pvp = PaymentVerificationPlanFactory(payment_plan=self.pp) + pvp2 = PaymentVerificationPlanFactory(payment_plan=self.pp_conflicted) + LogEntry.objects.create( + action=LogEntry.CREATE, + content_object=pvp, + user=self.user, + business_area=self.business_area, + object_repr=str(pvp), + changes=create_diff(None, pvp, PaymentVerificationPlan.ACTIVITY_LOG_MAPPING), + ) + LogEntry.objects.create( + action=LogEntry.CREATE, + content_object=pvp2, + user=self.user, + business_area=self.business_area, + object_repr=str(pvp2), + changes=create_diff(None, pvp2, PaymentVerificationPlan.ACTIVITY_LOG_MAPPING), + ) + + self.snapshot_graphql_request( + request_string=query, + context={"user": self.user}, + variables={"objectId": payment_plan_id, "businessArea": "afghanistan"}, + ) diff --git a/tests/unit/apps/payment/test_dashboard_queries.py b/tests/unit/apps/payment/test_dashboard_queries.py index b81534f076..1cbc0cb99d 100644 --- a/tests/unit/apps/payment/test_dashboard_queries.py +++ b/tests/unit/apps/payment/test_dashboard_queries.py @@ -22,7 +22,7 @@ PaymentPlanFactory, generate_delivery_mechanisms, ) -from hct_mis_api.apps.payment.models import DeliveryMechanism, GenericPayment, Payment +from hct_mis_api.apps.payment.models import DeliveryMechanism, Payment from hct_mis_api.apps.program.fixtures import ProgramFactory @@ -244,7 +244,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_cash, delivered_quantity=10 + num, delivered_quantity_usd=10 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, currency="PLN", ) @@ -255,7 +255,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_voucher, delivered_quantity=20 + num, delivered_quantity_usd=20 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, currency="PLN", ) @@ -266,7 +266,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_voucher, delivered_quantity=20 + num, delivered_quantity_usd=20 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, currency="PLN", ) @@ -277,7 +277,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_cash, delivered_quantity=30 + num, delivered_quantity_usd=30 + num, - status=GenericPayment.STATUS_ERROR, + status=Payment.STATUS_ERROR, business_area=business_area, currency="PLN", ) @@ -288,7 +288,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_voucher, delivered_quantity=20 + num, delivered_quantity_usd=20 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, currency="PLN", ) @@ -299,7 +299,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_voucher, delivered_quantity=20 + num, delivered_quantity_usd=20 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, currency="PLN", ) @@ -311,7 +311,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_cash, delivered_quantity=10 + num, delivered_quantity_usd=10 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, household=household2_admin1, currency="PLN", @@ -323,7 +323,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_voucher, delivered_quantity=20 + num, delivered_quantity_usd=20 + num, - status=GenericPayment.STATUS_SUCCESS, + status=Payment.STATUS_SUCCESS, business_area=business_area, household=household2_admin2, currency="PLN", @@ -335,7 +335,7 @@ def setUpTestData(cls) -> None: delivery_type=cls.dm_cash, delivered_quantity=30 + num, delivered_quantity_usd=30 + num, - status=GenericPayment.STATUS_ERROR, + status=Payment.STATUS_ERROR, business_area=business_area, household=household2_admin3, currency="PLN", diff --git a/tests/unit/apps/payment/test_fsp_in_payment_plan.py b/tests/unit/apps/payment/test_fsp_in_payment_plan.py index b7255b85c7..b7bb9dca5a 100644 --- a/tests/unit/apps/payment/test_fsp_in_payment_plan.py +++ b/tests/unit/apps/payment/test_fsp_in_payment_plan.py @@ -26,7 +26,7 @@ from hct_mis_api.apps.payment.models import ( DeliveryMechanism, DeliveryMechanismPerPaymentPlan, - GenericPayment, + Payment, PaymentPlan, ) from hct_mis_api.apps.payment.services.payment_plan_services import PaymentPlanService @@ -774,7 +774,7 @@ def test_successful_fsp_assigment_with_no_limit(self) -> None: collector=self.individuals_2[0], entitlement_quantity=1000000, # a lot entitlement_quantity_usd=200000, # a lot - status=GenericPayment.STATUS_NOT_DISTRIBUTED, + status=Payment.STATUS_NOT_DISTRIBUTED, household=self.household_2, delivery_type=None, financial_service_provider=None, @@ -934,7 +934,7 @@ def test_getting_volume_by_delivery_mechanism(self) -> None: entitlement_quantity=500, entitlement_quantity_usd=100, delivery_type=self.dm_cash, - status=GenericPayment.STATUS_NOT_DISTRIBUTED, + status=Payment.STATUS_NOT_DISTRIBUTED, household=self.household_2, currency="PLN", ) @@ -945,7 +945,7 @@ def test_getting_volume_by_delivery_mechanism(self) -> None: entitlement_quantity=1000, entitlement_quantity_usd=200, delivery_type=self.dm_transfer, - status=GenericPayment.STATUS_NOT_DISTRIBUTED, + status=Payment.STATUS_NOT_DISTRIBUTED, household=self.household_3, currency="PLN", ) @@ -1139,7 +1139,7 @@ def test_all_payments_covered_with_fsp_limit(self) -> None: collector=self.individuals_2[0], # DELIVERY_TYPE_TRANSFER entitlement_quantity=100, entitlement_quantity_usd=500, - status=GenericPayment.STATUS_NOT_DISTRIBUTED, + status=Payment.STATUS_NOT_DISTRIBUTED, household=self.household_2, delivery_type=None, financial_service_provider=None, @@ -1150,7 +1150,7 @@ def test_all_payments_covered_with_fsp_limit(self) -> None: collector=self.individuals_3[0], # DELIVERY_TYPE_TRANSFER entitlement_quantity=100, entitlement_quantity_usd=500, - status=GenericPayment.STATUS_NOT_DISTRIBUTED, + status=Payment.STATUS_NOT_DISTRIBUTED, household=self.household_3, delivery_type=None, financial_service_provider=None, @@ -1161,7 +1161,7 @@ def test_all_payments_covered_with_fsp_limit(self) -> None: collector=self.individuals_1[0], # DELIVERY_TYPE_VOUCHER entitlement_quantity=100, entitlement_quantity_usd=1000, - status=GenericPayment.STATUS_NOT_DISTRIBUTED, + status=Payment.STATUS_NOT_DISTRIBUTED, household=self.household_1, delivery_type=None, financial_service_provider=None, @@ -1218,7 +1218,7 @@ def test_not_all_payments_covered_because_of_fsp_limit(self) -> None: collector=self.individuals_2[0], # DELIVERY_TYPE_TRANSFER entitlement_quantity=100, entitlement_quantity_usd=1000, - status=GenericPayment.STATUS_NOT_DISTRIBUTED, + status=Payment.STATUS_NOT_DISTRIBUTED, household=self.household_2, delivery_type=None, financial_service_provider=None, @@ -1229,7 +1229,7 @@ def test_not_all_payments_covered_because_of_fsp_limit(self) -> None: collector=self.individuals_3[0], # DELIVERY_TYPE_TRANSFER entitlement_quantity=100, entitlement_quantity_usd=1000, - status=GenericPayment.STATUS_NOT_DISTRIBUTED, + status=Payment.STATUS_NOT_DISTRIBUTED, household=self.household_3, delivery_type=None, financial_service_provider=None, diff --git a/tests/unit/apps/payment/test_models1.py b/tests/unit/apps/payment/test_models1.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/apps/payment/test_payment_verification_mutations.py b/tests/unit/apps/payment/test_payment_verification_mutations.py index 2a192e5654..5953660434 100644 --- a/tests/unit/apps/payment/test_payment_verification_mutations.py +++ b/tests/unit/apps/payment/test_payment_verification_mutations.py @@ -25,7 +25,7 @@ PaymentVerificationSummaryFactory, ) from hct_mis_api.apps.payment.models import ( - GenericPayment, + Payment, PaymentPlan, PaymentVerification, PaymentVerificationPlan, @@ -220,7 +220,7 @@ def test_permissions(self) -> None: def test_edit_payment_verification_plan_mutation(self) -> None: payment_plan = PaymentPlanFactory(status=PaymentPlan.Status.FINISHED, business_area=self.business_area) PaymentVerificationSummaryFactory(payment_plan=payment_plan) - PaymentFactory(parent=payment_plan, currency="PLN", status=GenericPayment.STATUS_SUCCESS) + PaymentFactory(parent=payment_plan, currency="PLN", status=Payment.STATUS_SUCCESS) payment_verification_plan = PaymentVerificationPlanFactory( payment_plan=payment_plan, status=PaymentVerificationPlan.STATUS_PENDING, diff --git a/tests/unit/apps/payment/test_verification_plan_status_change_services.py b/tests/unit/apps/payment/test_verification_plan_status_change_services.py index 29f270cbe3..bcb0b7eeb8 100644 --- a/tests/unit/apps/payment/test_verification_plan_status_change_services.py +++ b/tests/unit/apps/payment/test_verification_plan_status_change_services.py @@ -125,7 +125,7 @@ def setUpTestData(cls) -> None: PaymentVerificationFactory( payment_verification_plan=other_payment_plan_payment_verification, - payment_obj=other_payment_record, + payment=other_payment_record, status=PaymentVerification.STATUS_PENDING, ) EntitlementCardFactory(household=other_household) diff --git a/tests/unit/apps/reporting/test_report_service.py b/tests/unit/apps/reporting/test_report_service.py index 450f987f35..10ba23b1de 100644 --- a/tests/unit/apps/reporting/test_report_service.py +++ b/tests/unit/apps/reporting/test_report_service.py @@ -1,5 +1,4 @@ from typing import Any -from unittest.mock import patch from django.conf import settings from django.test import TestCase @@ -10,7 +9,6 @@ from hct_mis_api.apps.account.fixtures import PartnerFactory, UserFactory from hct_mis_api.apps.core.fixtures import create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea from hct_mis_api.apps.geo import models as geo_models from hct_mis_api.apps.geo.fixtures import AreaFactory, AreaTypeFactory from hct_mis_api.apps.household.fixtures import create_household_and_individuals @@ -31,7 +29,7 @@ class TestGenerateReportService(TestCase): @classmethod def setUpTestData(self) -> None: - create_afghanistan() + self.business_area = create_afghanistan() PartnerFactory(name="UNICEF") from hct_mis_api.apps.reporting.services.generate_report_service import ( GenerateReportService, @@ -39,7 +37,6 @@ def setUpTestData(self) -> None: self.GenerateReportService = GenerateReportService - self.business_area = BusinessArea.objects.get(slug="afghanistan") self.partner = PartnerFactory(name="Test1") self.user = UserFactory.create(partner=self.partner) family_sizes_list = (2, 4, 5, 1, 3, 11, 14) @@ -86,20 +83,20 @@ def setUpTestData(self) -> None: self.payment_plan_1 = PaymentPlanFactory( business_area=self.business_area, program_cycle=self.program_1.cycles.first(), - # end_date=datetime.datetime.fromisoformat("2020-01-01 00:01:11+00:00"), + created_by=self.user, ) self.payment_plan_2 = PaymentPlanFactory( business_area=self.business_area, - # end_date=datetime.datetime.fromisoformat("2020-01-01 00:01:11+00:00") + created_by=self.user, ) self.payment_plan_3 = PaymentPlanFactory( business_area=self.business_area, program_cycle=self.program_1.cycles.first(), - # end_date=datetime.datetime.fromisoformat("2020-01-01 00:01:11+00:00"), + created_by=self.user, ) self.payment_plan_4 = PaymentPlanFactory( business_area=self.business_area, - # end_date=datetime.datetime.fromisoformat("2020-01-01 00:01:11+00:00") + created_by=self.user, ) PaymentVerificationSummary.objects.create(payment_plan=self.payment_plan_1) PaymentVerificationSummary.objects.create(payment_plan=self.payment_plan_2) @@ -174,12 +171,13 @@ def setUpTestData(self) -> None: ("payments_filter_admin_area", Report.PAYMENTS, True, False, 1), ("payment_verifications_no_filter", Report.PAYMENT_VERIFICATION, False, False, 2), ("payment_verifications_program", Report.PAYMENT_VERIFICATION, False, True, 1), - ("cash_plans_no_filter", Report.CASH_PLAN, False, False, 2), - ("cash_plans_program", Report.CASH_PLAN, False, True, 1), + ("cash_plans_no_filter", Report.PAYMENT_PLAN, False, False, 2), + ("cash_plans_program", Report.PAYMENT_PLAN, False, True, 1), ("individuals_payments_no_filter", Report.INDIVIDUALS_AND_PAYMENT, False, False, 4), ("individuals_payments_admin_area", Report.INDIVIDUALS_AND_PAYMENT, True, False, 2), ("individuals_payments_program", Report.INDIVIDUALS_AND_PAYMENT, False, True, 2), ("individuals_payments_admin_area_and_program", Report.INDIVIDUALS_AND_PAYMENT, True, True, 2), + ("cash_plan_verification", Report.CASH_PLAN_VERIFICATION, True, True, 2), ] ) def test_report_types( @@ -203,17 +201,17 @@ def test_report_types( report.save() report_service = self.GenerateReportService(report) - with ( - patch( - "hct_mis_api.apps.reporting.services.generate_report_service.GenerateReportService.save_wb_file_in_db" - ) as mock_save_wb_file_in_db, - patch( - "hct_mis_api.apps.reporting.services.generate_report_service.GenerateReportService.generate_workbook" - ) as mock_generate_workbook, - ): - report_service.generate_report() - assert mock_generate_workbook.called - assert mock_save_wb_file_in_db.called + # with ( + # # patch( + # # "hct_mis_api.apps.reporting.services.generate_report_service.GenerateReportService.save_wb_file_in_db" + # # ) as mock_save_wb_file_in_db, + # # patch( + # # "hct_mis_api.apps.reporting.services.generate_report_service.GenerateReportService.generate_workbook" + # # ) as mock_generate_workbook, + # ): + report_service.generate_report() + # assert mock_generate_workbook.called + # assert mock_save_wb_file_in_db.called report.refresh_from_db() self.assertEqual(report.status, Report.COMPLETED) # self.assertEqual(report.number_of_records, number_of_records) # when mocking generating workbook, this is not set diff --git a/tests/unit/one_time_scripts/test_migrate_cash_assist_models.py b/tests/unit/one_time_scripts/test_migrate_cash_assist_models.py deleted file mode 100644 index 659890a199..0000000000 --- a/tests/unit/one_time_scripts/test_migrate_cash_assist_models.py +++ /dev/null @@ -1,451 +0,0 @@ -import datetime - -from django.contrib.contenttypes.models import ContentType -from django.test import TestCase - -import factory -from factory.django import DjangoModelFactory -from pytz import utc - -from hct_mis_api.apps.core.fixtures import create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea -from hct_mis_api.apps.core.utils import CaIdIterator -from hct_mis_api.apps.grievance.fixtures import GrievanceTicketFactory -from hct_mis_api.apps.grievance.models import ( - GrievanceTicket, - TicketComplaintDetails, - TicketSensitiveDetails, -) -from hct_mis_api.apps.household.fixtures import HouseholdFactory, create_household -from hct_mis_api.apps.payment.delivery_mechanisms import DeliveryMechanismChoices -from hct_mis_api.apps.payment.fixtures import ( - DeliveryMechanismFactory, - PaymentVerificationFactory, - PaymentVerificationPlanFactory, - PaymentVerificationSummaryFactory, -) -from hct_mis_api.apps.payment.models import ( - CashPlan, - DeliveryMechanismPerPaymentPlan, - FinancialServiceProvider, - Payment, - PaymentPlan, - PaymentRecord, - PaymentVerification, - PaymentVerificationPlan, - PaymentVerificationSummary, - ServiceProvider, -) -from hct_mis_api.apps.program.fixtures import ProgramFactory -from hct_mis_api.apps.targeting.fixtures import TargetPopulationFactory -from hct_mis_api.one_time_scripts.migrate_cash_assist_models import ( - get_status, - migrate_cash_plan_to_payment_plan, -) - - -class CashPlanFactory(DjangoModelFactory): - class Meta: - model = CashPlan - - ca_id = factory.Sequence(lambda n: f"PP-0000-00-1122334{n}") - business_area = factory.LazyAttribute(lambda o: BusinessArea.objects.first()) - program = factory.SubFactory(ProgramFactory) - status_date = factory.Faker( - "date_time_this_decade", - before_now=False, - after_now=True, - tzinfo=utc, - ) - status = factory.fuzzy.FuzzyChoice( - CashPlan.STATUS_CHOICE, - getter=lambda c: c[0], - ) - name = factory.Faker( - "sentence", - nb_words=6, - variable_nb_words=True, - ext_word_list=None, - ) - distribution_level = "Registration Group" - dispersion_date = factory.Faker( - "date_time_this_decade", - before_now=False, - after_now=True, - tzinfo=utc, - ) - coverage_duration = factory.fuzzy.FuzzyInteger(1, 4) - coverage_unit = factory.Faker( - "random_element", - elements=["Day(s)", "Week(s)", "Month(s)", "Year(s)"], - ) - comments = factory.Faker( - "sentence", - nb_words=6, - variable_nb_words=True, - ext_word_list=None, - ) - delivery_type = factory.fuzzy.FuzzyChoice( - DeliveryMechanismChoices.DELIVERY_TYPE_CHOICES, - getter=lambda c: c[0], - ) - assistance_measurement = factory.Faker("currency_name") - assistance_through = factory.Faker("random_element", elements=["ING", "Bank of America", "mBank"]) - vision_id = factory.Faker("uuid4") - funds_commitment = factory.fuzzy.FuzzyInteger(1000, 99999999) - exchange_rate = factory.fuzzy.FuzzyDecimal(0.1, 9.9) - down_payment = factory.fuzzy.FuzzyInteger(1000, 99999999) - validation_alerts_count = factory.fuzzy.FuzzyInteger(1, 3) - total_persons_covered = factory.fuzzy.FuzzyInteger(1, 4) - total_persons_covered_revised = factory.fuzzy.FuzzyInteger(1, 4) - - total_entitled_quantity = factory.fuzzy.FuzzyDecimal(20000.0, 90000000.0) - total_entitled_quantity_revised = factory.fuzzy.FuzzyDecimal(20000.0, 90000000.0) - total_delivered_quantity = factory.fuzzy.FuzzyDecimal(20000.0, 90000000.0) - total_undelivered_quantity = factory.fuzzy.FuzzyDecimal(20000.0, 90000000.0) - - total_entitled_quantity_usd = factory.fuzzy.FuzzyDecimal(20000.0, 90000000.0) - total_entitled_quantity_revised_usd = factory.fuzzy.FuzzyDecimal(20000.0, 90000000.0) - total_delivered_quantity_usd = factory.fuzzy.FuzzyDecimal(20000.0, 90000000.0) - total_undelivered_quantity_usd = factory.fuzzy.FuzzyDecimal(20000.0, 90000000.0) - - -class ServiceProviderFactory(DjangoModelFactory): - class Meta: - model = ServiceProvider - - business_area = factory.LazyAttribute(lambda o: BusinessArea.objects.first()) - ca_id = factory.Iterator(CaIdIterator("SRV")) - full_name = factory.Faker("company") - short_name = factory.LazyAttribute(lambda o: o.full_name[0:3]) - country = factory.Faker("country_code") - vision_id = factory.fuzzy.FuzzyInteger(1342342, 9999999932) - - -class PaymentRecordFactory(DjangoModelFactory): - class Meta: - model = PaymentRecord - - business_area = factory.LazyAttribute(lambda o: BusinessArea.objects.first()) - status = factory.fuzzy.FuzzyChoice( - PaymentRecord.STATUS_CHOICE, - getter=lambda c: c[0], - ) - full_name = factory.Faker("name") - status_date = factory.Faker( - "date_time_this_decade", - before_now=False, - after_now=True, - tzinfo=utc, - ) - ca_id = factory.Iterator(CaIdIterator("PR")) - ca_hash_id = factory.Faker("uuid4") - parent = factory.SubFactory(CashPlanFactory) - household = factory.SubFactory(HouseholdFactory) - total_persons_covered = factory.fuzzy.FuzzyInteger(1, 7) - distribution_modality = factory.Faker( - "sentence", - nb_words=6, - variable_nb_words=True, - ext_word_list=None, - ) - target_population = factory.SubFactory(TargetPopulationFactory) - entitlement_card_number = factory.Faker("ssn") - entitlement_card_status = factory.fuzzy.FuzzyChoice( - PaymentRecord.ENTITLEMENT_CARD_STATUS_CHOICE, - getter=lambda c: c[0], - ) - entitlement_card_issue_date = factory.Faker( - "date_time_this_decade", - before_now=True, - after_now=False, - tzinfo=utc, - ) - currency = factory.Faker("currency_code") - entitlement_quantity = factory.fuzzy.FuzzyDecimal(100.0, 10000.0) - entitlement_quantity_usd = factory.fuzzy.FuzzyDecimal(100.0, 10000.0) - delivered_quantity = factory.fuzzy.FuzzyDecimal(100.0, 10000.0) - delivered_quantity_usd = factory.fuzzy.FuzzyDecimal(100.0, 10000.0) - delivery_date = factory.Faker( - "date_time_this_decade", - before_now=True, - after_now=False, - tzinfo=utc, - ) - service_provider = factory.SubFactory(ServiceProviderFactory) - registration_ca_id = factory.Faker("uuid4") - - -class TestMigrateCashPlanToPaymentPlan(TestCase): - @classmethod - def setUpTestData(cls) -> None: - cls.business_area = create_afghanistan() - - cls.content_type_for_payment_plan = ContentType.objects.get_for_model(PaymentPlan) - cls.content_type_for_cash_plan = ContentType.objects.get_for_model(CashPlan) - cls.content_type_for_payment = ContentType.objects.get_for_model(Payment) - - cls.cash_delivery_mechanism = DeliveryMechanismFactory(name="Cash") - cls.digital_delivery_mechanism = DeliveryMechanismFactory(name="Digital") - - cls.service_provider1 = ServiceProviderFactory() - cls.service_provider2 = ServiceProviderFactory() - cls.service_provider3 = ServiceProviderFactory() - - cls.tp1 = TargetPopulationFactory() - cls.tp2 = TargetPopulationFactory() - - cls.cash_plan = CashPlanFactory( - delivery_type=cls.cash_delivery_mechanism.name, - service_provider=cls.service_provider1, - is_migrated_to_payment_plan=False, - start_date=datetime.date(2021, 1, 1), - dispersion_date=datetime.date(2021, 1, 2), - status_date=datetime.date(2021, 1, 3), - ) - - cls.cash_plan2 = CashPlanFactory( - delivery_type=cls.cash_delivery_mechanism.name, - service_provider=cls.service_provider2, - is_migrated_to_payment_plan=False, - ) # CP without payments, not migrating - - cls.cash_plan3 = CashPlanFactory( - delivery_type=cls.digital_delivery_mechanism.name, - service_provider=cls.service_provider1, - is_migrated_to_payment_plan=False, - ) # CP without payments, not migrating, same service provider as CP1 so fsp delivery mechanisms should be updated - - hh1, _ = create_household(household_args={"size": 2}) - cls.pr1 = PaymentRecordFactory( - parent=cls.cash_plan, - delivery_type=cls.cash_delivery_mechanism, - target_population=cls.tp1, - head_of_household=hh1.head_of_household, - service_provider=cls.service_provider1, - ) - hh2, _ = create_household(household_args={"size": 2}) - cls.pr2 = PaymentRecordFactory( - parent=cls.cash_plan, - delivery_type=cls.digital_delivery_mechanism, - target_population=cls.tp2, - head_of_household=hh2.head_of_household, - service_provider=cls.service_provider1, - ) - cls.pvs = PaymentVerificationSummaryFactory( - payment_plan_obj=cls.cash_plan, - ) - cls.pvp = PaymentVerificationPlanFactory( - payment_plan_obj=cls.cash_plan, - payment_plan=None, - ) - cls.pv1 = PaymentVerificationFactory( - payment_verification_plan=cls.pvp, - payment_obj=cls.pr1, - status=PaymentVerification.STATUS_PENDING, - payment=None, - ) - cls.pv2 = PaymentVerificationFactory( - payment_verification_plan=cls.pvp, - payment_obj=cls.pr2, - status=PaymentVerification.STATUS_PENDING, - payment=None, - ) - - cls.grievance_ticket1 = GrievanceTicketFactory(status=GrievanceTicket.STATUS_IN_PROGRESS) - cls.ticket_complaint_details = TicketComplaintDetails.objects.create( - ticket=cls.grievance_ticket1, - payment_obj=cls.pr1, - payment_object_id=cls.pr1.id, - payment_content_type=cls.content_type_for_payment, - ) - cls.grievance_ticket2 = GrievanceTicketFactory(status=GrievanceTicket.STATUS_IN_PROGRESS) - cls.ticket_sensitive_details = TicketSensitiveDetails.objects.create( - ticket=cls.grievance_ticket2, - payment_obj=cls.pr2, - payment_object_id=cls.pr2.id, - payment_content_type=cls.content_type_for_payment, - ) - - def test_migrate(self) -> None: - assert FinancialServiceProvider.objects.count() == 0 - assert PaymentPlan.objects.count() == 0 - assert DeliveryMechanismPerPaymentPlan.objects.count() == 0 - assert Payment.objects.count() == 0 - assert PaymentVerificationSummary.objects.count() == 1 - assert PaymentVerificationPlan.objects.count() == 1 - assert PaymentVerification.objects.count() == 2 - - migrate_cash_plan_to_payment_plan() - - self.service_provider1.refresh_from_db() - self.service_provider2.refresh_from_db() - self.service_provider3.refresh_from_db() - self.cash_plan.refresh_from_db() - self.pr1.refresh_from_db() - self.pr2.refresh_from_db() - self.pvs.refresh_from_db() - self.pvp.refresh_from_db() - self.pv1.refresh_from_db() - self.pv2.refresh_from_db() - self.grievance_ticket1.refresh_from_db() - self.grievance_ticket2.refresh_from_db() - self.ticket_complaint_details.refresh_from_db() - self.ticket_sensitive_details.refresh_from_db() - - # Assert FinancialServiceProvider is created - - assert self.service_provider1.is_migrated_to_payment_plan is True - assert self.service_provider2.is_migrated_to_payment_plan is True - assert self.service_provider3.is_migrated_to_payment_plan is False # no cash plan assigned so not migrated - - assert FinancialServiceProvider.objects.count() == 2 - - fsp = FinancialServiceProvider.objects.get(vision_vendor_number=self.service_provider1.vision_id) - assert fsp.name == self.service_provider1.full_name - assert fsp.vision_vendor_number == self.service_provider1.vision_id - assert fsp.communication_channel == "API" - assert fsp.internal_data == { - "is_cash_assist": True, - "business_area": self.service_provider1.business_area.slug, - "country": self.service_provider1.country, - "ca_id": self.service_provider1.ca_id, - "short_name": self.service_provider1.short_name, - } - assert self.service_provider1.is_migrated_to_payment_plan is True - assert fsp.delivery_mechanisms.count() == 2 - - assert self.cash_plan.is_migrated_to_payment_plan is True - assert self.cash_plan2.is_migrated_to_payment_plan is False # no payments assigned so not migrated - - # Assert PaymentPlans created - pps = PaymentPlan.objects.filter(unicef_id=self.cash_plan.ca_id) - assert pps.count() == 2 # One for each target population - pp1 = pps.get(target_population=self.pr1.target_population) - assert pp1.unicef_id == self.cash_plan.ca_id - assert pp1.status == "FINISHED" - assert pp1.name == self.pr1.target_population.name - assert pp1.is_cash_assist is True - assert pp1.business_area_id == self.cash_plan.business_area.id - assert pp1.created_by_id == self.pr1.target_population.created_by.id - assert pp1.created_at == self.cash_plan.created_at - assert pp1.target_population_id == self.pr1.target_population.id - assert pp1.program_cycle_id == self.pr1.target_population.program_cycle.id - assert pp1.currency == self.pr1.currency - assert pp1.dispersion_start_date == datetime.date(2021, 1, 1) - assert pp1.dispersion_end_date == datetime.date(2021, 1, 2) - assert pp1.status_date == datetime.datetime(2021, 1, 3, tzinfo=utc) - assert pp1.exchange_rate == self.cash_plan.exchange_rate - - assert pp1.total_entitled_quantity == self.pr1.entitlement_quantity - assert pp1.total_entitled_quantity_usd == self.pr1.entitlement_quantity_usd - assert pp1.total_entitled_quantity_revised == self.cash_plan.total_entitled_quantity_revised - assert pp1.total_entitled_quantity_revised_usd == self.cash_plan.total_entitled_quantity_revised_usd - assert pp1.total_delivered_quantity == self.pr1.delivered_quantity - assert pp1.total_delivered_quantity_usd == self.pr1.delivered_quantity_usd - assert pp1.total_undelivered_quantity == pp1.total_entitled_quantity - self.pr1.delivered_quantity - assert pp1.total_undelivered_quantity_usd == pp1.total_entitled_quantity_usd - self.pr1.delivered_quantity_usd - - assert pp1.internal_data == { - "name": self.cash_plan.name, - "ca_hash_id": str(self.cash_plan.ca_hash_id), - "distribution_level": self.cash_plan.distribution_level, - "coverage_duration": self.cash_plan.coverage_duration, - "coverage_unit": self.cash_plan.coverage_unit, - "comments": self.cash_plan.comments, - "assistance_measurement": self.cash_plan.assistance_measurement, - "assistance_through": self.cash_plan.assistance_through, - "vision_id": self.cash_plan.vision_id, - "funds_commitment": self.cash_plan.funds_commitment, - "down_payment": self.cash_plan.down_payment, - "validation_alerts_count": self.cash_plan.validation_alerts_count, - "total_persons_covered": self.cash_plan.total_persons_covered, - "total_persons_covered_revised": self.cash_plan.total_persons_covered_revised, - } - - pp2 = pps.get(target_population=self.pr2.target_population) - - # Assert DeliveryMechanismPerPaymentPlans created - assert DeliveryMechanismPerPaymentPlan.objects.count() == 2 - dmp1 = DeliveryMechanismPerPaymentPlan.objects.get(payment_plan=pp1) - assert dmp1.delivery_mechanism == self.cash_delivery_mechanism - assert dmp1.sent_date == self.cash_plan.status_date - assert dmp1.delivery_mechanism_order == 1 - assert dmp1.created_by == self.pr1.target_population.created_by - assert dmp1.created_at == self.pr1.target_population.created_at - assert dmp1.financial_service_provider == fsp - - dmp2 = DeliveryMechanismPerPaymentPlan.objects.get(payment_plan=pp2) - assert dmp2.delivery_mechanism == self.digital_delivery_mechanism - - # Assert PaymentRecords created - assert Payment.objects.count() == 2 - - ps = Payment.objects.filter(parent_id=pp1.id) - assert ps.count() == 1 - p1 = ps.first() - assert p1.business_area_id == self.pr1.business_area.id - assert p1.status == get_status(self.pr1.status) - assert p1.status_date == self.pr1.status_date - assert p1.household_id == self.pr1.household.id - assert p1.head_of_household_id == self.pr1.head_of_household.id - assert p1.collector_id == self.pr1.head_of_household.id - assert p1.delivery_type_id == self.pr1.delivery_type.id - assert p1.currency == self.pr1.currency - assert p1.entitlement_quantity == self.pr1.entitlement_quantity - assert p1.entitlement_quantity_usd == self.pr1.entitlement_quantity_usd - assert p1.delivered_quantity == self.pr1.delivered_quantity - assert p1.delivered_quantity_usd == self.pr1.delivered_quantity_usd - assert p1.delivery_date == self.pr1.delivery_date - assert p1.transaction_reference_id == self.pr1.transaction_reference_id - assert p1.transaction_status_blockchain_link == self.pr1.transaction_status_blockchain_link - assert p1.financial_service_provider == fsp - assert p1.program_id == self.pr1.target_population.program_cycle.program_id - assert p1.is_cash_assist is True - assert p1.internal_data == { - "ca_hash_id": str(self.pr1.ca_hash_id), - "full_name": self.pr1.full_name, - "total_persons_covered": self.pr1.total_persons_covered, - "distribution_modality": self.pr1.distribution_modality, - "target_population_cash_assist_id": self.pr1.target_population_cash_assist_id, - "target_population": str(self.pr1.target_population.id), - "entitlement_card_number": self.pr1.entitlement_card_number, - "entitlement_card_status": self.pr1.entitlement_card_status, - "entitlement_card_issue_date": str(self.pr1.entitlement_card_issue_date), - "vision_id": self.pr1.vision_id, - "registration_ca_id": self.pr1.registration_ca_id, - "service_provider": str(self.pr1.service_provider_id), - } - - ps = Payment.objects.filter(parent_id=pp2.id) - assert ps.count() == 1 - p2 = ps.first() - assert p2.delivery_type_id == self.digital_delivery_mechanism.id - - # Assert PaymentVerificationSummary is created - assert PaymentVerificationSummary.objects.count() == 2 - assert PaymentVerificationSummary.objects.filter(payment_plan=pp1).count() == 1 - assert PaymentVerificationSummary.objects.filter(payment_plan=pp2).count() == 1 - - # Assert PaymentVerificationPlan is created - assert PaymentVerificationPlan.objects.count() == 2 - pvp1 = PaymentVerificationPlan.objects.get(payment_plan=pp1) - pvp2 = PaymentVerificationPlan.objects.get(payment_plan=pp2) - assert pvp2.unicef_id == pvp1.unicef_id - - # Assert PaymentVerification is created - assert PaymentVerification.objects.count() == 2 - pv1 = PaymentVerification.objects.get(payment_verification_plan=pvp1) - assert pv1.payment == p1 - - pv2 = PaymentVerification.objects.get(payment_verification_plan=pvp2) - assert pv2.payment == p2 - - # Assert GrievanceTickets updated - assert GrievanceTicket.objects.count() == 2 - assert TicketComplaintDetails.objects.count() == 1 - assert TicketSensitiveDetails.objects.count() == 1 - self.ticket_complaint_details.refresh_from_db() - self.ticket_sensitive_details.refresh_from_db() - assert self.ticket_complaint_details.payment == p1 - assert self.ticket_sensitive_details.payment == p2
- 0 - - 45 + 0