From 4e3a816f3cbde133cb6e71a1cde6f89fc41af26b Mon Sep 17 00:00:00 2001 From: Rachid Flih Date: Fri, 9 Aug 2024 13:59:35 -0700 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20Hris=20Data=20Model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.dev.yml | 32 +- docs/syncwithCode.sh | 2 +- packages/api/prisma/schema.prisma | 500 +++++++++++++++++---- packages/api/scripts/init.sql | 586 +++++++++++++++++++++---- packages/api/swagger/swagger-spec.yaml | 116 ++++- 5 files changed, 1051 insertions(+), 185 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 3c7257365..fe31d4e5d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -263,22 +263,22 @@ services: # volumes: # - pgadmin-data:/var/lib/pgadmin - ngrok: - image: ngrok/ngrok:latest - restart: always - command: - - "start" - - "--all" - - "--config" - - "/etc/ngrok.yml" - volumes: - - ./ngrok.yml:/etc/ngrok.yml - ports: - - 4040:4040 - depends_on: - api: - condition: service_healthy - network_mode: "host" + # ngrok: + # image: ngrok/ngrok:latest + # restart: always + # command: + # - "start" + # - "--all" + # - "--config" + # - "/etc/ngrok.yml" + # volumes: + # - ./ngrok.yml:/etc/ngrok.yml + # ports: + # - 4040:4040 + # depends_on: + # api: + # condition: service_healthy + # network_mode: "host" docs: build: diff --git a/docs/syncwithCode.sh b/docs/syncwithCode.sh index a9feea75c..fcfa30015 100644 --- a/docs/syncwithCode.sh +++ b/docs/syncwithCode.sh @@ -14,7 +14,7 @@ grep '^|' ../packages/api/src/ats/README.md > snippets/ats-catalog.mdx # File Storage grep '^|' ../packages/api/src/filestorage/README.md > snippets/filestorage-catalog.mdx -# File Storage +# Ecommerce grep '^|' ../packages/api/src/ecommerce/README.md > snippets/ecommerce-catalog.mdx npx @mintlify/scraping@latest openapi-file openapi-with-code-samples.yaml -o objects diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index 37071eb07..f6e86b15b 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -16,8 +16,8 @@ model users { first_name String last_name String id_stytch String? @unique(map: "force_stytch_id_unique") - created_at DateTime @default(now()) @db.Timestamp(6) - modified_at DateTime @default(now()) @db.Timestamp(6) + created_at DateTime @default(now()) @db.Timestamptz(6) + modified_at DateTime @default(now()) @db.Timestamptz(6) reset_token String? reset_token_expires_at DateTime? @db.Timestamptz(6) api_keys api_keys[] @@ -31,10 +31,10 @@ model webhook_endpoints { url String secret String active Boolean - created_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) scope String[] id_project String @db.Uuid - last_update DateTime? @db.Timestamp(6) + last_update DateTime? @db.Timestamptz(6) webhook_delivery_attempts webhook_delivery_attempts[] } @@ -61,8 +61,8 @@ model api_keys { projects projects @relation(fields: [id_project], references: [id_project], onDelete: NoAction, onUpdate: NoAction, map: "fk_7") users users @relation(fields: [id_user], references: [id_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_8") - @@index([id_user], map: "fk_2") @@index([id_project], map: "fk_api_keys_projects") + @@index([id_user], map: "fkx_api_keys_user_id") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments @@ -105,8 +105,8 @@ model connections { token_type String access_token String? refresh_token String? - expiration_timestamp DateTime? @db.Timestamp(6) - created_at DateTime @db.Timestamp(6) + expiration_timestamp DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) connection_token String? id_project String @db.Uuid id_linked_user String @db.Uuid @@ -130,8 +130,8 @@ model crm_addresses { id_crm_company String? @db.Uuid id_crm_contact String? @db.Uuid id_connection String @db.Uuid - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) owner_type String crm_contacts crm_contacts? @relation(fields: [id_crm_contact], references: [id_crm_contact], onDelete: NoAction, onUpdate: NoAction, map: "fk_14") crm_companies crm_companies? @relation(fields: [id_crm_company], references: [id_crm_company], onDelete: NoAction, onUpdate: NoAction, map: "fk_15") @@ -145,8 +145,8 @@ model crm_companies { name String? industry String? number_of_employees BigInt? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) remote_id String? remote_platform String? id_crm_user String? @db.Uuid @@ -169,8 +169,8 @@ model crm_contacts { id_crm_contact String @id(map: "pk_crm_contacts") @db.Uuid first_name String? last_name String? - created_at DateTime? @db.Timestamp(6) - modified_at DateTime? @db.Timestamp(6) + created_at DateTime? @db.Timestamptz(6) + modified_at DateTime? @db.Timestamptz(6) remote_id String? remote_platform String? id_crm_user String? @db.Uuid @@ -191,8 +191,8 @@ model crm_deals { name String description String? amount BigInt - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) remote_id String? remote_platform String? id_crm_user String? @db.Uuid @@ -214,8 +214,8 @@ model crm_deals { model crm_deals_stages { id_crm_deals_stage String @id(map: "pk_crm_deal_stages") @db.Uuid stage_name String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_linked_user String? @db.Uuid remote_id String? remote_platform String? @@ -229,8 +229,8 @@ model crm_email_addresses { email_address String email_address_type String owner_type String - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_crm_company String? @db.Uuid id_crm_contact String? @db.Uuid id_connection String @db.Uuid @@ -248,8 +248,8 @@ model crm_engagements { type String? direction String? subject String? - start_at DateTime? @db.Timestamp(6) - end_time DateTime? @db.Timestamp(6) + start_at DateTime? @db.Timestamptz(6) + end_time DateTime? @db.Timestamptz(6) remote_id String? id_linked_user String? @db.Uuid remote_platform String? @@ -257,8 +257,8 @@ model crm_engagements { id_crm_user String? @db.Uuid id_connection String @db.Uuid contacts String[] - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) crm_companies crm_companies? @relation(fields: [id_crm_company], references: [id_crm_company], onDelete: NoAction, onUpdate: NoAction, map: "fk_29") crm_users crm_users? @relation(fields: [id_crm_user], references: [id_crm_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_crm_engagement_crm_user") @@ -269,8 +269,8 @@ model crm_engagements { model crm_notes { id_crm_note String @id(map: "pk_crm_notes") @db.Uuid content String - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_crm_company String? @db.Uuid id_crm_contact String? @db.Uuid id_crm_deal String? @db.Uuid @@ -295,8 +295,8 @@ model crm_phone_numbers { phone_number String? phone_type String? owner_type String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_crm_company String? @db.Uuid id_crm_contact String? @db.Uuid id_connection String @db.Uuid @@ -312,10 +312,10 @@ model crm_tasks { subject String? content String? status String? - due_date DateTime? @db.Timestamp(6) - finished_date DateTime? @db.Timestamp(6) - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + due_date DateTime? @db.Timestamptz(6) + finished_date DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_crm_user String? @db.Uuid id_crm_company String? @db.Uuid id_crm_deal String? @db.Uuid @@ -336,8 +336,8 @@ model crm_users { id_crm_user String @id(map: "pk_crm_users") @db.Uuid name String? email String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_linked_user String? @db.Uuid remote_id String? remote_platform String? @@ -388,7 +388,7 @@ model events { method String url String provider String - timestamp DateTime @default(now()) @db.Timestamp(6) + timestamp DateTime @default(now()) @db.Timestamptz(6) id_linked_user String @db.Uuid linked_users linked_users @relation(fields: [id_linked_user], references: [id_linked_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_12") jobs_status_history jobs_status_history[] @@ -398,11 +398,13 @@ model events { } model invite_links { - id_invite_link String @id(map: "pk_invite_links") @db.Uuid - status String - email String? - id_linked_user String @db.Uuid - linked_users linked_users @relation(fields: [id_linked_user], references: [id_linked_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_37") + id_invite_link String @id(map: "pk_invite_links") @db.Uuid + status String + email String? + id_linked_user String @db.Uuid + displayed_verticals String[] + displayed_providers String[] + linked_users linked_users @relation(fields: [id_linked_user], references: [id_linked_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_37") @@index([id_linked_user], map: "fk_invite_link_linkeduserid") } @@ -410,7 +412,7 @@ model invite_links { /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model jobs_status_history { id_jobs_status_history String @id(map: "pk_jobs_status_history") @db.Uuid - timestamp DateTime @default(now()) @db.Timestamp(6) + timestamp DateTime @default(now()) @db.Timestamptz(6) previous_status String new_status String id_event String @db.Uuid @@ -457,7 +459,7 @@ model remote_data { ressource_owner_id String? @unique(map: "force_unique_ressourceownerid") @db.Uuid format String? data String? - created_at DateTime? @db.Timestamp(6) + created_at DateTime? @db.Timestamptz(6) } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments @@ -467,8 +469,8 @@ model tcg_accounts { name String? domains String[] remote_platform String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_linked_user String? @db.Uuid id_connection String @db.Uuid tcg_contacts tcg_contacts[] @@ -482,8 +484,8 @@ model tcg_attachments { file_name String? file_url String? uploader String @db.Uuid - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_linked_user String? @db.Uuid id_tcg_ticket String? @db.Uuid id_tcg_comment String? @db.Uuid @@ -504,8 +506,8 @@ model tcg_collections { collection_type String? parent_collection String? @db.Uuid id_tcg_ticket String? @db.Uuid - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_linked_user String @db.Uuid id_connection String @db.Uuid } @@ -524,8 +526,8 @@ model tcg_comments { id_tcg_contact String? @db.Uuid id_tcg_user String? @db.Uuid id_linked_user String? @db.Uuid - created_at DateTime? @db.Timestamp(6) - modified_at DateTime? @db.Timestamp(6) + created_at DateTime? @db.Timestamptz(6) + modified_at DateTime? @db.Timestamptz(6) id_connection String @db.Uuid tcg_attachments tcg_attachments[] tcg_tickets tcg_tickets? @relation(fields: [id_tcg_ticket], references: [id_tcg_ticket], onDelete: NoAction, onUpdate: NoAction, map: "fk_40_1") @@ -545,8 +547,8 @@ model tcg_contacts { details String? remote_id String? remote_platform String? - created_at DateTime? @db.Timestamp(6) - modified_at DateTime? @db.Timestamp(6) + created_at DateTime? @db.Timestamptz(6) + modified_at DateTime? @db.Timestamptz(6) id_tcg_account String? @db.Uuid id_linked_user String? @db.Uuid id_connection String @db.Uuid @@ -562,8 +564,8 @@ model tcg_tags { remote_id String? remote_platform String? id_tcg_ticket String? @db.Uuid - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_linked_user String? @db.Uuid id_connection String @db.Uuid tcg_tickets tcg_tickets? @relation(fields: [id_tcg_ticket], references: [id_tcg_ticket], onDelete: NoAction, onUpdate: NoAction, map: "fk_48") @@ -589,12 +591,12 @@ model tcg_tickets { name String? status String? description String? - due_date DateTime? @db.Timestamp(6) + due_date DateTime? @db.Timestamptz(6) ticket_type String? parent_ticket String? @db.Uuid tags String[] collections String[] - completed_at DateTime? @db.Timestamp(6) + completed_at DateTime? @db.Timestamptz(6) priority String? assigned_to String[] remote_id String? @@ -602,8 +604,8 @@ model tcg_tickets { creator_type String? id_tcg_user String? @db.Uuid id_linked_user String? @db.Uuid - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_connection String @db.Uuid tcg_attachments tcg_attachments[] tcg_comments tcg_comments[] @@ -622,8 +624,8 @@ model tcg_users { teams String[] id_linked_user String? @db.Uuid id_connection String @db.Uuid - created_at DateTime? @db.Timestamp(6) - modified_at DateTime? @db.Timestamp(6) + created_at DateTime? @db.Timestamptz(6) + modified_at DateTime? @db.Timestamptz(6) tcg_comments tcg_comments[] } @@ -645,9 +647,9 @@ model value { /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model webhook_delivery_attempts { id_webhook_delivery_attempt String @id(map: "pk_webhook_event") @db.Uuid - timestamp DateTime @db.Timestamp(6) + timestamp DateTime @db.Timestamptz(6) status String - next_retry DateTime? @db.Timestamp(6) + next_retry DateTime? @db.Timestamptz(6) attempt_count BigInt id_webhooks_payload String? @db.Uuid id_webhook_endpoint String? @db.Uuid @@ -678,6 +680,8 @@ model connector_sets { crm_zendesk Boolean? crm_close Boolean? fs_box Boolean? + tcg_github Boolean? + ecom_woocommerce Boolean? projects projects[] } @@ -690,18 +694,18 @@ model managed_webhooks { api_version String? active_events String[] remote_signing_secret String? - modified_at DateTime @db.Timestamp(6) - created_at DateTime @db.Timestamp(6) + modified_at DateTime @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) } model fs_drives { id_fs_drive String @id(map: "pk_fs_drives") @db.Uuid drive_url String? name String? - remote_created_at DateTime? @db.Timestamp(6) + remote_created_at DateTime? @db.Timestamptz(6) remote_id String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_connection String @db.Uuid } @@ -714,8 +718,8 @@ model fs_files { remote_id String? id_fs_permission String? @db.Uuid id_fs_folder String? @db.Uuid - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_connection String @db.Uuid @@index([id_fs_folder], map: "fk_fs_file_folderid") @@ -730,8 +734,8 @@ model fs_folders { description String? parent_folder String? @db.Uuid remote_id String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_fs_drive String? @db.Uuid id_connection String @db.Uuid id_fs_permission String? @db.Uuid @@ -748,8 +752,8 @@ model fs_permissions { group String? @db.Uuid type String? roles String[] - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_connection String @db.Uuid } @@ -1048,8 +1052,8 @@ model fs_groups { users String[] remote_id String? remote_was_deleted Boolean - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_connection String @db.Uuid } @@ -1059,8 +1063,8 @@ model fs_users { email String? is_me Boolean remote_id String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) id_connection String @db.Uuid } @@ -1763,3 +1767,347 @@ model ecom_fulfilment_orders { model ecom_order_line_items { id_ecom_order_line_item String @id(map: "pk_106") @db.Uuid } + +model hris_bank_infos { + id_hris_bank_info String @id(map: "pk_hris_bank_infos") @db.Uuid + account_type String? + bank_name String? + account_number String? + routing_number String? + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + id_hris_employee String? @db.Uuid + hris_employees hris_employees? @relation(fields: [id_hris_employee], references: [id_hris_employee], onDelete: NoAction, onUpdate: NoAction, map: "fk_bank_infos_employeeid") + + @@index([id_hris_employee], map: "fkx_bank_infos_employeeid") +} + +model hris_benefits { + id_hris_benefit String @id(map: "pk_hris_benefits") @db.Uuid + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + provider_name String? + id_hris_employee String? @db.Uuid + employee_contribution BigInt? + company_contribution BigInt? + start_date DateTime? @db.Timestamptz(6) + end_date DateTime? @db.Timestamptz(6) + id_hris_employer_benefit String? @db.Uuid + hris_employer_benefits hris_employer_benefits? @relation(fields: [id_hris_employer_benefit], references: [id_hris_employer_benefit], onDelete: NoAction, onUpdate: NoAction, map: "fk_hris_benefit_employer_benefit_id") + hris_employees hris_employees? @relation(fields: [id_hris_employee], references: [id_hris_employee], onDelete: NoAction, onUpdate: NoAction, map: "fk_hris_benefits_employeeid") + + @@index([id_hris_employer_benefit], map: "fkx_hris_benefit_employer_benefit_id") + @@index([id_hris_employee], map: "fkx_hris_benefits_employeeid") +} + +model hris_companies { + id_hris_company String @id(map: "pk_hris_companies") @db.Uuid + legal_name String? + display_name String? + eins String[] + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + hris_employees hris_employees[] +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model hris_dependents { + id_hris_dependents String @id(map: "pk_hris_dependents") @db.Uuid + first_name String? + last_name String? + middle_name String? + relationship String? + date_of_birth DateTime? @db.Date + gender String? + phone_number String? + home_location String? @db.Uuid + is_student Boolean? + ssn String? + id_hris_employee String? @db.Uuid + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + hris_employees hris_employees? @relation(fields: [id_hris_employee], references: [id_hris_employee], onDelete: NoAction, onUpdate: NoAction, map: "fk_hris_dependant_hris_employee_id") + + @@index([id_hris_employee], map: "fkx_hris_dependant_hris_employee_id") +} + +model hris_employee_payroll_runs { + id_hris_employee_payroll_run String @id(map: "pk_hris_employee_payroll_runs") @db.Uuid + id_hris_employee String? @db.Uuid + id_hris_payroll_run String? @db.Uuid + gross_pay BigInt? + net_pay BigInt? + start_date DateTime? @db.Timestamptz(6) + end_date DateTime? @db.Timestamptz(6) + check_date DateTime? @db.Timestamptz(6) + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + hris_payroll_runs hris_payroll_runs? @relation(fields: [id_hris_payroll_run], references: [id_hris_payroll_run], onDelete: NoAction, onUpdate: NoAction, map: "fk_employee_payroll_run_payroll_run_id") + hris_employees hris_employees? @relation(fields: [id_hris_employee], references: [id_hris_employee], onDelete: NoAction, onUpdate: NoAction, map: "fk_hris_employee_payroll_run_employee_id") + hris_employee_payroll_runs_deductions hris_employee_payroll_runs_deductions[] + hris_employee_payroll_runs_earnings hris_employee_payroll_runs_earnings[] + hris_employee_payroll_runs_taxes hris_employee_payroll_runs_taxes[] + + @@index([id_hris_payroll_run], map: "fkx_employee_payroll_run_payroll_run_id") + @@index([id_hris_employee], map: "fkx_hris_employee_payroll_run_employee_id") +} + +model hris_employee_payroll_runs_deductions { + id_hris_employee_payroll_runs_deduction String @id(map: "pk_hris_employee_payroll_runs_deductions") @db.Uuid + remote_id String? + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + id_hris_employee_payroll_run String? @db.Uuid + name String? + employee_deduction BigInt? + company_deduction BigInt? + hris_employee_payroll_runs hris_employee_payroll_runs? @relation(fields: [id_hris_employee_payroll_run], references: [id_hris_employee_payroll_run], onDelete: NoAction, onUpdate: NoAction, map: "fk_hris_employee_payroll_runs_deduction_hris_employee_payroll_i") + + @@index([id_hris_employee_payroll_run], map: "fkx_hris_employee_payroll_runs_deduction_hris_employee_payroll_") +} + +model hris_employee_payroll_runs_earnings { + id_hris_employee_payroll_runs_earning String @id(map: "pk_hris_employee_payroll_runs_earnings") @db.Uuid + amount BigInt? + type String? + id_hris_employee_payroll_run String? @db.Uuid + remote_id String? + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + hris_employee_payroll_runs hris_employee_payroll_runs? @relation(fields: [id_hris_employee_payroll_run], references: [id_hris_employee_payroll_run], onDelete: NoAction, onUpdate: NoAction, map: "fk_hris_employee_payroll_runs_earning_hris_employee_payroll_run") + + @@index([id_hris_employee_payroll_run], map: "fkx_hris_employee_payroll_runs_earning_hris_employee_payroll_ru") +} + +model hris_employee_payroll_runs_taxes { + id_hris_employee_payroll_runs_tax String @id(map: "pk_hris_employee_payroll_runs_taxes") @db.Uuid + name String? + amount BigInt? + employer_tax Boolean? + id_hris_employee_payroll_run String? @db.Uuid + remote_id String? + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + hris_employee_payroll_runs hris_employee_payroll_runs? @relation(fields: [id_hris_employee_payroll_run], references: [id_hris_employee_payroll_run], onDelete: NoAction, onUpdate: NoAction, map: "fk_hris_employee_payroll_run_tax_hris_employee_payroll_run_id") + + @@index([id_hris_employee_payroll_run], map: "fkx_hris_employee_payroll_run_tax_hris_employee_payroll_run_id") +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model hris_employees { + id_hris_employee String @id(map: "pk_hris_employees") @db.Uuid + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + groups String[] + employee_number String? + id_hris_company String? @db.Uuid + first_name String? + last_name String? + preferred_name String? + display_full_name String? + username String? + work_email String? + personal_email String? + mobile_phone_number String? + employments String[] + ssn String? + gender String? + ethnicity String? + marital_status String? + date_of_birth DateTime? @db.Date + start_date DateTime? @db.Date + employment_status String? + termination_date DateTime? @db.Date + avatar_url String? + hris_bank_infos hris_bank_infos[] + hris_benefits hris_benefits[] + hris_dependents hris_dependents[] + hris_employee_payroll_runs hris_employee_payroll_runs[] + hris_companies hris_companies? @relation(fields: [id_hris_company], references: [id_hris_company], onDelete: NoAction, onUpdate: NoAction, map: "fk_employee_companyid") + hris_employments hris_employments[] + hris_time_off_balances hris_time_off_balances[] + hris_timesheet_entries hris_timesheet_entries[] + + @@index([id_hris_company], map: "fkx_employee_companyid") +} + +model hris_employer_benefits { + id_hris_employer_benefit String @id(map: "pk_hris_employer_benefits") @db.Uuid + id_connection String @db.Uuid + benefit_plan_type String? + name String? + description String? + deduction_code String? + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + remote_was_deleted Boolean + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + hris_benefits hris_benefits[] +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model hris_employments { + id_hris_employment String @id(map: "pk_hris_employments") @db.Uuid + job_title String + pay_rate BigInt? + pay_period String? + pay_frequency String? + pay_currency String? + flsa_status String? + effective_date DateTime? @db.Date + employment_type String? + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + id_hris_pay_group String? @db.Uuid + id_hris_employee String? @db.Uuid + hris_employees hris_employees? @relation(fields: [id_hris_employee], references: [id_hris_employee], onDelete: NoAction, onUpdate: NoAction, map: "fk_107") + hris_pay_groups hris_pay_groups? @relation(fields: [id_hris_pay_group], references: [id_hris_pay_group], onDelete: NoAction, onUpdate: NoAction, map: "fk_employments_pay_group_id") + + @@index([id_hris_employee], map: "fk_2") + @@index([id_hris_pay_group], map: "fkx_employments_pay_group_id") +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model hris_groups { + id_hris_group String @id(map: "pk_hris_groups") @db.Uuid + parent_group String? @db.Uuid + name String? + type String? + remote_id String + remote_created_at DateTime @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model hris_locations { + id_hris_location String @id(map: "pk_hris_locations") @db.Uuid + name String? + phone_number String? + street_1 String? + street_2 String? + city String? + state String? + zip_code String? + country String? + location_type String? + remote_id String + remote_created_at DateTime @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid +} + +model hris_pay_groups { + id_hris_pay_group String @id(map: "pk_hris_pay_groups") @db.Uuid + pay_group_name String? + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + hris_employments hris_employments[] +} + +model hris_payroll_runs { + id_hris_payroll_run String @id(map: "pk_hris_payroll_runs") @db.Uuid + run_state String? + run_type String? + start_date DateTime? @db.Timestamptz(6) + end_date DateTime? @db.Timestamptz(6) + check_date DateTime? @db.Timestamptz(6) + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + hris_employee_payroll_runs hris_employee_payroll_runs[] +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model hris_time_off { + id_hris_time_off String @id(map: "pk_hris_time_off") @db.Uuid + employee String? @db.Uuid + approver String? @db.Uuid + status String? + employee_note String? + units String? + amount BigInt? + request_type String? + start_time DateTime? @db.Timestamptz(6) + end_time DateTime? @db.Timestamptz(6) + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid +} + +model hris_time_off_balances { + id_hris_time_off_balance String @id(map: "pk_hris_time_off_balances") @db.Uuid + balance BigInt? + id_hris_employee String? @db.Uuid + used BigInt? + policy_type String? + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + hris_employees hris_employees? @relation(fields: [id_hris_employee], references: [id_hris_employee], onDelete: NoAction, onUpdate: NoAction, map: "fk_hris_timeoff_balance_hris_employee_id") + + @@index([id_hris_employee], map: "fkx_hris_timeoff_balance_hris_employee_id") +} + +model hris_timesheet_entries { + id_hris_timesheet_entry String @id(map: "pk_hris_timesheet_entries") @db.Uuid + hours_worked BigInt? + start_time DateTime? @db.Timestamptz(6) + end_time DateTime? @db.Timestamptz(6) + id_hris_employee String? @db.Uuid + remote_id String? + remote_created_at DateTime? @db.Timestamptz(6) + created_at DateTime @db.Timestamptz(6) + modified_at DateTime @db.Timestamptz(6) + remote_was_deleted Boolean + id_connection String @db.Uuid + hris_employees hris_employees? @relation(fields: [id_hris_employee], references: [id_hris_employee], onDelete: NoAction, onUpdate: NoAction, map: "fk_timesheet_entry_employee_id") + + @@index([id_hris_employee], map: "fkx_timesheet_entry_employee_id") +} diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index 4a7ff8d0f..ec0ad6fdf 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -28,10 +28,10 @@ CREATE TABLE webhook_endpoints url text NOT NULL, secret text NOT NULL, active boolean NOT NULL, - created_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, "scope" text[] NULL, id_project uuid NOT NULL, - last_update timestamp NULL, + last_update timestamp with time zone NULL, CONSTRAINT PK_webhook_endpoint PRIMARY KEY ( id_webhook_endpoint ) ); COMMENT ON COLUMN webhook_endpoints.endpoint_description IS 'An optional description of what the webhook is used for'; @@ -49,8 +49,8 @@ CREATE TABLE users first_name text NOT NULL, last_name text NOT NULL, id_stytch text NULL, - created_at timestamp NOT NULL DEFAULT NOW(), - modified_at timestamp NOT NULL DEFAULT NOW(), + created_at timestamp with time zone NOT NULL DEFAULT NOW(), + modified_at timestamp with time zone NOT NULL DEFAULT NOW(), reset_token text NULL, reset_token_expires_at timestamp with time zone NULL, CONSTRAINT PK_users PRIMARY KEY ( id_user ), @@ -75,8 +75,8 @@ CREATE TABLE tcg_users teams text[] NULL, id_linked_user uuid NULL, id_connection uuid NOT NULL, - created_at timestamp NULL, - modified_at timestamp NULL, + created_at timestamp with time zone NULL, + modified_at timestamp with time zone NULL, CONSTRAINT PK_tcg_users PRIMARY KEY ( id_tcg_user ) ); COMMENT ON TABLE tcg_users IS 'The User object is used to represent an employee within a company.'; @@ -108,8 +108,8 @@ CREATE TABLE tcg_collections collection_type text NULL, parent_collection uuid NULL, id_tcg_ticket uuid NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_linked_user uuid NOT NULL, id_connection uuid NOT NULL, CONSTRAINT PK_tcg_collections PRIMARY KEY ( id_tcg_collection ) @@ -123,8 +123,8 @@ CREATE TABLE tcg_accounts name text NULL, domains text[] NULL, remote_platform text NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_linked_user uuid NULL, id_connection uuid NOT NULL, CONSTRAINT PK_tcg_account PRIMARY KEY ( id_tcg_account ) @@ -138,7 +138,7 @@ CREATE TABLE remote_data ressource_owner_id uuid NULL, "format" text NULL, data text NULL, - created_at timestamp NULL, + created_at timestamp with time zone NULL, CONSTRAINT PK_remote_data PRIMARY KEY ( id_remote_data ), CONSTRAINT Force_Unique_ressourceOwnerId UNIQUE ( ressource_owner_id ) ); @@ -155,13 +155,142 @@ CREATE TABLE managed_webhooks api_version text NULL, active_events text[] NULL, remote_signing_secret text NULL, - modified_at timestamp NOT NULL, - created_at timestamp NOT NULL, + modified_at timestamp with time zone NOT NULL, + created_at timestamp with time zone NOT NULL, CONSTRAINT PK_managed_webhooks PRIMARY KEY ( id_managed_webhook ) ); COMMENT ON COLUMN managed_webhooks.endpoint IS 'UUID that will be used in the final URL to help identify where to route data ex: api.panora.dev/mw/{managed_webhooks.endpoint}'; +-- ************************************** hris_time_off +CREATE TABLE hris_time_off +( + id_hris_time_off uuid NOT NULL, + employee uuid NULL, + approver uuid NULL, + status text NULL, + employee_note text NULL, + units text NULL, + amount bigint NULL, + request_type text NULL, + start_time timestamp with time zone NULL, + end_time timestamp with time zone NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_time_off PRIMARY KEY ( id_hris_time_off ) +); +COMMENT ON COLUMN hris_time_off.employee IS 'id_hris_employee of the employee requesting the time off'; +COMMENT ON COLUMN hris_time_off.approver IS 'id_hris_employee of the manager approving the time off'; + +-- ************************************** hris_payroll_runs +CREATE TABLE hris_payroll_runs +( + id_hris_payroll_run uuid NOT NULL, + run_state text NULL, + run_type text NULL, + start_date timestamp with time zone NULL, + end_date timestamp with time zone NULL, + check_date timestamp with time zone NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_payroll_runs PRIMARY KEY ( id_hris_payroll_run ) +); + +-- ************************************** hris_pay_groups +CREATE TABLE hris_pay_groups +( + id_hris_pay_group uuid NOT NULL, + pay_group_name text NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_pay_groups PRIMARY KEY ( id_hris_pay_group ) +); + +-- ************************************** hris_locations +CREATE TABLE hris_locations +( + id_hris_location uuid NOT NULL, + name text NULL, + phone_number text NULL, + street_1 text NULL, + street_2 text NULL, + city text NULL, + "state" text NULL, + zip_code text NULL, + country text NULL, + location_type text NULL, + remote_id text NOT NULL, + remote_created_at timestamp with time zone NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_locations PRIMARY KEY ( id_hris_location ) +); +COMMENT ON COLUMN hris_locations.location_type IS 'HOME, WORK'; + +-- ************************************** hris_groups +CREATE TABLE hris_groups +( + id_hris_group uuid NOT NULL, + parent_group uuid NULL, + name text NULL, + type text NULL, + remote_id text NOT NULL, + remote_created_at timestamp with time zone NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_groups PRIMARY KEY ( id_hris_group ) +); +COMMENT ON COLUMN hris_groups.parent_group IS 'id_hris_group of parent group'; + +-- ************************************** hris_employer_benefits +CREATE TABLE hris_employer_benefits +( + id_hris_employer_benefit uuid NOT NULL, + id_connection uuid NOT NULL, + benefit_plan_type text NULL, + name text NULL, + description text NULL, + deduction_code text NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + remote_was_deleted boolean NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + CONSTRAINT PK_hris_employer_benefits PRIMARY KEY ( id_hris_employer_benefit ) +); + +-- ************************************** hris_companies +CREATE TABLE hris_companies +( + id_hris_company uuid NOT NULL, + legal_name text NULL, + display_name text NULL, + eins text[] NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_companies PRIMARY KEY ( id_hris_company ) +); + -- ************************************** fs_users CREATE TABLE fs_users ( @@ -170,8 +299,8 @@ CREATE TABLE fs_users email text NULL, is_me boolean NOT NULL, remote_id text NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_connection uuid NOT NULL, CONSTRAINT PK_fs_users PRIMARY KEY ( id_fs_user ) ); @@ -206,8 +335,8 @@ CREATE TABLE fs_permissions "group" uuid NULL, type text NULL, roles text[] NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_connection uuid NOT NULL, CONSTRAINT PK_fs_permissions PRIMARY KEY ( id_fs_permission ) ); @@ -221,8 +350,8 @@ CREATE TABLE fs_groups users text[] NULL, remote_id text NULL, remote_was_deleted boolean NOT NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_connection uuid NOT NULL, CONSTRAINT PK_fs_groups PRIMARY KEY ( id_fs_group ) ); @@ -234,10 +363,10 @@ CREATE TABLE fs_drives id_fs_drive uuid NOT NULL, drive_url text NULL, name text NULL, - remote_created_at timestamp NULL, + remote_created_at timestamp with time zone NULL, remote_id text NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_connection uuid NOT NULL, CONSTRAINT PK_fs_drives PRIMARY KEY ( id_fs_drive ) ); @@ -335,8 +464,8 @@ CREATE TABLE crm_users id_crm_user uuid NOT NULL, name text NULL, email text NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_linked_user uuid NULL, remote_id text NULL, remote_platform text NULL, @@ -349,8 +478,8 @@ CREATE TABLE crm_deals_stages ( id_crm_deals_stage uuid NOT NULL, stage_name text NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_linked_user uuid NULL, remote_id text NULL, remote_platform text NULL, @@ -374,8 +503,9 @@ CREATE TABLE connector_sets crm_zendesk boolean NULL, crm_close boolean NULL, fs_box boolean NULL, - tcg_github boolean NULL, -CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set ) + tcg_github boolean NULL, + ecom_woocommerce boolean NULL, + CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set ) ); -- ************************************** connection_strategies @@ -763,12 +893,12 @@ CREATE TABLE tcg_tickets name text NULL, status text NULL, description text NULL, - due_date timestamp NULL, + due_date timestamp with time zone NULL, ticket_type text NULL, parent_ticket uuid NULL, tags text[] NULL, collections text[] NULL, - completed_at timestamp NULL, + completed_at timestamp with time zone NULL, priority text NULL, assigned_to text[] NULL, remote_id text NULL, @@ -776,8 +906,8 @@ CREATE TABLE tcg_tickets creator_type text NULL, id_tcg_user uuid NULL, id_linked_user uuid NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_connection uuid NOT NULL, CONSTRAINT PK_tcg_tickets PRIMARY KEY ( id_tcg_ticket ) ); @@ -802,8 +932,8 @@ CREATE TABLE tcg_contacts details text NULL, remote_id text NULL, remote_platform text NULL, - created_at timestamp NULL, - modified_at timestamp NULL, + created_at timestamp with time zone NULL, + modified_at timestamp with time zone NULL, id_tcg_account uuid NULL, id_linked_user uuid NULL, id_connection uuid NOT NULL, @@ -837,6 +967,48 @@ COMMENT ON COLUMN projects.sync_mode IS 'Can be realtime or periodic_pull'; COMMENT ON COLUMN projects.pull_frequency IS 'Frequency in seconds for pulls ex 3600 for one hour'; +-- ************************************** hris_employees +CREATE TABLE hris_employees +( + id_hris_employee uuid NOT NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + groups text[] NULL, + employee_number text NULL, + id_hris_company uuid NULL, + first_name text NULL, + last_name text NULL, + preferred_name text NULL, + display_full_name text NULL, + username text NULL, + work_email text NULL, + personal_email text NULL, + mobile_phone_number text NULL, + employments text[] NULL, + ssn text NULL, + gender text NULL, + ethnicity text NULL, + marital_status text NULL, + date_of_birth date NULL, + start_date date NULL, + employment_status text NULL, + termination_date date NULL, + avatar_url text NULL, + CONSTRAINT PK_hris_employees PRIMARY KEY ( id_hris_employee ), + CONSTRAINT FK_employee_companyId FOREIGN KEY ( id_hris_company ) REFERENCES hris_companies ( id_hris_company ) +); +CREATE INDEX FKX_employee_companyId ON hris_employees +( + id_hris_company +); +COMMENT ON COLUMN hris_employees.groups IS 'array of id_hris_group'; +COMMENT ON COLUMN hris_employees.employments IS 'array of id_hris_employment'; +COMMENT ON COLUMN hris_employees.gender IS 'The employee''s gender. Possible options are: MALE, FEMALE, NON-BINARY, OTHER, or PREFER_NOT_TO_DISCLOSE. If the original value doesn''t correspond to any of these categories, it will be returned as is.'; + -- ************************************** fs_folders CREATE TABLE fs_folders ( @@ -847,8 +1019,8 @@ CREATE TABLE fs_folders description text NULL, parent_folder uuid NULL, remote_id text NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_fs_drive uuid NULL, id_connection uuid NOT NULL, id_fs_permission uuid NULL, @@ -921,8 +1093,8 @@ CREATE TABLE crm_contacts id_crm_contact uuid NOT NULL, first_name text NULL, last_name text NULL, - created_at timestamp NULL, - modified_at timestamp NULL, + created_at timestamp with time zone NULL, + modified_at timestamp with time zone NULL, remote_id text NULL, remote_platform text NULL, id_crm_user uuid NULL, @@ -944,8 +1116,8 @@ CREATE TABLE crm_companies name text NULL, industry text NULL, number_of_employees bigint NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, remote_id text NULL, remote_platform text NULL, id_crm_user uuid NULL, @@ -1401,8 +1573,8 @@ CREATE TABLE tcg_tags remote_id text NULL, remote_platform text NULL, id_tcg_ticket uuid NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_linked_user uuid NULL, id_connection uuid NOT NULL, CONSTRAINT PK_tcg_tags PRIMARY KEY ( id_tcg_tag ), @@ -1428,8 +1600,8 @@ CREATE TABLE tcg_comments id_tcg_contact uuid NULL, id_tcg_user uuid NULL, id_linked_user uuid NULL, - created_at timestamp NULL, - modified_at timestamp NULL, + created_at timestamp with time zone NULL, + modified_at timestamp with time zone NULL, id_connection uuid NOT NULL, CONSTRAINT PK_tcg_comments PRIMARY KEY ( id_tcg_comment ), CONSTRAINT FK_41 FOREIGN KEY ( id_tcg_contact ) REFERENCES tcg_contacts ( id_tcg_contact ), @@ -1468,6 +1640,198 @@ CREATE INDEX FK_proectID_linked_users ON linked_users COMMENT ON COLUMN linked_users.linked_user_origin_id IS 'id of the customer, in our customers own systems'; COMMENT ON COLUMN linked_users.alias IS 'human-readable alias, for UI (ex ACME company)'; +-- ************************************** hris_timesheet_entries +CREATE TABLE hris_timesheet_entries +( + id_hris_timesheet_entry uuid NOT NULL, + hours_worked bigint NULL, + start_time timestamp with time zone NULL, + end_time timestamp with time zone NULL, + id_hris_employee uuid NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_timesheet_entries PRIMARY KEY ( id_hris_timesheet_entry ), + CONSTRAINT FK_timesheet_entry_employee_Id FOREIGN KEY ( id_hris_employee ) REFERENCES hris_employees ( id_hris_employee ) +); +CREATE INDEX FKx_timesheet_entry_employee_Id ON hris_timesheet_entries +( + id_hris_employee +); + +-- ************************************** hris_time_off_balances +CREATE TABLE hris_time_off_balances +( + id_hris_time_off_balance uuid NOT NULL, + balance bigint NULL, + id_hris_employee uuid NULL, + used bigint NULL, + policy_type text NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_time_off_balances PRIMARY KEY ( id_hris_time_off_balance ), + CONSTRAINT FK_hris_timeoff_balance_hris_employee_ID FOREIGN KEY ( id_hris_employee ) REFERENCES hris_employees ( id_hris_employee ) +); +CREATE INDEX FKx_hris_timeoff_balance_hris_employee_ID ON hris_time_off_balances +( + id_hris_employee +); + +-- ************************************** hris_employments +CREATE TABLE hris_employments +( + id_hris_employment uuid NOT NULL, + job_title text NOT NULL, + pay_rate bigint NULL, + pay_period text NULL, + pay_frequency text NULL, + pay_currency text NULL, + flsa_status text NULL, + effective_date date NULL, + employment_type text NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + id_hris_pay_group uuid NULL, + id_hris_employee uuid NULL, + CONSTRAINT PK_hris_employments PRIMARY KEY ( id_hris_employment ), + CONSTRAINT FK_107 FOREIGN KEY ( id_hris_employee ) REFERENCES hris_employees ( id_hris_employee ), + CONSTRAINT FK_employments_pay_group_Id FOREIGN KEY ( id_hris_pay_group ) REFERENCES hris_pay_groups ( id_hris_pay_group ) +); +CREATE INDEX FK_2 ON hris_employments +( + id_hris_employee +); +CREATE INDEX FKx_employments_pay_group_Id ON hris_employments +( + id_hris_pay_group +); +COMMENT ON COLUMN hris_employments.pay_rate IS 'pay rate, in usd, in cents'; +COMMENT ON COLUMN hris_employments.pay_period IS 'The time period covered by this pay rate. Available options are: HOUR, DAY, WEEK, EVERY_TWO_WEEKS, SEMIMONTHLY, MONTH, QUARTER, EVERY_SIX_MONTHS, and YEAR. If there is no direct match, the original value provided will be returned as is.'; + +-- ************************************** hris_employee_payroll_runs +CREATE TABLE hris_employee_payroll_runs +( + id_hris_employee_payroll_run uuid NOT NULL, + id_hris_employee uuid NULL, + id_hris_payroll_run uuid NULL, + gross_pay bigint NULL, + net_pay bigint NULL, + start_date timestamp with time zone NULL, + end_date timestamp with time zone NULL, + check_date timestamp with time zone NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_employee_payroll_runs PRIMARY KEY ( id_hris_employee_payroll_run ), + CONSTRAINT FK_employee_payroll_run_payroll_run_Id FOREIGN KEY ( id_hris_payroll_run ) REFERENCES hris_payroll_runs ( id_hris_payroll_run ), + CONSTRAINT FK_hris_employee_payroll_run_employee_Id FOREIGN KEY ( id_hris_employee ) REFERENCES hris_employees ( id_hris_employee ) +); +CREATE INDEX FKx_employee_payroll_run_payroll_run_Id ON hris_employee_payroll_runs +( + id_hris_payroll_run +); +CREATE INDEX FKx_hris_employee_payroll_run_employee_Id ON hris_employee_payroll_runs +( + id_hris_employee +); + +-- ************************************** hris_dependents +CREATE TABLE hris_dependents +( + id_hris_dependents uuid NOT NULL, + first_name text NULL, + last_name text NULL, + middle_name text NULL, + relationship text NULL, + date_of_birth date NULL, + gender text NULL, + phone_number text NULL, + home_location uuid NULL, + is_student boolean NULL, + ssn text NULL, + id_hris_employee uuid NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + CONSTRAINT PK_hris_dependents PRIMARY KEY ( id_hris_dependents ), + CONSTRAINT FK_hris_dependant_hris_employee_Id FOREIGN KEY ( id_hris_employee ) REFERENCES hris_employees ( id_hris_employee ) +); +CREATE INDEX FKx_hris_dependant_hris_employee_Id ON hris_dependents +( + id_hris_employee +); +COMMENT ON COLUMN hris_dependents.home_location IS 'contains a id_hris_location'; + +-- ************************************** hris_benefits +CREATE TABLE hris_benefits +( + id_hris_benefit uuid NOT NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + provider_name text NULL, + id_hris_employee uuid NULL, + employee_contribution bigint NULL, + company_contribution bigint NULL, + start_date timestamp with time zone NULL, + end_date timestamp with time zone NULL, + id_hris_employer_benefit uuid NULL, + CONSTRAINT PK_hris_benefits PRIMARY KEY ( id_hris_benefit ), + CONSTRAINT FK_hris_benefit_employer_benefit_Id FOREIGN KEY ( id_hris_employer_benefit ) REFERENCES hris_employer_benefits ( id_hris_employer_benefit ), + CONSTRAINT FK_hris_benefits_employeeId FOREIGN KEY ( id_hris_employee ) REFERENCES hris_employees ( id_hris_employee ) +); +CREATE INDEX FKx_hris_benefit_employer_benefit_Id ON hris_benefits +( + id_hris_employer_benefit +); +CREATE INDEX FKx_hris_benefits_employeeId ON hris_benefits +( + id_hris_employee +); + +-- ************************************** hris_bank_infos +CREATE TABLE hris_bank_infos +( + id_hris_bank_info uuid NOT NULL, + account_type text NULL, + bank_name text NULL, + account_number text NULL, + routing_number text NULL, + remote_id text NULL, + remote_created_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + remote_was_deleted boolean NOT NULL, + id_connection uuid NOT NULL, + id_hris_employee uuid NULL, + CONSTRAINT PK_hris_bank_infos PRIMARY KEY ( id_hris_bank_info ), + CONSTRAINT FK_bank_infos_employeeId FOREIGN KEY ( id_hris_employee ) REFERENCES hris_employees ( id_hris_employee ) +); +CREATE INDEX FKX_bank_infos_employeeId ON hris_bank_infos +( + id_hris_employee +); + -- ************************************** fs_files CREATE TABLE fs_files ( @@ -1479,8 +1843,8 @@ CREATE TABLE fs_files remote_id text NULL, id_fs_permission uuid NULL, id_fs_folder uuid NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_connection uuid NOT NULL, CONSTRAINT PK_fs_files PRIMARY KEY ( id_fs_file ) ); @@ -1553,8 +1917,8 @@ CREATE TABLE crm_phone_numbers phone_number text NULL, phone_type text NULL, owner_type text NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_crm_company uuid NULL, id_crm_contact uuid NULL, id_connection uuid NOT NULL, @@ -1580,8 +1944,8 @@ CREATE TABLE crm_engagements type text NULL, direction text NULL, subject text NULL, - start_at timestamp NULL, - end_time timestamp NULL, + start_at timestamp with time zone NULL, + end_time timestamp with time zone NULL, remote_id text NULL, id_linked_user uuid NULL, remote_platform text NULL, @@ -1589,8 +1953,8 @@ CREATE TABLE crm_engagements id_crm_user uuid NULL, id_connection uuid NOT NULL, contacts text[] NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, CONSTRAINT PK_crm_engagement PRIMARY KEY ( id_crm_engagement ), CONSTRAINT FK_crm_engagement_crm_user FOREIGN KEY ( id_crm_user ) REFERENCES crm_users ( id_crm_user ), CONSTRAINT FK_29 FOREIGN KEY ( id_crm_company ) REFERENCES crm_companies ( id_crm_company ) @@ -1614,8 +1978,8 @@ CREATE TABLE crm_email_addresses email_address text NOT NULL, email_address_type text NOT NULL, owner_type text NOT NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_crm_company uuid NULL, id_crm_contact uuid NULL, id_connection uuid NOT NULL, @@ -1640,8 +2004,8 @@ CREATE TABLE crm_deals name text NOT NULL, description text NULL, amount bigint NOT NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, remote_id text NULL, remote_platform text NULL, id_crm_user uuid NULL, @@ -1682,8 +2046,8 @@ CREATE TABLE crm_addresses id_crm_company uuid NULL, id_crm_contact uuid NULL, id_connection uuid NOT NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, owner_type text NOT NULL, CONSTRAINT PK_crm_addresses PRIMARY KEY ( id_crm_address ), CONSTRAINT FK_14 FOREIGN KEY ( id_crm_contact ) REFERENCES crm_contacts ( id_crm_contact ), @@ -1768,17 +2132,17 @@ CREATE TABLE api_keys id_user uuid NOT NULL, CONSTRAINT id_ PRIMARY KEY ( id_api_key ), CONSTRAINT unique_api_keys UNIQUE ( api_key_hash ), - CONSTRAINT FK_8 FOREIGN KEY ( id_user ) REFERENCES users ( id_user ), - CONSTRAINT FK_7 FOREIGN KEY ( id_project ) REFERENCES projects ( id_project ) -); -CREATE INDEX FK_2 ON api_keys -( - id_user + CONSTRAINT FK_api_key_project_Id FOREIGN KEY ( id_project ) REFERENCES projects ( id_project ), + CONSTRAINT FK_api_keys_user_Id FOREIGN KEY ( id_user ) REFERENCES users ( id_user ) ); CREATE INDEX FK_api_keys_projects ON api_keys ( id_project ); +CREATE INDEX FKx_api_keys_user_Id ON api_keys +( + id_user +); -- ************************************** acc_purchase_orders_line_items CREATE TABLE acc_purchase_orders_line_items @@ -2022,8 +2386,8 @@ CREATE TABLE tcg_attachments file_name text NULL, file_url text NULL, uploader uuid NOT NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_linked_user uuid NULL, id_tcg_ticket uuid NULL, id_tcg_comment uuid NULL, @@ -2047,10 +2411,12 @@ COMMENT ON COLUMN tcg_attachments.id_tcg_ticket IS 'For cases where the ticketin -- ************************************** invite_links CREATE TABLE invite_links ( - id_invite_link uuid NOT NULL, - status text NOT NULL, - email text NULL, - id_linked_user uuid NOT NULL, + id_invite_link uuid NOT NULL, + status text NOT NULL, + email text NULL, + id_linked_user uuid NOT NULL, + displayed_verticals text[] NULL, + displayed_providers text[] NULL, CONSTRAINT PK_invite_links PRIMARY KEY ( id_invite_link ), CONSTRAINT FK_37 FOREIGN KEY ( id_linked_user ) REFERENCES linked_users ( id_linked_user ) ); @@ -2059,6 +2425,62 @@ CREATE INDEX FK_invite_link_linkedUserID ON invite_links id_linked_user ); +-- ************************************** hris_employee_payroll_runs_taxes +CREATE TABLE hris_employee_payroll_runs_taxes +( + id_hris_employee_payroll_runs_tax uuid NOT NULL, + name text NULL, + amount bigint NULL, + employer_tax boolean NULL, + id_hris_employee_payroll_run uuid NULL, + remote_id text NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + CONSTRAINT PK_hris_employee_payroll_runs_taxes PRIMARY KEY ( id_hris_employee_payroll_runs_tax ), + CONSTRAINT FK_hris_employee_payroll_run_tax_hris_employee_payroll_run_id FOREIGN KEY ( id_hris_employee_payroll_run ) REFERENCES hris_employee_payroll_runs ( id_hris_employee_payroll_run ) +); +CREATE INDEX FKx_hris_employee_payroll_run_tax_hris_employee_payroll_run_id ON hris_employee_payroll_runs_taxes +( + id_hris_employee_payroll_run +); + +-- ************************************** hris_employee_payroll_runs_earnings +CREATE TABLE hris_employee_payroll_runs_earnings +( + id_hris_employee_payroll_runs_earning uuid NOT NULL, + amount bigint NULL, + type text NULL, + id_hris_employee_payroll_run uuid NULL, + remote_id text NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + CONSTRAINT PK_hris_employee_payroll_runs_earnings PRIMARY KEY ( id_hris_employee_payroll_runs_earning ), + CONSTRAINT FK_hris_employee_payroll_runs_earning_hris_employee_payroll_run_Id FOREIGN KEY ( id_hris_employee_payroll_run ) REFERENCES hris_employee_payroll_runs ( id_hris_employee_payroll_run ) +); +CREATE INDEX FKx_hris_employee_payroll_runs_earning_hris_employee_payroll_run_Id ON hris_employee_payroll_runs_earnings +( + id_hris_employee_payroll_run +); + +-- ************************************** hris_employee_payroll_runs_deductions +CREATE TABLE hris_employee_payroll_runs_deductions +( + id_hris_employee_payroll_runs_deduction uuid NOT NULL, + remote_id text NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, + id_hris_employee_payroll_run uuid NULL, + name text NULL, + employee_deduction bigint NULL, + company_deduction bigint NULL, + CONSTRAINT PK_hris_employee_payroll_runs_deductions PRIMARY KEY ( id_hris_employee_payroll_runs_deduction ), + CONSTRAINT FK_hris_employee_payroll_runs_deduction_hris_employee_payroll_Id FOREIGN KEY ( id_hris_employee_payroll_run ) REFERENCES hris_employee_payroll_runs ( id_hris_employee_payroll_run ) +); +CREATE INDEX FKx_hris_employee_payroll_runs_deduction_hris_employee_payroll_Id ON hris_employee_payroll_runs_deductions +( + id_hris_employee_payroll_run +); + -- ************************************** events CREATE TABLE events ( @@ -2071,7 +2493,7 @@ CREATE TABLE events method text NOT NULL, url text NOT NULL, provider text NOT NULL, - "timestamp" timestamp NOT NULL DEFAULT NOW(), + "timestamp" timestamp with time zone NOT NULL DEFAULT NOW(), id_linked_user uuid NOT NULL, CONSTRAINT PK_jobs PRIMARY KEY ( id_event ), CONSTRAINT FK_12 FOREIGN KEY ( id_linked_user ) REFERENCES linked_users ( id_linked_user ) @@ -2090,10 +2512,10 @@ CREATE TABLE crm_tasks subject text NULL, content text NULL, status text NULL, - due_date timestamp NULL, - finished_date timestamp NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + due_date timestamp with time zone NULL, + finished_date timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_crm_user uuid NULL, id_crm_company uuid NULL, id_crm_deal uuid NULL, @@ -2124,8 +2546,8 @@ CREATE TABLE crm_notes ( id_crm_note uuid NOT NULL, content text NOT NULL, - created_at timestamp NOT NULL, - modified_at timestamp NOT NULL, + created_at timestamp with time zone NOT NULL, + modified_at timestamp with time zone NOT NULL, id_crm_company uuid NULL, id_crm_contact uuid NULL, id_crm_deal uuid NULL, @@ -2167,8 +2589,8 @@ CREATE TABLE connections token_type text NOT NULL, access_token text NULL, refresh_token text NULL, - expiration_timestamp timestamp NULL, - created_at timestamp NOT NULL, + expiration_timestamp timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, connection_token text NULL, id_project uuid NOT NULL, id_linked_user uuid NOT NULL, @@ -2313,9 +2735,9 @@ CREATE INDEX FK_acc_expense_expense_lines_index ON acc_expense_lines CREATE TABLE webhook_delivery_attempts ( id_webhook_delivery_attempt uuid NOT NULL, - "timestamp" timestamp NOT NULL, + "timestamp" timestamp with time zone NOT NULL, status text NOT NULL, - next_retry timestamp NULL, + next_retry timestamp with time zone NULL, attempt_count bigint NOT NULL, id_webhooks_payload uuid NULL, id_webhook_endpoint uuid NULL, @@ -2354,7 +2776,7 @@ can be 0 1 2 3 4 5 6'; CREATE TABLE jobs_status_history ( id_jobs_status_history uuid NOT NULL, - "timestamp" timestamp NOT NULL DEFAULT NOW(), + "timestamp" timestamp with time zone NOT NULL DEFAULT NOW(), previous_status text NOT NULL, new_status text NOT NULL, id_event uuid NOT NULL, diff --git a/packages/api/swagger/swagger-spec.yaml b/packages/api/swagger/swagger-spec.yaml index bb601fcfc..7fc0ec35f 100644 --- a/packages/api/swagger/swagger-spec.yaml +++ b/packages/api/swagger/swagger-spec.yaml @@ -107,6 +107,8 @@ paths: schema: type: string responses: + '200': + description: '' '201': description: '' content: @@ -127,6 +129,8 @@ paths: schema: type: string responses: + '200': + description: '' '201': description: '' content: @@ -157,6 +161,8 @@ paths: type: object additionalProperties: true description: Dynamic event payload + '201': + description: '' tags: *ref_0 x-speakeasy-group: webhooks /ticketing/tickets: @@ -183,6 +189,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -307,6 +314,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -398,6 +406,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -487,6 +496,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -622,6 +632,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -746,6 +757,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -870,6 +882,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -993,6 +1006,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -1117,6 +1131,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -1241,6 +1256,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -1332,6 +1348,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -1455,6 +1472,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -1546,6 +1564,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -1638,6 +1657,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -1765,6 +1785,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -1856,6 +1877,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -2155,11 +2177,15 @@ paths: required: false in: query schema: + minimum: 1 + default: 1 type: number - name: limit required: false in: query schema: + minimum: 1 + default: 10 type: number responses: '200': @@ -2195,6 +2221,12 @@ paths: application/json: schema: type: object + '201': + description: '' + content: + application/json: + schema: + type: object tags: &ref_21 - passthrough x-speakeasy-group: passthrough @@ -2240,6 +2272,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -2331,6 +2364,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -2422,6 +2456,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -2513,6 +2548,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -2604,6 +2640,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -2696,6 +2733,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -2819,6 +2857,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -2911,6 +2950,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3002,6 +3042,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3093,6 +3134,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3184,6 +3226,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3275,6 +3318,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3366,6 +3410,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3489,6 +3534,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3580,6 +3626,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3709,6 +3756,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3840,6 +3888,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -3969,6 +4018,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4098,6 +4148,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4192,6 +4243,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4286,6 +4338,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4414,6 +4467,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4508,6 +4562,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4636,6 +4691,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4730,6 +4786,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4854,6 +4911,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -4978,6 +5036,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5102,6 +5161,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5226,6 +5286,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5317,6 +5378,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5441,6 +5503,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5533,6 +5596,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5624,6 +5688,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5715,6 +5780,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5806,6 +5872,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5897,6 +5964,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -5988,6 +6056,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6079,6 +6148,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6170,6 +6240,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6259,6 +6330,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6383,6 +6455,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6474,6 +6547,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6599,6 +6673,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6691,6 +6766,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6783,6 +6859,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6875,6 +6952,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -6999,6 +7077,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7091,6 +7170,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7215,6 +7295,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7307,6 +7388,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7431,6 +7513,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7522,6 +7605,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7647,6 +7731,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7771,6 +7856,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7863,6 +7949,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -7988,6 +8075,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8079,6 +8167,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8171,6 +8260,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8263,6 +8353,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8355,6 +8446,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8446,6 +8538,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8570,6 +8663,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8694,6 +8788,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8785,6 +8880,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8876,6 +8972,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -8998,6 +9095,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -9120,6 +9218,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -9209,6 +9308,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -9299,6 +9399,7 @@ paths: example: 10 description: Set to get the number of records. schema: + default: 50 type: number - name: cursor required: false @@ -9429,7 +9530,6 @@ components: password_hash: type: string required: - - id_user - email - password_hash Connection: @@ -9517,8 +9617,8 @@ components: description: The unique UUID of the webhook. endpoint_description: type: string - example: Webhook to receive connection events nullable: true + example: Webhook to receive connection events description: The description of the webhook. url: type: string @@ -9556,8 +9656,8 @@ components: last_update: format: date-time type: string - example: '2024-10-01T12:00:00Z' nullable: true + example: '2024-10-01T12:00:00Z' description: The last update date of the webhook. required: - id_webhook_endpoint @@ -9592,7 +9692,6 @@ components: type: string required: - url - - description - scope SignatureVerificationDto: type: object @@ -11725,8 +11824,6 @@ components: - id_project - name - sync_mode - - pull_frequency - - redirect_url - id_user - id_connector_set CreateProjectDto: @@ -12095,10 +12192,10 @@ components: type: object properties: method: - type: string enum: - GET - POST + type: string path: type: string nullable: true @@ -12117,12 +12214,11 @@ components: type: object additionalProperties: true nullable: true + headers: + type: object required: - method - path - - data - - request_format - - overrideBaseUrl UnifiedHrisBankinfoOutput: type: object properties: {} From 7ee2e5bdbd454b278931b948c5d6ee1a08d06e8a Mon Sep 17 00:00:00 2001 From: Rachid Flih Date: Fri, 9 Aug 2024 14:06:59 -0700 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20Added=20shopify=20t?= =?UTF-8?q?o=20connector=5Fsets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/scripts/init.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index ec0ad6fdf..fcaf4af8e 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -505,6 +505,7 @@ CREATE TABLE connector_sets fs_box boolean NULL, tcg_github boolean NULL, ecom_woocommerce boolean NULL, + ecom_shopify boolean NULL, CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set ) ); From f6b85cb70c1dbb5d4557724ba869b25441efda47 Mon Sep 17 00:00:00 2001 From: Rachid Flih Date: Fri, 9 Aug 2024 14:45:41 -0700 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=8E=A8=20CLA=20Assistant=20Signature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/scripts/init.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index fcaf4af8e..8bea8b33b 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -509,6 +509,7 @@ CREATE TABLE connector_sets CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set ) ); + -- ************************************** connection_strategies CREATE TABLE connection_strategies ( From 97047f3e134f3b7678ac8652139053ea89ab349b Mon Sep 17 00:00:00 2001 From: nael Date: Sat, 10 Aug 2024 19:07:45 +0200 Subject: [PATCH 4/8] :bug: Added types to unified models --- packages/api/prisma/schema.prisma | 3 +- .../accounting/account/types/model.unified.ts | 180 +- .../accounting/address/types/model.unified.ts | 175 +- .../attachment/types/model.unified.ts | 111 +- .../balancesheet/types/model.unified.ts | 177 +- .../cashflowstatement/types/model.unified.ts | 165 +- .../companyinfo/types/model.unified.ts | 182 +- .../accounting/contact/types/model.unified.ts | 172 +- .../creditnote/types/model.unified.ts | 237 +- .../accounting/expense/types/model.unified.ts | 203 +- .../incomestatement/types/model.unified.ts | 151 +- .../accounting/invoice/types/model.unified.ts | 263 +- .../accounting/item/types/model.unified.ts | 159 +- .../journalentry/types/model.unified.ts | 214 +- .../accounting/payment/types/model.unified.ts | 203 +- .../phonenumber/types/model.unified.ts | 114 +- .../purchaseorder/types/model.unified.ts | 245 +- .../accounting/taxrate/types/model.unified.ts | 121 +- .../trackingcategory/types/model.unified.ts | 115 +- .../transaction/types/model.unified.ts | 174 +- .../vendorcredit/types/model.unified.ts | 155 +- .../src/hris/bankinfo/types/model.unified.ts | 148 +- .../src/hris/benefit/types/model.unified.ts | 170 +- .../src/hris/company/types/model.unified.ts | 133 +- .../src/hris/dependent/types/model.unified.ts | 212 +- .../src/hris/employee/types/model.unified.ts | 315 +- .../employeepayrollrun/types/model.unified.ts | 288 +- .../employerbenefit/types/model.unified.ts | 143 +- .../hris/employment/types/model.unified.ts | 203 +- .../api/src/hris/group/types/model.unified.ts | 129 +- .../src/hris/location/types/model.unified.ts | 192 +- .../src/hris/paygroup/types/model.unified.ts | 112 +- .../hris/payrollrun/types/model.unified.ts | 163 +- .../src/hris/timeoff/types/model.unified.ts | 193 +- .../timeoffbalance/types/model.unified.ts | 144 +- packages/api/swagger/swagger-spec.yaml | 5924 +++++++++++++---- 36 files changed, 10789 insertions(+), 1199 deletions(-) diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index f6e86b15b..075441d2f 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -682,6 +682,7 @@ model connector_sets { fs_box Boolean? tcg_github Boolean? ecom_woocommerce Boolean? + ecom_shopify Boolean? projects projects[] } @@ -2057,7 +2058,7 @@ model hris_payroll_runs { id_connection String @db.Uuid hris_employee_payroll_runs hris_employee_payroll_runs[] } - + /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model hris_time_off { id_hris_time_off String @id(map: "pk_hris_time_off") @db.Uuid diff --git a/packages/api/src/accounting/account/types/model.unified.ts b/packages/api/src/accounting/account/types/model.unified.ts index 087f5cfa3..d27a94beb 100644 --- a/packages/api/src/accounting/account/types/model.unified.ts +++ b/packages/api/src/accounting/account/types/model.unified.ts @@ -1,3 +1,179 @@ -export class UnifiedAccountingAccountInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, +} from 'class-validator'; -export class UnifiedAccountingAccountOutput extends UnifiedAccountingAccountInput {} +export class UnifiedAccountingAccountInput { + @ApiPropertyOptional({ + type: String, + example: 'Cash', + nullable: true, + description: 'The name of the account', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Main cash account for daily operations', + nullable: true, + description: 'A description of the account', + }) + @IsString() + @IsOptional() + description?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Asset', + nullable: true, + description: 'The classification of the account', + }) + @IsString() + @IsOptional() + classification?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Current Asset', + nullable: true, + description: 'The type of the account', + }) + @IsString() + @IsOptional() + type?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Active', + nullable: true, + description: 'The status of the account', + }) + @IsString() + @IsOptional() + status?: string; + + @ApiPropertyOptional({ + type: Number, + example: 10000, + nullable: true, + description: 'The current balance of the account', + }) + @IsNumber() + @IsOptional() + current_balance?: number; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the account', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '1000', + nullable: true, + description: 'The account number', + }) + @IsString() + @IsOptional() + account_number?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the parent account', + }) + @IsUUID() + @IsOptional() + parent_account?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + company_info_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingAccountOutput extends UnifiedAccountingAccountInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the account record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'account_1234', + nullable: true, + description: 'The remote ID of the account in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the account in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the account record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the account record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/address/types/model.unified.ts b/packages/api/src/accounting/address/types/model.unified.ts index 4ab21d9c6..311c7925b 100644 --- a/packages/api/src/accounting/address/types/model.unified.ts +++ b/packages/api/src/accounting/address/types/model.unified.ts @@ -1,3 +1,174 @@ -export class UnifiedAccountingAddressInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsUUID, IsOptional, IsString, IsDateString } from 'class-validator'; -export class UnifiedAccountingAddressOutput extends UnifiedAccountingAddressInput {} +export class UnifiedAccountingAddressInput { + @ApiPropertyOptional({ + type: String, + example: 'Billing', + nullable: true, + description: 'The type of the address', + }) + @IsString() + @IsOptional() + type?: string; + + @ApiPropertyOptional({ + type: String, + example: '123 Main St', + nullable: true, + description: 'The first line of the street address', + }) + @IsString() + @IsOptional() + street_1?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Apt 4B', + nullable: true, + description: 'The second line of the street address', + }) + @IsString() + @IsOptional() + street_2?: string; + + @ApiPropertyOptional({ + type: String, + example: 'New York', + nullable: true, + description: 'The city of the address', + }) + @IsString() + @IsOptional() + city?: string; + + @ApiPropertyOptional({ + type: String, + example: 'NY', + nullable: true, + description: 'The state of the address', + }) + @IsString() + @IsOptional() + state?: string; + + @ApiPropertyOptional({ + type: String, + example: 'New York', + nullable: true, + description: + 'The country subdivision (e.g., province or state) of the address', + }) + @IsString() + @IsOptional() + country_subdivision?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USA', + nullable: true, + description: 'The country of the address', + }) + @IsString() + @IsOptional() + country?: string; + + @ApiPropertyOptional({ + type: String, + example: '10001', + nullable: true, + description: 'The zip or postal code of the address', + }) + @IsString() + @IsOptional() + zip?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated contact', + }) + @IsUUID() + @IsOptional() + contact_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + company_info_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingAddressOutput extends UnifiedAccountingAddressInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the address record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'address_1234', + nullable: true, + description: 'The remote ID of the address in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the address in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the address record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the address record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/attachment/types/model.unified.ts b/packages/api/src/accounting/attachment/types/model.unified.ts index 6f654f503..ce96dcc26 100644 --- a/packages/api/src/accounting/attachment/types/model.unified.ts +++ b/packages/api/src/accounting/attachment/types/model.unified.ts @@ -1,3 +1,110 @@ -export class UnifiedAccountingAttachmentInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsUrl, + IsDateString, +} from 'class-validator'; -export class UnifiedAccountingAttachmentOutput extends UnifiedAccountingAttachmentInput {} +export class UnifiedAccountingAttachmentInput { + @ApiPropertyOptional({ + type: String, + example: 'invoice.pdf', + nullable: true, + description: 'The name of the attached file', + }) + @IsString() + @IsOptional() + file_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'https://example.com/files/invoice.pdf', + nullable: true, + description: 'The URL where the file can be accessed', + }) + @IsUrl() + @IsOptional() + file_url?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated account', + }) + @IsUUID() + @IsOptional() + account_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingAttachmentOutput extends UnifiedAccountingAttachmentInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the attachment record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'attachment_1234', + nullable: true, + description: + 'The remote ID of the attachment in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the attachment in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the attachment record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the attachment record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/balancesheet/types/model.unified.ts b/packages/api/src/accounting/balancesheet/types/model.unified.ts index 71140028d..dd96cae05 100644 --- a/packages/api/src/accounting/balancesheet/types/model.unified.ts +++ b/packages/api/src/accounting/balancesheet/types/model.unified.ts @@ -1,3 +1,176 @@ -export class UnifiedAccountingBalancesheetInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingBalancesheetOutput extends UnifiedAccountingBalancesheetInput {} +// todo balance sheet report items ? +export class UnifiedAccountingBalancesheetInput { + @ApiPropertyOptional({ + type: String, + example: 'Q2 2024 Balance Sheet', + nullable: true, + description: 'The name of the balance sheet', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency used in the balance sheet', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + company_info_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-30T23:59:59Z', + nullable: true, + description: 'The date of the balance sheet', + }) + @IsDateString() + @IsOptional() + date?: string; + + @ApiPropertyOptional({ + type: Number, + example: 1000000, + nullable: true, + description: 'The net assets value', + }) + @IsNumber() + @IsOptional() + net_assets?: number; + + @ApiPropertyOptional({ + type: [String], + example: ['Cash', 'Accounts Receivable', 'Inventory'], + nullable: true, + description: 'The list of assets', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + assets?: string[]; + + @ApiPropertyOptional({ + type: [String], + example: ['Accounts Payable', 'Long-term Debt'], + nullable: true, + description: 'The list of liabilities', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + liabilities?: string[]; + + @ApiPropertyOptional({ + type: [String], + example: ['Common Stock', 'Retained Earnings'], + nullable: true, + description: 'The list of equity items', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + equity?: string[]; + + @ApiPropertyOptional({ + type: String, + example: '2024-07-01T12:00:00Z', + nullable: true, + description: + 'The date when the balance sheet was generated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_generated_at?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingBalancesheetOutput extends UnifiedAccountingBalancesheetInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the balance sheet record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'balancesheet_1234', + nullable: true, + description: + 'The remote ID of the balance sheet in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the balance sheet in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the balance sheet record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the balance sheet record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/cashflowstatement/types/model.unified.ts b/packages/api/src/accounting/cashflowstatement/types/model.unified.ts index 25c4e0459..d18565123 100644 --- a/packages/api/src/accounting/cashflowstatement/types/model.unified.ts +++ b/packages/api/src/accounting/cashflowstatement/types/model.unified.ts @@ -1,3 +1,164 @@ -export class UnifiedAccountingCashflowstatementInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, +} from 'class-validator'; -export class UnifiedAccountingCashflowstatementOutput extends UnifiedAccountingCashflowstatementInput {} +// todo cashflow statement report items +export class UnifiedAccountingCashflowstatementInput { + @ApiPropertyOptional({ + type: String, + example: 'Q2 2024 Cash Flow Statement', + nullable: true, + description: 'The name of the cash flow statement', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency used in the cash flow statement', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company', + }) + @IsUUID() + @IsOptional() + company_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-04-01T00:00:00Z', + nullable: true, + description: + 'The start date of the period covered by the cash flow statement', + }) + @IsDateString() + @IsOptional() + start_period?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-30T23:59:59Z', + nullable: true, + description: + 'The end date of the period covered by the cash flow statement', + }) + @IsDateString() + @IsOptional() + end_period?: string; + + @ApiPropertyOptional({ + type: Number, + example: 1000000, + nullable: true, + description: 'The cash balance at the beginning of the period', + }) + @IsNumber() + @IsOptional() + cash_at_beginning_of_period?: number; + + @ApiPropertyOptional({ + type: Number, + example: 1200000, + nullable: true, + description: 'The cash balance at the end of the period', + }) + @IsNumber() + @IsOptional() + cash_at_end_of_period?: number; + + @ApiPropertyOptional({ + type: String, + example: '2024-07-01T12:00:00Z', + nullable: true, + description: + 'The date when the cash flow statement was generated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_generated_at?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingCashflowstatementOutput extends UnifiedAccountingCashflowstatementInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the cash flow statement record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'cashflowstatement_1234', + nullable: true, + description: + 'The remote ID of the cash flow statement in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the cash flow statement in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the cash flow statement record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the cash flow statement record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/companyinfo/types/model.unified.ts b/packages/api/src/accounting/companyinfo/types/model.unified.ts index a54ca447c..92361f52f 100644 --- a/packages/api/src/accounting/companyinfo/types/model.unified.ts +++ b/packages/api/src/accounting/companyinfo/types/model.unified.ts @@ -1,3 +1,181 @@ -export class UnifiedAccountingCompanyinfoInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, + IsUrl, + Min, + Max, +} from 'class-validator'; -export class UnifiedAccountingCompanyinfoOutput extends UnifiedAccountingCompanyinfoInput {} +export class UnifiedAccountingCompanyinfoInput { + @ApiPropertyOptional({ + type: String, + example: 'Acme Corporation', + nullable: true, + description: 'The name of the company', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Acme Corporation LLC', + nullable: true, + description: 'The legal name of the company', + }) + @IsString() + @IsOptional() + legal_name?: string; + + @ApiPropertyOptional({ + type: String, + example: '123456789', + nullable: true, + description: 'The tax number of the company', + }) + @IsString() + @IsOptional() + tax_number?: string; + + @ApiPropertyOptional({ + type: Number, + example: 12, + nullable: true, + description: 'The month of the fiscal year end (1-12)', + }) + @IsNumber() + @Min(1) + @Max(12) + @IsOptional() + fiscal_year_end_month?: number; + + @ApiPropertyOptional({ + type: Number, + example: 31, + nullable: true, + description: 'The day of the fiscal year end (1-31)', + }) + @IsNumber() + @Min(1) + @Max(31) + @IsOptional() + fiscal_year_end_day?: number; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency used by the company', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['https://www.acmecorp.com', 'https://store.acmecorp.com'], + nullable: true, + description: 'The URLs associated with the company', + }) + @IsArray() + @IsUrl({}, { each: true }) + @IsOptional() + urls?: string[]; + + @ApiPropertyOptional({ + type: [String], + example: ['Department', 'Project', 'Location'], + nullable: true, + description: 'The tracking categories used by the company', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingCompanyinfoOutput extends UnifiedAccountingCompanyinfoInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the company info record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'company_1234', + nullable: true, + description: + 'The remote ID of the company info in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the company info in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the company info was created in the remote system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the company info record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the company info record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/contact/types/model.unified.ts b/packages/api/src/accounting/contact/types/model.unified.ts index 28bb89a80..61528f63f 100644 --- a/packages/api/src/accounting/contact/types/model.unified.ts +++ b/packages/api/src/accounting/contact/types/model.unified.ts @@ -1,3 +1,171 @@ -export class UnifiedAccountingContactInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsBoolean, + IsEmail, + IsDateString, +} from 'class-validator'; -export class UnifiedAccountingContactOutput extends UnifiedAccountingContactInput {} +export class UnifiedAccountingContactInput { + @ApiPropertyOptional({ + type: String, + example: 'John Doe', + nullable: true, + description: 'The name of the contact', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: true, + nullable: true, + description: 'Indicates if the contact is a supplier', + }) + @IsBoolean() + @IsOptional() + is_supplier?: boolean; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the contact is a customer', + }) + @IsBoolean() + @IsOptional() + is_customer?: boolean; + + @ApiPropertyOptional({ + type: String, + example: 'john.doe@example.com', + nullable: true, + description: 'The email address of the contact', + }) + @IsEmail() + @IsOptional() + email_address?: string; + + @ApiPropertyOptional({ + type: String, + example: '123456789', + nullable: true, + description: 'The tax number of the contact', + }) + @IsString() + @IsOptional() + tax_number?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Active', + nullable: true, + description: 'The status of the contact', + }) + @IsString() + @IsOptional() + status?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency associated with the contact', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the contact was last updated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_updated_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + company_info_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingContactOutput extends UnifiedAccountingContactInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the contact record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'contact_1234', + nullable: true, + description: 'The remote ID of the contact in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the contact in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the contact record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the contact record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/creditnote/types/model.unified.ts b/packages/api/src/accounting/creditnote/types/model.unified.ts index f40205bfa..20330bf3b 100644 --- a/packages/api/src/accounting/creditnote/types/model.unified.ts +++ b/packages/api/src/accounting/creditnote/types/model.unified.ts @@ -1,3 +1,236 @@ -export class UnifiedAccountingCreditnoteInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingCreditnoteOutput extends UnifiedAccountingCreditnoteInput {} +export class UnifiedAccountingCreditnoteInput { + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date of the credit note transaction', + }) + @IsDateString() + @IsOptional() + transaction_date?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Issued', + nullable: true, + description: 'The status of the credit note', + }) + @IsString() + @IsOptional() + status?: string; + + @ApiPropertyOptional({ + type: String, + example: 'CN-001', + nullable: true, + description: 'The number of the credit note', + }) + @IsString() + @IsOptional() + number?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated contact', + }) + @IsUUID() + @IsOptional() + contact_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company', + }) + @IsUUID() + @IsOptional() + company_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the credit note', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: Number, + example: 10000, + nullable: true, + description: 'The total amount of the credit note', + }) + @IsNumber() + @IsOptional() + total_amount?: number; + + @ApiPropertyOptional({ + type: Number, + example: 5000, + nullable: true, + description: 'The remaining credit on the credit note', + }) + @IsNumber() + @IsOptional() + remaining_credit?: number; + + @ApiPropertyOptional({ + type: [String], + example: ['Project A', 'Department B'], + nullable: true, + description: 'The tracking categories associated with the credit note', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the credit note', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['PAYMENT-001', 'PAYMENT-002'], + nullable: true, + description: 'The payments associated with the credit note', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + payments?: string[]; + + @ApiPropertyOptional({ + type: [String], + example: ['APPLIED-001', 'APPLIED-002'], + nullable: true, + description: 'The applied payments associated with the credit note', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + applied_payments?: string[]; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated accounting period', + }) + @IsUUID() + @IsOptional() + accounting_period_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingCreditnoteOutput extends UnifiedAccountingCreditnoteInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the credit note record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'creditnote_1234', + nullable: true, + description: + 'The remote ID of the credit note in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the credit note in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the credit note was created in the remote system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the credit note was last updated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_updated_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the credit note record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the credit note record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/expense/types/model.unified.ts b/packages/api/src/accounting/expense/types/model.unified.ts index 52d124695..25d0a8558 100644 --- a/packages/api/src/accounting/expense/types/model.unified.ts +++ b/packages/api/src/accounting/expense/types/model.unified.ts @@ -1,3 +1,202 @@ -export class UnifiedAccountingExpenseInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingExpenseOutput extends UnifiedAccountingExpenseInput {} +// todo: expense line +export class UnifiedAccountingExpenseInput { + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date of the expense transaction', + }) + @IsDateString() + @IsOptional() + transaction_date?: string; + + @ApiPropertyOptional({ + type: Number, + example: 10000, + nullable: true, + description: 'The total amount of the expense', + }) + @IsNumber() + @IsOptional() + total_amount?: number; + + @ApiPropertyOptional({ + type: Number, + example: 9000, + nullable: true, + description: 'The sub-total amount of the expense (before tax)', + }) + @IsNumber() + @IsOptional() + sub_total?: number; + + @ApiPropertyOptional({ + type: Number, + example: 1000, + nullable: true, + description: 'The total tax amount of the expense', + }) + @IsNumber() + @IsOptional() + total_tax_amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the expense', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the expense', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Business lunch with client', + nullable: true, + description: 'A memo or description for the expense', + }) + @IsString() + @IsOptional() + memo?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated account', + }) + @IsUUID() + @IsOptional() + account_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated contact', + }) + @IsUUID() + @IsOptional() + contact_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + company_info_id?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['Project A', 'Department B'], + nullable: true, + description: 'The tracking categories associated with the expense', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingExpenseOutput extends UnifiedAccountingExpenseInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the expense record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'expense_1234', + nullable: true, + description: 'The remote ID of the expense in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the expense in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date when the expense was created in the remote system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the expense record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the expense record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/incomestatement/types/model.unified.ts b/packages/api/src/accounting/incomestatement/types/model.unified.ts index 1baee39a2..46eb641bc 100644 --- a/packages/api/src/accounting/incomestatement/types/model.unified.ts +++ b/packages/api/src/accounting/incomestatement/types/model.unified.ts @@ -1,3 +1,150 @@ -export class UnifiedAccountingIncomestatementInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, +} from 'class-validator'; -export class UnifiedAccountingIncomestatementOutput extends UnifiedAccountingIncomestatementInput {} +export class UnifiedAccountingIncomestatementInput { + @ApiPropertyOptional({ + type: String, + example: 'Q2 2024 Income Statement', + nullable: true, + description: 'The name of the income statement', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency used in the income statement', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-04-01T00:00:00Z', + nullable: true, + description: 'The start date of the period covered by the income statement', + }) + @IsDateString() + @IsOptional() + start_period?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-30T23:59:59Z', + nullable: true, + description: 'The end date of the period covered by the income statement', + }) + @IsDateString() + @IsOptional() + end_period?: string; + + @ApiPropertyOptional({ + type: Number, + example: 1000000, + nullable: true, + description: 'The gross profit for the period', + }) + @IsNumber() + @IsOptional() + gross_profit?: number; + + @ApiPropertyOptional({ + type: Number, + example: 800000, + nullable: true, + description: 'The net operating income for the period', + }) + @IsNumber() + @IsOptional() + net_operating_income?: number; + + @ApiPropertyOptional({ + type: Number, + example: 750000, + nullable: true, + description: 'The net income for the period', + }) + @IsNumber() + @IsOptional() + net_income?: number; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingIncomestatementOutput extends UnifiedAccountingIncomestatementInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the income statement record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'incomestatement_1234', + nullable: true, + description: + 'The remote ID of the income statement in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the income statement in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the income statement record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the income statement record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/invoice/types/model.unified.ts b/packages/api/src/accounting/invoice/types/model.unified.ts index 9ec1789bf..62e6d7ac3 100644 --- a/packages/api/src/accounting/invoice/types/model.unified.ts +++ b/packages/api/src/accounting/invoice/types/model.unified.ts @@ -1,3 +1,262 @@ -export class UnifiedAccountingInvoiceInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingInvoiceOutput extends UnifiedAccountingInvoiceInput {} +export class UnifiedAccountingInvoiceInput { + @ApiPropertyOptional({ + type: String, + example: 'Sales', + nullable: true, + description: 'The type of the invoice', + }) + @IsString() + @IsOptional() + type?: string; + + @ApiPropertyOptional({ + type: String, + example: 'INV-001', + nullable: true, + description: 'The invoice number', + }) + @IsString() + @IsOptional() + number?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date the invoice was issued', + }) + @IsDateString() + @IsOptional() + issue_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-07-15T12:00:00Z', + nullable: true, + description: 'The due date of the invoice', + }) + @IsDateString() + @IsOptional() + due_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-07-10T12:00:00Z', + nullable: true, + description: 'The date the invoice was paid', + }) + @IsDateString() + @IsOptional() + paid_on_date?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Payment for services rendered', + nullable: true, + description: 'A memo or note on the invoice', + }) + @IsString() + @IsOptional() + memo?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the invoice', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the invoice', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: Number, + example: 1000, + nullable: true, + description: 'The total discount applied to the invoice', + }) + @IsNumber() + @IsOptional() + total_discount?: number; + + @ApiPropertyOptional({ + type: Number, + example: 10000, + nullable: true, + description: 'The subtotal of the invoice', + }) + @IsNumber() + @IsOptional() + sub_total?: number; + + @ApiPropertyOptional({ + type: String, + example: 'Paid', + nullable: true, + description: 'The status of the invoice', + }) + @IsString() + @IsOptional() + status?: string; + + @ApiPropertyOptional({ + type: Number, + example: 1000, + nullable: true, + description: 'The total tax amount on the invoice', + }) + @IsNumber() + @IsOptional() + total_tax_amount?: number; + + @ApiPropertyOptional({ + type: Number, + example: 11000, + nullable: true, + description: 'The total amount of the invoice', + }) + @IsNumber() + @IsOptional() + total_amount?: number; + + @ApiPropertyOptional({ + type: Number, + example: 0, + nullable: true, + description: 'The remaining balance on the invoice', + }) + @IsNumber() + @IsOptional() + balance?: number; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated contact', + }) + @IsUUID() + @IsOptional() + contact_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated accounting period', + }) + @IsUUID() + @IsOptional() + accounting_period_id?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['Project A', 'Department B'], + nullable: true, + description: 'The tracking categories associated with the invoice', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingInvoiceOutput extends UnifiedAccountingInvoiceInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the invoice record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'invoice_1234', + nullable: true, + description: 'The remote ID of the invoice in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the invoice in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the invoice was last updated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_updated_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the invoice record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the invoice record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/item/types/model.unified.ts b/packages/api/src/accounting/item/types/model.unified.ts index 3200cb8bd..ecf6d22d0 100644 --- a/packages/api/src/accounting/item/types/model.unified.ts +++ b/packages/api/src/accounting/item/types/model.unified.ts @@ -1,3 +1,158 @@ -export class UnifiedAccountingItemInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, +} from 'class-validator'; -export class UnifiedAccountingItemOutput extends UnifiedAccountingItemInput {} +export class UnifiedAccountingItemInput { + @ApiPropertyOptional({ + type: String, + example: 'Product A', + nullable: true, + description: 'The name of the accounting item', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Active', + nullable: true, + description: 'The status of the accounting item', + }) + @IsString() + @IsOptional() + status?: string; + + @ApiPropertyOptional({ + type: Number, + example: 1000, + nullable: true, + description: 'The unit price of the item in cents', + }) + @IsNumber() + @IsOptional() + unit_price?: number; + + @ApiPropertyOptional({ + type: Number, + example: 800, + nullable: true, + description: 'The purchase price of the item in cents', + }) + @IsNumber() + @IsOptional() + purchase_price?: number; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated sales account', + }) + @IsUUID() + @IsOptional() + sales_account?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated purchase account', + }) + @IsUUID() + @IsOptional() + purchase_account?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + id_acc_company_info?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingItemOutput extends UnifiedAccountingItemInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the accounting item record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'item_1234', + nullable: true, + description: 'The remote ID of the item in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date when the item was last updated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_updated_at?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: 'The remote data of the item in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the accounting item record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the accounting item record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/journalentry/types/model.unified.ts b/packages/api/src/accounting/journalentry/types/model.unified.ts index 2d9ece7c6..d8ab47c88 100644 --- a/packages/api/src/accounting/journalentry/types/model.unified.ts +++ b/packages/api/src/accounting/journalentry/types/model.unified.ts @@ -1,3 +1,213 @@ -export class UnifiedAccountingJournalentryInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingJournalentryOutput extends UnifiedAccountingJournalentryInput {} +export class UnifiedAccountingJournalentryInput { + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date of the transaction', + }) + @IsDateString() + @IsOptional() + transaction_date?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['payment1', 'payment2'], + nullable: true, + description: 'The payments associated with the journal entry', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + payments?: string[]; + + @ApiPropertyOptional({ + type: [String], + example: ['appliedPayment1', 'appliedPayment2'], + nullable: true, + description: 'The applied payments for the journal entry', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + applied_payments?: string[]; + + @ApiPropertyOptional({ + type: String, + example: 'Monthly expense journal entry', + nullable: true, + description: 'A memo or note for the journal entry', + }) + @IsString() + @IsOptional() + memo?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the journal entry', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the journal entry', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: false, + description: 'The UUID of the associated company info', + }) + @IsUUID() + id_acc_company_info: string; + + @ApiPropertyOptional({ + type: String, + example: 'JE-001', + nullable: true, + description: 'The journal number', + }) + @IsString() + @IsOptional() + journal_number?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['Category1', 'Category2'], + nullable: true, + description: 'The tracking categories associated with the journal entry', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated accounting period', + }) + @IsUUID() + @IsOptional() + id_acc_accounting_period?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Posted', + nullable: true, + description: 'The posting status of the journal entry', + }) + @IsString() + @IsOptional() + posting_status?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingJournalentryOutput extends UnifiedAccountingJournalentryInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the journal entry record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'journal_entry_1234', + nullable: false, + description: + 'The remote ID of the journal entry in the context of the 3rd Party', + }) + @IsString() + remote_id: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the journal entry was created in the remote system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the journal entry was last modified in the remote system', + }) + @IsDateString() + @IsOptional() + remote_modiified_at?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the journal entry in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the journal entry record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the journal entry record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/payment/types/model.unified.ts b/packages/api/src/accounting/payment/types/model.unified.ts index bded5d6ad..66c1bd3a8 100644 --- a/packages/api/src/accounting/payment/types/model.unified.ts +++ b/packages/api/src/accounting/payment/types/model.unified.ts @@ -1,3 +1,202 @@ -export class UnifiedAccountingPaymentInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingPaymentOutput extends UnifiedAccountingPaymentInput {} +export class UnifiedAccountingPaymentInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated invoice', + }) + @IsUUID() + @IsOptional() + id_acc_invoice?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date of the transaction', + }) + @IsDateString() + @IsOptional() + transaction_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated contact', + }) + @IsUUID() + @IsOptional() + id_acc_contact?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated account', + }) + @IsUUID() + @IsOptional() + id_acc_account?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the payment', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the payment', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: Number, + example: 10000, + nullable: true, + description: 'The total amount of the payment in cents', + }) + @IsNumber() + @IsOptional() + total_amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'Credit Card', + nullable: true, + description: 'The type of payment', + }) + @IsString() + @IsOptional() + type?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + id_acc_company_info?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated accounting period', + }) + @IsUUID() + @IsOptional() + id_acc_accounting_period?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['Category1', 'Category2'], + nullable: true, + description: 'The tracking categories associated with the payment', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingPaymentOutput extends UnifiedAccountingPaymentInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the payment record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'payment_1234', + nullable: true, + description: 'The remote ID of the payment in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the payment was last updated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_updated_at?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the payment in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the payment record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the payment record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/phonenumber/types/model.unified.ts b/packages/api/src/accounting/phonenumber/types/model.unified.ts index 952614096..59b54aaaf 100644 --- a/packages/api/src/accounting/phonenumber/types/model.unified.ts +++ b/packages/api/src/accounting/phonenumber/types/model.unified.ts @@ -1,3 +1,113 @@ -export class UnifiedAccountingPhonenumberInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsUUID, IsOptional, IsString, IsDateString } from 'class-validator'; -export class UnifiedAccountingPhonenumberOutput extends UnifiedAccountingPhonenumberInput {} +export class UnifiedAccountingPhonenumberInput { + @ApiPropertyOptional({ + type: String, + example: '+1234567890', + nullable: true, + description: 'The phone number', + }) + @IsString() + @IsOptional() + number?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Mobile', + nullable: true, + description: 'The type of phone number', + }) + @IsString() + @IsOptional() + type?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + id_acc_company_info?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: false, + description: 'The UUID of the associated contact', + }) + @IsUUID() + id_acc_contact: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingPhonenumberOutput extends UnifiedAccountingPhonenumberInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the phone number record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'phone_1234', + nullable: true, + description: + 'The remote ID of the phone number in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the phone number in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the phone number record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the phone number record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/purchaseorder/types/model.unified.ts b/packages/api/src/accounting/purchaseorder/types/model.unified.ts index 399c7bcd6..117ce9802 100644 --- a/packages/api/src/accounting/purchaseorder/types/model.unified.ts +++ b/packages/api/src/accounting/purchaseorder/types/model.unified.ts @@ -1,3 +1,244 @@ -export class UnifiedAccountingPurchaseorderInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingPurchaseorderOutput extends UnifiedAccountingPurchaseorderInput {} +export class UnifiedAccountingPurchaseorderInput { + @ApiPropertyOptional({ + type: String, + example: 'Pending', + nullable: true, + description: 'The status of the purchase order', + }) + @IsString() + @IsOptional() + status?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The issue date of the purchase order', + }) + @IsDateString() + @IsOptional() + issue_date?: string; + + @ApiPropertyOptional({ + type: String, + example: 'PO-001', + nullable: true, + description: 'The purchase order number', + }) + @IsString() + @IsOptional() + purchase_order_number?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-07-15T12:00:00Z', + nullable: true, + description: 'The delivery date for the purchase order', + }) + @IsDateString() + @IsOptional() + delivery_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the delivery address', + }) + @IsUUID() + @IsOptional() + delivery_address?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the customer', + }) + @IsUUID() + @IsOptional() + customer?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the vendor', + }) + @IsUUID() + @IsOptional() + vendor?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Purchase order for Q3 inventory', + nullable: true, + description: 'A memo or note for the purchase order', + }) + @IsString() + @IsOptional() + memo?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the company', + }) + @IsUUID() + @IsOptional() + company?: string; + + @ApiPropertyOptional({ + type: Number, + example: 100000, + nullable: true, + description: 'The total amount of the purchase order in cents', + }) + @IsNumber() + @IsOptional() + total_amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the purchase order', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the purchase order', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['Category1', 'Category2'], + nullable: true, + description: 'The tracking categories associated with the purchase order', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated accounting period', + }) + @IsUUID() + @IsOptional() + id_acc_accounting_period?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingPurchaseorderOutput extends UnifiedAccountingPurchaseorderInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the purchase order record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'po_1234', + nullable: true, + description: + 'The remote ID of the purchase order in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the purchase order was created in the remote system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the purchase order was last updated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_updated_at?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the purchase order in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the purchase order record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the purchase order record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/taxrate/types/model.unified.ts b/packages/api/src/accounting/taxrate/types/model.unified.ts index df05a1f44..1cdbe6327 100644 --- a/packages/api/src/accounting/taxrate/types/model.unified.ts +++ b/packages/api/src/accounting/taxrate/types/model.unified.ts @@ -1,3 +1,120 @@ -export class UnifiedAccountingTaxrateInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, +} from 'class-validator'; -export class UnifiedAccountingTaxrateOutput extends UnifiedAccountingTaxrateInput {} +export class UnifiedAccountingTaxrateInput { + @ApiPropertyOptional({ + type: String, + example: 'VAT 20%', + nullable: true, + description: 'The description of the tax rate', + }) + @IsString() + @IsOptional() + description?: string; + + @ApiPropertyOptional({ + type: Number, + example: 2000, + nullable: true, + description: 'The total tax rate in basis points (e.g., 2000 for 20%)', + }) + @IsNumber() + @IsOptional() + total_tax_ratge?: number; + + @ApiPropertyOptional({ + type: Number, + example: 1900, + nullable: true, + description: 'The effective tax rate in basis points (e.g., 1900 for 19%)', + }) + @IsNumber() + @IsOptional() + effective_tax_rate?: number; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company', + }) + @IsUUID() + @IsOptional() + company?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingTaxrateOutput extends UnifiedAccountingTaxrateInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the tax rate record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'tax_rate_1234', + nullable: true, + description: + 'The remote ID of the tax rate in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the tax rate in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the tax rate record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the tax rate record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/trackingcategory/types/model.unified.ts b/packages/api/src/accounting/trackingcategory/types/model.unified.ts index afb3085c3..6f0c8ec9b 100644 --- a/packages/api/src/accounting/trackingcategory/types/model.unified.ts +++ b/packages/api/src/accounting/trackingcategory/types/model.unified.ts @@ -1,3 +1,114 @@ -export class UnifiedAccountingTrackingcategoryInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsUUID, IsOptional, IsString, IsDateString } from 'class-validator'; -export class UnifiedAccountingTrackingcategoryOutput extends UnifiedAccountingTrackingcategoryInput {} +export class UnifiedAccountingTrackingcategoryInput { + @ApiPropertyOptional({ + type: String, + example: 'Department', + nullable: true, + description: 'The name of the tracking category', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Active', + nullable: true, + description: 'The status of the tracking category', + }) + @IsString() + @IsOptional() + status?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Expense', + nullable: true, + description: 'The type of the tracking category', + }) + @IsString() + @IsOptional() + category_type?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the parent category, if applicable', + }) + @IsUUID() + @IsOptional() + parent_category?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedAccountingTrackingcategoryOutput extends UnifiedAccountingTrackingcategoryInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the tracking category record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'tracking_category_1234', + nullable: true, + description: + 'The remote ID of the tracking category in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the tracking category in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the tracking category record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the tracking category record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; +} diff --git a/packages/api/src/accounting/transaction/types/model.unified.ts b/packages/api/src/accounting/transaction/types/model.unified.ts index 105ce9ae0..31ac3b963 100644 --- a/packages/api/src/accounting/transaction/types/model.unified.ts +++ b/packages/api/src/accounting/transaction/types/model.unified.ts @@ -1,3 +1,173 @@ -export class UnifiedAccountingTransactionInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingTransactionOutput extends UnifiedAccountingTransactionInput {} +export class UnifiedAccountingTransactionInput { + @ApiPropertyOptional({ + type: String, + example: 'Sale', + nullable: true, + description: 'The type of the transaction', + }) + @IsString() + @IsOptional() + transaction_type?: string; + + @ApiPropertyOptional({ + type: String, + example: '1001', + nullable: true, + description: 'The transaction number', + }) + @IsNumber() + @IsOptional() + number?: number; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date of the transaction', + }) + @IsDateString() + @IsOptional() + transaction_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '1000', + nullable: true, + description: 'The total amount of the transaction', + }) + @IsString() + @IsOptional() + total_amount?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the transaction', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the transaction', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: + 'The UUID of tracking categories associated with the transaction', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated account', + }) + @IsUUID() + @IsOptional() + id_acc_account?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated contact', + }) + @IsUUID() + @IsOptional() + id_acc_contact?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + id_acc_company_info?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated accounting period', + }) + @IsUUID() + @IsOptional() + id_acc_accounting_period?: string; +} + +export class UnifiedAccountingTransactionOutput extends UnifiedAccountingTransactionInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the transaction record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'remote_id_1234', + nullable: false, + description: 'The remote ID of the transaction', + }) + @IsString() + remote_id: string; // Required field + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: false, + description: 'The created date of the transaction', + }) + @IsDateString() + created_at: string; // Required field + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: false, + description: 'The last modified date of the transaction', + }) + @IsDateString() + modified_at: string; // Required field + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the transaction was last updated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_updated_at?: string; +} diff --git a/packages/api/src/accounting/vendorcredit/types/model.unified.ts b/packages/api/src/accounting/vendorcredit/types/model.unified.ts index bef9c0d44..ccdf49f13 100644 --- a/packages/api/src/accounting/vendorcredit/types/model.unified.ts +++ b/packages/api/src/accounting/vendorcredit/types/model.unified.ts @@ -1,3 +1,154 @@ -export class UnifiedAccountingVendorcreditInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsArray, +} from 'class-validator'; -export class UnifiedAccountingVendorcreditOutput extends UnifiedAccountingVendorcreditInput {} +export class UnifiedAccountingVendorcreditInput { + @ApiPropertyOptional({ + type: String, + example: 'VC-001', + nullable: true, + description: 'The number of the vendor credit', + }) + @IsString() + @IsOptional() + number?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date of the transaction', + }) + @IsDateString() + @IsOptional() + transaction_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the vendor associated with the credit', + }) + @IsUUID() + @IsOptional() + vendor?: string; + + @ApiPropertyOptional({ + type: String, + example: '1000', + nullable: true, + description: 'The total amount of the vendor credit', + }) + @IsNumber() + @IsOptional() + total_amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the vendor credit', + }) + @IsString() + @IsOptional() + currency?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the vendor credit', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company', + }) + @IsUUID() + @IsOptional() + company?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: + 'The UUID of tracking categories associated with the vendor credit', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated accounting period', + }) + @IsUUID() + @IsOptional() + accounting_period?: string; +} + +export class UnifiedAccountingVendorcreditOutput extends UnifiedAccountingVendorcreditInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the vendor credit record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'remote_id_1234', + nullable: true, + description: 'The remote ID of the vendor credit', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: false, + description: 'The created date of the vendor credit', + }) + @IsDateString() + created_at: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: false, + description: 'The last modified date of the vendor credit', + }) + @IsDateString() + modified_at: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the vendor credit was last updated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_updated_at?: string; +} diff --git a/packages/api/src/hris/bankinfo/types/model.unified.ts b/packages/api/src/hris/bankinfo/types/model.unified.ts index a101d7dc1..3b5f1103d 100644 --- a/packages/api/src/hris/bankinfo/types/model.unified.ts +++ b/packages/api/src/hris/bankinfo/types/model.unified.ts @@ -1,3 +1,147 @@ -export class UnifiedHrisBankinfoInput {} +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisBankinfoOutput extends UnifiedHrisBankinfoInput {} +export class UnifiedHrisBankinfoInput { + @ApiPropertyOptional({ + type: String, + example: 'checking', + nullable: true, + description: 'The type of the bank account', + }) + @IsString() + @IsOptional() + account_type?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Bank of America', + nullable: true, + description: 'The name of the bank', + }) + @IsString() + @IsOptional() + bank_name?: string; + + @ApiPropertyOptional({ + type: String, + example: '1234567890', + nullable: true, + description: 'The account number', + }) + @IsString() + @IsOptional() + account_number?: string; + + @ApiPropertyOptional({ + type: String, + example: '021000021', + nullable: true, + description: 'The routing number of the bank', + }) + @IsString() + @IsOptional() + routing_number?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated employee', + }) + @IsUUID() + @IsOptional() + employee_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisBankinfoOutput extends UnifiedHrisBankinfoInput { + @ApiProperty({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the bank info record', + }) + @IsUUID() + id: string; + + @ApiPropertyOptional({ + type: String, + example: 'id_1', + nullable: true, + description: + 'The remote ID of the bank info in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the bank info in the context of the 3rd Party', + }) + @IsOptional() + remote_data: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the bank info was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at: string; + + @ApiProperty({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the bank info record', + }) + @IsDateString() + created_at: string; + + @ApiProperty({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the bank info record', + }) + @IsDateString() + modified_at: string; + + @ApiProperty({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the bank info was deleted in the remote system', + }) + @IsBoolean() + remote_was_deleted: boolean; +} diff --git a/packages/api/src/hris/benefit/types/model.unified.ts b/packages/api/src/hris/benefit/types/model.unified.ts index ebe523e39..f348f6e07 100644 --- a/packages/api/src/hris/benefit/types/model.unified.ts +++ b/packages/api/src/hris/benefit/types/model.unified.ts @@ -1,3 +1,169 @@ -export class UnifiedHrisBenefitInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsNumber, +} from 'class-validator'; -export class UnifiedHrisBenefitOutput extends UnifiedHrisBenefitInput {} +export class UnifiedHrisBenefitInput { + @ApiPropertyOptional({ + type: String, + example: 'Health Insurance Provider', + nullable: true, + description: 'The name of the benefit provider', + }) + @IsString() + @IsOptional() + provider_name?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated employee', + }) + @IsUUID() + @IsOptional() + employee_id?: string; + + @ApiPropertyOptional({ + type: Number, + example: 100, + nullable: true, + description: 'The employee contribution amount', + }) + @IsNumber() + @IsOptional() + employee_contribution?: number; + + @ApiPropertyOptional({ + type: Number, + example: 200, + nullable: true, + description: 'The company contribution amount', + }) + @IsNumber() + @IsOptional() + company_contribution?: number; + + @ApiPropertyOptional({ + type: String, + example: '2024-01-01T00:00:00Z', + nullable: true, + description: 'The start date of the benefit', + }) + @IsDateString() + @IsOptional() + start_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-12-31T23:59:59Z', + nullable: true, + description: 'The end date of the benefit', + }) + @IsDateString() + @IsOptional() + end_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated employer benefit', + }) + @IsUUID() + @IsOptional() + employer_benefit_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisBenefitOutput extends UnifiedHrisBenefitInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the benefit record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'benefit_1234', + nullable: true, + description: 'The remote ID of the benefit in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the benefit in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the benefit was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the benefit record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the benefit record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the benefit was deleted in the remote system', + }) + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/company/types/model.unified.ts b/packages/api/src/hris/company/types/model.unified.ts index bccfa4ea6..ed68a960e 100644 --- a/packages/api/src/hris/company/types/model.unified.ts +++ b/packages/api/src/hris/company/types/model.unified.ts @@ -1,3 +1,132 @@ -export class UnifiedHrisCompanyInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsArray, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisCompanyOutput extends UnifiedHrisCompanyInput {} +export class UnifiedHrisCompanyInput { + @ApiPropertyOptional({ + type: String, + example: 'Acme Corporation', + nullable: true, + description: 'The legal name of the company', + }) + @IsString() + @IsOptional() + legal_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Acme Corp', + nullable: true, + description: 'The display name of the company', + }) + @IsString() + @IsOptional() + display_name?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['12-3456789', '98-7654321'], + nullable: true, + description: 'The Employer Identification Numbers (EINs) of the company', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + eins?: string[]; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisCompanyOutput extends UnifiedHrisCompanyInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the company record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'company_1234', + nullable: true, + description: 'The remote ID of the company in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the company in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the company was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the company record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the company record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the company was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/dependent/types/model.unified.ts b/packages/api/src/hris/dependent/types/model.unified.ts index d43929548..d4a82da1b 100644 --- a/packages/api/src/hris/dependent/types/model.unified.ts +++ b/packages/api/src/hris/dependent/types/model.unified.ts @@ -1,3 +1,211 @@ -export class UnifiedHrisDependentInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisDependentOutput extends UnifiedHrisDependentInput {} +export class UnifiedHrisDependentInput { + @ApiPropertyOptional({ + type: String, + example: 'John', + nullable: true, + description: 'The first name of the dependent', + }) + @IsString() + @IsOptional() + first_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Doe', + nullable: true, + description: 'The last name of the dependent', + }) + @IsString() + @IsOptional() + last_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Michael', + nullable: true, + description: 'The middle name of the dependent', + }) + @IsString() + @IsOptional() + middle_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Child', + nullable: true, + description: 'The relationship of the dependent to the employee', + }) + @IsString() + @IsOptional() + relationship?: string; + + @ApiPropertyOptional({ + type: String, + example: '2020-01-01', + nullable: true, + description: 'The date of birth of the dependent', + }) + @IsDateString() + @IsOptional() + date_of_birth?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Male', + nullable: true, + description: 'The gender of the dependent', + }) + @IsString() + @IsOptional() + gender?: string; + + @ApiPropertyOptional({ + type: String, + example: '+1234567890', + nullable: true, + description: 'The phone number of the dependent', + }) + @IsString() + @IsOptional() + phone_number?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the home location', + }) + @IsUUID() + @IsOptional() + home_location?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: true, + nullable: true, + description: 'Indicates if the dependent is a student', + }) + @IsBoolean() + @IsOptional() + is_student?: boolean; + + @ApiPropertyOptional({ + type: String, + example: '123-45-6789', + nullable: true, + description: 'The Social Security Number of the dependent', + }) + @IsString() + @IsOptional() + ssn?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated employee', + }) + @IsUUID() + @IsOptional() + employee_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisDependentOutput extends UnifiedHrisDependentInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the dependent record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'dependent_1234', + nullable: true, + description: + 'The remote ID of the dependent in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the dependent in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the dependent was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the dependent record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the dependent record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the dependent was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/employee/types/model.unified.ts b/packages/api/src/hris/employee/types/model.unified.ts index e64967d81..8d395c64c 100644 --- a/packages/api/src/hris/employee/types/model.unified.ts +++ b/packages/api/src/hris/employee/types/model.unified.ts @@ -1,3 +1,314 @@ -export class UnifiedHrisEmployeeInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsArray, + IsDateString, + IsEmail, + IsUrl, +} from 'class-validator'; -export class UnifiedHrisEmployeeOutput extends UnifiedHrisEmployeeInput {} +export class UnifiedHrisEmployeeInput { + @ApiPropertyOptional({ + type: [String], + example: ['Group1', 'Group2'], + nullable: true, + description: 'The groups the employee belongs to', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + groups?: string[]; + + @ApiPropertyOptional({ + type: String, + example: 'EMP001', + nullable: true, + description: 'The employee number', + }) + @IsString() + @IsOptional() + employee_number?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company', + }) + @IsUUID() + @IsOptional() + company_id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'John', + nullable: true, + description: 'The first name of the employee', + }) + @IsString() + @IsOptional() + first_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Doe', + nullable: true, + description: 'The last name of the employee', + }) + @IsString() + @IsOptional() + last_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Johnny', + nullable: true, + description: 'The preferred name of the employee', + }) + @IsString() + @IsOptional() + preferred_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'John Doe', + nullable: true, + description: 'The full display name of the employee', + }) + @IsString() + @IsOptional() + display_full_name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'johndoe', + nullable: true, + description: 'The username of the employee', + }) + @IsString() + @IsOptional() + username?: string; + + @ApiPropertyOptional({ + type: String, + example: 'john.doe@company.com', + nullable: true, + description: 'The work email of the employee', + }) + @IsEmail() + @IsOptional() + work_email?: string; + + @ApiPropertyOptional({ + type: String, + example: 'john.doe@personal.com', + nullable: true, + description: 'The personal email of the employee', + }) + @IsEmail() + @IsOptional() + personal_email?: string; + + @ApiPropertyOptional({ + type: String, + example: '+1234567890', + nullable: true, + description: 'The mobile phone number of the employee', + }) + @IsString() + @IsOptional() + mobile_phone_number?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['Employment1', 'Employment2'], + nullable: true, + description: 'The employments of the employee', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + employments?: string[]; + + @ApiPropertyOptional({ + type: String, + example: '123-45-6789', + nullable: true, + description: 'The Social Security Number of the employee', + }) + @IsString() + @IsOptional() + ssn?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Male', + nullable: true, + description: 'The gender of the employee', + }) + @IsString() + @IsOptional() + gender?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Caucasian', + nullable: true, + description: 'The ethnicity of the employee', + }) + @IsString() + @IsOptional() + ethnicity?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Married', + nullable: true, + description: 'The marital status of the employee', + }) + @IsString() + @IsOptional() + marital_status?: string; + + @ApiPropertyOptional({ + type: String, + example: '1990-01-01', + nullable: true, + description: 'The date of birth of the employee', + }) + @IsDateString() + @IsOptional() + date_of_birth?: string; + + @ApiPropertyOptional({ + type: String, + example: '2020-01-01', + nullable: true, + description: 'The start date of the employee', + }) + @IsDateString() + @IsOptional() + start_date?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Active', + nullable: true, + description: 'The employment status of the employee', + }) + @IsString() + @IsOptional() + employment_status?: string; + + @ApiPropertyOptional({ + type: String, + example: '2025-01-01', + nullable: true, + description: 'The termination date of the employee', + }) + @IsDateString() + @IsOptional() + termination_date?: string; + + @ApiPropertyOptional({ + type: String, + example: 'https://example.com/avatar.jpg', + nullable: true, + description: "The URL of the employee's avatar", + }) + @IsUrl() + @IsOptional() + avatar_url?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisEmployeeOutput extends UnifiedHrisEmployeeInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the employee record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'employee_1234', + nullable: true, + description: + 'The remote ID of the employee in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the employee in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the employee was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the employee record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the employee record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the employee was deleted in the remote system', + }) + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/employeepayrollrun/types/model.unified.ts b/packages/api/src/hris/employeepayrollrun/types/model.unified.ts index b32914ba1..617647e04 100644 --- a/packages/api/src/hris/employeepayrollrun/types/model.unified.ts +++ b/packages/api/src/hris/employeepayrollrun/types/model.unified.ts @@ -1,3 +1,287 @@ -export class UnifiedHrisEmployeepayrollrunInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsBoolean, + IsArray, +} from 'class-validator'; -export class UnifiedHrisEmployeepayrollrunOutput extends UnifiedHrisEmployeepayrollrunInput {} +class DeductionItem { + @ApiPropertyOptional({ + type: String, + example: 'Health Insurance', + nullable: true, + description: 'The name of the deduction', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: Number, + example: 100, + nullable: true, + description: 'The amount of employee deduction', + }) + @IsNumber() + @IsOptional() + employee_deduction?: number; + + @ApiPropertyOptional({ + type: Number, + example: 200, + nullable: true, + description: 'The amount of company deduction', + }) + @IsNumber() + @IsOptional() + company_deduction?: number; +} + +class EarningItem { + @ApiPropertyOptional({ + type: Number, + example: 1000, + nullable: true, + description: 'The amount of the earning', + }) + @IsNumber() + @IsOptional() + amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'Salary', + nullable: true, + description: 'The type of the earning', + }) + @IsString() + @IsOptional() + type?: string; +} + +class TaxItem { + @ApiPropertyOptional({ + type: String, + example: 'Federal Income Tax', + nullable: true, + description: 'The name of the tax', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: Number, + example: 250, + nullable: true, + description: 'The amount of the tax', + }) + @IsNumber() + @IsOptional() + amount?: number; + + @ApiPropertyOptional({ + type: Boolean, + example: true, + nullable: true, + description: 'Indicates if this is an employer tax', + }) + @IsBoolean() + @IsOptional() + employer_tax?: boolean; +} + +export class UnifiedHrisEmployeepayrollrunInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated employee', + }) + @IsUUID() + @IsOptional() + employee_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated payroll run', + }) + @IsUUID() + @IsOptional() + payroll_run_id?: string; + + @ApiPropertyOptional({ + type: Number, + example: 5000, + nullable: true, + description: 'The gross pay amount', + }) + @IsNumber() + @IsOptional() + gross_pay?: number; + + @ApiPropertyOptional({ + type: Number, + example: 4000, + nullable: true, + description: 'The net pay amount', + }) + @IsNumber() + @IsOptional() + net_pay?: number; + + @ApiPropertyOptional({ + type: String, + example: '2023-01-01T00:00:00Z', + nullable: true, + description: 'The start date of the pay period', + }) + @IsDateString() + @IsOptional() + start_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '2023-01-15T23:59:59Z', + nullable: true, + description: 'The end date of the pay period', + }) + @IsDateString() + @IsOptional() + end_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '2023-01-20T00:00:00Z', + nullable: true, + description: 'The date the check was issued', + }) + @IsDateString() + @IsOptional() + check_date?: string; + + @ApiPropertyOptional({ + type: [DeductionItem], + nullable: true, + description: 'The list of deductions for this payroll run', + }) + @IsArray() + @IsOptional() + deductions?: DeductionItem[]; + + @ApiPropertyOptional({ + type: [EarningItem], + nullable: true, + description: 'The list of earnings for this payroll run', + }) + @IsArray() + @IsOptional() + earnings?: EarningItem[]; + + @ApiPropertyOptional({ + type: [TaxItem], + nullable: true, + description: 'The list of taxes for this payroll run', + }) + @IsArray() + @IsOptional() + taxes?: TaxItem[]; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisEmployeepayrollrunOutput extends UnifiedHrisEmployeepayrollrunInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the employee payroll run record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'payroll_run_1234', + nullable: true, + description: + 'The remote ID of the employee payroll run in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the employee payroll run in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the employee payroll run was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the employee payroll run record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the employee payroll run record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: + 'Indicates if the employee payroll run was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/employerbenefit/types/model.unified.ts b/packages/api/src/hris/employerbenefit/types/model.unified.ts index f5949144c..9c83f3fe2 100644 --- a/packages/api/src/hris/employerbenefit/types/model.unified.ts +++ b/packages/api/src/hris/employerbenefit/types/model.unified.ts @@ -1,3 +1,142 @@ -export class UnifiedHrisEmployerbenefitInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisEmployerbenefitOutput extends UnifiedHrisEmployerbenefitInput {} +export class UnifiedHrisEmployerbenefitInput { + @ApiPropertyOptional({ + type: String, + example: 'Health Insurance', + nullable: true, + description: 'The type of the benefit plan', + }) + @IsString() + @IsOptional() + benefit_plan_type?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Company Health Plan', + nullable: true, + description: 'The name of the employer benefit', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Comprehensive health insurance coverage for employees', + nullable: true, + description: 'The description of the employer benefit', + }) + @IsString() + @IsOptional() + description?: string; + + @ApiPropertyOptional({ + type: String, + example: 'HEALTH-001', + nullable: true, + description: 'The deduction code for the employer benefit', + }) + @IsString() + @IsOptional() + deduction_code?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisEmployerbenefitOutput extends UnifiedHrisEmployerbenefitInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the employer benefit record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'benefit_1234', + nullable: true, + description: + 'The remote ID of the employer benefit in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the employer benefit in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the employer benefit was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the employer benefit record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the employer benefit record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: + 'Indicates if the employer benefit was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/employment/types/model.unified.ts b/packages/api/src/hris/employment/types/model.unified.ts index 5c936ddb6..a2c1f90c1 100644 --- a/packages/api/src/hris/employment/types/model.unified.ts +++ b/packages/api/src/hris/employment/types/model.unified.ts @@ -1,3 +1,202 @@ -export class UnifiedHrisEmploymentInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisEmploymentOutput extends UnifiedHrisEmploymentInput {} +export class UnifiedHrisEmploymentInput { + @ApiPropertyOptional({ + type: String, + example: 'Software Engineer', + nullable: true, + description: 'The job title of the employment', + }) + @IsString() + @IsOptional() + job_title?: string; + + @ApiPropertyOptional({ + type: Number, + example: 100000, + nullable: true, + description: 'The pay rate of the employment', + }) + @IsNumber() + @IsOptional() + pay_rate?: number; + + @ApiPropertyOptional({ + type: String, + example: 'Monthly', + nullable: true, + description: 'The pay period of the employment', + }) + @IsString() + @IsOptional() + pay_period?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Bi-weekly', + nullable: true, + description: 'The pay frequency of the employment', + }) + @IsString() + @IsOptional() + pay_frequency?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + description: 'The currency of the pay', + }) + @IsString() + @IsOptional() + pay_currency?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Exempt', + nullable: true, + description: 'The FLSA status of the employment', + }) + @IsString() + @IsOptional() + flsa_status?: string; + + @ApiPropertyOptional({ + type: String, + example: '2023-01-01', + nullable: true, + description: 'The effective date of the employment', + }) + @IsDateString() + @IsOptional() + effective_date?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Full-time', + nullable: true, + description: 'The type of employment', + }) + @IsString() + @IsOptional() + employment_type?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated pay group', + }) + @IsUUID() + @IsOptional() + pay_group_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated employee', + }) + @IsUUID() + @IsOptional() + employee_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisEmploymentOutput extends UnifiedHrisEmploymentInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the employment record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'employment_1234', + nullable: true, + description: + 'The remote ID of the employment in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the employment in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the employment was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the employment record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the employment record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the employment was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/group/types/model.unified.ts b/packages/api/src/hris/group/types/model.unified.ts index c9e196fc2..8170a26a0 100644 --- a/packages/api/src/hris/group/types/model.unified.ts +++ b/packages/api/src/hris/group/types/model.unified.ts @@ -1,3 +1,128 @@ -export class UnifiedHrisGroupInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisGroupOutput extends UnifiedHrisGroupInput {} +export class UnifiedHrisGroupInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the parent group', + }) + @IsUUID() + @IsOptional() + parent_group?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Engineering Team', + nullable: true, + description: 'The name of the group', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Department', + nullable: true, + description: 'The type of the group', + }) + @IsString() + @IsOptional() + type?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisGroupOutput extends UnifiedHrisGroupInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the group record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'group_1234', + nullable: true, + description: 'The remote ID of the group in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: 'The remote data of the group in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The date when the group was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the group record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the group record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the group was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/location/types/model.unified.ts b/packages/api/src/hris/location/types/model.unified.ts index d966a0e3c..629470a19 100644 --- a/packages/api/src/hris/location/types/model.unified.ts +++ b/packages/api/src/hris/location/types/model.unified.ts @@ -1,3 +1,191 @@ -export class UnifiedHrisLocationInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisLocationOutput extends UnifiedHrisLocationInput {} +export class UnifiedHrisLocationInput { + @ApiPropertyOptional({ + type: String, + example: 'Headquarters', + nullable: true, + description: 'The name of the location', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: String, + example: '+1234567890', + nullable: true, + description: 'The phone number of the location', + }) + @IsString() + @IsOptional() + phone_number?: string; + + @ApiPropertyOptional({ + type: String, + example: '123 Main St', + nullable: true, + description: 'The first line of the street address', + }) + @IsString() + @IsOptional() + street_1?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Suite 456', + nullable: true, + description: 'The second line of the street address', + }) + @IsString() + @IsOptional() + street_2?: string; + + @ApiPropertyOptional({ + type: String, + example: 'San Francisco', + nullable: true, + description: 'The city of the location', + }) + @IsString() + @IsOptional() + city?: string; + + @ApiPropertyOptional({ + type: String, + example: 'CA', + nullable: true, + description: 'The state or region of the location', + }) + @IsString() + @IsOptional() + state?: string; + + @ApiPropertyOptional({ + type: String, + example: '94105', + nullable: true, + description: 'The zip or postal code of the location', + }) + @IsString() + @IsOptional() + zip_code?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USA', + nullable: true, + description: 'The country of the location', + }) + @IsString() + @IsOptional() + country?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Office', + nullable: true, + description: 'The type of the location', + }) + @IsString() + @IsOptional() + location_type?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisLocationOutput extends UnifiedHrisLocationInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the location record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'location_1234', + nullable: true, + description: + 'The remote ID of the location in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the location in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the location was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the location record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the location record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the location was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/paygroup/types/model.unified.ts b/packages/api/src/hris/paygroup/types/model.unified.ts index d07174303..780169de2 100644 --- a/packages/api/src/hris/paygroup/types/model.unified.ts +++ b/packages/api/src/hris/paygroup/types/model.unified.ts @@ -1,3 +1,111 @@ -export class UnifiedHrisPaygroupInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisPaygroupOutput extends UnifiedHrisPaygroupInput {} +export class UnifiedHrisPaygroupInput { + @ApiPropertyOptional({ + type: String, + example: 'Monthly Salaried', + nullable: true, + description: 'The name of the pay group', + }) + @IsString() + @IsOptional() + pay_group_name?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisPaygroupOutput extends UnifiedHrisPaygroupInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the pay group record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'paygroup_1234', + nullable: true, + description: + 'The remote ID of the pay group in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the pay group in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the pay group was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the pay group record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the pay group record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the pay group was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/payrollrun/types/model.unified.ts b/packages/api/src/hris/payrollrun/types/model.unified.ts index 131ae365c..dc0a152ad 100644 --- a/packages/api/src/hris/payrollrun/types/model.unified.ts +++ b/packages/api/src/hris/payrollrun/types/model.unified.ts @@ -1,3 +1,162 @@ -export class UnifiedHrisPayrollrunInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisPayrollrunOutput extends UnifiedHrisPayrollrunInput {} +export class UnifiedHrisPayrollrunInput { + @ApiPropertyOptional({ + type: String, + example: 'Completed', + nullable: true, + description: 'The state of the payroll run', + }) + @IsString() + @IsOptional() + run_state?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Regular', + nullable: true, + description: 'The type of the payroll run', + }) + @IsString() + @IsOptional() + run_type?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-01-01T00:00:00Z', + nullable: true, + description: 'The start date of the payroll run', + }) + @IsDateString() + @IsOptional() + start_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-01-15T23:59:59Z', + nullable: true, + description: 'The end date of the payroll run', + }) + @IsDateString() + @IsOptional() + end_date?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-01-20T00:00:00Z', + nullable: true, + description: 'The check date of the payroll run', + }) + @IsDateString() + @IsOptional() + check_date?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisPayrollrunOutput extends UnifiedHrisPayrollrunInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the payroll run record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'payroll_run_1234', + nullable: true, + description: + 'The remote ID of the payroll run in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the payroll run in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the payroll run was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The created date of the payroll run record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: 'The last modified date of the payroll run record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: + 'Indicates if the payroll run was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; + + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: + 'The UUIDs of the employee payroll runs associated with this payroll run', + }) + @IsOptional() + employee_payroll_runs?: string[]; +} diff --git a/packages/api/src/hris/timeoff/types/model.unified.ts b/packages/api/src/hris/timeoff/types/model.unified.ts index 09af4f654..e822f64bd 100644 --- a/packages/api/src/hris/timeoff/types/model.unified.ts +++ b/packages/api/src/hris/timeoff/types/model.unified.ts @@ -1,3 +1,192 @@ -export class UnifiedHrisTimeoffInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisTimeoffOutput extends UnifiedHrisTimeoffInput {} +export class UnifiedHrisTimeoffInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the employee taking time off', + }) + @IsUUID() + @IsOptional() + employee?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the approver for the time off request', + }) + @IsUUID() + @IsOptional() + approver?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Approved', + nullable: true, + description: 'The status of the time off request', + }) + @IsString() + @IsOptional() + status?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Annual vacation', + nullable: true, + description: 'A note from the employee about the time off request', + }) + @IsString() + @IsOptional() + employee_note?: string; + + @ApiPropertyOptional({ + type: String, + example: 'Days', + nullable: true, + description: 'The units used for the time off (e.g., Days, Hours)', + }) + @IsString() + @IsOptional() + units?: string; + + @ApiPropertyOptional({ + type: Number, + example: 5, + nullable: true, + description: 'The amount of time off requested', + }) + @IsNumber() + @IsOptional() + amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'Vacation', + nullable: true, + description: 'The type of time off request', + }) + @IsString() + @IsOptional() + request_type?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-07-01T09:00:00Z', + nullable: true, + description: 'The start time of the time off', + }) + @IsDateString() + @IsOptional() + start_time?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-07-05T17:00:00Z', + nullable: true, + description: 'The end time of the time off', + }) + @IsDateString() + @IsOptional() + end_time?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisTimeoffOutput extends UnifiedHrisTimeoffInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the time off record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'timeoff_1234', + nullable: true, + description: + 'The remote ID of the time off in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the time off in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the time off was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the time off record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the time off record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: 'Indicates if the time off was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/src/hris/timeoffbalance/types/model.unified.ts b/packages/api/src/hris/timeoffbalance/types/model.unified.ts index 47ec3b04f..266d5634a 100644 --- a/packages/api/src/hris/timeoffbalance/types/model.unified.ts +++ b/packages/api/src/hris/timeoffbalance/types/model.unified.ts @@ -1,3 +1,143 @@ -export class UnifiedHrisTimeoffbalanceInput {} +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsNumber, + IsDateString, + IsBoolean, +} from 'class-validator'; -export class UnifiedHrisTimeoffbalanceOutput extends UnifiedHrisTimeoffbalanceInput {} +export class UnifiedHrisTimeoffbalanceInput { + @ApiPropertyOptional({ + type: Number, + example: 80, + nullable: true, + description: 'The current balance of time off', + }) + @IsNumber() + @IsOptional() + balance?: number; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated employee', + }) + @IsUUID() + @IsOptional() + employee_id?: string; + + @ApiPropertyOptional({ + type: Number, + example: 40, + nullable: true, + description: 'The amount of time off used', + }) + @IsNumber() + @IsOptional() + used?: number; + + @ApiPropertyOptional({ + type: String, + example: 'Vacation', + nullable: true, + description: 'The type of time off policy', + }) + @IsString() + @IsOptional() + policy_type?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisTimeoffbalanceOutput extends UnifiedHrisTimeoffbalanceInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the time off balance record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'timeoff_balance_1234', + nullable: true, + description: + 'The remote ID of the time off balance in the context of the 3rd Party', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the time off balance in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: + 'The date when the time off balance was created in the 3rd party system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The created date of the time off balance record', + }) + @IsDateString() + @IsOptional() + created_at?: string; + + @ApiPropertyOptional({ + type: String, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The last modified date of the time off balance record', + }) + @IsDateString() + @IsOptional() + modified_at?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + nullable: true, + description: + 'Indicates if the time off balance was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; +} diff --git a/packages/api/swagger/swagger-spec.yaml b/packages/api/swagger/swagger-spec.yaml index 7fc0ec35f..4e4daddfa 100644 --- a/packages/api/swagger/swagger-spec.yaml +++ b/packages/api/swagger/swagger-spec.yaml @@ -12221,148 +12221,3328 @@ components: - path UnifiedHrisBankinfoOutput: type: object - properties: {} + properties: + account_type: + type: string + example: checking + nullable: true + description: The type of the bank account + bank_name: + type: string + example: Bank of America + nullable: true + description: The name of the bank + account_number: + type: string + example: '1234567890' + nullable: true + description: The account number + routing_number: + type: string + example: '021000021' + nullable: true + description: The routing number of the bank + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the bank info record + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the bank info in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the bank info in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the bank info was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the bank info record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the bank info record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the bank info was deleted in the remote system + required: + - id + - created_at + - modified_at + - remote_was_deleted UnifiedHrisBenefitOutput: type: object - properties: {} + properties: + provider_name: + type: string + example: Health Insurance Provider + nullable: true + description: The name of the benefit provider + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + employee_contribution: + type: number + example: 100 + nullable: true + description: The employee contribution amount + company_contribution: + type: number + example: 200 + nullable: true + description: The company contribution amount + start_date: + type: string + example: '2024-01-01T00:00:00Z' + nullable: true + description: The start date of the benefit + end_date: + type: string + example: '2024-12-31T23:59:59Z' + nullable: true + description: The end date of the benefit + employer_benefit_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employer benefit + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the benefit record + remote_id: + type: string + example: benefit_1234 + nullable: true + description: The remote ID of the benefit in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the benefit in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the benefit was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the benefit record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the benefit record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the benefit was deleted in the remote system UnifiedHrisCompanyOutput: type: object - properties: {} + properties: + legal_name: + type: string + example: Acme Corporation + nullable: true + description: The legal name of the company + display_name: + type: string + example: Acme Corp + nullable: true + description: The display name of the company + eins: + example: + - 12-3456789 + - 98-7654321 + nullable: true + description: The Employer Identification Numbers (EINs) of the company + type: array + items: + type: string + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company record + remote_id: + type: string + example: company_1234 + nullable: true + description: The remote ID of the company in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the company in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the company was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the company record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the company record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the company was deleted in the remote system UnifiedHrisDependentOutput: type: object - properties: {} - UnifiedHrisEmployeepayrollrunOutput: - type: object - properties: {} - UnifiedHrisEmployeeOutput: + properties: + first_name: + type: string + example: John + nullable: true + description: The first name of the dependent + last_name: + type: string + example: Doe + nullable: true + description: The last name of the dependent + middle_name: + type: string + example: Michael + nullable: true + description: The middle name of the dependent + relationship: + type: string + example: Child + nullable: true + description: The relationship of the dependent to the employee + date_of_birth: + type: string + example: '2020-01-01' + nullable: true + description: The date of birth of the dependent + gender: + type: string + example: Male + nullable: true + description: The gender of the dependent + phone_number: + type: string + example: '+1234567890' + nullable: true + description: The phone number of the dependent + home_location: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the home location + is_student: + type: boolean + example: true + nullable: true + description: Indicates if the dependent is a student + ssn: + type: string + example: 123-45-6789 + nullable: true + description: The Social Security Number of the dependent + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the dependent record + remote_id: + type: string + example: dependent_1234 + nullable: true + description: The remote ID of the dependent in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the dependent in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the dependent was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the dependent record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the dependent record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the dependent was deleted in the remote system + DeductionItem: type: object - properties: {} - UnifiedHrisEmployeeInput: + properties: + name: + type: string + example: Health Insurance + nullable: true + description: The name of the deduction + employee_deduction: + type: number + example: 100 + nullable: true + description: The amount of employee deduction + company_deduction: + type: number + example: 200 + nullable: true + description: The amount of company deduction + EarningItem: type: object - properties: {} - UnifiedHrisEmployerbenefitOutput: + properties: + amount: + type: number + example: 1000 + nullable: true + description: The amount of the earning + type: + type: string + example: Salary + nullable: true + description: The type of the earning + TaxItem: type: object - properties: {} - UnifiedHrisEmploymentOutput: + properties: + name: + type: string + example: Federal Income Tax + nullable: true + description: The name of the tax + amount: + type: number + example: 250 + nullable: true + description: The amount of the tax + employer_tax: + type: boolean + example: true + nullable: true + description: Indicates if this is an employer tax + UnifiedHrisEmployeepayrollrunOutput: type: object - properties: {} - UnifiedHrisGroupOutput: + properties: + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + payroll_run_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated payroll run + gross_pay: + type: number + example: 5000 + nullable: true + description: The gross pay amount + net_pay: + type: number + example: 4000 + nullable: true + description: The net pay amount + start_date: + type: string + example: '2023-01-01T00:00:00Z' + nullable: true + description: The start date of the pay period + end_date: + type: string + example: '2023-01-15T23:59:59Z' + nullable: true + description: The end date of the pay period + check_date: + type: string + example: '2023-01-20T00:00:00Z' + nullable: true + description: The date the check was issued + deductions: + nullable: true + description: The list of deductions for this payroll run + type: array + items: + $ref: '#/components/schemas/DeductionItem' + earnings: + nullable: true + description: The list of earnings for this payroll run + type: array + items: + $ref: '#/components/schemas/EarningItem' + taxes: + nullable: true + description: The list of taxes for this payroll run + type: array + items: + $ref: '#/components/schemas/TaxItem' + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee payroll run record + remote_id: + type: string + example: payroll_run_1234 + nullable: true + description: >- + The remote ID of the employee payroll run in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the employee payroll run in the context of the + 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: >- + The date when the employee payroll run was created in the 3rd party + system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the employee payroll run record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the employee payroll run record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: >- + Indicates if the employee payroll run was deleted in the remote + system + UnifiedHrisEmployeeOutput: + type: object + properties: + groups: + example: &ref_123 + - Group1 + - Group2 + nullable: true + description: The groups the employee belongs to + type: array + items: + type: string + employee_number: + type: string + example: EMP001 + nullable: true + description: The employee number + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + first_name: + type: string + example: John + nullable: true + description: The first name of the employee + last_name: + type: string + example: Doe + nullable: true + description: The last name of the employee + preferred_name: + type: string + example: Johnny + nullable: true + description: The preferred name of the employee + display_full_name: + type: string + example: John Doe + nullable: true + description: The full display name of the employee + username: + type: string + example: johndoe + nullable: true + description: The username of the employee + work_email: + type: string + example: john.doe@company.com + nullable: true + description: The work email of the employee + personal_email: + type: string + example: john.doe@personal.com + nullable: true + description: The personal email of the employee + mobile_phone_number: + type: string + example: '+1234567890' + nullable: true + description: The mobile phone number of the employee + employments: + example: &ref_124 + - Employment1 + - Employment2 + nullable: true + description: The employments of the employee + type: array + items: + type: string + ssn: + type: string + example: 123-45-6789 + nullable: true + description: The Social Security Number of the employee + gender: + type: string + example: Male + nullable: true + description: The gender of the employee + ethnicity: + type: string + example: Caucasian + nullable: true + description: The ethnicity of the employee + marital_status: + type: string + example: Married + nullable: true + description: The marital status of the employee + date_of_birth: + type: string + example: '1990-01-01' + nullable: true + description: The date of birth of the employee + start_date: + type: string + example: '2020-01-01' + nullable: true + description: The start date of the employee + employment_status: + type: string + example: Active + nullable: true + description: The employment status of the employee + termination_date: + type: string + example: '2025-01-01' + nullable: true + description: The termination date of the employee + avatar_url: + type: string + example: https://example.com/avatar.jpg + nullable: true + description: The URL of the employee's avatar + field_mappings: + type: object + example: &ref_125 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee record + remote_id: + type: string + example: employee_1234 + nullable: true + description: The remote ID of the employee in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the employee in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the employee was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the employee record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the employee record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the employee was deleted in the remote system + UnifiedHrisEmployeeInput: + type: object + properties: + groups: + example: *ref_123 + nullable: true + description: The groups the employee belongs to + type: array + items: + type: string + employee_number: + type: string + example: EMP001 + nullable: true + description: The employee number + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + first_name: + type: string + example: John + nullable: true + description: The first name of the employee + last_name: + type: string + example: Doe + nullable: true + description: The last name of the employee + preferred_name: + type: string + example: Johnny + nullable: true + description: The preferred name of the employee + display_full_name: + type: string + example: John Doe + nullable: true + description: The full display name of the employee + username: + type: string + example: johndoe + nullable: true + description: The username of the employee + work_email: + type: string + example: john.doe@company.com + nullable: true + description: The work email of the employee + personal_email: + type: string + example: john.doe@personal.com + nullable: true + description: The personal email of the employee + mobile_phone_number: + type: string + example: '+1234567890' + nullable: true + description: The mobile phone number of the employee + employments: + example: *ref_124 + nullable: true + description: The employments of the employee + type: array + items: + type: string + ssn: + type: string + example: 123-45-6789 + nullable: true + description: The Social Security Number of the employee + gender: + type: string + example: Male + nullable: true + description: The gender of the employee + ethnicity: + type: string + example: Caucasian + nullable: true + description: The ethnicity of the employee + marital_status: + type: string + example: Married + nullable: true + description: The marital status of the employee + date_of_birth: + type: string + example: '1990-01-01' + nullable: true + description: The date of birth of the employee + start_date: + type: string + example: '2020-01-01' + nullable: true + description: The start date of the employee + employment_status: + type: string + example: Active + nullable: true + description: The employment status of the employee + termination_date: + type: string + example: '2025-01-01' + nullable: true + description: The termination date of the employee + avatar_url: + type: string + example: https://example.com/avatar.jpg + nullable: true + description: The URL of the employee's avatar + field_mappings: + type: object + example: *ref_125 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedHrisEmployerbenefitOutput: + type: object + properties: + benefit_plan_type: + type: string + example: Health Insurance + nullable: true + description: The type of the benefit plan + name: + type: string + example: Company Health Plan + nullable: true + description: The name of the employer benefit + description: + type: string + example: Comprehensive health insurance coverage for employees + nullable: true + description: The description of the employer benefit + deduction_code: + type: string + example: HEALTH-001 + nullable: true + description: The deduction code for the employer benefit + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employer benefit record + remote_id: + type: string + example: benefit_1234 + nullable: true + description: >- + The remote ID of the employer benefit in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the employer benefit in the context of the 3rd + Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: >- + The date when the employer benefit was created in the 3rd party + system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the employer benefit record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the employer benefit record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the employer benefit was deleted in the remote system + UnifiedHrisEmploymentOutput: + type: object + properties: + job_title: + type: string + example: Software Engineer + nullable: true + description: The job title of the employment + pay_rate: + type: number + example: 100000 + nullable: true + description: The pay rate of the employment + pay_period: + type: string + example: Monthly + nullable: true + description: The pay period of the employment + pay_frequency: + type: string + example: Bi-weekly + nullable: true + description: The pay frequency of the employment + pay_currency: + type: string + example: USD + nullable: true + description: The currency of the pay + flsa_status: + type: string + example: Exempt + nullable: true + description: The FLSA status of the employment + effective_date: + type: string + example: '2023-01-01' + nullable: true + description: The effective date of the employment + employment_type: + type: string + example: Full-time + nullable: true + description: The type of employment + pay_group_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated pay group + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employment record + remote_id: + type: string + example: employment_1234 + nullable: true + description: The remote ID of the employment in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the employment in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the employment was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the employment record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the employment record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the employment was deleted in the remote system + UnifiedHrisGroupOutput: + type: object + properties: + parent_group: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent group + name: + type: string + example: Engineering Team + nullable: true + description: The name of the group + type: + type: string + example: Department + nullable: true + description: The type of the group + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the group record + remote_id: + type: string + example: group_1234 + nullable: true + description: The remote ID of the group in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the group in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the group was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the group record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the group record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the group was deleted in the remote system + UnifiedHrisLocationOutput: + type: object + properties: + name: + type: string + example: Headquarters + nullable: true + description: The name of the location + phone_number: + type: string + example: '+1234567890' + nullable: true + description: The phone number of the location + street_1: + type: string + example: 123 Main St + nullable: true + description: The first line of the street address + street_2: + type: string + example: Suite 456 + nullable: true + description: The second line of the street address + city: + type: string + example: San Francisco + nullable: true + description: The city of the location + state: + type: string + example: CA + nullable: true + description: The state or region of the location + zip_code: + type: string + example: '94105' + nullable: true + description: The zip or postal code of the location + country: + type: string + example: USA + nullable: true + description: The country of the location + location_type: + type: string + example: Office + nullable: true + description: The type of the location + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the location record + remote_id: + type: string + example: location_1234 + nullable: true + description: The remote ID of the location in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the location in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the location was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the location record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the location record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the location was deleted in the remote system + UnifiedHrisPaygroupOutput: + type: object + properties: + pay_group_name: + type: string + example: Monthly Salaried + nullable: true + description: The name of the pay group + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the pay group record + remote_id: + type: string + example: paygroup_1234 + nullable: true + description: The remote ID of the pay group in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the pay group in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the pay group was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the pay group record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the pay group record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the pay group was deleted in the remote system + UnifiedHrisPayrollrunOutput: + type: object + properties: + run_state: + type: string + example: Completed + nullable: true + description: The state of the payroll run + run_type: + type: string + example: Regular + nullable: true + description: The type of the payroll run + start_date: + type: string + example: '2024-01-01T00:00:00Z' + nullable: true + description: The start date of the payroll run + end_date: + type: string + example: '2024-01-15T23:59:59Z' + nullable: true + description: The end date of the payroll run + check_date: + type: string + example: '2024-01-20T00:00:00Z' + nullable: true + description: The check date of the payroll run + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the payroll run record + remote_id: + type: string + example: payroll_run_1234 + nullable: true + description: The remote ID of the payroll run in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the payroll run in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the payroll run was created in the 3rd party system + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the payroll run record + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the payroll run record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the payroll run was deleted in the remote system + employee_payroll_runs: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: >- + The UUIDs of the employee payroll runs associated with this payroll + run + type: array + items: + type: string + UnifiedHrisTimeoffOutput: + type: object + properties: + employee: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee taking time off + approver: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the approver for the time off request + status: + type: string + example: Approved + nullable: true + description: The status of the time off request + employee_note: + type: string + example: Annual vacation + nullable: true + description: A note from the employee about the time off request + units: + type: string + example: Days + nullable: true + description: The units used for the time off (e.g., Days, Hours) + amount: + type: number + example: 5 + nullable: true + description: The amount of time off requested + request_type: + type: string + example: Vacation + nullable: true + description: The type of time off request + start_time: + type: string + example: '2024-07-01T09:00:00Z' + nullable: true + description: The start time of the time off + end_time: + type: string + example: '2024-07-05T17:00:00Z' + nullable: true + description: The end time of the time off + field_mappings: + type: object + example: &ref_126 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the time off record + remote_id: + type: string + example: timeoff_1234 + nullable: true + description: The remote ID of the time off in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the time off in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the time off was created in the 3rd party system + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the time off record + modified_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the time off record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the time off was deleted in the remote system + UnifiedHrisTimeoffInput: + type: object + properties: + employee: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee taking time off + approver: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the approver for the time off request + status: + type: string + example: Approved + nullable: true + description: The status of the time off request + employee_note: + type: string + example: Annual vacation + nullable: true + description: A note from the employee about the time off request + units: + type: string + example: Days + nullable: true + description: The units used for the time off (e.g., Days, Hours) + amount: + type: number + example: 5 + nullable: true + description: The amount of time off requested + request_type: + type: string + example: Vacation + nullable: true + description: The type of time off request + start_time: + type: string + example: '2024-07-01T09:00:00Z' + nullable: true + description: The start time of the time off + end_time: + type: string + example: '2024-07-05T17:00:00Z' + nullable: true + description: The end time of the time off + field_mappings: + type: object + example: *ref_126 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedHrisTimeoffbalanceOutput: + type: object + properties: + balance: + type: number + example: 80 + nullable: true + description: The current balance of time off + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + used: + type: number + example: 40 + nullable: true + description: The amount of time off used + policy_type: + type: string + example: Vacation + nullable: true + description: The type of time off policy + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the time off balance record + remote_id: + type: string + example: timeoff_balance_1234 + nullable: true + description: >- + The remote ID of the time off balance in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the time off balance in the context of the 3rd + Party + remote_created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: >- + The date when the time off balance was created in the 3rd party + system + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the time off balance record + modified_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the time off balance record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the time off balance was deleted in the remote system + UnifiedMarketingautomationActionOutput: type: object properties: {} - UnifiedHrisLocationOutput: + UnifiedMarketingautomationActionInput: type: object properties: {} - UnifiedHrisPaygroupOutput: + UnifiedMarketingautomationAutomationOutput: type: object properties: {} - UnifiedHrisPayrollrunOutput: + UnifiedMarketingautomationAutomationInput: + type: object + properties: {} + UnifiedMarketingautomationCampaignOutput: + type: object + properties: {} + UnifiedMarketingautomationCampaignInput: + type: object + properties: {} + UnifiedMarketingautomationContactOutput: + type: object + properties: {} + UnifiedMarketingautomationContactInput: + type: object + properties: {} + UnifiedMarketingautomationEmailOutput: + type: object + properties: {} + UnifiedMarketingautomationEventOutput: + type: object + properties: {} + UnifiedMarketingautomationListOutput: + type: object + properties: {} + UnifiedMarketingautomationListInput: + type: object + properties: {} + UnifiedMarketingautomationMessageOutput: + type: object + properties: {} + UnifiedMarketingautomationTemplateOutput: + type: object + properties: {} + UnifiedMarketingautomationTemplateInput: + type: object + properties: {} + UnifiedMarketingautomationUserOutput: + type: object + properties: {} + UnifiedAtsActivityOutput: + type: object + properties: + activity_type: + type: string + enum: &ref_127 + - NOTE + - EMAIL + - OTHER + example: NOTE + nullable: true + description: The type of activity + subject: + type: string + example: Email subject + nullable: true + description: The subject of the activity + body: + type: string + example: Dear Diana, I love you + nullable: true + description: The body of the activity + visibility: + type: string + enum: &ref_128 + - ADMIN_ONLY + - PUBLIC + - PRIVATE + example: PUBLIC + nullable: true + description: The visibility of the activity + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + remote_created_at: + type: string + format: date-time + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote creation date of the activity + field_mappings: + type: object + example: &ref_129 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the activity + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the activity in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the activity in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsActivityInput: + type: object + properties: + activity_type: + type: string + enum: *ref_127 + example: NOTE + nullable: true + description: The type of activity + subject: + type: string + example: Email subject + nullable: true + description: The subject of the activity + body: + type: string + example: Dear Diana, I love you + nullable: true + description: The body of the activity + visibility: + type: string + enum: *ref_128 + example: PUBLIC + nullable: true + description: The visibility of the activity + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + remote_created_at: + type: string + format: date-time + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote creation date of the activity + field_mappings: + type: object + example: *ref_129 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAtsApplicationOutput: type: object - properties: {} - UnifiedHrisTimeoffOutput: + properties: + applied_at: + format: date-time + type: string + nullable: true + description: The application date + example: '2024-10-01T12:00:00Z' + rejected_at: + format: date-time + type: string + nullable: true + description: The rejection date + example: '2024-10-01T12:00:00Z' + offers: + nullable: true + description: The offers UUIDs for the application + example: &ref_130 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + - 12345678-1234-1234-1234-123456789012 + type: array + items: + type: string + source: + type: string + nullable: true + description: The source of the application + example: Source Name + credited_to: + type: string + nullable: true + description: The UUID of the person credited for the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + current_stage: + type: string + nullable: true + description: The UUID of the current stage of the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + reject_reason: + type: string + nullable: true + description: The rejection reason for the application + example: Candidate not experienced enough + candidate_id: + type: string + nullable: true + description: The UUID of the candidate + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + job_id: + type: string + description: The UUID of the job + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + field_mappings: + type: object + example: &ref_131 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + nullable: true + description: The UUID of the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + remote_id: + type: string + nullable: true + description: The remote ID of the application in the context of the 3rd Party + example: id_1 + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the application in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + remote_created_at: + format: date-time + type: string + nullable: true + description: The remote created date of the object + remote_modified_at: + format: date-time + type: string + nullable: true + description: The remote modified date of the object + UnifiedAtsApplicationInput: type: object - properties: {} - UnifiedHrisTimeoffInput: + properties: + applied_at: + format: date-time + type: string + nullable: true + description: The application date + example: '2024-10-01T12:00:00Z' + rejected_at: + format: date-time + type: string + nullable: true + description: The rejection date + example: '2024-10-01T12:00:00Z' + offers: + nullable: true + description: The offers UUIDs for the application + example: *ref_130 + type: array + items: + type: string + source: + type: string + nullable: true + description: The source of the application + example: Source Name + credited_to: + type: string + nullable: true + description: The UUID of the person credited for the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + current_stage: + type: string + nullable: true + description: The UUID of the current stage of the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + reject_reason: + type: string + nullable: true + description: The rejection reason for the application + example: Candidate not experienced enough + candidate_id: + type: string + nullable: true + description: The UUID of the candidate + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + job_id: + type: string + description: The UUID of the job + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + field_mappings: + type: object + example: *ref_131 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAtsAttachmentOutput: type: object - properties: {} - UnifiedHrisTimeoffbalanceOutput: + properties: + file_url: + type: string + example: https://example.com/file.pdf + nullable: true + description: The URL of the file + file_name: + type: string + example: file.pdf + nullable: true + description: The name of the file + attachment_type: + type: string + example: RESUME + enum: &ref_132 + - RESUME + - COVER_LETTER + - OFFER_LETTER + - OTHER + nullable: true + description: The type of the file + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the attachment + remote_modified_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the attachment + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + field_mappings: + type: object + example: &ref_133 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the attachment + remote_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The remote ID of the attachment + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the attachment in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsAttachmentInput: type: object - properties: {} - UnifiedMarketingautomationActionOutput: + properties: + file_url: + type: string + example: https://example.com/file.pdf + nullable: true + description: The URL of the file + file_name: + type: string + example: file.pdf + nullable: true + description: The name of the file + attachment_type: + type: string + example: RESUME + enum: *ref_132 + nullable: true + description: The type of the file + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the attachment + remote_modified_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the attachment + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + field_mappings: + type: object + example: *ref_133 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + Url: type: object - properties: {} - UnifiedMarketingautomationActionInput: + properties: + url: + type: string + nullable: true + description: The url. + url_type: + type: string + nullable: true + description: The url type. It takes [WEBSITE | BLOG | LINKEDIN | GITHUB | OTHER] + required: + - url + - url_type + UnifiedAtsCandidateOutput: type: object - properties: {} - UnifiedMarketingautomationAutomationOutput: + properties: + first_name: + type: string + example: Joe + nullable: true + description: The first name of the candidate + last_name: + type: string + example: Doe + nullable: true + description: The last name of the candidate + company: + type: string + example: Acme + nullable: true + description: The company of the candidate + title: + type: string + example: Analyst + nullable: true + description: The title of the candidate + locations: + type: string + example: New York + nullable: true + description: The locations of the candidate + is_private: + type: boolean + example: false + nullable: true + description: Whether the candidate is private + email_reachable: + type: boolean + example: true + nullable: true + description: Whether the candidate is reachable by email + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the candidate + remote_modified_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the candidate + last_interaction_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The last interaction date with the candidate + attachments: + type: array + items: &ref_134 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' + example: &ref_135 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The attachments UUIDs of the candidate + applications: + type: array + items: &ref_136 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedAtsApplicationOutput' + example: &ref_137 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The applications UUIDs of the candidate + tags: + type: array + items: &ref_138 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedAtsTagOutput' + example: &ref_139 + - tag_1 + - tag_2 + nullable: true + description: The tags of the candidate + urls: + example: &ref_140 + - url: mywebsite.com + url_type: WEBSITE + nullable: true + description: >- + The urls of the candidate, possible values for Url type are WEBSITE, + BLOG, LINKEDIN, GITHUB, or OTHER + type: array + items: + $ref: '#/components/schemas/Url' + phone_numbers: + example: &ref_141 + - phone_number: '+33660688899' + phone_type: WORK + nullable: true + description: The phone numbers of the candidate + type: array + items: + $ref: '#/components/schemas/Phone' + email_addresses: + example: &ref_142 + - email_address: joedoe@gmail.com + email_address_type: WORK + nullable: true + description: The email addresses of the candidate + type: array + items: + $ref: '#/components/schemas/Email' + field_mappings: + type: object + example: &ref_143 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the candidate in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the candidate in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsCandidateInput: + type: object + properties: + first_name: + type: string + example: Joe + nullable: true + description: The first name of the candidate + last_name: + type: string + example: Doe + nullable: true + description: The last name of the candidate + company: + type: string + example: Acme + nullable: true + description: The company of the candidate + title: + type: string + example: Analyst + nullable: true + description: The title of the candidate + locations: + type: string + example: New York + nullable: true + description: The locations of the candidate + is_private: + type: boolean + example: false + nullable: true + description: Whether the candidate is private + email_reachable: + type: boolean + example: true + nullable: true + description: Whether the candidate is reachable by email + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the candidate + remote_modified_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the candidate + last_interaction_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The last interaction date with the candidate + attachments: + type: array + items: *ref_134 + example: *ref_135 + nullable: true + description: The attachments UUIDs of the candidate + applications: + type: array + items: *ref_136 + example: *ref_137 + nullable: true + description: The applications UUIDs of the candidate + tags: + type: array + items: *ref_138 + example: *ref_139 + nullable: true + description: The tags of the candidate + urls: + example: *ref_140 + nullable: true + description: >- + The urls of the candidate, possible values for Url type are WEBSITE, + BLOG, LINKEDIN, GITHUB, or OTHER + type: array + items: + $ref: '#/components/schemas/Url' + phone_numbers: + example: *ref_141 + nullable: true + description: The phone numbers of the candidate + type: array + items: + $ref: '#/components/schemas/Phone' + email_addresses: + example: *ref_142 + nullable: true + description: The email addresses of the candidate + type: array + items: + $ref: '#/components/schemas/Email' + field_mappings: + type: object + example: *ref_143 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAtsDepartmentOutput: type: object - properties: {} - UnifiedMarketingautomationAutomationInput: + properties: + name: + type: string + example: Sales + nullable: true + description: The name of the department + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the department + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the department in the context of the 3rd Party + remote_data: + type: object + example: + key1: value1 + key2: 42 + key3: true + nullable: true + additionalProperties: true + description: The remote data of the department in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2023-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsInterviewOutput: type: object - properties: {} - UnifiedMarketingautomationCampaignOutput: + properties: + status: + type: string + enum: &ref_144 + - SCHEDULED + - AWAITING_FEEDBACK + - COMPLETED + example: SCHEDULED + nullable: true + description: The status of the interview + application_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the application + job_interview_stage_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job interview stage + organized_by: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the organizer + interviewers: + example: &ref_145 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUIDs of the interviewers + type: array + items: + type: string + location: + type: string + example: San Francisco + nullable: true + description: The location of the interview + start_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The start date and time of the interview + end_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The end date and time of the interview + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote creation date of the interview + remote_updated_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote modification date of the interview + field_mappings: + type: object + example: &ref_146 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the interview + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the interview in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the interview in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsInterviewInput: type: object - properties: {} - UnifiedMarketingautomationCampaignInput: + properties: + status: + type: string + enum: *ref_144 + example: SCHEDULED + nullable: true + description: The status of the interview + application_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the application + job_interview_stage_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job interview stage + organized_by: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the organizer + interviewers: + example: *ref_145 + nullable: true + description: The UUIDs of the interviewers + type: array + items: + type: string + location: + type: string + example: San Francisco + nullable: true + description: The location of the interview + start_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The start date and time of the interview + end_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The end date and time of the interview + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote creation date of the interview + remote_updated_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote modification date of the interview + field_mappings: + type: object + example: *ref_146 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAtsJobinterviewstageOutput: type: object - properties: {} - UnifiedMarketingautomationContactOutput: + properties: + name: + type: string + example: Second Call + nullable: true + description: The name of the job interview stage + stage_order: + type: number + example: 1 + nullable: true + description: The order of the stage + job_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job interview stage + remote_id: + type: string + example: id_1 + nullable: true + description: >- + The remote ID of the job interview stage in the context of the 3rd + Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: >- + The remote data of the job interview stage in the context of the 3rd + Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsJobOutput: type: object - properties: {} - UnifiedMarketingautomationContactInput: + properties: + name: + type: string + example: Financial Analyst + nullable: true + description: The name of the job + description: + type: string + example: Extract financial data and write detailed investment thesis + nullable: true + description: The description of the job + code: + type: string + example: JOB123 + nullable: true + description: The code of the job + status: + type: string + enum: + - OPEN + - CLOSED + - DRAFT + - ARCHIVED + - PENDING + example: OPEN + nullable: true + description: The status of the job + type: + type: string + example: POSTING + enum: + - POSTING + - REQUISITION + - PROFILE + nullable: true + description: The type of the job + confidential: + type: boolean + example: true + nullable: true + description: Whether the job is confidential + departments: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The departments UUIDs associated with the job + type: array + items: + type: string + offices: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The offices UUIDs associated with the job + type: array + items: + type: string + managers: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The managers UUIDs associated with the job + type: array + items: + type: string + recruiters: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The recruiters UUIDs associated with the job + type: array + items: + type: string + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the job + remote_updated_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the job + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the job in the context of the 3rd Party + remote_data: + type: object + example: + key1: value1 + key2: 42 + key3: true + nullable: true + additionalProperties: true + description: The remote data of the job in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2023-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsOfferOutput: type: object - properties: {} - UnifiedMarketingautomationEmailOutput: + properties: + created_by: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the creator + nullable: true + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The remote creation date of the offer + nullable: true + closed_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The closing date of the offer + nullable: true + sent_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The sending date of the offer + nullable: true + start_date: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The start date of the offer + nullable: true + status: + type: string + example: DRAFT + enum: + - DRAFT + - APPROVAL_SENT + - APPROVED + - SENT + - SENT_MANUALLY + - OPENED + - DENIED + - SIGNED + - DEPRECATED + description: The status of the offer + nullable: true + application_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the application + nullable: true + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + nullable: true + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the offer + nullable: true + remote_id: + type: string + example: id_1 + description: The remote ID of the offer in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + description: The remote data of the offer in the context of the 3rd Party + nullable: true + additionalProperties: true + created_at: + type: object + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + type: object + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + UnifiedAtsOfficeOutput: type: object - properties: {} - UnifiedMarketingautomationEventOutput: + properties: + name: + type: string + example: Condo Office 5th + nullable: true + description: The name of the office + location: + type: string + example: New York + nullable: true + description: The location of the office + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the office + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the office in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the office in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsRejectreasonOutput: type: object - properties: {} - UnifiedMarketingautomationListOutput: + properties: + name: + type: string + example: Candidate inexperienced + nullable: true + description: The name of the reject reason + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + nullable: true + description: The UUID of the reject reason + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + remote_id: + type: string + nullable: true + description: The remote ID of the reject reason in the context of the 3rd Party + example: id_1 + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the reject reason in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsScorecardOutput: type: object - properties: {} - UnifiedMarketingautomationListInput: + properties: + overall_recommendation: + type: string + enum: + - DEFINITELY_NO + - 'NO' + - 'YES' + - STRONG_YES + - NO_DECISION + example: 'YES' + nullable: true + description: The overall recommendation + application_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the application + interview_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the interview + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the scorecard + submitted_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The submission date of the scorecard + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the scorecard + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the scorecard in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the scorecard in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsTagOutput: type: object - properties: {} - UnifiedMarketingautomationMessageOutput: + properties: + name: + type: string + example: Important + nullable: true + description: The name of the tag + id_ats_candidate: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the tag + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the tag in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the tag in the context of the 3rd Party + created_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The creation date of the tag + modified_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The modification date of the tag + UnifiedAtsUserOutput: type: object - properties: {} - UnifiedMarketingautomationTemplateOutput: + properties: + first_name: + type: string + example: John + description: The first name of the user + nullable: true + last_name: + type: string + example: Doe + description: The last name of the user + nullable: true + email: + type: string + example: john.doe@example.com + description: The email of the user + nullable: true + disabled: + type: boolean + example: false + description: Whether the user is disabled + nullable: true + access_role: + type: string + example: ADMIN + enum: + - SUPER_ADMIN + - ADMIN + - TEAM_MEMBER + - LIMITED_TEAM_MEMBER + - INTERVIEWER + description: The access role of the user + nullable: true + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The remote creation date of the user + nullable: true + remote_modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The remote modification date of the user + nullable: true + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + nullable: true + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user + nullable: true + remote_id: + type: string + example: id_1 + description: The remote ID of the user in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + description: The remote data of the user in the context of the 3rd Party + nullable: true + additionalProperties: true + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + UnifiedAtsEeocsOutput: type: object - properties: {} - UnifiedMarketingautomationTemplateInput: + properties: + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + submitted_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The submission date of the EEOC + race: + type: string + enum: + - AMERICAN_INDIAN_OR_ALASKAN_NATIVE + - ASIAN + - BLACK_OR_AFRICAN_AMERICAN + - HISPANIC_OR_LATINO + - WHITE + - NATIVE_HAWAIIAN_OR_OTHER_PACIFIC_ISLANDER + - TWO_OR_MORE_RACES + - DECLINE_TO_SELF_IDENTIFY + example: AMERICAN_INDIAN_OR_ALASKAN_NATIVE + nullable: true + description: The race of the candidate + gender: + type: string + example: MALE + enum: + - MALE + - FEMALE + - NON_BINARY + - OTHER + - DECLINE_TO_SELF_IDENTIFY + nullable: true + description: The gender of the candidate + veteran_status: + type: string + example: I_AM_NOT_A_PROTECTED_VETERAN + enum: + - I_AM_NOT_A_PROTECTED_VETERAN + - >- + I_IDENTIFY_AS_ONE_OR_MORE_OF_THE_CLASSIFICATIONS_OF_A_PROTECTED_VETERAN + - I_DONT_WISH_TO_ANSWER + nullable: true + description: The veteran status of the candidate + disability_status: + type: string + enum: + - YES_I_HAVE_A_DISABILITY_OR_PREVIOUSLY_HAD_A_DISABILITY + - NO_I_DONT_HAVE_A_DISABILITY + - I_DONT_WISH_TO_ANSWER + example: YES_I_HAVE_A_DISABILITY_OR_PREVIOUSLY_HAD_A_DISABILITY + nullable: true + description: The disability status of the candidate + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the EEOC + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the EEOC in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the EEOC in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAccountingAccountOutput: type: object - properties: {} - UnifiedMarketingautomationUserOutput: + properties: + name: + type: string + example: Cash + nullable: true + description: The name of the account + description: + type: string + example: Main cash account for daily operations + nullable: true + description: A description of the account + classification: + type: string + example: Asset + nullable: true + description: The classification of the account + type: + type: string + example: Current Asset + nullable: true + description: The type of the account + status: + type: string + example: Active + nullable: true + description: The status of the account + current_balance: + type: number + example: 10000 + nullable: true + description: The current balance of the account + currency: + type: string + example: USD + nullable: true + description: The currency of the account + account_number: + type: string + example: '1000' + nullable: true + description: The account number + parent_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent account + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: &ref_147 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the account record + remote_id: + type: string + example: account_1234 + nullable: true + description: The remote ID of the account in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the account in the context of the 3rd Party + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the account record + modified_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the account record + UnifiedAccountingAccountInput: type: object - properties: {} - UnifiedAtsActivityOutput: + properties: + name: + type: string + example: Cash + nullable: true + description: The name of the account + description: + type: string + example: Main cash account for daily operations + nullable: true + description: A description of the account + classification: + type: string + example: Asset + nullable: true + description: The classification of the account + type: + type: string + example: Current Asset + nullable: true + description: The type of the account + status: + type: string + example: Active + nullable: true + description: The status of the account + current_balance: + type: number + example: 10000 + nullable: true + description: The current balance of the account + currency: + type: string + example: USD + nullable: true + description: The currency of the account + account_number: + type: string + example: '1000' + nullable: true + description: The account number + parent_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent account + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: *ref_147 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingAddressOutput: type: object properties: - activity_type: + type: type: string - enum: &ref_123 - - NOTE - - EMAIL - - OTHER - example: NOTE + example: Billing nullable: true - description: The type of activity - subject: + description: The type of the address + street_1: type: string - example: Email subject + example: 123 Main St nullable: true - description: The subject of the activity - body: + description: The first line of the street address + street_2: type: string - example: Dear Diana, I love you + example: Apt 4B nullable: true - description: The body of the activity - visibility: + description: The second line of the street address + city: type: string - enum: &ref_124 - - ADMIN_ONLY - - PUBLIC - - PRIVATE - example: PUBLIC + example: New York nullable: true - description: The visibility of the activity - candidate_id: + description: The city of the address + state: + type: string + example: NY + nullable: true + description: The state of the address + country_subdivision: + type: string + example: New York + nullable: true + description: The country subdivision (e.g., province or state) of the address + country: + type: string + example: USA + nullable: true + description: The country of the address + zip: + type: string + example: '10001' + nullable: true + description: The zip or postal code of the address + contact_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the candidate - remote_created_at: + description: The UUID of the associated contact + company_info_id: type: string - format: date-time - example: '2024-10-01T12:00:00Z' + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The remote creation date of the activity + description: The UUID of the associated company info field_mappings: type: object - example: &ref_125 - fav_dish: broccoli - fav_color: red - additionalProperties: true + example: + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -12371,284 +15551,436 @@ components: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the activity + description: The UUID of the address record remote_id: type: string - example: id_1 + example: address_1234 nullable: true - description: The remote ID of the activity in the context of the 3rd Party + description: The remote ID of the address in the context of the 3rd Party remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the activity in the context of the 3rd Party + description: The remote data of the address in the context of the 3rd Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the address record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsActivityInput: + description: The last modified date of the address record + UnifiedAccountingAttachmentOutput: type: object properties: - activity_type: + file_name: type: string - enum: *ref_123 - example: NOTE + example: invoice.pdf nullable: true - description: The type of activity - subject: + description: The name of the attached file + file_url: type: string - example: Email subject + example: https://example.com/files/invoice.pdf nullable: true - description: The subject of the activity - body: + description: The URL where the file can be accessed + account_id: type: string - example: Dear Diana, I love you + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The body of the activity - visibility: - type: string - enum: *ref_124 - example: PUBLIC + description: The UUID of the associated account + field_mappings: + type: object + example: &ref_148 + custom_field_1: value1 + custom_field_2: value2 nullable: true - description: The visibility of the activity - candidate_id: + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the candidate - remote_created_at: + description: The UUID of the attachment record + remote_id: type: string - format: date-time - example: '2024-10-01T12:00:00Z' + example: attachment_1234 nullable: true - description: The remote creation date of the activity + description: The remote ID of the attachment in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the attachment in the context of the 3rd Party + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the attachment record + modified_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the attachment record + UnifiedAccountingAttachmentInput: + type: object + properties: + file_name: + type: string + example: invoice.pdf + nullable: true + description: The name of the attached file + file_url: + type: string + example: https://example.com/files/invoice.pdf + nullable: true + description: The URL where the file can be accessed + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account field_mappings: type: object - example: *ref_125 - additionalProperties: true + example: *ref_148 nullable: true description: >- The custom field mappings of the object between the remote 3rd party & Panora - UnifiedAtsApplicationOutput: + UnifiedAccountingBalancesheetOutput: type: object properties: - applied_at: - format: date-time + name: type: string + example: Q2 2024 Balance Sheet nullable: true - description: The application date - example: '2024-10-01T12:00:00Z' - rejected_at: - format: date-time + description: The name of the balance sheet + currency: type: string + example: USD nullable: true - description: The rejection date - example: '2024-10-01T12:00:00Z' - offers: + description: The currency used in the balance sheet + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The offers UUIDs for the application - example: &ref_126 - - 801f9ede-c698-4e66-a7fc-48d19eebaa4f - - 12345678-1234-1234-1234-123456789012 + description: The UUID of the associated company info + date: + type: string + example: '2024-06-30T23:59:59Z' + nullable: true + description: The date of the balance sheet + net_assets: + type: number + example: 1000000 + nullable: true + description: The net assets value + assets: + example: + - Cash + - Accounts Receivable + - Inventory + nullable: true + description: The list of assets type: array items: type: string - source: - type: string + liabilities: + example: + - Accounts Payable + - Long-term Debt nullable: true - description: The source of the application - example: Source Name - credited_to: + description: The list of liabilities + type: array + items: + type: string + equity: + example: + - Common Stock + - Retained Earnings + nullable: true + description: The list of equity items + type: array + items: + type: string + remote_generated_at: type: string + example: '2024-07-01T12:00:00Z' nullable: true - description: The UUID of the person credited for the application + description: The date when the balance sheet was generated in the remote system + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - current_stage: + nullable: true + description: The UUID of the balance sheet record + remote_id: type: string + example: balancesheet_1234 nullable: true - description: The UUID of the current stage of the application + description: The remote ID of the balance sheet in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the balance sheet in the context of the 3rd Party + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the balance sheet record + modified_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the balance sheet record + UnifiedAccountingCashflowstatementOutput: + type: object + properties: + name: + type: string + example: Q2 2024 Cash Flow Statement + nullable: true + description: The name of the cash flow statement + currency: + type: string + example: USD + nullable: true + description: The currency used in the cash flow statement + company_id: + type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - reject_reason: + nullable: true + description: The UUID of the associated company + start_period: type: string + example: '2024-04-01T00:00:00Z' nullable: true - description: The rejection reason for the application - example: Candidate not experienced enough - candidate_id: + description: The start date of the period covered by the cash flow statement + end_period: type: string + example: '2024-06-30T23:59:59Z' + nullable: true + description: The end date of the period covered by the cash flow statement + cash_at_beginning_of_period: + type: number + example: 1000000 + nullable: true + description: The cash balance at the beginning of the period + cash_at_end_of_period: + type: number + example: 1200000 nullable: true - description: The UUID of the candidate - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - job_id: + description: The cash balance at the end of the period + remote_generated_at: type: string - description: The UUID of the job - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: '2024-07-01T12:00:00Z' + nullable: true + description: >- + The date when the cash flow statement was generated in the remote + system field_mappings: type: object - example: &ref_127 - fav_dish: broccoli - fav_color: red - additionalProperties: true + example: + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party & Panora id: type: string - nullable: true - description: The UUID of the application example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the cash flow statement record remote_id: type: string + example: cashflowstatement_1234 nullable: true - description: The remote ID of the application in the context of the 3rd Party - example: id_1 + description: >- + The remote ID of the cash flow statement in the context of the 3rd + Party remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the application in the context of the 3rd Party + description: >- + The remote data of the cash flow statement in the context of the 3rd + Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the cash flow statement record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - remote_created_at: - format: date-time + description: The last modified date of the cash flow statement record + UnifiedAccountingCompanyinfoOutput: + type: object + properties: + name: type: string + example: Acme Corporation nullable: true - description: The remote created date of the object - remote_modified_at: - format: date-time + description: The name of the company + legal_name: type: string + example: Acme Corporation LLC nullable: true - description: The remote modified date of the object - UnifiedAtsApplicationInput: - type: object - properties: - applied_at: - format: date-time + description: The legal name of the company + tax_number: type: string + example: '123456789' nullable: true - description: The application date - example: '2024-10-01T12:00:00Z' - rejected_at: - format: date-time + description: The tax number of the company + fiscal_year_end_month: + type: number + example: 12 + nullable: true + description: The month of the fiscal year end (1-12) + fiscal_year_end_day: + type: number + example: 31 + nullable: true + description: The day of the fiscal year end (1-31) + currency: type: string + example: USD nullable: true - description: The rejection date - example: '2024-10-01T12:00:00Z' - offers: + description: The currency used by the company + urls: + example: + - https://www.acmecorp.com + - https://store.acmecorp.com nullable: true - description: The offers UUIDs for the application - example: *ref_126 + description: The URLs associated with the company type: array items: type: string - source: - type: string + tracking_categories: + example: + - Department + - Project + - Location nullable: true - description: The source of the application - example: Source Name - credited_to: - type: string + description: The tracking categories used by the company + type: array + items: + type: string + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 nullable: true - description: The UUID of the person credited for the application + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - current_stage: + nullable: true + description: The UUID of the company info record + remote_id: type: string + example: company_1234 nullable: true - description: The UUID of the current stage of the application - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - reject_reason: + description: The remote ID of the company info in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the company info in the context of the 3rd Party + remote_created_at: type: string + example: '2024-06-15T12:00:00Z' nullable: true - description: The rejection reason for the application - example: Candidate not experienced enough - candidate_id: + description: The date when the company info was created in the remote system + created_at: type: string + example: '2024-06-15T12:00:00Z' nullable: true - description: The UUID of the candidate - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - job_id: + description: The created date of the company info record + modified_at: type: string - description: The UUID of the job - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - field_mappings: - type: object - example: *ref_127 - additionalProperties: true + example: '2024-06-15T12:00:00Z' nullable: true - description: >- - The custom field mappings of the object between the remote 3rd party - & Panora - UnifiedAtsAttachmentOutput: + description: The last modified date of the company info record + UnifiedAccountingContactOutput: type: object properties: - file_url: + name: type: string - example: https://example.com/file.pdf + example: John Doe nullable: true - description: The URL of the file - file_name: + description: The name of the contact + is_supplier: + type: boolean + example: true + nullable: true + description: Indicates if the contact is a supplier + is_customer: + type: boolean + example: false + nullable: true + description: Indicates if the contact is a customer + email_address: type: string - example: file.pdf + example: john.doe@example.com nullable: true - description: The name of the file - attachment_type: + description: The email address of the contact + tax_number: type: string - example: RESUME - enum: &ref_128 - - RESUME - - COVER_LETTER - - OFFER_LETTER - - OTHER + example: '123456789' nullable: true - description: The type of the file - remote_created_at: + description: The tax number of the contact + status: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: Active nullable: true - description: The remote creation date of the attachment - remote_modified_at: + description: The status of the contact + currency: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: USD nullable: true - description: The remote modification date of the attachment - candidate_id: + description: The currency associated with the contact + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the contact was last updated in the remote system + company_info_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the candidate + description: The UUID of the associated company info field_mappings: type: object - example: &ref_129 - fav_dish: broccoli - fav_color: red - additionalProperties: true + example: &ref_149 + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -12657,212 +15989,277 @@ components: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the attachment + description: The UUID of the contact record remote_id: type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: contact_1234 nullable: true - description: The remote ID of the attachment + description: The remote ID of the contact in the context of the 3rd Party remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the attachment in the context of the 3rd Party + description: The remote data of the contact in the context of the 3rd Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the contact record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsAttachmentInput: + description: The last modified date of the contact record + UnifiedAccountingContactInput: type: object properties: - file_url: + name: type: string - example: https://example.com/file.pdf + example: John Doe nullable: true - description: The URL of the file - file_name: + description: The name of the contact + is_supplier: + type: boolean + example: true + nullable: true + description: Indicates if the contact is a supplier + is_customer: + type: boolean + example: false + nullable: true + description: Indicates if the contact is a customer + email_address: type: string - example: file.pdf + example: john.doe@example.com nullable: true - description: The name of the file - attachment_type: + description: The email address of the contact + tax_number: type: string - example: RESUME - enum: *ref_128 + example: '123456789' nullable: true - description: The type of the file - remote_created_at: + description: The tax number of the contact + status: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: Active nullable: true - description: The remote creation date of the attachment - remote_modified_at: + description: The status of the contact + currency: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: USD + nullable: true + description: The currency associated with the contact + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the contact was last updated in the remote system + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: *ref_149 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingCreditnoteOutput: + type: object + properties: + transaction_date: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the credit note transaction + status: + type: string + example: Issued + nullable: true + description: The status of the credit note + number: + type: string + example: CN-001 + nullable: true + description: The number of the credit note + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the credit note + total_amount: + type: number + example: 10000 + nullable: true + description: The total amount of the credit note + remaining_credit: + type: number + example: 5000 + nullable: true + description: The remaining credit on the credit note + tracking_categories: + example: + - Project A + - Department B + nullable: true + description: The tracking categories associated with the credit note + type: array + items: + type: string + currency: + type: string + example: USD + nullable: true + description: The currency of the credit note + payments: + example: + - PAYMENT-001 + - PAYMENT-002 + nullable: true + description: The payments associated with the credit note + type: array + items: + type: string + applied_payments: + example: + - APPLIED-001 + - APPLIED-002 nullable: true - description: The remote modification date of the attachment - candidate_id: + description: The applied payments associated with the credit note + type: array + items: + type: string + accounting_period_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the candidate + description: The UUID of the associated accounting period field_mappings: type: object - example: *ref_129 - additionalProperties: true + example: + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party & Panora - Url: - type: object - properties: - url: + id: type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The url. - url_type: + description: The UUID of the credit note record + remote_id: type: string + example: creditnote_1234 nullable: true - description: The url type. It takes [WEBSITE | BLOG | LINKEDIN | GITHUB | OTHER] - required: - - url - - url_type - UnifiedAtsCandidateOutput: - type: object - properties: - first_name: + description: The remote ID of the credit note in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the credit note in the context of the 3rd Party + remote_created_at: type: string - example: Joe + example: '2024-06-15T12:00:00Z' nullable: true - description: The first name of the candidate - last_name: + description: The date when the credit note was created in the remote system + remote_updated_at: type: string - example: Doe + example: '2024-06-15T12:00:00Z' nullable: true - description: The last name of the candidate - company: + description: The date when the credit note was last updated in the remote system + created_at: type: string - example: Acme + example: '2024-06-15T12:00:00Z' nullable: true - description: The company of the candidate - title: + description: The created date of the credit note record + modified_at: type: string - example: Analyst + example: '2024-06-15T12:00:00Z' nullable: true - description: The title of the candidate - locations: + description: The last modified date of the credit note record + UnifiedAccountingExpenseOutput: + type: object + properties: + transaction_date: type: string - example: New York + example: '2024-06-15T12:00:00Z' nullable: true - description: The locations of the candidate - is_private: - type: boolean - example: false + description: The date of the expense transaction + total_amount: + type: number + example: 10000 nullable: true - description: Whether the candidate is private - email_reachable: - type: boolean - example: true + description: The total amount of the expense + sub_total: + type: number + example: 9000 nullable: true - description: Whether the candidate is reachable by email - remote_created_at: - type: string - example: '2024-10-01T12:00:00Z' - format: date-time + description: The sub-total amount of the expense (before tax) + total_tax_amount: + type: number + example: 1000 nullable: true - description: The remote creation date of the candidate - remote_modified_at: + description: The total tax amount of the expense + currency: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: USD nullable: true - description: The remote modification date of the candidate - last_interaction_at: + description: The currency of the expense + exchange_rate: type: string - example: '2024-10-01T12:00:00Z' - format: date-time - nullable: true - description: The last interaction date with the candidate - attachments: - type: array - items: &ref_130 - oneOf: - - type: string - - $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' - example: &ref_131 - - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: '1.2' nullable: true - description: The attachments UUIDs of the candidate - applications: - type: array - items: &ref_132 - oneOf: - - type: string - - $ref: '#/components/schemas/UnifiedAtsApplicationOutput' - example: &ref_133 - - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The exchange rate applied to the expense + memo: + type: string + example: Business lunch with client nullable: true - description: The applications UUIDs of the candidate - tags: - type: array - items: &ref_134 - oneOf: - - type: string - - $ref: '#/components/schemas/UnifiedAtsTagOutput' - example: &ref_135 - - tag_1 - - tag_2 + description: A memo or description for the expense + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tags of the candidate - urls: - example: &ref_136 - - url: mywebsite.com - url_type: WEBSITE + description: The UUID of the associated account + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: >- - The urls of the candidate, possible values for Url type are WEBSITE, - BLOG, LINKEDIN, GITHUB, or OTHER - type: array - items: - $ref: '#/components/schemas/Url' - phone_numbers: - example: &ref_137 - - phone_number: '+33660688899' - phone_type: WORK + description: The UUID of the associated contact + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The phone numbers of the candidate - type: array - items: - $ref: '#/components/schemas/Phone' - email_addresses: - example: &ref_138 - - email_address: joedoe@gmail.com - email_address_type: WORK + description: The UUID of the associated company info + tracking_categories: + example: &ref_150 + - Project A + - Department B nullable: true - description: The email addresses of the candidate + description: The tracking categories associated with the expense type: array items: - $ref: '#/components/schemas/Email' + type: string field_mappings: type: object - example: &ref_139 - fav_dish: broccoli - fav_color: red - additionalProperties: true + example: &ref_151 + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -12871,151 +16268,144 @@ components: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the candidate + description: The UUID of the expense record remote_id: type: string - example: id_1 + example: expense_1234 nullable: true - description: The id of the candidate in the context of the 3rd Party + description: The remote ID of the expense in the context of the 3rd Party remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the candidate in the context of the 3rd Party + description: The remote data of the expense in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the expense was created in the remote system created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the expense record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsCandidateInput: + description: The last modified date of the expense record + UnifiedAccountingExpenseInput: type: object properties: - first_name: + transaction_date: type: string - example: Joe + example: '2024-06-15T12:00:00Z' nullable: true - description: The first name of the candidate - last_name: - type: string - example: Doe + description: The date of the expense transaction + total_amount: + type: number + example: 10000 nullable: true - description: The last name of the candidate - company: - type: string - example: Acme + description: The total amount of the expense + sub_total: + type: number + example: 9000 nullable: true - description: The company of the candidate - title: - type: string - example: Analyst + description: The sub-total amount of the expense (before tax) + total_tax_amount: + type: number + example: 1000 nullable: true - description: The title of the candidate - locations: + description: The total tax amount of the expense + currency: type: string - example: New York - nullable: true - description: The locations of the candidate - is_private: - type: boolean - example: false - nullable: true - description: Whether the candidate is private - email_reachable: - type: boolean - example: true + example: USD nullable: true - description: Whether the candidate is reachable by email - remote_created_at: + description: The currency of the expense + exchange_rate: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: '1.2' nullable: true - description: The remote creation date of the candidate - remote_modified_at: + description: The exchange rate applied to the expense + memo: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: Business lunch with client nullable: true - description: The remote modification date of the candidate - last_interaction_at: + description: A memo or description for the expense + account_id: type: string - example: '2024-10-01T12:00:00Z' - format: date-time - nullable: true - description: The last interaction date with the candidate - attachments: - type: array - items: *ref_130 - example: *ref_131 - nullable: true - description: The attachments UUIDs of the candidate - applications: - type: array - items: *ref_132 - example: *ref_133 - nullable: true - description: The applications UUIDs of the candidate - tags: - type: array - items: *ref_134 - example: *ref_135 + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tags of the candidate - urls: - example: *ref_136 + description: The UUID of the associated account + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: >- - The urls of the candidate, possible values for Url type are WEBSITE, - BLOG, LINKEDIN, GITHUB, or OTHER - type: array - items: - $ref: '#/components/schemas/Url' - phone_numbers: - example: *ref_137 + description: The UUID of the associated contact + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The phone numbers of the candidate - type: array - items: - $ref: '#/components/schemas/Phone' - email_addresses: - example: *ref_138 + description: The UUID of the associated company info + tracking_categories: + example: *ref_150 nullable: true - description: The email addresses of the candidate + description: The tracking categories associated with the expense type: array items: - $ref: '#/components/schemas/Email' + type: string field_mappings: type: object - example: *ref_139 - additionalProperties: true + example: *ref_151 nullable: true description: >- The custom field mappings of the object between the remote 3rd party & Panora - UnifiedAtsDepartmentOutput: + UnifiedAccountingIncomestatementOutput: type: object properties: name: type: string - example: Sales + example: Q2 2024 Income Statement + nullable: true + description: The name of the income statement + currency: + type: string + example: USD + nullable: true + description: The currency used in the income statement + start_period: + type: string + example: '2024-04-01T00:00:00Z' + nullable: true + description: The start date of the period covered by the income statement + end_period: + type: string + example: '2024-06-30T23:59:59Z' nullable: true - description: The name of the department + description: The end date of the period covered by the income statement + gross_profit: + type: number + example: 1000000 + nullable: true + description: The gross profit for the period + net_operating_income: + type: number + example: 800000 + nullable: true + description: The net operating income for the period + net_income: + type: number + example: 750000 + nullable: true + description: The net income for the period field_mappings: type: object example: - fav_dish: broccoli - fav_color: red - additionalProperties: true + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13024,103 +16414,130 @@ components: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the department + description: The UUID of the income statement record remote_id: type: string - example: id_1 + example: incomestatement_1234 nullable: true - description: The remote ID of the department in the context of the 3rd Party + description: >- + The remote ID of the income statement in the context of the 3rd + Party remote_data: type: object example: - key1: value1 - key2: 42 - key3: true + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the department in the context of the 3rd Party + description: >- + The remote data of the income statement in the context of the 3rd + Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the income statement record modified_at: - format: date-time type: string - example: '2023-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsInterviewOutput: + description: The last modified date of the income statement record + UnifiedAccountingInvoiceOutput: type: object properties: - status: + type: type: string - enum: &ref_140 - - SCHEDULED - - AWAITING_FEEDBACK - - COMPLETED - example: SCHEDULED + example: Sales nullable: true - description: The status of the interview - application_id: + description: The type of the invoice + number: type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: INV-001 nullable: true - description: The UUID of the application - job_interview_stage_id: + description: The invoice number + issue_date: type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: '2024-06-15T12:00:00Z' nullable: true - description: The UUID of the job interview stage - organized_by: + description: The date the invoice was issued + due_date: type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: '2024-07-15T12:00:00Z' nullable: true - description: The UUID of the organizer - interviewers: - example: &ref_141 - - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The due date of the invoice + paid_on_date: + type: string + example: '2024-07-10T12:00:00Z' nullable: true - description: The UUIDs of the interviewers - type: array - items: - type: string - location: + description: The date the invoice was paid + memo: type: string - example: San Francisco + example: Payment for services rendered nullable: true - description: The location of the interview - start_at: - format: date-time + description: A memo or note on the invoice + currency: type: string - example: '2024-10-01T12:00:00Z' + example: USD nullable: true - description: The start date and time of the interview - end_at: - format: date-time + description: The currency of the invoice + exchange_rate: type: string - example: '2024-10-01T12:00:00Z' + example: '1.2' nullable: true - description: The end date and time of the interview - remote_created_at: - format: date-time + description: The exchange rate applied to the invoice + total_discount: + type: number + example: 1000 + nullable: true + description: The total discount applied to the invoice + sub_total: + type: number + example: 10000 + nullable: true + description: The subtotal of the invoice + status: type: string - example: '2024-10-01T12:00:00Z' + example: Paid nullable: true - description: The remote creation date of the interview - remote_updated_at: - format: date-time + description: The status of the invoice + total_tax_amount: + type: number + example: 1000 + nullable: true + description: The total tax amount on the invoice + total_amount: + type: number + example: 11000 + nullable: true + description: The total amount of the invoice + balance: + type: number + example: 0 + nullable: true + description: The remaining balance on the invoice + contact_id: type: string - example: '2024-10-01T12:00:00Z' + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The remote modification date of the interview + description: The UUID of the associated contact + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + tracking_categories: + example: &ref_152 + - Project A + - Department B + nullable: true + description: The tracking categories associated with the invoice + type: array + items: + type: string field_mappings: type: object - example: &ref_142 - fav_dish: broccoli - fav_color: red - additionalProperties: true + example: &ref_153 + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13129,124 +16546,285 @@ components: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the interview + description: The UUID of the invoice record remote_id: type: string - example: id_1 + example: invoice_1234 nullable: true - description: The remote ID of the interview in the context of the 3rd Party + description: The remote ID of the invoice in the context of the 3rd Party remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the interview in the context of the 3rd Party + description: The remote data of the invoice in the context of the 3rd Party + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the invoice was last updated in the remote system created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the invoice record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsInterviewInput: + description: The last modified date of the invoice record + UnifiedAccountingInvoiceInput: type: object properties: - status: + type: type: string - enum: *ref_140 - example: SCHEDULED + example: Sales nullable: true - description: The status of the interview - application_id: + description: The type of the invoice + number: type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: INV-001 nullable: true - description: The UUID of the application - job_interview_stage_id: + description: The invoice number + issue_date: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date the invoice was issued + due_date: + type: string + example: '2024-07-15T12:00:00Z' + nullable: true + description: The due date of the invoice + paid_on_date: + type: string + example: '2024-07-10T12:00:00Z' + nullable: true + description: The date the invoice was paid + memo: + type: string + example: Payment for services rendered + nullable: true + description: A memo or note on the invoice + currency: + type: string + example: USD + nullable: true + description: The currency of the invoice + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the invoice + total_discount: + type: number + example: 1000 + nullable: true + description: The total discount applied to the invoice + sub_total: + type: number + example: 10000 + nullable: true + description: The subtotal of the invoice + status: + type: string + example: Paid + nullable: true + description: The status of the invoice + total_tax_amount: + type: number + example: 1000 + nullable: true + description: The total tax amount on the invoice + total_amount: + type: number + example: 11000 + nullable: true + description: The total amount of the invoice + balance: + type: number + example: 0 + nullable: true + description: The remaining balance on the invoice + contact_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the job interview stage - organized_by: + description: The UUID of the associated contact + accounting_period_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the organizer - interviewers: - example: *ref_141 + description: The UUID of the associated accounting period + tracking_categories: + example: *ref_152 nullable: true - description: The UUIDs of the interviewers + description: The tracking categories associated with the invoice type: array items: type: string - location: + field_mappings: + type: object + example: *ref_153 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingItemOutput: + type: object + properties: + name: type: string - example: San Francisco + example: Product A nullable: true - description: The location of the interview - start_at: - format: date-time + description: The name of the accounting item + status: type: string - example: '2024-10-01T12:00:00Z' + example: Active nullable: true - description: The start date and time of the interview - end_at: - format: date-time + description: The status of the accounting item + unit_price: + type: number + example: 1000 + nullable: true + description: The unit price of the item in cents + purchase_price: + type: number + example: 800 + nullable: true + description: The purchase price of the item in cents + sales_account: type: string - example: '2024-10-01T12:00:00Z' + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The end date and time of the interview - remote_created_at: - format: date-time + description: The UUID of the associated sales account + purchase_account: type: string - example: '2024-10-01T12:00:00Z' + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The remote creation date of the interview - remote_updated_at: - format: date-time + description: The UUID of the associated purchase account + id_acc_company_info: type: string - example: '2024-10-01T12:00:00Z' + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The remote modification date of the interview + description: The UUID of the associated company info field_mappings: type: object - example: *ref_142 - additionalProperties: true + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the accounting item record + remote_id: + type: string + example: item_1234 + nullable: true + description: The remote ID of the item in the context of the 3rd Party + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the item was last updated in the remote system + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the item in the context of the 3rd Party + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the accounting item record + modified_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the accounting item record + UnifiedAccountingJournalentryOutput: + type: object + properties: + transaction_date: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the transaction + payments: + example: &ref_154 + - payment1 + - payment2 + nullable: true + description: The payments associated with the journal entry + type: array + items: + type: string + applied_payments: + example: &ref_155 + - appliedPayment1 + - appliedPayment2 + nullable: true + description: The applied payments for the journal entry + type: array + items: + type: string + memo: + type: string + example: Monthly expense journal entry + nullable: true + description: A memo or note for the journal entry + currency: + type: string + example: USD + nullable: true + description: The currency of the journal entry + exchange_rate: + type: string + example: '1.2' nullable: true - description: >- - The custom field mappings of the object between the remote 3rd party - & Panora - UnifiedAtsJobinterviewstageOutput: - type: object - properties: - name: + description: The exchange rate applied to the journal entry + id_acc_company_info: type: string - example: Second Call + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: false + description: The UUID of the associated company info + journal_number: + type: string + example: JE-001 nullable: true - description: The name of the job interview stage - stage_order: - type: number - example: 1 + description: The journal number + tracking_categories: + example: &ref_156 + - Category1 + - Category2 nullable: true - description: The order of the stage - job_id: + description: The tracking categories associated with the journal entry + type: array + items: + type: string + id_acc_accounting_period: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the job + description: The UUID of the associated accounting period + posting_status: + type: string + example: Posted + nullable: true + description: The posting status of the journal entry field_mappings: type: object - example: - fav_dish: broccoli - fav_color: red - additionalProperties: true + example: &ref_157 + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13255,129 +16833,179 @@ components: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the job interview stage + description: The UUID of the journal entry record remote_id: type: string - example: id_1 + example: journal_entry_1234 + nullable: false + description: The remote ID of the journal entry in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the journal entry was created in the remote system + remote_modiified_at: + type: string + example: '2024-06-15T12:00:00Z' nullable: true description: >- - The remote ID of the job interview stage in the context of the 3rd - Party + The date when the journal entry was last modified in the remote + system remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: >- - The remote data of the job interview stage in the context of the 3rd - Party + description: The remote data of the journal entry in the context of the 3rd Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the journal entry record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsJobOutput: + description: The last modified date of the journal entry record + UnifiedAccountingJournalentryInput: type: object properties: - name: + transaction_date: type: string - example: Financial Analyst + example: '2024-06-15T12:00:00Z' nullable: true - description: The name of the job - description: - type: string - example: Extract financial data and write detailed investment thesis + description: The date of the transaction + payments: + example: *ref_154 nullable: true - description: The description of the job - code: + description: The payments associated with the journal entry + type: array + items: + type: string + applied_payments: + example: *ref_155 + nullable: true + description: The applied payments for the journal entry + type: array + items: + type: string + memo: type: string - example: JOB123 + example: Monthly expense journal entry nullable: true - description: The code of the job - status: + description: A memo or note for the journal entry + currency: type: string - enum: - - OPEN - - CLOSED - - DRAFT - - ARCHIVED - - PENDING - example: OPEN + example: USD nullable: true - description: The status of the job - type: + description: The currency of the journal entry + exchange_rate: type: string - example: POSTING - enum: - - POSTING - - REQUISITION - - PROFILE + example: '1.2' nullable: true - description: The type of the job - confidential: - type: boolean - example: true + description: The exchange rate applied to the journal entry + id_acc_company_info: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: false + description: The UUID of the associated company info + journal_number: + type: string + example: JE-001 nullable: true - description: Whether the job is confidential - departments: - example: - - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The journal number + tracking_categories: + example: *ref_156 nullable: true - description: The departments UUIDs associated with the job + description: The tracking categories associated with the journal entry type: array items: type: string - offices: - example: - - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + id_acc_accounting_period: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The offices UUIDs associated with the job - type: array - items: - type: string - managers: - example: - - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the associated accounting period + posting_status: + type: string + example: Posted nullable: true - description: The managers UUIDs associated with the job - type: array - items: - type: string - recruiters: - example: - - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The posting status of the journal entry + field_mappings: + type: object + example: *ref_157 nullable: true - description: The recruiters UUIDs associated with the job - type: array - items: - type: string - remote_created_at: + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingPaymentOutput: + type: object + properties: + id_acc_invoice: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The remote creation date of the job - remote_updated_at: + description: The UUID of the associated invoice + transaction_date: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: '2024-06-15T12:00:00Z' nullable: true - description: The remote modification date of the job + description: The date of the transaction + id_acc_contact: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + id_acc_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + currency: + type: string + example: USD + nullable: true + description: The currency of the payment + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the payment + total_amount: + type: number + example: 10000 + nullable: true + description: The total amount of the payment in cents + type: + type: string + example: Credit Card + nullable: true + description: The type of payment + id_acc_company_info: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + id_acc_accounting_period: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + tracking_categories: + example: &ref_158 + - Category1 + - Category2 + nullable: true + description: The tracking categories associated with the payment + type: array + items: + type: string field_mappings: type: object - example: - fav_dish: broccoli - fav_color: red - additionalProperties: true + example: &ref_159 + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13386,142 +17014,129 @@ components: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the job + description: The UUID of the payment record remote_id: type: string - example: id_1 + example: payment_1234 nullable: true - description: The remote ID of the job in the context of the 3rd Party + description: The remote ID of the payment in the context of the 3rd Party + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the payment was last updated in the remote system remote_data: type: object example: - key1: value1 - key2: 42 - key3: true + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the job in the context of the 3rd Party + description: The remote data of the payment in the context of the 3rd Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the payment record modified_at: - format: date-time type: string - example: '2023-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsOfferOutput: + description: The last modified date of the payment record + UnifiedAccountingPaymentInput: type: object properties: - created_by: + id_acc_invoice: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - description: The UUID of the creator nullable: true - remote_created_at: - format: date-time + description: The UUID of the associated invoice + transaction_date: type: string - example: '2024-10-01T12:00:00Z' - description: The remote creation date of the offer + example: '2024-06-15T12:00:00Z' nullable: true - closed_at: - format: date-time + description: The date of the transaction + id_acc_contact: type: string - example: '2024-10-01T12:00:00Z' - description: The closing date of the offer + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - sent_at: - format: date-time + description: The UUID of the associated contact + id_acc_account: type: string - example: '2024-10-01T12:00:00Z' - description: The sending date of the offer + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - start_date: - format: date-time + description: The UUID of the associated account + currency: type: string - example: '2024-10-01T12:00:00Z' - description: The start date of the offer + example: USD nullable: true - status: + description: The currency of the payment + exchange_rate: type: string - example: DRAFT - enum: - - DRAFT - - APPROVAL_SENT - - APPROVED - - SENT - - SENT_MANUALLY - - OPENED - - DENIED - - SIGNED - - DEPRECATED - description: The status of the offer + example: '1.2' nullable: true - application_id: - type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - description: The UUID of the application + description: The exchange rate applied to the payment + total_amount: + type: number + example: 10000 nullable: true - field_mappings: - type: object - example: - fav_dish: broccoli - fav_color: red - description: >- - The custom field mappings of the object between the remote 3rd party - & Panora + description: The total amount of the payment in cents + type: + type: string + example: Credit Card nullable: true - additionalProperties: true - id: + description: The type of payment + id_acc_company_info: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - description: The UUID of the offer nullable: true - remote_id: + description: The UUID of the associated company info + id_acc_accounting_period: type: string - example: id_1 - description: The remote ID of the offer in the context of the 3rd Party - nullable: true - remote_data: - type: object - example: - fav_dish: broccoli - fav_color: red - description: The remote data of the offer in the context of the 3rd Party + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - additionalProperties: true - created_at: - type: object - example: '2024-10-01T12:00:00Z' - description: The created date of the object + description: The UUID of the associated accounting period + tracking_categories: + example: *ref_158 nullable: true - modified_at: + description: The tracking categories associated with the payment + type: array + items: + type: string + field_mappings: type: object - example: '2024-10-01T12:00:00Z' - description: The modified date of the object + example: *ref_159 nullable: true - UnifiedAtsOfficeOutput: + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingPhonenumberOutput: type: object properties: - name: + number: type: string - example: Condo Office 5th + example: '+1234567890' nullable: true - description: The name of the office - location: + description: The phone number + type: type: string - example: New York + example: Mobile nullable: true - description: The location of the office + description: The type of phone number + id_acc_company_info: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + id_acc_contact: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: false + description: The UUID of the associated contact field_mappings: type: object example: - fav_dish: broccoli - fav_color: red - additionalProperties: true + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13529,174 +17144,267 @@ components: id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - description: The UUID of the office + nullable: true + description: The UUID of the phone number record remote_id: type: string - example: id_1 + example: phone_1234 nullable: true - description: The remote ID of the office in the context of the 3rd Party + description: The remote ID of the phone number in the context of the 3rd Party remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the office in the context of the 3rd Party + description: The remote data of the phone number in the context of the 3rd Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the phone number record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsRejectreasonOutput: + description: The last modified date of the phone number record + UnifiedAccountingPurchaseorderOutput: type: object properties: - name: + status: type: string - example: Candidate inexperienced + example: Pending nullable: true - description: The name of the reject reason + description: The status of the purchase order + issue_date: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The issue date of the purchase order + purchase_order_number: + type: string + example: PO-001 + nullable: true + description: The purchase order number + delivery_date: + type: string + example: '2024-07-15T12:00:00Z' + nullable: true + description: The delivery date for the purchase order + delivery_address: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the delivery address + customer: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the customer + vendor: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the vendor + memo: + type: string + example: Purchase order for Q3 inventory + nullable: true + description: A memo or note for the purchase order + company: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company + total_amount: + type: number + example: 100000 + nullable: true + description: The total amount of the purchase order in cents + currency: + type: string + example: USD + nullable: true + description: The currency of the purchase order + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the purchase order + tracking_categories: + example: &ref_160 + - Category1 + - Category2 + nullable: true + description: The tracking categories associated with the purchase order + type: array + items: + type: string + id_acc_accounting_period: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period field_mappings: type: object - example: - fav_dish: broccoli - fav_color: red - additionalProperties: true + example: &ref_161 + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party & Panora id: type: string - nullable: true - description: The UUID of the reject reason example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the purchase order record remote_id: type: string + example: po_1234 nullable: true - description: The remote ID of the reject reason in the context of the 3rd Party - example: id_1 + description: The remote ID of the purchase order in the context of the 3rd Party + remote_created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the purchase order was created in the remote system + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: >- + The date when the purchase order was last updated in the remote + system remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the reject reason in the context of the 3rd Party + description: >- + The remote data of the purchase order in the context of the 3rd + Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The created date of the object + description: The created date of the purchase order record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAtsScorecardOutput: + description: The last modified date of the purchase order record + UnifiedAccountingPurchaseorderInput: type: object properties: - overall_recommendation: + status: type: string - enum: - - DEFINITELY_NO - - 'NO' - - 'YES' - - STRONG_YES - - NO_DECISION - example: 'YES' + example: Pending nullable: true - description: The overall recommendation - application_id: + description: The status of the purchase order + issue_date: type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: '2024-06-15T12:00:00Z' nullable: true - description: The UUID of the application - interview_id: + description: The issue date of the purchase order + purchase_order_number: type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: PO-001 nullable: true - description: The UUID of the interview - remote_created_at: + description: The purchase order number + delivery_date: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: '2024-07-15T12:00:00Z' nullable: true - description: The remote creation date of the scorecard - submitted_at: + description: The delivery date for the purchase order + delivery_address: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The submission date of the scorecard - field_mappings: - type: object - example: - fav_dish: broccoli - fav_color: red - additionalProperties: true + description: The UUID of the delivery address + customer: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: >- - The custom field mappings of the object between the remote 3rd party - & Panora - id: + description: The UUID of the customer + vendor: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - description: The UUID of the scorecard - remote_id: + nullable: true + description: The UUID of the vendor + memo: type: string - example: id_1 + example: Purchase order for Q3 inventory nullable: true - description: The remote ID of the scorecard in the context of the 3rd Party - remote_data: - type: object - example: - fav_dish: broccoli - fav_color: red + description: A memo or note for the purchase order + company: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - additionalProperties: true - description: The remote data of the scorecard in the context of the 3rd Party - created_at: - format: date-time + description: The UUID of the company + total_amount: + type: number + example: 100000 + nullable: true + description: The total amount of the purchase order in cents + currency: type: string - example: '2024-10-01T12:00:00Z' + example: USD nullable: true - description: The created date of the object - modified_at: - format: date-time + description: The currency of the purchase order + exchange_rate: type: string - example: '2024-10-01T12:00:00Z' + example: '1.2' nullable: true - description: The modified date of the object - UnifiedAtsTagOutput: + description: The exchange rate applied to the purchase order + tracking_categories: + example: *ref_160 + nullable: true + description: The tracking categories associated with the purchase order + type: array + items: + type: string + id_acc_accounting_period: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + field_mappings: + type: object + example: *ref_161 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingTaxrateOutput: type: object properties: - name: + description: type: string - example: Important + example: VAT 20% nullable: true - description: The name of the tag - id_ats_candidate: + description: The description of the tax rate + total_tax_ratge: + type: number + example: 2000 + nullable: true + description: The total tax rate in basis points (e.g., 2000 for 20%) + effective_tax_rate: + type: number + example: 1900 + nullable: true + description: The effective tax rate in basis points (e.g., 1900 for 19%) + company: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the candidate + description: The UUID of the associated company field_mappings: type: object example: - fav_dish: broccoli - fav_color: red - additionalProperties: true + custom_field_1: value1 + custom_field_2: value2 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13705,300 +17413,256 @@ components: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the tag + description: The UUID of the tax rate record remote_id: type: string - example: id_1 + example: tax_rate_1234 nullable: true - description: The remote ID of the tag in the context of the 3rd Party + description: The remote ID of the tax rate in the context of the 3rd Party remote_data: type: object example: - fav_dish: broccoli - fav_color: red + raw_data: + additional_field: some value nullable: true - additionalProperties: true - description: The remote data of the tag in the context of the 3rd Party + description: The remote data of the tax rate in the context of the 3rd Party created_at: - format: date-time type: string + example: '2024-06-15T12:00:00Z' nullable: true - example: '2024-10-01T12:00:00Z' - description: The creation date of the tag + description: The created date of the tax rate record modified_at: - format: date-time type: string + example: '2024-06-15T12:00:00Z' nullable: true - example: '2024-10-01T12:00:00Z' - description: The modification date of the tag - UnifiedAtsUserOutput: + description: The last modified date of the tax rate record + UnifiedAccountingTrackingcategoryOutput: type: object properties: - first_name: - type: string - example: John - description: The first name of the user - nullable: true - last_name: - type: string - example: Doe - description: The last name of the user - nullable: true - email: + name: type: string - example: john.doe@example.com - description: The email of the user - nullable: true - disabled: - type: boolean - example: false - description: Whether the user is disabled + example: Department nullable: true - access_role: + description: The name of the tracking category + status: type: string - example: ADMIN - enum: - - SUPER_ADMIN - - ADMIN - - TEAM_MEMBER - - LIMITED_TEAM_MEMBER - - INTERVIEWER - description: The access role of the user + example: Active nullable: true - remote_created_at: - format: date-time + description: The status of the tracking category + category_type: type: string - example: '2024-10-01T12:00:00Z' - description: The remote creation date of the user - nullable: true - remote_modified_at: - format: date-time + example: Expense + nullable: true + description: The type of the tracking category + parent_category: type: string - example: '2024-10-01T12:00:00Z' - description: The remote modification date of the user + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true + description: The UUID of the parent category, if applicable field_mappings: type: object example: - fav_dish: broccoli - fav_color: red + custom_field_1: value1 + custom_field_2: value2 + nullable: true description: >- The custom field mappings of the object between the remote 3rd party & Panora - nullable: true - additionalProperties: true id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - description: The UUID of the user nullable: true + description: The UUID of the tracking category record remote_id: type: string - example: id_1 - description: The remote ID of the user in the context of the 3rd Party + example: tracking_category_1234 nullable: true + description: >- + The remote ID of the tracking category in the context of the 3rd + Party remote_data: type: object example: - fav_dish: broccoli - fav_color: red - description: The remote data of the user in the context of the 3rd Party + raw_data: + additional_field: some value nullable: true - additionalProperties: true + description: >- + The remote data of the tracking category in the context of the 3rd + Party created_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' - description: The created date of the object + example: '2024-06-15T12:00:00Z' nullable: true + description: The created date of the tracking category record modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' - description: The modified date of the object + example: '2024-06-15T12:00:00Z' nullable: true - UnifiedAtsEeocsOutput: + description: The last modified date of the tracking category record + UnifiedAccountingTransactionOutput: type: object properties: - candidate_id: + transaction_type: type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + example: Sale nullable: true - description: The UUID of the candidate - submitted_at: + description: The type of the transaction + number: type: string - example: '2024-10-01T12:00:00Z' - format: date-time + example: '1001' nullable: true - description: The submission date of the EEOC - race: + description: The transaction number + transaction_date: type: string - enum: - - AMERICAN_INDIAN_OR_ALASKAN_NATIVE - - ASIAN - - BLACK_OR_AFRICAN_AMERICAN - - HISPANIC_OR_LATINO - - WHITE - - NATIVE_HAWAIIAN_OR_OTHER_PACIFIC_ISLANDER - - TWO_OR_MORE_RACES - - DECLINE_TO_SELF_IDENTIFY - example: AMERICAN_INDIAN_OR_ALASKAN_NATIVE + example: '2024-06-15T12:00:00Z' nullable: true - description: The race of the candidate - gender: + description: The date of the transaction + total_amount: type: string - example: MALE - enum: - - MALE - - FEMALE - - NON_BINARY - - OTHER - - DECLINE_TO_SELF_IDENTIFY + example: '1000' nullable: true - description: The gender of the candidate - veteran_status: + description: The total amount of the transaction + exchange_rate: type: string - example: I_AM_NOT_A_PROTECTED_VETERAN - enum: - - I_AM_NOT_A_PROTECTED_VETERAN - - >- - I_IDENTIFY_AS_ONE_OR_MORE_OF_THE_CLASSIFICATIONS_OF_A_PROTECTED_VETERAN - - I_DONT_WISH_TO_ANSWER + example: '1.2' nullable: true - description: The veteran status of the candidate - disability_status: + description: The exchange rate applied to the transaction + currency: type: string - enum: - - YES_I_HAVE_A_DISABILITY_OR_PREVIOUSLY_HAD_A_DISABILITY - - NO_I_DONT_HAVE_A_DISABILITY - - I_DONT_WISH_TO_ANSWER - example: YES_I_HAVE_A_DISABILITY_OR_PREVIOUSLY_HAD_A_DISABILITY + example: USD nullable: true - description: The disability status of the candidate - field_mappings: - type: object + description: The currency of the transaction + tracking_categories: example: - fav_dish: broccoli - fav_color: red - additionalProperties: true + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: >- - The custom field mappings of the object between the remote 3rd party - & Panora + description: The UUID of tracking categories associated with the transaction + type: array + items: + type: string + id_acc_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + id_acc_contact: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + id_acc_company_info: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + id_acc_accounting_period: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of the EEOC + description: The UUID of the transaction record remote_id: type: string - example: id_1 + example: remote_id_1234 + nullable: false + description: The remote ID of the transaction + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: false + description: The created date of the transaction + modified_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: false + description: The last modified date of the transaction + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' nullable: true - description: The remote ID of the EEOC in the context of the 3rd Party - remote_data: - type: object + description: The date when the transaction was last updated in the remote system + UnifiedAccountingVendorcreditOutput: + type: object + properties: + number: + type: string + example: VC-001 + nullable: true + description: The number of the vendor credit + transaction_date: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the transaction + vendor: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the vendor associated with the credit + total_amount: + type: string + example: '1000' + nullable: true + description: The total amount of the vendor credit + currency: + type: string + example: USD + nullable: true + description: The currency of the vendor credit + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the vendor credit + company: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + tracking_categories: example: - fav_dish: broccoli - fav_color: red + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - additionalProperties: true - description: The remote data of the EEOC in the context of the 3rd Party - created_at: - format: date-time + description: The UUID of tracking categories associated with the vendor credit + type: array + items: + type: string + accounting_period: type: string - example: '2024-10-01T12:00:00Z' + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The created date of the object + description: The UUID of the associated accounting period + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the vendor credit record + remote_id: + type: string + example: remote_id_1234 + nullable: true + description: The remote ID of the vendor credit + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: false + description: The created date of the vendor credit modified_at: - format: date-time type: string - example: '2024-10-01T12:00:00Z' + example: '2024-06-15T12:00:00Z' + nullable: false + description: The last modified date of the vendor credit + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' nullable: true - description: The modified date of the object - UnifiedAccountingAccountOutput: - type: object - properties: {} - UnifiedAccountingAccountInput: - type: object - properties: {} - UnifiedAccountingAddressOutput: - type: object - properties: {} - UnifiedAccountingAttachmentOutput: - type: object - properties: {} - UnifiedAccountingAttachmentInput: - type: object - properties: {} - UnifiedAccountingBalancesheetOutput: - type: object - properties: {} - UnifiedAccountingCashflowstatementOutput: - type: object - properties: {} - UnifiedAccountingCompanyinfoOutput: - type: object - properties: {} - UnifiedAccountingContactOutput: - type: object - properties: {} - UnifiedAccountingContactInput: - type: object - properties: {} - UnifiedAccountingCreditnoteOutput: - type: object - properties: {} - UnifiedAccountingExpenseOutput: - type: object - properties: {} - UnifiedAccountingExpenseInput: - type: object - properties: {} - UnifiedAccountingIncomestatementOutput: - type: object - properties: {} - UnifiedAccountingInvoiceOutput: - type: object - properties: {} - UnifiedAccountingInvoiceInput: - type: object - properties: {} - UnifiedAccountingItemOutput: - type: object - properties: {} - UnifiedAccountingJournalentryOutput: - type: object - properties: {} - UnifiedAccountingJournalentryInput: - type: object - properties: {} - UnifiedAccountingPaymentOutput: - type: object - properties: {} - UnifiedAccountingPaymentInput: - type: object - properties: {} - UnifiedAccountingPhonenumberOutput: - type: object - properties: {} - UnifiedAccountingPurchaseorderOutput: - type: object - properties: {} - UnifiedAccountingPurchaseorderInput: - type: object - properties: {} - UnifiedAccountingTaxrateOutput: - type: object - properties: {} - UnifiedAccountingTrackingcategoryOutput: - type: object - properties: {} - UnifiedAccountingTransactionOutput: - type: object - properties: {} - UnifiedAccountingVendorcreditOutput: - type: object - properties: {} + description: >- + The date when the vendor credit was last updated in the remote + system UnifiedFilestorageDriveOutput: type: object properties: @@ -14101,7 +17765,7 @@ components: nullable: true field_mappings: type: object - example: &ref_143 + example: &ref_162 fav_dish: broccoli fav_color: red description: >- @@ -14187,7 +17851,7 @@ components: nullable: true field_mappings: type: object - example: *ref_143 + example: *ref_162 description: >- The custom field mappings of the object between the remote 3rd party & Panora @@ -14245,7 +17909,7 @@ components: description: The UUID of the permission tied to the folder field_mappings: type: object - example: &ref_144 + example: &ref_163 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14336,7 +18000,7 @@ components: description: The UUID of the permission tied to the folder field_mappings: type: object - example: *ref_144 + example: *ref_163 additionalProperties: true nullable: true description: >- @@ -14501,13 +18165,13 @@ components: type: string example: ACTIVE nullable: true - enum: &ref_145 + enum: &ref_164 - ARCHIVED - ACTIVE - DRAFT description: The status of the product. Either ACTIVE, DRAFT OR ARCHIVED. images_urls: - example: &ref_146 + example: &ref_165 - https://myproduct/image nullable: true description: The URLs of the product images @@ -14525,7 +18189,7 @@ components: nullable: true description: The vendor of the product variants: - example: &ref_147 + example: &ref_166 - title: teeshirt price: 20 sku: '3' @@ -14537,7 +18201,7 @@ components: items: $ref: '#/components/schemas/Variant' tags: - example: &ref_148 + example: &ref_167 - tag_1 nullable: true description: The tags associated with the product @@ -14546,7 +18210,7 @@ components: type: string field_mappings: type: object - example: &ref_149 + example: &ref_168 fav_dish: broccoli fav_color: red nullable: true @@ -14597,10 +18261,10 @@ components: type: string example: ACTIVE nullable: true - enum: *ref_145 + enum: *ref_164 description: The status of the product. Either ACTIVE, DRAFT OR ARCHIVED. images_urls: - example: *ref_146 + example: *ref_165 nullable: true description: The URLs of the product images type: array @@ -14617,13 +18281,13 @@ components: nullable: true description: The vendor of the product variants: - example: *ref_147 + example: *ref_166 description: The variants of the product type: array items: $ref: '#/components/schemas/Variant' tags: - example: *ref_148 + example: *ref_167 nullable: true description: The tags associated with the product type: array @@ -14631,7 +18295,7 @@ components: type: string field_mappings: type: object - example: *ref_149 + example: *ref_168 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -14658,7 +18322,7 @@ components: type: string nullable: true example: AUD - enum: &ref_150 + enum: &ref_169 - AED - AFN - ALL @@ -14857,11 +18521,11 @@ components: items: type: object nullable: true - example: &ref_151 {} + example: &ref_170 {} description: The items in the order field_mappings: type: object - example: &ref_152 + example: &ref_171 fav_dish: broccoli fav_color: red nullable: true @@ -14917,7 +18581,7 @@ components: type: string nullable: true example: AUD - enum: *ref_150 + enum: *ref_169 description: >- The currency of the order. Authorized value must be of type CurrencyCode (ISO 4217) @@ -14954,11 +18618,11 @@ components: items: type: object nullable: true - example: *ref_151 + example: *ref_170 description: The items in the order field_mappings: type: object - example: *ref_152 + example: *ref_171 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -15135,7 +18799,7 @@ components: field_mappings: type: object nullable: true - example: &ref_153 + example: &ref_172 fav_dish: broccoli fav_color: red description: >- @@ -15207,7 +18871,7 @@ components: field_mappings: type: object nullable: true - example: *ref_153 + example: *ref_172 description: >- The custom field mappings of the attachment between the remote 3rd party & Panora From 105201097410ebc4a4708c1d80a53cba852fe7c3 Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 13 Aug 2024 08:04:24 +0200 Subject: [PATCH 5/8] :sparkles: Add gusto integration + deel connection + accounting code --- apps/webapp/src/components/Nav/main-nav.tsx | 6 - .../src/components/shared/team-switcher.tsx | 4 +- docker-compose.dev.yml | 8 +- docker-compose.source.yml | 8 +- docker-compose.yml | 8 +- .../open-source/self_hosting/envVariables.mdx | 8 +- packages/api/prisma/schema.prisma | 5 +- packages/api/scripts/init.sql | 3 + .../connections/connections.controller.ts | 28 +- .../@core/connections/connections.module.ts | 6 +- .../hris/services/deel/deel.service.ts | 8 +- .../productivity.connection.module.ts} | 8 +- .../services/notion/notion.service.ts | 14 +- .../productivity.connection.service.ts} | 6 +- .../services/registry.service.ts | 0 .../services/slack/slack.service.ts | 14 +- .../api/src/@core/utils/decorators/utils.ts | 98 + .../utils/types/original/original.hris.ts | 34 +- .../account/services/account.service.ts | 206 +- .../accounting/account/sync/sync.service.ts | 154 +- .../api/src/accounting/account/types/index.ts | 11 +- .../accounting/account/types/model.unified.ts | 12 +- .../address/services/address.service.ts | 159 +- .../accounting/address/sync/sync.service.ts | 145 +- .../api/src/accounting/address/types/index.ts | 11 +- .../accounting/address/types/model.unified.ts | 8 +- .../attachment/services/attachment.service.ts | 179 +- .../attachment/sync/sync.service.ts | 150 +- .../src/accounting/attachment/types/index.ts | 10 +- .../attachment/types/model.unified.ts | 8 +- .../services/balancesheet.service.ts | 208 +- .../balancesheet/sync/sync.service.ts | 214 +- .../accounting/balancesheet/types/index.ts | 10 +- .../balancesheet/types/model.unified.ts | 29 +- .../services/cashflowstatement.service.ts | 234 +- .../cashflowstatement/sync/sync.service.ts | 233 +- .../cashflowstatement/types/index.ts | 11 +- .../cashflowstatement/types/model.unified.ts | 125 +- .../services/companyinfo.service.ts | 164 +- .../companyinfo/sync/sync.service.ts | 144 +- .../src/accounting/companyinfo/types/index.ts | 10 +- .../companyinfo/types/model.unified.ts | 23 +- .../contact/services/contact.service.ts | 201 +- .../accounting/contact/sync/sync.service.ts | 140 +- .../api/src/accounting/contact/types/index.ts | 11 +- .../accounting/contact/types/model.unified.ts | 12 +- .../creditnote/services/creditnote.service.ts | 182 +- .../creditnote/sync/sync.service.ts | 154 +- .../src/accounting/creditnote/types/index.ts | 10 +- .../creditnote/types/model.unified.ts | 27 +- .../expense/services/expense.service.ts | 285 +- .../accounting/expense/sync/sync.service.ts | 223 +- .../api/src/accounting/expense/types/index.ts | 11 +- .../accounting/expense/types/model.unified.ts | 104 +- .../services/incomestatement.service.ts | 185 +- .../incomestatement/sync/sync.service.ts | 158 +- .../accounting/incomestatement/types/index.ts | 11 +- .../incomestatement/types/model.unified.ts | 20 +- .../invoice/services/invoice.service.ts | 330 +- .../accounting/invoice/sync/sync.service.ts | 239 +- .../api/src/accounting/invoice/types/index.ts | 11 +- .../accounting/invoice/types/model.unified.ts | 159 +- .../accounting/item/services/item.service.ts | 166 +- .../src/accounting/item/sync/sync.service.ts | 140 +- .../api/src/accounting/item/types/index.ts | 11 +- .../accounting/item/types/model.unified.ts | 14 +- .../services/journalentry.service.ts | 272 +- .../journalentry/sync/sync.service.ts | 231 +- .../accounting/journalentry/types/index.ts | 10 +- .../journalentry/types/model.unified.ts | 143 +- .../payment/services/payment.service.ts | 271 +- .../accounting/payment/sync/sync.service.ts | 223 +- .../api/src/accounting/payment/types/index.ts | 11 +- .../accounting/payment/types/model.unified.ts | 113 +- .../services/phonenumber.service.ts | 150 +- .../phonenumber/sync/sync.service.ts | 135 +- .../src/accounting/phonenumber/types/index.ts | 10 +- .../phonenumber/types/model.unified.ts | 12 +- .../services/purchaseorder.service.ts | 310 +- .../purchaseorder/sync/sync.service.ts | 243 +- .../accounting/purchaseorder/types/index.ts | 11 +- .../purchaseorder/types/model.unified.ts | 178 +- .../taxrate/services/taxrate.service.ts | 171 +- .../accounting/taxrate/sync/sync.service.ts | 137 +- .../api/src/accounting/taxrate/types/index.ts | 11 +- .../accounting/taxrate/types/model.unified.ts | 10 +- .../services/trackingcategory.service.ts | 179 +- .../trackingcategory/sync/sync.service.ts | 146 +- .../trackingcategory.controller.ts | 8 +- .../trackingcategory/types/index.ts | 11 +- .../trackingcategory/types/model.unified.ts | 8 +- .../services/transaction.service.ts | 222 +- .../transaction/sync/sync.service.ts | 225 +- .../src/accounting/transaction/types/index.ts | 10 +- .../transaction/types/model.unified.ts | 216 +- .../services/vendorcredit.service.ts | 216 +- .../vendorcredit/sync/sync.service.ts | 229 +- .../accounting/vendorcredit/types/index.ts | 10 +- .../vendorcredit/types/model.unified.ts | 159 +- .../crm/company/services/company.service.ts | 2 +- .../api/src/crm/company/sync/sync.service.ts | 16 +- packages/api/src/hris/@lib/@types/index.ts | 13 +- packages/api/src/hris/@lib/@utils/index.ts | 52 + .../bankinfo/services/bankinfo.service.ts | 156 +- .../src/hris/bankinfo/sync/sync.processor.ts | 19 + .../src/hris/bankinfo/sync/sync.service.ts | 145 +- packages/api/src/hris/bankinfo/types/index.ts | 11 +- .../src/hris/bankinfo/types/model.unified.ts | 23 +- .../api/src/hris/benefit/benefit.module.ts | 21 +- .../hris/benefit/services/benefit.service.ts | 173 +- .../src/hris/benefit/services/gusto/index.ts | 72 + .../hris/benefit/services/gusto/mappers.ts | 93 + .../src/hris/benefit/services/gusto/types.ts | 28 + .../src/hris/benefit/sync/sync.processor.ts | 19 + .../api/src/hris/benefit/sync/sync.service.ts | 159 +- packages/api/src/hris/benefit/types/index.ts | 13 +- .../src/hris/benefit/types/model.unified.ts | 20 +- .../src/hris/company/company.controller.ts | 24 +- .../api/src/hris/company/company.module.ts | 21 +- .../hris/company/services/company.service.ts | 163 +- .../src/hris/company/services/gusto/index.ts | 81 + .../hris/company/services/gusto/mappers.ts | 119 + .../src/hris/company/services/gusto/types.ts | 76 + .../src/hris/company/sync/sync.processor.ts | 19 + .../api/src/hris/company/sync/sync.service.ts | 141 +- packages/api/src/hris/company/types/index.ts | 6 +- .../src/hris/company/types/model.unified.ts | 22 +- .../dependent/services/dependent.service.ts | 181 +- .../src/hris/dependent/sync/sync.processor.ts | 19 + .../src/hris/dependent/sync/sync.service.ts | 150 +- .../api/src/hris/dependent/types/index.ts | 11 +- .../src/hris/dependent/types/model.unified.ts | 35 +- .../api/src/hris/employee/employee.module.ts | 21 +- .../employee/services/employee.service.ts | 228 +- .../src/hris/employee/services/gusto/index.ts | 72 + .../hris/employee/services/gusto/mappers.ts | 139 + .../src/hris/employee/services/gusto/types.ts | 123 + .../src/hris/employee/sync/sync.processor.ts | 19 + .../src/hris/employee/sync/sync.service.ts | 166 +- packages/api/src/hris/employee/types/index.ts | 13 +- .../src/hris/employee/types/model.unified.ts | 108 +- .../services/employeepayrollrun.service.ts | 239 +- .../employeepayrollrun/sync/sync.processor.ts | 21 + .../employeepayrollrun/sync/sync.service.ts | 198 +- .../hris/employeepayrollrun/types/index.ts | 6 +- .../employeepayrollrun/types/model.unified.ts | 24 +- .../employerbenefit.controller.ts | 25 +- .../employerbenefit/employerbenefit.module.ts | 20 +- .../services/employerbenefit.service.ts | 167 +- .../employerbenefit/services/gusto/index.ts | 96 + .../employerbenefit/services/gusto/mappers.ts | 90 + .../employerbenefit/services/gusto/types.ts | 20 + .../employerbenefit/sync/sync.processor.ts | 19 + .../hris/employerbenefit/sync/sync.service.ts | 147 +- .../src/hris/employerbenefit/types/index.ts | 15 +- .../employerbenefit/types/model.unified.ts | 27 +- .../src/hris/employment/employment.module.ts | 16 +- .../employment/services/employment.service.ts | 183 +- .../hris/employment/services/gusto/mappers.ts | 92 + .../hris/employment/services/gusto/types.ts | 31 + .../hris/employment/sync/sync.processor.ts | 19 + .../src/hris/employment/sync/sync.service.ts | 150 +- .../api/src/hris/employment/types/index.ts | 6 +- .../hris/employment/types/model.unified.ts | 95 +- packages/api/src/hris/group/group.module.ts | 21 +- .../src/hris/group/services/group.service.ts | 167 +- .../src/hris/group/services/gusto/index.ts | 72 + .../src/hris/group/services/gusto/mappers.ts | 61 + .../src/hris/group/services/gusto/types.ts | 12 + .../api/src/hris/group/sync/sync.processor.ts | 19 + .../api/src/hris/group/sync/sync.service.ts | 143 +- packages/api/src/hris/group/types/index.ts | 11 +- .../api/src/hris/group/types/model.unified.ts | 23 +- packages/api/src/hris/hris.module.ts | 3 + .../src/hris/location/services/gusto/index.ts | 101 + .../hris/location/services/gusto/mappers.ts | 93 + .../src/hris/location/services/gusto/types.ts | 9 + .../location/services/location.service.ts | 174 +- .../src/hris/location/sync/sync.processor.ts | 19 + .../src/hris/location/sync/sync.service.ts | 149 +- packages/api/src/hris/location/types/index.ts | 13 +- .../src/hris/location/types/model.unified.ts | 20 +- .../paygroup/services/paygroup.service.ts | 158 +- .../src/hris/paygroup/sync/sync.processor.ts | 19 + .../src/hris/paygroup/sync/sync.service.ts | 141 +- packages/api/src/hris/paygroup/types/index.ts | 11 +- .../src/hris/paygroup/types/model.unified.ts | 12 +- .../payrollrun/services/payrollrun.service.ts | 177 +- .../hris/payrollrun/sync/sync.processor.ts | 19 + .../src/hris/payrollrun/sync/sync.service.ts | 149 +- .../api/src/hris/payrollrun/types/index.ts | 6 +- .../hris/payrollrun/types/model.unified.ts | 47 +- .../hris/timeoff/services/timeoff.service.ts | 209 +- .../src/hris/timeoff/sync/sync.processor.ts | 19 + .../api/src/hris/timeoff/sync/sync.service.ts | 146 +- packages/api/src/hris/timeoff/types/index.ts | 11 +- .../src/hris/timeoff/types/model.unified.ts | 56 +- .../services/timeoffbalance.service.ts | 184 +- .../timeoffbalance/sync/sync.processor.ts | 19 + .../hris/timeoffbalance/sync/sync.service.ts | 150 +- .../src/hris/timeoffbalance/types/index.ts | 10 +- .../timeoffbalance/types/model.unified.ts | 26 +- .../services/registry.service.ts | 23 + .../services/timesheetentry.service.ts | 262 ++ .../timesheetentry/sync/sync.processor.ts | 19 + .../hris/timesheetentry/sync/sync.service.ts | 173 + .../timesheetentry.controller.ts | 175 + .../timesheetentry/timesheetentry.module.ts | 24 + .../src/hris/timesheetentry/types/index.ts | 38 + .../timesheetentry/types/model.unified.ts | 137 + .../src/hris/timesheetentry/utils/index.ts | 1 + packages/api/src/main.ts | 6 +- .../comment/services/github/mappers.ts | 161 +- packages/api/swagger/swagger-spec.yaml | 3761 ++++++++++++++--- packages/api/variables.MD | 8 +- packages/shared/src/authUrl.ts | 13 + packages/shared/src/categories.ts | 2 +- packages/shared/src/connectors/enum.ts | 12 +- packages/shared/src/connectors/index.ts | 8 +- packages/shared/src/connectors/metadata.ts | 8 +- packages/shared/src/standardObjects.ts | 105 +- 221 files changed, 19981 insertions(+), 2151 deletions(-) rename packages/api/src/@core/connections/{management/management.connection.module.ts => productivity/productivity.connection.module.ts} (81%) rename packages/api/src/@core/connections/{management => productivity}/services/notion/notion.service.ts (92%) rename packages/api/src/@core/connections/{management/services/management.connection.service.ts => productivity/services/productivity.connection.service.ts} (95%) rename packages/api/src/@core/connections/{management => productivity}/services/registry.service.ts (100%) rename packages/api/src/@core/connections/{management => productivity}/services/slack/slack.service.ts (92%) create mode 100644 packages/api/src/@core/utils/decorators/utils.ts create mode 100644 packages/api/src/hris/@lib/@utils/index.ts create mode 100644 packages/api/src/hris/bankinfo/sync/sync.processor.ts create mode 100644 packages/api/src/hris/benefit/services/gusto/index.ts create mode 100644 packages/api/src/hris/benefit/services/gusto/mappers.ts create mode 100644 packages/api/src/hris/benefit/services/gusto/types.ts create mode 100644 packages/api/src/hris/benefit/sync/sync.processor.ts create mode 100644 packages/api/src/hris/company/services/gusto/index.ts create mode 100644 packages/api/src/hris/company/services/gusto/mappers.ts create mode 100644 packages/api/src/hris/company/services/gusto/types.ts create mode 100644 packages/api/src/hris/company/sync/sync.processor.ts create mode 100644 packages/api/src/hris/dependent/sync/sync.processor.ts create mode 100644 packages/api/src/hris/employee/services/gusto/index.ts create mode 100644 packages/api/src/hris/employee/services/gusto/mappers.ts create mode 100644 packages/api/src/hris/employee/services/gusto/types.ts create mode 100644 packages/api/src/hris/employee/sync/sync.processor.ts create mode 100644 packages/api/src/hris/employeepayrollrun/sync/sync.processor.ts create mode 100644 packages/api/src/hris/employerbenefit/services/gusto/index.ts create mode 100644 packages/api/src/hris/employerbenefit/services/gusto/mappers.ts create mode 100644 packages/api/src/hris/employerbenefit/services/gusto/types.ts create mode 100644 packages/api/src/hris/employerbenefit/sync/sync.processor.ts create mode 100644 packages/api/src/hris/employment/services/gusto/mappers.ts create mode 100644 packages/api/src/hris/employment/services/gusto/types.ts create mode 100644 packages/api/src/hris/employment/sync/sync.processor.ts create mode 100644 packages/api/src/hris/group/services/gusto/index.ts create mode 100644 packages/api/src/hris/group/services/gusto/mappers.ts create mode 100644 packages/api/src/hris/group/services/gusto/types.ts create mode 100644 packages/api/src/hris/group/sync/sync.processor.ts create mode 100644 packages/api/src/hris/location/services/gusto/index.ts create mode 100644 packages/api/src/hris/location/services/gusto/mappers.ts create mode 100644 packages/api/src/hris/location/services/gusto/types.ts create mode 100644 packages/api/src/hris/location/sync/sync.processor.ts create mode 100644 packages/api/src/hris/paygroup/sync/sync.processor.ts create mode 100644 packages/api/src/hris/payrollrun/sync/sync.processor.ts create mode 100644 packages/api/src/hris/timeoff/sync/sync.processor.ts create mode 100644 packages/api/src/hris/timeoffbalance/sync/sync.processor.ts create mode 100644 packages/api/src/hris/timesheetentry/services/registry.service.ts create mode 100644 packages/api/src/hris/timesheetentry/services/timesheetentry.service.ts create mode 100644 packages/api/src/hris/timesheetentry/sync/sync.processor.ts create mode 100644 packages/api/src/hris/timesheetentry/sync/sync.service.ts create mode 100644 packages/api/src/hris/timesheetentry/timesheetentry.controller.ts create mode 100644 packages/api/src/hris/timesheetentry/timesheetentry.module.ts create mode 100644 packages/api/src/hris/timesheetentry/types/index.ts create mode 100644 packages/api/src/hris/timesheetentry/types/model.unified.ts create mode 100644 packages/api/src/hris/timesheetentry/utils/index.ts diff --git a/apps/webapp/src/components/Nav/main-nav.tsx b/apps/webapp/src/components/Nav/main-nav.tsx index 1efe65cf5..7732ae037 100644 --- a/apps/webapp/src/components/Nav/main-nav.tsx +++ b/apps/webapp/src/components/Nav/main-nav.tsx @@ -82,13 +82,11 @@ export function MainNav({ ); } -const navIconClassName = "text-gray-400 w-5"; const navItems: Omit[] = [ { name: 'connections', content: ( <> - Connections ), @@ -97,7 +95,6 @@ const navItems: Omit[] = [ name: 'events', content: ( <> - Events ), @@ -106,7 +103,6 @@ const navItems: Omit[] = [ name: 'configuration', content: ( <> - Configuration ), @@ -115,7 +111,6 @@ const navItems: Omit[] = [ name: 'api-keys', content: ( <> - API Keys ), @@ -124,7 +119,6 @@ const navItems: Omit[] = [ name: 'docs', content: ( <> -

Docs

), diff --git a/apps/webapp/src/components/shared/team-switcher.tsx b/apps/webapp/src/components/shared/team-switcher.tsx index 5121ad8a7..4a0328eda 100644 --- a/apps/webapp/src/components/shared/team-switcher.tsx +++ b/apps/webapp/src/components/shared/team-switcher.tsx @@ -70,7 +70,7 @@ const projectFormSchema = z.object({ type PopoverTriggerProps = React.ComponentPropsWithoutRef interface TeamSwitcherProps extends PopoverTriggerProps { - projects:Project[] + projects: Project[] } interface ModalObj { @@ -88,7 +88,7 @@ export default function TeamSwitcher({ className ,projects}: TeamSwitcherProps) const { profile } = useProfileStore(); const { idProject, setIdProject } = useProjectStore(); - const {mutate : refreshAccessToken} = useRefreshAccessTokenMutation() + const { mutate : refreshAccessToken } = useRefreshAccessTokenMutation() const handleOpenChange = (open: boolean) => { setShowNewDialog(prevState => ({ ...prevState, open })); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index fe31d4e5d..69679181e 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -150,10 +150,10 @@ services: FACTORIAL_HRIS_CLOUD_CLIENT_SECRET: ${FACTORIAL_ATS_CLOUD_CLIENT_SECRET} PAYFIT_HRIS_CLOUD_CLIENT_ID: ${PAYFIT_HRIS_CLOUD_CLIENT_ID} PAYFIT_HRIS_CLOUD_CLIENT_SECRET: ${PAYFIT_HRIS_CLOUD_CLIENT_SECRET} - NOTION_MANAGEMENT_CLOUD_CLIENT_ID: ${NOTION_MANAGEMENT_CLOUD_CLIENT_ID} - NOTION_MANAGEMENT_CLOUD_CLIENT_SECRET: ${NOTION_MANAGEMENT_CLOUD_CLIENT_SECRET} - SLACK_MANAGEMENT_CLOUD_CLIENT_ID: ${SLACK_MANAGEMENT_CLOUD_CLIENT_ID} - SLACK_MANAGEMENT_CLOUD_CLIENT_SECRET: ${SLACK_MANAGEMENT_CLOUD_CLIENT_SECRET} + NOTION_PRODUCTIVITY_CLOUD_CLIENT_ID: ${NOTION_PRODUCTIVITY_CLOUD_CLIENT_ID} + NOTION_PRODUCTIVITY_CLOUD_CLIENT_SECRET: ${NOTION_PRODUCTIVITY_CLOUD_CLIENT_SECRET} + SLACK_PRODUCTIVITY_CLOUD_CLIENT_ID: ${SLACK_PRODUCTIVITY_CLOUD_CLIENT_ID} + SLACK_PRODUCTIVITY_CLOUD_CLIENT_SECRET: ${SLACK_PRODUCTIVITY_CLOUD_CLIENT_SECRET} NAMELY_HRIS_CLOUD_CLIENT_ID: ${NAMELY_HRIS_CLOUD_CLIENT_ID} NAMELY_HRIS_CLOUD_CLIENT_SECRET: ${NAMELY_HRIS_CLOUD_CLIENT_SECRET} NAMELY_HRIS_CLOUD_SUBDOMAIN: ${NAMELY_HRIS_CLOUD_SUBDOMAIN} diff --git a/docker-compose.source.yml b/docker-compose.source.yml index 1858d5516..4ce8ed9f9 100644 --- a/docker-compose.source.yml +++ b/docker-compose.source.yml @@ -150,10 +150,10 @@ services: FACTORIAL_HRIS_CLOUD_CLIENT_SECRET: ${FACTORIAL_ATS_CLOUD_CLIENT_SECRET} PAYFIT_HRIS_CLOUD_CLIENT_ID: ${PAYFIT_HRIS_CLOUD_CLIENT_ID} PAYFIT_HRIS_CLOUD_CLIENT_SECRET: ${PAYFIT_HRIS_CLOUD_CLIENT_SECRET} - NOTION_MANAGEMENT_CLOUD_CLIENT_ID: ${NOTION_MANAGEMENT_CLOUD_CLIENT_ID} - NOTION_MANAGEMENT_CLOUD_CLIENT_SECRET: ${NOTION_MANAGEMENT_CLOUD_CLIENT_SECRET} - SLACK_MANAGEMENT_CLOUD_CLIENT_ID: ${SLACK_MANAGEMENT_CLOUD_CLIENT_ID} - SLACK_MANAGEMENT_CLOUD_CLIENT_SECRET: ${SLACK_MANAGEMENT_CLOUD_CLIENT_SECRET} + NOTION_PRODUCTIVITY_CLOUD_CLIENT_ID: ${NOTION_PRODUCTIVITY_CLOUD_CLIENT_ID} + NOTION_PRODUCTIVITY_CLOUD_CLIENT_SECRET: ${NOTION_PRODUCTIVITY_CLOUD_CLIENT_SECRET} + SLACK_PRODUCTIVITY_CLOUD_CLIENT_ID: ${SLACK_PRODUCTIVITY_CLOUD_CLIENT_ID} + SLACK_PRODUCTIVITY_CLOUD_CLIENT_SECRET: ${SLACK_PRODUCTIVITY_CLOUD_CLIENT_SECRET} NAMELY_HRIS_CLOUD_CLIENT_ID: ${NAMELY_HRIS_CLOUD_CLIENT_ID} NAMELY_HRIS_CLOUD_CLIENT_SECRET: ${NAMELY_HRIS_CLOUD_CLIENT_SECRET} NAMELY_HRIS_CLOUD_SUBDOMAIN: ${NAMELY_HRIS_CLOUD_SUBDOMAIN} diff --git a/docker-compose.yml b/docker-compose.yml index 154d0a060..51fccb528 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -144,10 +144,10 @@ services: FACTORIAL_HRIS_CLOUD_CLIENT_SECRET: ${FACTORIAL_ATS_CLOUD_CLIENT_SECRET} PAYFIT_HRIS_CLOUD_CLIENT_ID: ${PAYFIT_HRIS_CLOUD_CLIENT_ID} PAYFIT_HRIS_CLOUD_CLIENT_SECRET: ${PAYFIT_HRIS_CLOUD_CLIENT_SECRET} - NOTION_MANAGEMENT_CLOUD_CLIENT_ID: ${NOTION_MANAGEMENT_CLOUD_CLIENT_ID} - NOTION_MANAGEMENT_CLOUD_CLIENT_SECRET: ${NOTION_MANAGEMENT_CLOUD_CLIENT_SECRET} - SLACK_MANAGEMENT_CLOUD_CLIENT_ID: ${SLACK_MANAGEMENT_CLOUD_CLIENT_ID} - SLACK_MANAGEMENT_CLOUD_CLIENT_SECRET: ${SLACK_MANAGEMENT_CLOUD_CLIENT_SECRET} + NOTION_PRODUCTIVITY_CLOUD_CLIENT_ID: ${NOTION_PRODUCTIVITY_CLOUD_CLIENT_ID} + NOTION_PRODUCTIVITY_CLOUD_CLIENT_SECRET: ${NOTION_PRODUCTIVITY_CLOUD_CLIENT_SECRET} + SLACK_PRODUCTIVITY_CLOUD_CLIENT_ID: ${SLACK_PRODUCTIVITY_CLOUD_CLIENT_ID} + SLACK_PRODUCTIVITY_CLOUD_CLIENT_SECRET: ${SLACK_PRODUCTIVITY_CLOUD_CLIENT_SECRET} NAMELY_HRIS_CLOUD_CLIENT_ID: ${NAMELY_HRIS_CLOUD_CLIENT_ID} NAMELY_HRIS_CLOUD_CLIENT_SECRET: ${NAMELY_HRIS_CLOUD_CLIENT_SECRET} NAMELY_HRIS_CLOUD_SUBDOMAIN: ${NAMELY_HRIS_CLOUD_SUBDOMAIN} diff --git a/docs/open-source/self_hosting/envVariables.mdx b/docs/open-source/self_hosting/envVariables.mdx index 7bfb33e6e..efb5e7a60 100644 --- a/docs/open-source/self_hosting/envVariables.mdx +++ b/docs/open-source/self_hosting/envVariables.mdx @@ -159,10 +159,10 @@ description: "" | MAILCHIMP_MARKETINGAUTOMATION_CLOUD_CLIENT_SECRET | | | | KLAVIYO_TICKETING_CLOUD_CLIENT_ID | | | | KLAVIYO_TICKETING_CLOUD_CLIENT_SECRET | | | -| NOTION_MANAGEMENT_CLOUD_CLIENT_ID | | | -| NOTION_MANAGEMENT_CLOUD_CLIENT_SECRET | | | -| SLACK_MANAGEMENT_CLOUD_CLIENT_ID | | | -| SLACK_MANAGEMENT_CLOUD_CLIENT_SECRET | | | +| NOTION_PRODUCTIVITY_CLOUD_CLIENT_ID | | | +| NOTION_PRODUCTIVITY_CLOUD_CLIENT_SECRET | | | +| SLACK_PRODUCTIVITY_CLOUD_CLIENT_ID | | | +| SLACK_PRODUCTIVITY_CLOUD_CLIENT_SECRET | | | | GREENHOUSE_ATS_CLOUD_CLIENT_ID | | | | GREENHOUSE_ATS_CLOUD_CLIENT_SECRET | | | | JOBADDER_ATS_CLOUD_CLIENT_ID | | | diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index 075441d2f..b3eff7b44 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -1110,6 +1110,7 @@ model acc_addresses { street_1 String? street_2 String? city String? + remote_id String? state String? country_subdivision String? country String? @@ -1439,6 +1440,7 @@ model acc_payments { currency String? exchange_rate String? total_amount BigInt? + remote_id String? type String? remote_updated_at DateTime? @db.Timestamptz(6) id_acc_company_info String? @db.Uuid @@ -1476,6 +1478,7 @@ model acc_phone_numbers { id_acc_phone_number String @id(map: "pk_acc_phone_numbers") @db.Uuid number String? type String? + remote_id String? created_at DateTime @db.Timestamptz(6) modified_at DateTime @db.Timestamptz(6) id_acc_company_info String? @db.Uuid @@ -2058,7 +2061,7 @@ model hris_payroll_runs { id_connection String @db.Uuid hris_employee_payroll_runs hris_employee_payroll_runs[] } - + /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model hris_time_off { id_hris_time_off String @id(map: "pk_hris_time_off") @db.Uuid diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index 8bea8b33b..fa91ac866 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -2177,6 +2177,7 @@ CREATE TABLE acc_phone_numbers id_acc_phone_number uuid NOT NULL, "number" text NULL, type text NULL, + remote_id text NULL, created_at timestamp with time zone NOT NULL, modified_at timestamp with time zone NOT NULL, id_acc_company_info uuid NULL, @@ -2353,6 +2354,7 @@ CREATE TABLE acc_addresses street_1 text NULL, street_2 text NULL, city text NULL, + remote_id text NULL, "state" text NULL, country_subdivision text NULL, country text NULL, @@ -2650,6 +2652,7 @@ CREATE TABLE acc_payments currency text NULL, exchange_rate text NULL, total_amount bigint NULL, + remote_id text NULL, type text NULL, remote_updated_at timestamp with time zone NULL, id_acc_company_info uuid NULL, diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts index 6beee5fcd..2404b64e2 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -80,8 +80,20 @@ export class ConnectionsController { message: `No Callback Params found for state, found ${state}`, }); } + let stateData: StateDataType; + if (state.includes('deel_delimiter')) { + // squarespace asks for a random alphanumeric value + // Split the random part and the base64 part + const [randomPart, base64Part] = + decodeURIComponent(state).split('deel_delimiter'); + // Decode the base64 part to get the original JSON + const jsonString = Buffer.from(base64Part, 'base64').toString('utf-8'); + stateData = JSON.parse(jsonString); + } else { + // If no HTML entities are present, parse directly + stateData = JSON.parse(state); + } - const stateData: StateDataType = JSON.parse(decodeURIComponent(state)); const { projectId, vertical, @@ -178,11 +190,15 @@ export class ConnectionsController { const service = this.categoryConnectionRegistry.getService( vertical.toLowerCase(), ); - await service.handleCallBack(providerName, { - projectId, - linkedUserId, - body, - }, strategy_type); + await service.handleCallBack( + providerName, + { + projectId, + linkedUserId, + body, + }, + strategy_type, + ); /*if ( CONNECTORS_METADATA[vertical.toLowerCase()][providerName.toLowerCase()] .active !== false diff --git a/packages/api/src/@core/connections/connections.module.ts b/packages/api/src/@core/connections/connections.module.ts index 834317431..80c2a0d71 100644 --- a/packages/api/src/@core/connections/connections.module.ts +++ b/packages/api/src/@core/connections/connections.module.ts @@ -8,7 +8,7 @@ import { ConnectionsController } from './connections.controller'; import { CrmConnectionModule } from './crm/crm.connection.module'; import { FilestorageConnectionModule } from './filestorage/filestorage.connection.module'; import { HrisConnectionModule } from './hris/hris.connection.module'; -import { ManagementConnectionsModule } from './management/management.connection.module'; +import { ProductivityConnectionsModule } from './productivity/productivity.connection.module'; import { MarketingAutomationConnectionsModule } from './marketingautomation/marketingautomation.connection.module'; import { TicketingConnectionModule } from './ticketing/ticketing.connection.module'; import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.module'; @@ -17,7 +17,7 @@ import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.modu controllers: [ConnectionsController], imports: [ CrmConnectionModule, - ManagementConnectionsModule, + ProductivityConnectionsModule, TicketingConnectionModule, AccountingConnectionModule, AtsConnectionModule, @@ -39,7 +39,7 @@ import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.modu FilestorageConnectionModule, EcommerceConnectionModule, HrisConnectionModule, - ManagementConnectionsModule, + ProductivityConnectionsModule, ], }) export class ConnectionsModule {} diff --git a/packages/api/src/@core/connections/hris/services/deel/deel.service.ts b/packages/api/src/@core/connections/hris/services/deel/deel.service.ts index 531cf6332..c80edf18c 100644 --- a/packages/api/src/@core/connections/hris/services/deel/deel.service.ts +++ b/packages/api/src/@core/connections/hris/services/deel/deel.service.ts @@ -102,7 +102,9 @@ export class DeelConnectionService extends AbstractBaseConnectionService { //reconstruct the redirect URI that was passed in the githubend it must be the same const REDIRECT_URI = `${ - this.env.getPanoraBaseUrl() + this.env.getDistributionMode() == 'selfhost' + ? this.env.getTunnelIngress() + : this.env.getPanoraBaseUrl() }/connections/oauth/callback`; const CREDENTIALS = (await this.cService.getCredentials( @@ -190,7 +192,9 @@ export class DeelConnectionService extends AbstractBaseConnectionService { try { const { connectionId, refreshToken, projectId } = opts; const REDIRECT_URI = `${ - this.env.getPanoraBaseUrl() + this.env.getDistributionMode() == 'selfhost' + ? this.env.getTunnelIngress() + : this.env.getPanoraBaseUrl() }/connections/oauth/callback`; const formData = new URLSearchParams({ diff --git a/packages/api/src/@core/connections/management/management.connection.module.ts b/packages/api/src/@core/connections/productivity/productivity.connection.module.ts similarity index 81% rename from packages/api/src/@core/connections/management/management.connection.module.ts rename to packages/api/src/@core/connections/productivity/productivity.connection.module.ts index cba149655..686c595b5 100644 --- a/packages/api/src/@core/connections/management/management.connection.module.ts +++ b/packages/api/src/@core/connections/productivity/productivity.connection.module.ts @@ -4,14 +4,14 @@ import { WebhookModule } from '@@core/@core-services/webhooks/panora-webhooks/we import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; import { Module } from '@nestjs/common'; -import { ManagementConnectionsService } from './services/management.connection.service'; +import { ProductivityConnectionsService } from './services/productivity.connection.service'; import { NotionConnectionService } from './services/notion/notion.service'; import { ServiceRegistry } from './services/registry.service'; import { SlackConnectionService } from './services/slack/slack.service'; @Module({ imports: [WebhookModule, BullQueueModule], providers: [ - ManagementConnectionsService, + ProductivityConnectionsService, WebhookService, EnvironmentService, ServiceRegistry, @@ -20,6 +20,6 @@ import { SlackConnectionService } from './services/slack/slack.service'; NotionConnectionService, SlackConnectionService, ], - exports: [ManagementConnectionsService], + exports: [ProductivityConnectionsService], }) -export class ManagementConnectionsModule {} +export class ProductivityConnectionsModule {} diff --git a/packages/api/src/@core/connections/management/services/notion/notion.service.ts b/packages/api/src/@core/connections/productivity/services/notion/notion.service.ts similarity index 92% rename from packages/api/src/@core/connections/management/services/notion/notion.service.ts rename to packages/api/src/@core/connections/productivity/services/notion/notion.service.ts index 89a7c91a2..83efd4444 100644 --- a/packages/api/src/@core/connections/management/services/notion/notion.service.ts +++ b/packages/api/src/@core/connections/productivity/services/notion/notion.service.ts @@ -48,7 +48,7 @@ export class NotionConnectionService extends AbstractBaseConnectionService { super(prisma, cryptoService); this.logger.setContext(NotionConnectionService.name); this.registry.registerService('notion', this); - this.type = providerToType('notion', 'management', AuthStrategy.oauth2); + this.type = providerToType('notion', 'productivity', AuthStrategy.oauth2); } async passthrough( @@ -81,7 +81,7 @@ export class NotionConnectionService extends AbstractBaseConnectionService { data: config.data, headers: config.headers, }, - 'management.notion.passthrough', + 'productivity.notion.passthrough', config.linkedUserId, ); } catch (error) { @@ -100,7 +100,7 @@ export class NotionConnectionService extends AbstractBaseConnectionService { where: { id_linked_user: linkedUserId, provider_slug: 'notion', - vertical: 'management', + vertical: 'productivity', }, }); @@ -130,7 +130,7 @@ export class NotionConnectionService extends AbstractBaseConnectionService { ); const data: NotionOAuthResponse = res.data; this.logger.log( - 'OAuth credentials : notion management ' + JSON.stringify(data), + 'OAuth credentials : notion productivity ' + JSON.stringify(data), ); let db_res; @@ -143,7 +143,7 @@ export class NotionConnectionService extends AbstractBaseConnectionService { }, data: { access_token: this.cryptoService.encrypt(data.access_token), - account_url: CONNECTORS_METADATA['management']['notion'].urls + account_url: CONNECTORS_METADATA['productivity']['notion'].urls .apiUrl as string, status: 'valid', created_at: new Date(), @@ -155,9 +155,9 @@ export class NotionConnectionService extends AbstractBaseConnectionService { id_connection: uuidv4(), connection_token: connection_token, provider_slug: 'notion', - vertical: 'management', + vertical: 'productivity', token_type: 'oauth2', - account_url: CONNECTORS_METADATA['management']['notion'].urls + account_url: CONNECTORS_METADATA['productivity']['notion'].urls .apiUrl as string, access_token: this.cryptoService.encrypt(data.access_token), status: 'valid', diff --git a/packages/api/src/@core/connections/management/services/management.connection.service.ts b/packages/api/src/@core/connections/productivity/services/productivity.connection.service.ts similarity index 95% rename from packages/api/src/@core/connections/management/services/management.connection.service.ts rename to packages/api/src/@core/connections/productivity/services/productivity.connection.service.ts index 6cc6ac021..2182004b5 100644 --- a/packages/api/src/@core/connections/management/services/management.connection.service.ts +++ b/packages/api/src/@core/connections/productivity/services/productivity.connection.service.ts @@ -15,7 +15,7 @@ import { CategoryConnectionRegistry } from '@@core/@core-services/registries/con import { PassthroughResponse } from '@@core/passthrough/types'; @Injectable() -export class ManagementConnectionsService implements IConnectionCategory { +export class ProductivityConnectionsService implements IConnectionCategory { constructor( private serviceRegistry: ServiceRegistry, private connectionCategoryRegistry: CategoryConnectionRegistry, @@ -23,8 +23,8 @@ export class ManagementConnectionsService implements IConnectionCategory { private logger: LoggerService, private prisma: PrismaService, ) { - this.logger.setContext(ManagementConnectionsService.name); - this.connectionCategoryRegistry.registerService('management', this); + this.logger.setContext(ProductivityConnectionsService.name); + this.connectionCategoryRegistry.registerService('productivity', this); } //STEP 1:[FRONTEND STEP] //create a frontend SDK snippet in which an authorization embedded link is set up so when users click diff --git a/packages/api/src/@core/connections/management/services/registry.service.ts b/packages/api/src/@core/connections/productivity/services/registry.service.ts similarity index 100% rename from packages/api/src/@core/connections/management/services/registry.service.ts rename to packages/api/src/@core/connections/productivity/services/registry.service.ts diff --git a/packages/api/src/@core/connections/management/services/slack/slack.service.ts b/packages/api/src/@core/connections/productivity/services/slack/slack.service.ts similarity index 92% rename from packages/api/src/@core/connections/management/services/slack/slack.service.ts rename to packages/api/src/@core/connections/productivity/services/slack/slack.service.ts index 782dd538a..03a7b867b 100644 --- a/packages/api/src/@core/connections/management/services/slack/slack.service.ts +++ b/packages/api/src/@core/connections/productivity/services/slack/slack.service.ts @@ -64,7 +64,7 @@ export class SlackConnectionService extends AbstractBaseConnectionService { super(prisma, cryptoService); this.logger.setContext(SlackConnectionService.name); this.registry.registerService('slack', this); - this.type = providerToType('slack', 'management', AuthStrategy.oauth2); + this.type = providerToType('slack', 'productivity', AuthStrategy.oauth2); } async passthrough( @@ -97,7 +97,7 @@ export class SlackConnectionService extends AbstractBaseConnectionService { data: config.data, headers: config.headers, }, - 'management.slack.passthrough', + 'productivity.slack.passthrough', config.linkedUserId, ); } catch (error) { @@ -116,7 +116,7 @@ export class SlackConnectionService extends AbstractBaseConnectionService { where: { id_linked_user: linkedUserId, provider_slug: 'slack', - vertical: 'management', + vertical: 'productivity', }, }); @@ -142,7 +142,7 @@ export class SlackConnectionService extends AbstractBaseConnectionService { ); const data: SlackOAuthResponse = res.data; this.logger.log( - 'OAuth credentials : slack management ' + JSON.stringify(data), + 'OAuth credentials : slack productivity ' + JSON.stringify(data), ); let db_res; @@ -157,7 +157,7 @@ export class SlackConnectionService extends AbstractBaseConnectionService { access_token: this.cryptoService.encrypt( data.authed_user.access_token, ), - account_url: CONNECTORS_METADATA['management']['slack'].urls + account_url: CONNECTORS_METADATA['productivity']['slack'].urls .apiUrl as string, status: 'valid', created_at: new Date(), @@ -169,9 +169,9 @@ export class SlackConnectionService extends AbstractBaseConnectionService { id_connection: uuidv4(), connection_token: connection_token, provider_slug: 'slack', - vertical: 'management', + vertical: 'productivity', token_type: 'oauth2', - account_url: CONNECTORS_METADATA['management']['slack'].urls + account_url: CONNECTORS_METADATA['productivity']['slack'].urls .apiUrl as string, access_token: this.cryptoService.encrypt( data.authed_user.access_token, diff --git a/packages/api/src/@core/utils/decorators/utils.ts b/packages/api/src/@core/utils/decorators/utils.ts new file mode 100644 index 000000000..3f0fc49e3 --- /dev/null +++ b/packages/api/src/@core/utils/decorators/utils.ts @@ -0,0 +1,98 @@ +import { + CRM_PROVIDERS, + HRIS_PROVIDERS, + ATS_PROVIDERS, + ACCOUNTING_PROVIDERS, + TICKETING_PROVIDERS, + MARKETINGAUTOMATION_PROVIDERS, + FILESTORAGE_PROVIDERS, + ECOMMERCE_PROVIDERS, + EcommerceObject, + CrmObject, + FileStorageObject, + TicketingObject, + HrisObject, + AccountingObject, + MarketingAutomationObject, + AtsObject, +} from '@panora/shared'; +import * as fs from 'fs'; +import * as path from 'path'; + +interface ProviderMetadata { + actions: string[]; + supportedFields: string[][]; +} + +export async function generatePanoraParamsSpec(spec: any) { + const verticals = { + crm: [CRM_PROVIDERS, CrmObject], + hris: [HRIS_PROVIDERS, HrisObject], + ats: [ATS_PROVIDERS, AtsObject], + accounting: [ACCOUNTING_PROVIDERS, AccountingObject], + ticketing: [TICKETING_PROVIDERS, TicketingObject], + marketingautomation: [ + MARKETINGAUTOMATION_PROVIDERS, + MarketingAutomationObject, + ], + filestorage: [FILESTORAGE_PROVIDERS, FileStorageObject], + ecommerce: [ECOMMERCE_PROVIDERS, EcommerceObject], + }; + + for (const [vertical, [providers, COMMON_OBJECTS]] of Object.entries( + verticals, + )) { + for (const objectKey of Object.values(COMMON_OBJECTS)) { + for (const provider of providers as string[]) { + try { + const metadataPath = path.join( + process.cwd(), + 'src', + vertical.toLowerCase(), + objectKey as string, + 'services', + provider, + 'metadata.json', + ); + + const metadataRaw = fs.readFileSync(metadataPath, 'utf8'); + const metadata: ProviderMetadata = JSON.parse(metadataRaw); + + if (metadata) { + metadata.actions.forEach((action, index) => { + const path = `/${vertical.toLowerCase()}/${objectKey}s`; + const op = + action === 'list' ? 'get' : action === 'create' ? 'post' : ''; + + if (spec.paths[path] && spec.paths[path][op]) { + if (!spec.paths[path][op]['x-panora-remote-platforms']) { + spec.paths[path][op]['x-panora-remote-platforms'] = {}; + } + // Ensure the provider array is initialized + if ( + !spec.paths[path][op]['x-panora-remote-platforms'][provider] + ) { + spec.paths[path][op]['x-panora-remote-platforms'][provider] = + []; // Initialize as an array + } + for (const field of metadata.supportedFields[index]) { + spec.paths[path][op]['x-panora-remote-platforms'][ + provider + ].push(field); + } + } else { + console.warn( + `Path or operation not found in spec: ${path} ${op}`, + ); + } + }); + } + } catch (error) { + console.error(error); + } + } + } + } + + return spec; +} diff --git a/packages/api/src/@core/utils/types/original/original.hris.ts b/packages/api/src/@core/utils/types/original/original.hris.ts index ccb190529..e85d40144 100644 --- a/packages/api/src/@core/utils/types/original/original.hris.ts +++ b/packages/api/src/@core/utils/types/original/original.hris.ts @@ -1,5 +1,13 @@ /* INPUT */ +import { GustoBenefitOutput } from '@hris/benefit/services/gusto/types'; +import { GustoCompanyOutput } from '@hris/company/services/gusto/types'; +import { GustoEmployeeOutput } from '@hris/employee/services/gusto/types'; +import { GustoEmployerbenefitOutput } from '@hris/employerbenefit/services/gusto/types'; +import { GustoEmploymentOutput } from '@hris/employment/services/gusto/types'; +import { GustoGroupOutput } from '@hris/group/services/gusto/types'; +import { GustoLocationOutput } from '@hris/location/services/gusto/types'; + /* bankinfo */ export type OriginalBankInfoInput = any; @@ -42,6 +50,9 @@ export type OriginalTimeoffInput = any; /* timeoffbalance */ export type OriginalTimeoffBalanceInput = any; +/* timesheetentry */ +export type OriginalTimesheetentryInput = any; + export type HrisObjectInput = | OriginalBankInfoInput | OriginalBenefitInput @@ -56,7 +67,8 @@ export type HrisObjectInput = | OriginalPayGroupInput | OriginalPayrollRunInput | OriginalTimeoffInput - | OriginalTimeoffBalanceInput; + | OriginalTimeoffBalanceInput + | OriginalTimesheetentryInput; /* OUTPUT */ @@ -64,31 +76,31 @@ export type HrisObjectInput = export type OriginalBankInfoOutput = any; /* benefit */ -export type OriginalBenefitOutput = any; +export type OriginalBenefitOutput = GustoBenefitOutput; /* company */ -export type OriginalCompanyOutput = any; +export type OriginalCompanyOutput = GustoCompanyOutput; /* dependent */ export type OriginalDependentOutput = any; /* employee */ -export type OriginalEmployeeOutput = any; +export type OriginalEmployeeOutput = GustoEmployeeOutput; /* employeepayrollrun */ export type OriginalEmployeePayrollRunOutput = any; /* employerbenefit */ -export type OriginalEmployerBenefitOutput = any; +export type OriginalEmployerBenefitOutput = GustoEmployerbenefitOutput; /* employment */ -export type OriginalEmploymentOutput = any; +export type OriginalEmploymentOutput = GustoEmploymentOutput; /* group */ -export type OriginalGroupOutput = any; +export type OriginalGroupOutput = GustoGroupOutput; /* location */ -export type OriginalLocationOutput = any; +export type OriginalLocationOutput = GustoLocationOutput; /* paygroup */ export type OriginalPayGroupOutput = any; @@ -102,6 +114,9 @@ export type OriginalTimeoffOutput = any; /* timeoffbalance */ export type OriginalTimeoffBalanceOutput = any; +/* timesheetentry */ +export type OriginalTimesheetentryOutput = any; + export type HrisObjectOutput = | OriginalBankInfoOutput | OriginalBenefitOutput @@ -116,4 +131,5 @@ export type HrisObjectOutput = | OriginalPayGroupOutput | OriginalPayrollRunOutput | OriginalTimeoffOutput - | OriginalTimeoffBalanceOutput; + | OriginalTimeoffBalanceOutput + | OriginalTimesheetentryOutput; diff --git a/packages/api/src/accounting/account/services/account.service.ts b/packages/api/src/accounting/account/services/account.service.ts index 526617587..4b1e0545b 100644 --- a/packages/api/src/accounting/account/services/account.service.ts +++ b/packages/api/src/accounting/account/services/account.service.ts @@ -1,12 +1,14 @@ -import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; +import { throwTypedError } from '@@core/utils/errors'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { Injectable } from '@nestjs/common'; import { UnifiedAccountingAccountInput, UnifiedAccountingAccountOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; @@ -28,18 +30,120 @@ export class AccountService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addAccount(unifiedAccountData, linkedUserId); + + const savedAccount = await this.prisma.acc_accounts.create({ + data: { + id_acc_account: uuidv4(), + ...unifiedAccountData, + current_balance: unifiedAccountData.current_balance + ? Number(unifiedAccountData.current_balance) + : null, + remote_id: resp.data.remote_id, + id_connection: resp.data.id_connection, + created_at: new Date(), + modified_at: new Date(), + }, + }); + + const result: UnifiedAccountingAccountOutput = { + ...savedAccount, + currency: savedAccount.currency as CurrencyCode, + id: savedAccount.id_acc_account, + current_balance: savedAccount.current_balance + ? Number(savedAccount.current_balance) + : undefined, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getAccount( - id_accounting_account: string, + id_acc_account: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const account = await this.prisma.acc_accounts.findUnique({ + where: { id_acc_account: id_acc_account }, + }); + + if (!account) { + throw new Error(`Account with ID ${id_acc_account} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: account.id_acc_account }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedAccount: UnifiedAccountingAccountOutput = { + id: account.id_acc_account, + name: account.name, + description: account.description, + classification: account.classification, + type: account.type, + status: account.status, + current_balance: account.current_balance + ? Number(account.current_balance) + : undefined, + currency: account.currency as CurrencyCode, + account_number: account.account_number, + parent_account: account.parent_account, + company_info_id: account.id_acc_company_info, + field_mappings: field_mappings, + remote_id: account.remote_id, + created_at: account.created_at, + modified_at: account.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: account.id_acc_account }, + }); + unifiedAccount.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.account.pull', + method: 'GET', + url: '/accounting/account', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedAccount; + } catch (error) { + throw error; + } } async getAccounts( @@ -50,7 +154,93 @@ export class AccountService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingAccountOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const accounts = await this.prisma.acc_accounts.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_account: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = accounts.length > limit; + if (hasNextPage) accounts.pop(); + + const unifiedAccounts = await Promise.all( + accounts.map(async (account) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: account.id_acc_account }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedAccount: UnifiedAccountingAccountOutput = { + id: account.id_acc_account, + name: account.name, + description: account.description, + classification: account.classification, + type: account.type, + status: account.status, + current_balance: account.current_balance + ? Number(account.current_balance) + : undefined, + currency: account.currency as CurrencyCode, + account_number: account.account_number, + parent_account: account.parent_account, + company_info_id: account.id_acc_company_info, + field_mappings: field_mappings, + remote_id: account.remote_id, + created_at: account.created_at, + modified_at: account.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: account.id_acc_account }, + }); + unifiedAccount.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedAccount; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.account.pull', + method: 'GET', + url: '/accounting/accounts', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedAccounts, + next_cursor: hasNextPage + ? accounts[accounts.length - 1].id_acc_account + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/account/sync/sync.service.ts b/packages/api/src/accounting/account/sync/sync.service.ts index 7e6d8604b..1f6584b6b 100644 --- a/packages/api/src/accounting/account/sync/sync.service.ts +++ b/packages/api/src/accounting/account/sync/sync.service.ts @@ -1,10 +1,21 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { Cron } from '@nestjs/schedule'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; +import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; -import { Injectable, OnModuleInit } from '@nestjs/common'; import { ServiceRegistry } from '../services/registry.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { UnifiedAccountingAccountOutput } from '../types/model.unified'; +import { IAccountService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_accounts as AccAccount } from '@prisma/client'; +import { OriginalAccountOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -14,23 +25,142 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'account', this); + } + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting accounts...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IAccountService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingAccountOutput, + OriginalAccountOutput, + IAccountService + >(integrationId, linkedUserId, 'accounting', 'account', service, []); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + accounts: UnifiedAccountingAccountOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const accountResults: AccAccount[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + const originId = account.remote_id; + + let existingAccount = await this.prisma.acc_accounts.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); - // Additional methods and logic + const accountData = { + name: account.name, + description: account.description, + classification: account.classification, + type: account.type, + status: account.status, + current_balance: account.current_balance + ? Number(account.current_balance) + : null, + currency: account.currency as CurrencyCode, + account_number: account.account_number, + parent_account: account.parent_account, + id_acc_company_info: account.company_info_id, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingAccount) { + existingAccount = await this.prisma.acc_accounts.update({ + where: { id_acc_account: existingAccount.id_acc_account }, + data: accountData, + }); + } else { + existingAccount = await this.prisma.acc_accounts.create({ + data: { + ...accountData, + id_acc_account: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + accountResults.push(existingAccount); + + // Process field mappings + await this.ingestService.processFieldMappings( + account.field_mappings, + existingAccount.id_acc_account, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingAccount.id_acc_account, + remote_data[i], + ); + } + + return accountResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/accounting/account/types/index.ts b/packages/api/src/accounting/account/types/index.ts index 6164e8f8d..d04929121 100644 --- a/packages/api/src/accounting/account/types/index.ts +++ b/packages/api/src/accounting/account/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedAccountingAccountInput, UnifiedAccountingAccountOutput } from './model.unified'; +import { + UnifiedAccountingAccountInput, + UnifiedAccountingAccountOutput, +} from './model.unified'; import { OriginalAccountOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IAccountService { addAccount( @@ -9,10 +13,7 @@ export interface IAccountService { linkedUserId: string, ): Promise>; - syncAccounts( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IAccountMapper { diff --git a/packages/api/src/accounting/account/types/model.unified.ts b/packages/api/src/accounting/account/types/model.unified.ts index d27a94beb..25a221d9d 100644 --- a/packages/api/src/accounting/account/types/model.unified.ts +++ b/packages/api/src/accounting/account/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -71,12 +72,13 @@ export class UnifiedAccountingAccountInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the account', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -158,22 +160,22 @@ export class UnifiedAccountingAccountOutput extends UnifiedAccountingAccountInpu remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the account record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the account record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/address/services/address.service.ts b/packages/api/src/accounting/address/services/address.service.ts index ec453395a..e4b2a15ef 100644 --- a/packages/api/src/accounting/address/services/address.service.ts +++ b/packages/api/src/accounting/address/services/address.service.ts @@ -9,12 +9,9 @@ import { UnifiedAccountingAddressInput, UnifiedAccountingAddressOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { IAddressService } from '../types'; - @Injectable() export class AddressService { constructor( @@ -28,14 +25,79 @@ export class AddressService { } async getAddress( - id_addressing_address: string, + id_acc_address: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const address = await this.prisma.acc_addresses.findUnique({ + where: { id_acc_address: id_acc_address }, + }); + + if (!address) { + throw new Error(`Address with ID ${id_acc_address} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: address.id_acc_address }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedAddress: UnifiedAccountingAddressOutput = { + id: address.id_acc_address, + type: address.type, + street_1: address.street_1, + street_2: address.street_2, + city: address.city, + state: address.state, + country_subdivision: address.country_subdivision, + country: address.country, + zip: address.zip, + contact_id: address.id_acc_contact, + company_info_id: address.id_acc_company_info, + field_mappings: field_mappings, + created_at: address.created_at, + modified_at: address.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: address.id_acc_address }, + }); + unifiedAddress.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.address.pull', + method: 'GET', + url: '/accounting/address', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedAddress; + } catch (error) { + throw error; + } } async getAddresss( @@ -46,7 +108,90 @@ export class AddressService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingAddressOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const addresses = await this.prisma.acc_addresses.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_address: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = addresses.length > limit; + if (hasNextPage) addresses.pop(); + + const unifiedAddresses = await Promise.all( + addresses.map(async (address) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: address.id_acc_address }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedAddress: UnifiedAccountingAddressOutput = { + id: address.id_acc_address, + type: address.type, + street_1: address.street_1, + street_2: address.street_2, + city: address.city, + state: address.state, + country_subdivision: address.country_subdivision, + country: address.country, + zip: address.zip, + contact_id: address.id_acc_contact, + company_info_id: address.id_acc_company_info, + field_mappings: field_mappings, + created_at: address.created_at, + modified_at: address.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: address.id_acc_address }, + }); + unifiedAddress.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedAddress; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.address.pull', + method: 'GET', + url: '/accounting/addresses', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedAddresses, + next_cursor: hasNextPage + ? addresses[addresses.length - 1].id_acc_address + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/address/sync/sync.service.ts b/packages/api/src/accounting/address/sync/sync.service.ts index 9821525dc..85a590437 100644 --- a/packages/api/src/accounting/address/sync/sync.service.ts +++ b/packages/api/src/accounting/address/sync/sync.service.ts @@ -10,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingAddressOutput } from '../types/model.unified'; import { IAddressService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_addresses as AccAddress } from '@prisma/client'; +import { OriginalAddressOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,26 +25,139 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'address', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting addresses...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IAddressService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingAddressOutput, + OriginalAddressOutput, + IAddressService + >(integrationId, linkedUserId, 'accounting', 'address', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + addresses: UnifiedAccountingAddressOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } - removeInDb?(connection_id: string, remote_id: string): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const addressResults: AccAddress[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < addresses.length; i++) { + const address = addresses[i]; + const originId = address.remote_id; + + let existingAddress = await this.prisma.acc_addresses.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const addressData = { + type: address.type, + street_1: address.street_1, + street_2: address.street_2, + city: address.city, + state: address.state, + country_subdivision: address.country_subdivision, + country: address.country, + zip: address.zip, + id_acc_contact: address.contact_id, + id_acc_company_info: address.company_info_id, + modified_at: new Date(), + }; - // Additional methods and logic + if (existingAddress) { + existingAddress = await this.prisma.acc_addresses.update({ + where: { id_acc_address: existingAddress.id_acc_address }, + data: addressData, + }); + } else { + existingAddress = await this.prisma.acc_addresses.create({ + data: { + ...addressData, + id_acc_address: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + addressResults.push(existingAddress); + + // Process field mappings + await this.ingestService.processFieldMappings( + address.field_mappings, + existingAddress.id_acc_address, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingAddress.id_acc_address, + remote_data[i], + ); + } + + return addressResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/accounting/address/types/index.ts b/packages/api/src/accounting/address/types/index.ts index 0d7caf1b1..b50aa6297 100644 --- a/packages/api/src/accounting/address/types/index.ts +++ b/packages/api/src/accounting/address/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedAccountingAddressInput, UnifiedAccountingAddressOutput } from './model.unified'; +import { + UnifiedAccountingAddressInput, + UnifiedAccountingAddressOutput, +} from './model.unified'; import { OriginalAddressOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IAddressService { addAddress( @@ -9,10 +13,7 @@ export interface IAddressService { linkedUserId: string, ): Promise>; - syncAddresss( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IAddressMapper { diff --git a/packages/api/src/accounting/address/types/model.unified.ts b/packages/api/src/accounting/address/types/model.unified.ts index 311c7925b..72b534872 100644 --- a/packages/api/src/accounting/address/types/model.unified.ts +++ b/packages/api/src/accounting/address/types/model.unified.ts @@ -153,22 +153,22 @@ export class UnifiedAccountingAddressOutput extends UnifiedAccountingAddressInpu remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the address record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the address record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/attachment/services/attachment.service.ts b/packages/api/src/accounting/attachment/services/attachment.service.ts index f38b1538e..a99d30c6a 100644 --- a/packages/api/src/accounting/attachment/services/attachment.service.ts +++ b/packages/api/src/accounting/attachment/services/attachment.service.ts @@ -9,12 +9,9 @@ import { UnifiedAccountingAttachmentInput, UnifiedAccountingAttachmentOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { IAttachmentService } from '../types'; - @Injectable() export class AttachmentService { constructor( @@ -35,18 +32,107 @@ export class AttachmentService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addAttachment( + unifiedAttachmentData, + linkedUserId, + ); + + const savedAttachment = await this.prisma.acc_attachments.create({ + data: { + id_acc_attachment: uuidv4(), + ...unifiedAttachmentData, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + }, + }); + + const result: UnifiedAccountingAttachmentOutput = { + ...savedAttachment, + id: savedAttachment.id_acc_attachment, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getAttachment( - id_attachmenting_attachment: string, + id_acc_attachment: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const attachment = await this.prisma.acc_attachments.findUnique({ + where: { id_acc_attachment: id_acc_attachment }, + }); + + if (!attachment) { + throw new Error(`Attachment with ID ${id_acc_attachment} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: attachment.id_acc_attachment }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedAttachment: UnifiedAccountingAttachmentOutput = { + id: attachment.id_acc_attachment, + file_name: attachment.file_name, + file_url: attachment.file_url, + account_id: attachment.id_acc_account, + field_mappings: field_mappings, + remote_id: attachment.remote_id, + created_at: attachment.created_at, + modified_at: attachment.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: attachment.id_acc_attachment }, + }); + unifiedAttachment.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.attachment.pull', + method: 'GET', + url: '/accounting/attachment', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedAttachment; + } catch (error) { + throw error; + } } async getAttachments( @@ -57,7 +143,84 @@ export class AttachmentService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingAttachmentOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const attachments = await this.prisma.acc_attachments.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_attachment: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = attachments.length > limit; + if (hasNextPage) attachments.pop(); + + const unifiedAttachments = await Promise.all( + attachments.map(async (attachment) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: attachment.id_acc_attachment }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedAttachment: UnifiedAccountingAttachmentOutput = { + id: attachment.id_acc_attachment, + file_name: attachment.file_name, + file_url: attachment.file_url, + account_id: attachment.id_acc_account, + field_mappings: field_mappings, + remote_id: attachment.remote_id, + created_at: attachment.created_at, + modified_at: attachment.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: attachment.id_acc_attachment }, + }); + unifiedAttachment.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedAttachment; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.attachment.pull', + method: 'GET', + url: '/accounting/attachments', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedAttachments, + next_cursor: hasNextPage + ? attachments[attachments.length - 1].id_acc_attachment + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/attachment/sync/sync.service.ts b/packages/api/src/accounting/attachment/sync/sync.service.ts index 950182fde..a19b71b99 100644 --- a/packages/api/src/accounting/attachment/sync/sync.service.ts +++ b/packages/api/src/accounting/attachment/sync/sync.service.ts @@ -10,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingAttachmentOutput } from '../types/model.unified'; import { IAttachmentService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_attachments as AccAttachment } from '@prisma/client'; +import { OriginalAttachmentOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,26 +25,146 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'attachment', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting attachments...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IAttachmentService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingAttachmentOutput, + OriginalAttachmentOutput, + IAttachmentService + >(integrationId, linkedUserId, 'accounting', 'attachment', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + attachments: UnifiedAccountingAttachmentOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } - removeInDb?(connection_id: string, remote_id: string): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const attachmentResults: AccAttachment[] = []; - async onModuleInit() { - // Initialization logic + for (let i = 0; i < attachments.length; i++) { + const attachment = attachments[i]; + const originId = attachment.remote_id; + + let existingAttachment = await this.prisma.acc_attachments.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const attachmentData = { + file_name: attachment.file_name, + file_url: attachment.file_url, + id_acc_account: attachment.account_id, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingAttachment) { + existingAttachment = await this.prisma.acc_attachments.update({ + where: { id_acc_attachment: existingAttachment.id_acc_attachment }, + data: attachmentData, + }); + } else { + existingAttachment = await this.prisma.acc_attachments.create({ + data: { + ...attachmentData, + id_acc_attachment: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + attachmentResults.push(existingAttachment); + + // Process field mappings + await this.ingestService.processFieldMappings( + attachment.field_mappings, + existingAttachment.id_acc_attachment, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingAttachment.id_acc_attachment, + remote_data[i], + ); + } + + return attachmentResults; + } catch (error) { + throw error; + } } - // Additional methods and logic + async removeInDb(connection_id: string, remote_id: string): Promise { + try { + await this.prisma.acc_attachments.deleteMany({ + where: { + remote_id: remote_id, + id_connection: connection_id, + }, + }); + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/accounting/attachment/types/index.ts b/packages/api/src/accounting/attachment/types/index.ts index cd1e3c776..19864b2b9 100644 --- a/packages/api/src/accounting/attachment/types/index.ts +++ b/packages/api/src/accounting/attachment/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalAttachmentOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IAttachmentService { addAttachment( @@ -12,10 +13,7 @@ export interface IAttachmentService { linkedUserId: string, ): Promise>; - syncAttachments( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IAttachmentMapper { @@ -34,5 +32,7 @@ export interface IAttachmentMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedAccountingAttachmentOutput | UnifiedAccountingAttachmentOutput[] + >; } diff --git a/packages/api/src/accounting/attachment/types/model.unified.ts b/packages/api/src/accounting/attachment/types/model.unified.ts index ce96dcc26..fb1e1945c 100644 --- a/packages/api/src/accounting/attachment/types/model.unified.ts +++ b/packages/api/src/accounting/attachment/types/model.unified.ts @@ -89,22 +89,22 @@ export class UnifiedAccountingAttachmentOutput extends UnifiedAccountingAttachme remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the attachment record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the attachment record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/balancesheet/services/balancesheet.service.ts b/packages/api/src/accounting/balancesheet/services/balancesheet.service.ts index 29787130b..207b0f5f3 100644 --- a/packages/api/src/accounting/balancesheet/services/balancesheet.service.ts +++ b/packages/api/src/accounting/balancesheet/services/balancesheet.service.ts @@ -1,20 +1,12 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedAccountingBalancesheetInput, - UnifiedAccountingBalancesheetOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedAccountingBalancesheetOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalBalanceSheetOutput } from '@@core/utils/types/original/original.accounting'; - -import { IBalanceSheetService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class BalanceSheetService { @@ -29,14 +21,97 @@ export class BalanceSheetService { } async getBalanceSheet( - id_balancesheeting_balancesheet: string, + id_acc_balance_sheet: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const balanceSheet = await this.prisma.acc_balance_sheets.findUnique({ + where: { id_acc_balance_sheet: id_acc_balance_sheet }, + }); + + if (!balanceSheet) { + throw new Error( + `Balance sheet with ID ${id_acc_balance_sheet} not found.`, + ); + } + + const lineItems = + await this.prisma.acc_balance_sheets_report_items.findMany({ + where: { id_acc_company_info: balanceSheet.id_acc_company_info }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: balanceSheet.id_acc_balance_sheet }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedBalanceSheet: UnifiedAccountingBalancesheetOutput = { + id: balanceSheet.id_acc_balance_sheet, + name: balanceSheet.name, + currency: balanceSheet.currency as CurrencyCode, + company_info_id: balanceSheet.id_acc_company_info, + date: balanceSheet.date, + net_assets: balanceSheet.net_assets + ? Number(balanceSheet.net_assets) + : undefined, + assets: balanceSheet.assets, + liabilities: balanceSheet.liabilities, + equity: balanceSheet.equity, + remote_generated_at: balanceSheet.remote_generated_at, + field_mappings: field_mappings, + remote_id: balanceSheet.remote_id, + created_at: balanceSheet.created_at, + modified_at: balanceSheet.modified_at, + line_items: lineItems.map((item) => ({ + name: item.name, + value: item.value ? Number(item.value) : undefined, + parent_item: item.parent_item, + company_info_id: item.id_acc_company_info, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: balanceSheet.id_acc_balance_sheet }, + }); + unifiedBalanceSheet.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.balance_sheet.pull', + method: 'GET', + url: '/accounting/balance_sheet', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedBalanceSheet; + } catch (error) { + throw error; + } } async getBalanceSheets( @@ -47,7 +122,106 @@ export class BalanceSheetService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingBalancesheetOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const balanceSheets = await this.prisma.acc_balance_sheets.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_balance_sheet: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = balanceSheets.length > limit; + if (hasNextPage) balanceSheets.pop(); + + const unifiedBalanceSheets = await Promise.all( + balanceSheets.map(async (balanceSheet) => { + const lineItems = + await this.prisma.acc_balance_sheets_report_items.findMany({ + where: { id_acc_company_info: balanceSheet.id_acc_company_info }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: balanceSheet.id_acc_balance_sheet }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedBalanceSheet: UnifiedAccountingBalancesheetOutput = { + id: balanceSheet.id_acc_balance_sheet, + name: balanceSheet.name, + currency: balanceSheet.currency as CurrencyCode, + company_info_id: balanceSheet.id_acc_company_info, + date: balanceSheet.date, + net_assets: balanceSheet.net_assets + ? Number(balanceSheet.net_assets) + : undefined, + assets: balanceSheet.assets, + liabilities: balanceSheet.liabilities, + equity: balanceSheet.equity, + remote_generated_at: balanceSheet.remote_generated_at, + field_mappings: field_mappings, + remote_id: balanceSheet.remote_id, + created_at: balanceSheet.created_at, + modified_at: balanceSheet.modified_at, + line_items: lineItems.map((item) => ({ + name: item.name, + value: item.value ? Number(item.value) : undefined, + parent_item: item.parent_item, + company_info_id: item.id_acc_company_info, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: balanceSheet.id_acc_balance_sheet }, + }); + unifiedBalanceSheet.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedBalanceSheet; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.balance_sheet.pull', + method: 'GET', + url: '/accounting/balance_sheets', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedBalanceSheets, + next_cursor: hasNextPage + ? balanceSheets[balanceSheets.length - 1].id_acc_balance_sheet + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/balancesheet/sync/sync.service.ts b/packages/api/src/accounting/balancesheet/sync/sync.service.ts index 5744d9ba7..8ed1ba82d 100644 --- a/packages/api/src/accounting/balancesheet/sync/sync.service.ts +++ b/packages/api/src/accounting/balancesheet/sync/sync.service.ts @@ -1,15 +1,22 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalBalanceSheetOutput } from '@@core/utils/types/original/original.accounting'; +import { LineItem } from '@accounting/cashflowstatement/types/model.unified'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_balance_sheets as AccBalanceSheet } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingBalancesheetOutput } from '../types/model.unified'; import { IBalanceSheetService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { UnifiedAccountingBalancesheetOutput } from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,22 +26,203 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'balancesheet', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting balance sheets...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IBalanceSheetService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingBalancesheetOutput, + OriginalBalanceSheetOutput, + IBalanceSheetService + >(integrationId, linkedUserId, 'accounting', 'balancesheet', service, []); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + balanceSheets: UnifiedAccountingBalancesheetOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const balanceSheetResults: AccBalanceSheet[] = []; + + for (let i = 0; i < balanceSheets.length; i++) { + const balanceSheet = balanceSheets[i]; + const originId = balanceSheet.remote_id; + + let existingBalanceSheet = + await this.prisma.acc_balance_sheets.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const balanceSheetData = { + name: balanceSheet.name, + currency: balanceSheet.currency as CurrencyCode, + id_acc_company_info: balanceSheet.company_info_id, + date: balanceSheet.date, + net_assets: balanceSheet.net_assets + ? Number(balanceSheet.net_assets) + : null, + assets: balanceSheet.assets, + liabilities: balanceSheet.liabilities, + equity: balanceSheet.equity, + remote_generated_at: balanceSheet.remote_generated_at, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingBalanceSheet) { + existingBalanceSheet = await this.prisma.acc_balance_sheets.update({ + where: { + id_acc_balance_sheet: existingBalanceSheet.id_acc_balance_sheet, + }, + data: balanceSheetData, + }); + } else { + existingBalanceSheet = await this.prisma.acc_balance_sheets.create({ + data: { + ...balanceSheetData, + id_acc_balance_sheet: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + balanceSheetResults.push(existingBalanceSheet); + + // Process field mappings + await this.ingestService.processFieldMappings( + balanceSheet.field_mappings, + existingBalanceSheet.id_acc_balance_sheet, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingBalanceSheet.id_acc_balance_sheet, + remote_data[i], + ); + + // Handle report items + if (balanceSheet.line_items && balanceSheet.line_items.length > 0) { + await this.processBalanceSheetReportItems( + balanceSheet.line_items, + existingBalanceSheet.id_acc_balance_sheet, + ); + } + } + + return balanceSheetResults; + } catch (error) { + throw error; + } + } + + private async processBalanceSheetReportItems( + lineItems: LineItem[], + balanceSheetId: string, + ): Promise { + for (const lineItem of lineItems) { + const lineItemData = { + name: lineItem.name, + value: lineItem.value ? Number(lineItem.value) : null, + parent_item: lineItem.parent_item, + id_acc_company_info: lineItem.company_info_id, + remote_id: lineItem.remote_id, + modified_at: new Date(), + }; + + const existingReportItem = + await this.prisma.acc_balance_sheets_report_items.findFirst({ + where: { + remote_id: lineItem.remote_id, + }, + }); + + if (existingReportItem) { + await this.prisma.acc_balance_sheets_report_items.update({ + where: { + id_acc_balance_sheets_report_item: + existingReportItem.id_acc_balance_sheets_report_item, + }, + data: lineItemData, + }); + } else { + await this.prisma.acc_balance_sheets_report_items.create({ + data: { + ...lineItemData, + id_acc_balance_sheets_report_item: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing report items that are not in the current set + const currentRemoteIds = lineItems.map((item) => item.remote_id); + await this.prisma.acc_balance_sheets_report_items.deleteMany({ + where: { + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); } - // Additional methods and logic } diff --git a/packages/api/src/accounting/balancesheet/types/index.ts b/packages/api/src/accounting/balancesheet/types/index.ts index fb92474f6..d873ad836 100644 --- a/packages/api/src/accounting/balancesheet/types/index.ts +++ b/packages/api/src/accounting/balancesheet/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalBalanceSheetOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IBalanceSheetService { addBalanceSheet( @@ -12,10 +13,7 @@ export interface IBalanceSheetService { linkedUserId: string, ): Promise>; - syncBalanceSheets( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IBalanceSheetMapper { @@ -34,5 +32,7 @@ export interface IBalanceSheetMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedAccountingBalancesheetOutput | UnifiedAccountingBalancesheetOutput[] + >; } diff --git a/packages/api/src/accounting/balancesheet/types/model.unified.ts b/packages/api/src/accounting/balancesheet/types/model.unified.ts index dd96cae05..ba4cf4439 100644 --- a/packages/api/src/accounting/balancesheet/types/model.unified.ts +++ b/packages/api/src/accounting/balancesheet/types/model.unified.ts @@ -1,3 +1,5 @@ +import { CurrencyCode } from '@@core/utils/types'; +import { LineItem } from '@accounting/cashflowstatement/types/model.unified'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -23,12 +25,13 @@ export class UnifiedAccountingBalancesheetInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency used in the balance sheet', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -41,14 +44,14 @@ export class UnifiedAccountingBalancesheetInput { company_info_id?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-30T23:59:59Z', nullable: true, description: 'The date of the balance sheet', }) @IsDateString() @IsOptional() - date?: string; + date?: Date; @ApiPropertyOptional({ type: Number, @@ -94,7 +97,7 @@ export class UnifiedAccountingBalancesheetInput { equity?: string[]; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-07-01T12:00:00Z', nullable: true, description: @@ -102,7 +105,15 @@ export class UnifiedAccountingBalancesheetInput { }) @IsDateString() @IsOptional() - remote_generated_at?: string; + remote_generated_at?: Date; + + @ApiPropertyOptional({ + type: [LineItem], + description: 'The report items associated with this balance sheet', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; @ApiPropertyOptional({ type: Object, @@ -155,22 +166,22 @@ export class UnifiedAccountingBalancesheetOutput extends UnifiedAccountingBalanc remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the balance sheet record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the balance sheet record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/cashflowstatement/services/cashflowstatement.service.ts b/packages/api/src/accounting/cashflowstatement/services/cashflowstatement.service.ts index 9a29e004a..bc7892f5c 100644 --- a/packages/api/src/accounting/cashflowstatement/services/cashflowstatement.service.ts +++ b/packages/api/src/accounting/cashflowstatement/services/cashflowstatement.service.ts @@ -1,20 +1,12 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedAccountingCashflowstatementInput, - UnifiedAccountingCashflowstatementOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedAccountingCashflowstatementOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalCashflowStatementOutput } from '@@core/utils/types/original/original.accounting'; - -import { ICashflowStatementService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class CashflowStatementService { @@ -29,14 +21,107 @@ export class CashflowStatementService { } async getCashflowStatement( - id_cashflowstatementing_cashflowstatement: string, + id_acc_cash_flow_statement: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const cashFlowStatement = + await this.prisma.acc_cash_flow_statements.findUnique({ + where: { id_acc_cash_flow_statement: id_acc_cash_flow_statement }, + }); + + if (!cashFlowStatement) { + throw new Error( + `Cash flow statement with ID ${id_acc_cash_flow_statement} not found.`, + ); + } + + const lineItems = + await this.prisma.acc_cash_flow_statement_report_items.findMany({ + where: { id_acc_cash_flow_statement: id_acc_cash_flow_statement }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: cashFlowStatement.id_acc_cash_flow_statement, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedCashFlowStatement: UnifiedAccountingCashflowstatementOutput = + { + id: cashFlowStatement.id_acc_cash_flow_statement, + name: cashFlowStatement.name, + currency: cashFlowStatement.currency as CurrencyCode, + company_id: cashFlowStatement.company, + start_period: cashFlowStatement.start_period, + end_period: cashFlowStatement.end_period, + cash_at_beginning_of_period: + cashFlowStatement.cash_at_beginning_of_period + ? Number(cashFlowStatement.cash_at_beginning_of_period) + : undefined, + cash_at_end_of_period: cashFlowStatement.cash_at_end_of_period + ? Number(cashFlowStatement.cash_at_end_of_period) + : undefined, + remote_generated_at: cashFlowStatement.remote_generated_at, + field_mappings: field_mappings, + remote_id: cashFlowStatement.remote_id, + created_at: cashFlowStatement.created_at, + modified_at: cashFlowStatement.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_cash_flow_statement_report_item, + name: item.name, + value: item.value ? Number(item.value) : undefined, + type: item.type, + parent_item: item.parent_item, + remote_id: item.remote_id, + remote_generated_at: item.remote_generated_at, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: cashFlowStatement.id_acc_cash_flow_statement, + }, + }); + unifiedCashFlowStatement.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.cashflow_statement.pull', + method: 'GET', + url: '/accounting/cashflow_statement', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedCashFlowStatement; + } catch (error) { + throw error; + } } async getCashflowStatements( @@ -47,7 +132,122 @@ export class CashflowStatementService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingCashflowstatementOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const cashFlowStatements = + await this.prisma.acc_cash_flow_statements.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_cash_flow_statement: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = cashFlowStatements.length > limit; + if (hasNextPage) cashFlowStatements.pop(); + + const unifiedCashFlowStatements = await Promise.all( + cashFlowStatements.map(async (cashFlowStatement) => { + const lineItems = + await this.prisma.acc_cash_flow_statement_report_items.findMany({ + where: { + id_acc_cash_flow_statement: + cashFlowStatement.id_acc_cash_flow_statement, + }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: + cashFlowStatement.id_acc_cash_flow_statement, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedCashFlowStatement: UnifiedAccountingCashflowstatementOutput = + { + id: cashFlowStatement.id_acc_cash_flow_statement, + name: cashFlowStatement.name, + currency: cashFlowStatement.currency as CurrencyCode, + company_id: cashFlowStatement.company, + start_period: cashFlowStatement.start_period, + end_period: cashFlowStatement.end_period, + cash_at_beginning_of_period: + cashFlowStatement.cash_at_beginning_of_period + ? Number(cashFlowStatement.cash_at_beginning_of_period) + : undefined, + cash_at_end_of_period: cashFlowStatement.cash_at_end_of_period + ? Number(cashFlowStatement.cash_at_end_of_period) + : undefined, + remote_generated_at: cashFlowStatement.remote_generated_at, + field_mappings: field_mappings, + remote_id: cashFlowStatement.remote_id, + created_at: cashFlowStatement.created_at, + modified_at: cashFlowStatement.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_cash_flow_statement_report_item, + name: item.name, + value: item.value ? Number(item.value) : undefined, + type: item.type, + parent_item: item.parent_item, + remote_id: item.remote_id, + remote_generated_at: item.remote_generated_at, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: + cashFlowStatement.id_acc_cash_flow_statement, + }, + }); + unifiedCashFlowStatement.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedCashFlowStatement; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.cashflow_statement.pull', + method: 'GET', + url: '/accounting/cashflow_statements', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedCashFlowStatements, + next_cursor: hasNextPage + ? cashFlowStatements[cashFlowStatements.length - 1] + .id_acc_cash_flow_statement + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/cashflowstatement/sync/sync.service.ts b/packages/api/src/accounting/cashflowstatement/sync/sync.service.ts index a9841e4c9..a33b883de 100644 --- a/packages/api/src/accounting/cashflowstatement/sync/sync.service.ts +++ b/packages/api/src/accounting/cashflowstatement/sync/sync.service.ts @@ -1,15 +1,24 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalCashflowStatementOutput } from '@@core/utils/types/original/original.accounting'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_cash_flow_statements as AccCashFlowStatement } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingCashflowstatementOutput } from '../types/model.unified'; import { ICashflowStatementService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { + LineItem, + UnifiedAccountingCashflowstatementOutput, +} from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,24 +28,222 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'cashflow_statement', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting cash flow statements...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ICashflowStatementService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingCashflowstatementOutput, + OriginalCashflowStatementOutput, + ICashflowStatementService + >( + integrationId, + linkedUserId, + 'accounting', + 'cashflow_statement', + service, + [], + ); + } catch (error) { + throw error; + } } - saveToDb( + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + cashFlowStatements: UnifiedAccountingCashflowstatementOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const cashFlowStatementResults: AccCashFlowStatement[] = []; + + for (let i = 0; i < cashFlowStatements.length; i++) { + const cashFlowStatement = cashFlowStatements[i]; + const originId = cashFlowStatement.remote_id; + + let existingCashFlowStatement = + await this.prisma.acc_cash_flow_statements.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const cashFlowStatementData = { + name: cashFlowStatement.name, + currency: cashFlowStatement.currency as CurrencyCode, + company: cashFlowStatement.company_id, + start_period: cashFlowStatement.start_period, + end_period: cashFlowStatement.end_period, + cash_at_beginning_of_period: + cashFlowStatement.cash_at_beginning_of_period + ? Number(cashFlowStatement.cash_at_beginning_of_period) + : null, + cash_at_end_of_period: cashFlowStatement.cash_at_end_of_period + ? Number(cashFlowStatement.cash_at_end_of_period) + : null, + remote_generated_at: cashFlowStatement.remote_generated_at, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingCashFlowStatement) { + existingCashFlowStatement = + await this.prisma.acc_cash_flow_statements.update({ + where: { + id_acc_cash_flow_statement: + existingCashFlowStatement.id_acc_cash_flow_statement, + }, + data: cashFlowStatementData, + }); + } else { + existingCashFlowStatement = + await this.prisma.acc_cash_flow_statements.create({ + data: { + ...cashFlowStatementData, + id_acc_cash_flow_statement: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + cashFlowStatementResults.push(existingCashFlowStatement); + + // Process field mappings + await this.ingestService.processFieldMappings( + cashFlowStatement.field_mappings, + existingCashFlowStatement.id_acc_cash_flow_statement, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingCashFlowStatement.id_acc_cash_flow_statement, + remote_data[i], + ); + + // Handle report items + if ( + cashFlowStatement.line_items && + cashFlowStatement.line_items.length > 0 + ) { + await this.processCashFlowStatementReportItems( + existingCashFlowStatement.id_acc_cash_flow_statement, + cashFlowStatement.line_items, + ); + } + } + + return cashFlowStatementResults; + } catch (error) { + throw error; + } } - // Additional methods and logic + private async processCashFlowStatementReportItems( + cashFlowStatementId: string, + reportItems: LineItem[], + ): Promise { + for (const reportItem of reportItems) { + const reportItemData = { + name: reportItem.name, + value: reportItem.value ? Number(reportItem.value) : null, + type: reportItem.type, + parent_item: reportItem.parent_item, + remote_generated_at: reportItem.remote_generated_at, + remote_id: reportItem.remote_id, + modified_at: new Date(), + id_acc_cash_flow_statement: cashFlowStatementId, + }; + + const existingReportItem = + await this.prisma.acc_cash_flow_statement_report_items.findFirst({ + where: { + remote_id: reportItem.remote_id, + id_acc_cash_flow_statement: cashFlowStatementId, + }, + }); + + if (existingReportItem) { + await this.prisma.acc_cash_flow_statement_report_items.update({ + where: { + id_acc_cash_flow_statement_report_item: + existingReportItem.id_acc_cash_flow_statement_report_item, + }, + data: reportItemData, + }); + } else { + await this.prisma.acc_cash_flow_statement_report_items.create({ + data: { + ...reportItemData, + id_acc_cash_flow_statement_report_item: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing report items that are not in the current set + const currentRemoteIds = reportItems.map((item) => item.remote_id); + await this.prisma.acc_cash_flow_statement_report_items.deleteMany({ + where: { + id_acc_cash_flow_statement: cashFlowStatementId, + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); + } } diff --git a/packages/api/src/accounting/cashflowstatement/types/index.ts b/packages/api/src/accounting/cashflowstatement/types/index.ts index 4ca7daa48..93970e832 100644 --- a/packages/api/src/accounting/cashflowstatement/types/index.ts +++ b/packages/api/src/accounting/cashflowstatement/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalCashflowStatementOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ICashflowStatementService { addCashflowStatement( @@ -12,9 +13,8 @@ export interface ICashflowStatementService { linkedUserId: string, ): Promise>; - syncCashflowStatements( - linkedUserId: string, - custom_properties?: string[], + sync( + data: SyncParam, ): Promise>; } @@ -34,5 +34,8 @@ export interface ICashflowStatementMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + | UnifiedAccountingCashflowstatementOutput + | UnifiedAccountingCashflowstatementOutput[] + >; } diff --git a/packages/api/src/accounting/cashflowstatement/types/model.unified.ts b/packages/api/src/accounting/cashflowstatement/types/model.unified.ts index d18565123..9bfedd309 100644 --- a/packages/api/src/accounting/cashflowstatement/types/model.unified.ts +++ b/packages/api/src/accounting/cashflowstatement/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -5,9 +6,100 @@ import { IsString, IsNumber, IsDateString, + IsArray, } from 'class-validator'; -// todo cashflow statement report items +export class LineItem { + @ApiPropertyOptional({ + type: String, + example: 'Net Income', + nullable: true, + description: 'The name of the report item', + }) + @IsString() + @IsOptional() + name?: string; + + @ApiPropertyOptional({ + type: Number, + example: 100000, + nullable: true, + description: 'The value of the report item', + }) + @IsNumber() + @IsOptional() + value?: number; + + @ApiPropertyOptional({ + type: String, + example: 'Operating Activities', + nullable: true, + description: 'The type of the report item', + }) + @IsString() + @IsOptional() + type?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the parent item', + }) + @IsUUID() + @IsOptional() + parent_item?: string; + + @ApiPropertyOptional({ + type: String, + example: 'report_item_1234', + nullable: true, + description: 'The remote ID of the report item', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-07-01T12:00:00Z', + nullable: true, + description: + 'The date when the report item was generated in the remote system', + }) + @IsDateString() + @IsOptional() + remote_generated_at?: Date; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info object', + }) + @IsUUID() + @IsOptional() + company_info_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The created date of the report item', + }) + @IsDateString() + @IsOptional() + created_at?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The last modified date of the report item', + }) + @IsDateString() + @IsOptional() + modified_at?: Date; +} + export class UnifiedAccountingCashflowstatementInput { @ApiPropertyOptional({ type: String, @@ -22,12 +114,13 @@ export class UnifiedAccountingCashflowstatementInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency used in the cash flow statement', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -40,7 +133,7 @@ export class UnifiedAccountingCashflowstatementInput { company_id?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-04-01T00:00:00Z', nullable: true, description: @@ -48,10 +141,10 @@ export class UnifiedAccountingCashflowstatementInput { }) @IsDateString() @IsOptional() - start_period?: string; + start_period?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-30T23:59:59Z', nullable: true, description: @@ -59,7 +152,7 @@ export class UnifiedAccountingCashflowstatementInput { }) @IsDateString() @IsOptional() - end_period?: string; + end_period?: Date; @ApiPropertyOptional({ type: Number, @@ -82,7 +175,7 @@ export class UnifiedAccountingCashflowstatementInput { cash_at_end_of_period?: number; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-07-01T12:00:00Z', nullable: true, description: @@ -90,7 +183,15 @@ export class UnifiedAccountingCashflowstatementInput { }) @IsDateString() @IsOptional() - remote_generated_at?: string; + remote_generated_at?: Date; + + @ApiPropertyOptional({ + type: [LineItem], + description: 'The report items associated with this cash flow statement', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; @ApiPropertyOptional({ type: Object, @@ -143,22 +244,22 @@ export class UnifiedAccountingCashflowstatementOutput extends UnifiedAccountingC remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the cash flow statement record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the cash flow statement record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/companyinfo/services/companyinfo.service.ts b/packages/api/src/accounting/companyinfo/services/companyinfo.service.ts index e818a6f27..773f281d4 100644 --- a/packages/api/src/accounting/companyinfo/services/companyinfo.service.ts +++ b/packages/api/src/accounting/companyinfo/services/companyinfo.service.ts @@ -2,19 +2,15 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; import { throwTypedError } from '@@core/utils/errors'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { UnifiedAccountingCompanyinfoInput, UnifiedAccountingCompanyinfoOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalCompanyInfoOutput } from '@@core/utils/types/original/original.accounting'; - -import { ICompanyInfoService } from '../types'; @Injectable() export class CompanyInfoService { @@ -29,14 +25,81 @@ export class CompanyInfoService { } async getCompanyInfo( - id_companyinfoing_companyinfo: string, + id_acc_company_info: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const companyInfo = await this.prisma.acc_company_infos.findUnique({ + where: { id_acc_company_info: id_acc_company_info }, + }); + + if (!companyInfo) { + throw new Error( + `Company info with ID ${id_acc_company_info} not found.`, + ); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: companyInfo.id_acc_company_info }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedCompanyInfo: UnifiedAccountingCompanyinfoOutput = { + id: companyInfo.id_acc_company_info, + name: companyInfo.name, + legal_name: companyInfo.legal_name, + tax_number: companyInfo.tax_number, + fiscal_year_end_month: companyInfo.fiscal_year_end_month, + fiscal_year_end_day: companyInfo.fiscal_year_end_day, + currency: companyInfo.currency as CurrencyCode, + urls: companyInfo.urls, + tracking_categories: companyInfo.tracking_categories, + field_mappings: field_mappings, + remote_id: companyInfo.remote_id, + remote_created_at: companyInfo.remote_created_at, + created_at: companyInfo.created_at, + modified_at: companyInfo.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: companyInfo.id_acc_company_info }, + }); + unifiedCompanyInfo.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.company_info.pull', + method: 'GET', + url: '/accounting/company_info', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedCompanyInfo; + } catch (error) { + throw error; + } } async getCompanyInfos( @@ -47,7 +110,90 @@ export class CompanyInfoService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingCompanyinfoOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const companyInfos = await this.prisma.acc_company_infos.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_company_info: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = companyInfos.length > limit; + if (hasNextPage) companyInfos.pop(); + + const unifiedCompanyInfos = await Promise.all( + companyInfos.map(async (companyInfo) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: companyInfo.id_acc_company_info }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedCompanyInfo: UnifiedAccountingCompanyinfoOutput = { + id: companyInfo.id_acc_company_info, + name: companyInfo.name, + legal_name: companyInfo.legal_name, + tax_number: companyInfo.tax_number, + fiscal_year_end_month: companyInfo.fiscal_year_end_month, + fiscal_year_end_day: companyInfo.fiscal_year_end_day, + currency: companyInfo.currency as CurrencyCode, + urls: companyInfo.urls, + tracking_categories: companyInfo.tracking_categories, + field_mappings: field_mappings, + remote_id: companyInfo.remote_id, + remote_created_at: companyInfo.remote_created_at, + created_at: companyInfo.created_at, + modified_at: companyInfo.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: companyInfo.id_acc_company_info }, + }); + unifiedCompanyInfo.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedCompanyInfo; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.company_info.pull', + method: 'GET', + url: '/accounting/company_infos', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedCompanyInfos, + next_cursor: hasNextPage + ? companyInfos[companyInfos.length - 1].id_acc_company_info + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/companyinfo/sync/sync.service.ts b/packages/api/src/accounting/companyinfo/sync/sync.service.ts index fcb8224d1..5a96908fc 100644 --- a/packages/api/src/accounting/companyinfo/sync/sync.service.ts +++ b/packages/api/src/accounting/companyinfo/sync/sync.service.ts @@ -1,9 +1,8 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingCompanyinfoOutput } from '../types/model.unified'; import { ICompanyInfoService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_company_infos as AccCompanyInfo } from '@prisma/client'; +import { OriginalCompanyInfoOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,24 +25,143 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'company_info', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting company info...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } } - saveToDb( + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ICompanyInfoService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingCompanyinfoOutput, + OriginalCompanyInfoOutput, + ICompanyInfoService + >(integrationId, linkedUserId, 'accounting', 'company_info', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + companyInfos: UnifiedAccountingCompanyinfoOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const companyInfoResults: AccCompanyInfo[] = []; - // Additional methods and logic + for (let i = 0; i < companyInfos.length; i++) { + const companyInfo = companyInfos[i]; + const originId = companyInfo.remote_id; + + let existingCompanyInfo = await this.prisma.acc_company_infos.findFirst( + { + where: { + remote_id: originId, + id_connection: connection_id, + }, + }, + ); + + const companyInfoData = { + name: companyInfo.name, + legal_name: companyInfo.legal_name, + tax_number: companyInfo.tax_number, + fiscal_year_end_month: companyInfo.fiscal_year_end_month, + fiscal_year_end_day: companyInfo.fiscal_year_end_day, + currency: companyInfo.currency as CurrencyCode, + urls: companyInfo.urls, + tracking_categories: companyInfo.tracking_categories, + remote_created_at: companyInfo.remote_created_at, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingCompanyInfo) { + existingCompanyInfo = await this.prisma.acc_company_infos.update({ + where: { + id_acc_company_info: existingCompanyInfo.id_acc_company_info, + }, + data: companyInfoData, + }); + } else { + existingCompanyInfo = await this.prisma.acc_company_infos.create({ + data: { + ...companyInfoData, + id_acc_company_info: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + companyInfoResults.push(existingCompanyInfo); + + // Process field mappings + await this.ingestService.processFieldMappings( + companyInfo.field_mappings, + existingCompanyInfo.id_acc_company_info, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingCompanyInfo.id_acc_company_info, + remote_data[i], + ); + } + + return companyInfoResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/accounting/companyinfo/types/index.ts b/packages/api/src/accounting/companyinfo/types/index.ts index 3f260960c..e54504ade 100644 --- a/packages/api/src/accounting/companyinfo/types/index.ts +++ b/packages/api/src/accounting/companyinfo/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalCompanyInfoOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ICompanyInfoService { addCompanyInfo( @@ -12,10 +13,7 @@ export interface ICompanyInfoService { linkedUserId: string, ): Promise>; - syncCompanyInfos( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ICompanyInfoMapper { @@ -34,5 +32,7 @@ export interface ICompanyInfoMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedAccountingCompanyinfoOutput | UnifiedAccountingCompanyinfoOutput[] + >; } diff --git a/packages/api/src/accounting/companyinfo/types/model.unified.ts b/packages/api/src/accounting/companyinfo/types/model.unified.ts index 92361f52f..1474454b5 100644 --- a/packages/api/src/accounting/companyinfo/types/model.unified.ts +++ b/packages/api/src/accounting/companyinfo/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -69,12 +70,13 @@ export class UnifiedAccountingCompanyinfoInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency used by the company', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: [String], @@ -89,9 +91,12 @@ export class UnifiedAccountingCompanyinfoInput { @ApiPropertyOptional({ type: [String], - example: ['Department', 'Project', 'Location'], + example: [ + '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + ], nullable: true, - description: 'The tracking categories used by the company', + description: 'The UUIDs of the tracking categories used by the company', }) @IsArray() @IsString({ each: true }) @@ -149,7 +154,7 @@ export class UnifiedAccountingCompanyinfoOutput extends UnifiedAccountingCompany remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -157,25 +162,25 @@ export class UnifiedAccountingCompanyinfoOutput extends UnifiedAccountingCompany }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the company info record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the company info record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/contact/services/contact.service.ts b/packages/api/src/accounting/contact/services/contact.service.ts index e96a760d5..1d87d1c26 100644 --- a/packages/api/src/accounting/contact/services/contact.service.ts +++ b/packages/api/src/accounting/contact/services/contact.service.ts @@ -1,20 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { CurrencyCode } from '@@core/utils/types'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; import { UnifiedAccountingContactInput, UnifiedAccountingContactOutput, } from '../types/model.unified'; - -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalContactOutput } from '@@core/utils/types/original/original.accounting'; - -import { IContactService } from '../types'; @Injectable() export class ContactService { @@ -36,18 +31,111 @@ export class ContactService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addContact(unifiedContactData, linkedUserId); + + const savedContact = await this.prisma.acc_contacts.create({ + data: { + id_acc_contact: uuidv4(), + ...unifiedContactData, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + }, + }); + + const result: UnifiedAccountingContactOutput = { + ...savedContact, + currency: savedContact.currency as CurrencyCode, + id: savedContact.id_acc_contact, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getContact( - id_contacting_contact: string, + id_acc_contact: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const contact = await this.prisma.acc_contacts.findUnique({ + where: { id_acc_contact: id_acc_contact }, + }); + + if (!contact) { + throw new Error(`Contact with ID ${id_acc_contact} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: contact.id_acc_contact }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedContact: UnifiedAccountingContactOutput = { + id: contact.id_acc_contact, + name: contact.name, + is_supplier: contact.is_supplier, + is_customer: contact.is_customer, + email_address: contact.email_address, + tax_number: contact.tax_number, + status: contact.status, + currency: contact.currency as CurrencyCode, + remote_updated_at: contact.remote_updated_at || null, + company_info_id: contact.id_acc_company_info, + field_mappings: field_mappings, + remote_id: contact.remote_id, + created_at: contact.created_at, + modified_at: contact.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: contact.id_acc_contact }, + }); + unifiedContact.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.contact.pull', + method: 'GET', + url: '/accounting/contact', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedContact; + } catch (error) { + throw error; + } } async getContacts( @@ -58,7 +146,90 @@ export class ContactService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingContactOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const contacts = await this.prisma.acc_contacts.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_contact: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = contacts.length > limit; + if (hasNextPage) contacts.pop(); + + const unifiedContacts = await Promise.all( + contacts.map(async (contact) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: contact.id_acc_contact }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedContact: UnifiedAccountingContactOutput = { + id: contact.id_acc_contact, + name: contact.name, + is_supplier: contact.is_supplier, + is_customer: contact.is_customer, + email_address: contact.email_address, + tax_number: contact.tax_number, + status: contact.status, + currency: contact.currency as CurrencyCode, + remote_updated_at: contact.remote_updated_at || null, + company_info_id: contact.id_acc_company_info, + field_mappings: field_mappings, + remote_id: contact.remote_id, + created_at: contact.created_at, + modified_at: contact.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: contact.id_acc_contact }, + }); + unifiedContact.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedContact; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.contact.pull', + method: 'GET', + url: '/accounting/contacts', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedContacts, + next_cursor: hasNextPage + ? contacts[contacts.length - 1].id_acc_contact + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/contact/sync/sync.service.ts b/packages/api/src/accounting/contact/sync/sync.service.ts index 2bb5fcfb2..f3e6a1919 100644 --- a/packages/api/src/accounting/contact/sync/sync.service.ts +++ b/packages/api/src/accounting/contact/sync/sync.service.ts @@ -1,9 +1,8 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingContactOutput } from '../types/model.unified'; import { IContactService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_contacts as AccContact } from '@prisma/client'; +import { OriginalContactOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,24 +25,139 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'contact', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting contacts...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } } - saveToDb( + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IContactService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingContactOutput, + OriginalContactOutput, + IContactService + >(integrationId, linkedUserId, 'accounting', 'contact', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + contacts: UnifiedAccountingContactOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const contactResults: AccContact[] = []; - // Additional methods and logic + for (let i = 0; i < contacts.length; i++) { + const contact = contacts[i]; + const originId = contact.remote_id; + + let existingContact = await this.prisma.acc_contacts.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const contactData = { + name: contact.name, + is_supplier: contact.is_supplier, + is_customer: contact.is_customer, + email_address: contact.email_address, + tax_number: contact.tax_number, + status: contact.status, + currency: contact.currency as CurrencyCode, + remote_updated_at: contact.remote_updated_at, + id_acc_company_info: contact.company_info_id, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingContact) { + existingContact = await this.prisma.acc_contacts.update({ + where: { id_acc_contact: existingContact.id_acc_contact }, + data: contactData, + }); + } else { + existingContact = await this.prisma.acc_contacts.create({ + data: { + ...contactData, + id_acc_contact: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + contactResults.push(existingContact); + + // Process field mappings + await this.ingestService.processFieldMappings( + contact.field_mappings, + existingContact.id_acc_contact, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingContact.id_acc_contact, + remote_data[i], + ); + } + + return contactResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/accounting/contact/types/index.ts b/packages/api/src/accounting/contact/types/index.ts index d6cb71f9c..a9c26c209 100644 --- a/packages/api/src/accounting/contact/types/index.ts +++ b/packages/api/src/accounting/contact/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedAccountingContactInput, UnifiedAccountingContactOutput } from './model.unified'; +import { + UnifiedAccountingContactInput, + UnifiedAccountingContactOutput, +} from './model.unified'; import { OriginalContactOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IContactService { addContact( @@ -9,10 +13,7 @@ export interface IContactService { linkedUserId: string, ): Promise>; - syncContacts( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IContactMapper { diff --git a/packages/api/src/accounting/contact/types/model.unified.ts b/packages/api/src/accounting/contact/types/model.unified.ts index 61528f63f..6bce88f18 100644 --- a/packages/api/src/accounting/contact/types/model.unified.ts +++ b/packages/api/src/accounting/contact/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -73,11 +74,12 @@ export class UnifiedAccountingContactInput { type: String, example: 'USD', nullable: true, + enum: CurrencyCode, description: 'The currency associated with the contact', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -150,22 +152,22 @@ export class UnifiedAccountingContactOutput extends UnifiedAccountingContactInpu remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the contact record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the contact record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/creditnote/services/creditnote.service.ts b/packages/api/src/accounting/creditnote/services/creditnote.service.ts index a5e719772..d2b6dc4ac 100644 --- a/packages/api/src/accounting/creditnote/services/creditnote.service.ts +++ b/packages/api/src/accounting/creditnote/services/creditnote.service.ts @@ -2,19 +2,15 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; import { throwTypedError } from '@@core/utils/errors'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { UnifiedAccountingCreditnoteInput, UnifiedAccountingCreditnoteOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalCreditNoteOutput } from '@@core/utils/types/original/original.accounting'; - -import { ICreditNoteService } from '../types'; @Injectable() export class CreditNoteService { @@ -29,14 +25,89 @@ export class CreditNoteService { } async getCreditNote( - id_creditnoteing_creditnote: string, + id_acc_credit_note: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const creditNote = await this.prisma.acc_credit_notes.findUnique({ + where: { id_acc_credit_note: id_acc_credit_note }, + }); + + if (!creditNote) { + throw new Error(`Credit note with ID ${id_acc_credit_note} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: creditNote.id_acc_credit_note }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedCreditNote: UnifiedAccountingCreditnoteOutput = { + id: creditNote.id_acc_credit_note, + transaction_date: creditNote.transaction_date?.toISOString(), + status: creditNote.status, + number: creditNote.number, + contact_id: creditNote.id_acc_contact, + company_id: creditNote.company, + exchange_rate: creditNote.exchange_rate, + total_amount: creditNote.total_amount + ? Number(creditNote.total_amount) + : undefined, + remaining_credit: creditNote.remaining_credit + ? Number(creditNote.remaining_credit) + : undefined, + tracking_categories: creditNote.tracking_categories, + currency: creditNote.currency as CurrencyCode, + payments: creditNote.payments, + applied_payments: creditNote.applied_payments, + accounting_period_id: creditNote.id_acc_accounting_period, + field_mappings: field_mappings, + remote_id: creditNote.remote_id, + remote_created_at: creditNote.remote_created_at, + remote_updated_at: creditNote.remote_updated_at, + created_at: creditNote.created_at, + modified_at: creditNote.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: creditNote.id_acc_credit_note }, + }); + unifiedCreditNote.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.credit_note.pull', + method: 'GET', + url: '/accounting/credit_note', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedCreditNote; + } catch (error) { + throw error; + } } async getCreditNotes( @@ -47,7 +118,100 @@ export class CreditNoteService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingCreditnoteOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const creditNotes = await this.prisma.acc_credit_notes.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_credit_note: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = creditNotes.length > limit; + if (hasNextPage) creditNotes.pop(); + + const unifiedCreditNotes = await Promise.all( + creditNotes.map(async (creditNote) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: creditNote.id_acc_credit_note }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedCreditNote: UnifiedAccountingCreditnoteOutput = { + id: creditNote.id_acc_credit_note, + transaction_date: creditNote.transaction_date?.toISOString(), + status: creditNote.status, + number: creditNote.number, + contact_id: creditNote.id_acc_contact, + company_id: creditNote.company, + exchange_rate: creditNote.exchange_rate, + total_amount: creditNote.total_amount + ? Number(creditNote.total_amount) + : undefined, + remaining_credit: creditNote.remaining_credit + ? Number(creditNote.remaining_credit) + : undefined, + tracking_categories: creditNote.tracking_categories, + currency: creditNote.currency as CurrencyCode, + payments: creditNote.payments, + applied_payments: creditNote.applied_payments, + accounting_period_id: creditNote.id_acc_accounting_period, + field_mappings: field_mappings, + remote_id: creditNote.remote_id, + remote_created_at: creditNote.remote_created_at, + remote_updated_at: creditNote.remote_updated_at, + created_at: creditNote.created_at, + modified_at: creditNote.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: creditNote.id_acc_credit_note }, + }); + unifiedCreditNote.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedCreditNote; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.credit_note.pull', + method: 'GET', + url: '/accounting/credit_notes', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedCreditNotes, + next_cursor: hasNextPage + ? creditNotes[creditNotes.length - 1].id_acc_credit_note + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/creditnote/sync/sync.service.ts b/packages/api/src/accounting/creditnote/sync/sync.service.ts index 79780ff69..e26428023 100644 --- a/packages/api/src/accounting/creditnote/sync/sync.service.ts +++ b/packages/api/src/accounting/creditnote/sync/sync.service.ts @@ -1,9 +1,8 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingCreditnoteOutput } from '../types/model.unified'; import { ICreditNoteService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_credit_notes as AccCreditNote } from '@prisma/client'; +import { OriginalCreditNoteOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,24 +25,153 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'credit_note', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting credit notes...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } } - saveToDb( + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ICreditNoteService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingCreditnoteOutput, + OriginalCreditNoteOutput, + ICreditNoteService + >(integrationId, linkedUserId, 'accounting', 'credit_note', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + creditNotes: UnifiedAccountingCreditnoteOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const creditNoteResults: AccCreditNote[] = []; - // Additional methods and logic + for (let i = 0; i < creditNotes.length; i++) { + const creditNote = creditNotes[i]; + const originId = creditNote.remote_id; + + let existingCreditNote = await this.prisma.acc_credit_notes.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const creditNoteData = { + transaction_date: creditNote.transaction_date + ? new Date(creditNote.transaction_date) + : null, + status: creditNote.status, + number: creditNote.number, + id_acc_contact: creditNote.contact_id, + company: creditNote.company_id, + exchange_rate: creditNote.exchange_rate, + total_amount: creditNote.total_amount + ? Number(creditNote.total_amount) + : null, + remaining_credit: creditNote.remaining_credit + ? Number(creditNote.remaining_credit) + : null, + tracking_categories: creditNote.tracking_categories, + currency: creditNote.currency as CurrencyCode, + payments: creditNote.payments, + applied_payments: creditNote.applied_payments, + id_acc_accounting_period: creditNote.accounting_period_id, + remote_id: originId, + remote_created_at: creditNote.remote_created_at, + remote_updated_at: creditNote.remote_updated_at, + modified_at: new Date(), + }; + + if (existingCreditNote) { + existingCreditNote = await this.prisma.acc_credit_notes.update({ + where: { + id_acc_credit_note: existingCreditNote.id_acc_credit_note, + }, + data: creditNoteData, + }); + } else { + existingCreditNote = await this.prisma.acc_credit_notes.create({ + data: { + ...creditNoteData, + id_acc_credit_note: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + creditNoteResults.push(existingCreditNote); + + // Process field mappings + await this.ingestService.processFieldMappings( + creditNote.field_mappings, + existingCreditNote.id_acc_credit_note, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingCreditNote.id_acc_credit_note, + remote_data[i], + ); + } + + return creditNoteResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/accounting/creditnote/types/index.ts b/packages/api/src/accounting/creditnote/types/index.ts index 7fe41bc14..36021a4a9 100644 --- a/packages/api/src/accounting/creditnote/types/index.ts +++ b/packages/api/src/accounting/creditnote/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalCreditNoteOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ICreditNoteService { addCreditNote( @@ -12,10 +13,7 @@ export interface ICreditNoteService { linkedUserId: string, ): Promise>; - syncCreditNotes( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ICreditNoteMapper { @@ -34,5 +32,7 @@ export interface ICreditNoteMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedAccountingCreditnoteOutput | UnifiedAccountingCreditnoteOutput[] + >; } diff --git a/packages/api/src/accounting/creditnote/types/model.unified.ts b/packages/api/src/accounting/creditnote/types/model.unified.ts index 20330bf3b..12a03f82b 100644 --- a/packages/api/src/accounting/creditnote/types/model.unified.ts +++ b/packages/api/src/accounting/creditnote/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -10,7 +11,7 @@ import { export class UnifiedAccountingCreditnoteInput { @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date of the credit note transaction', @@ -91,9 +92,10 @@ export class UnifiedAccountingCreditnoteInput { @ApiPropertyOptional({ type: [String], - example: ['Project A', 'Department B'], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], nullable: true, - description: 'The tracking categories associated with the credit note', + description: + 'The UUIDs of the tracking categories associated with the credit note', }) @IsArray() @IsString({ each: true }) @@ -103,12 +105,13 @@ export class UnifiedAccountingCreditnoteInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the credit note', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: [String], @@ -193,7 +196,7 @@ export class UnifiedAccountingCreditnoteOutput extends UnifiedAccountingCreditno remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -201,10 +204,10 @@ export class UnifiedAccountingCreditnoteOutput extends UnifiedAccountingCreditno }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -212,25 +215,25 @@ export class UnifiedAccountingCreditnoteOutput extends UnifiedAccountingCreditno }) @IsDateString() @IsOptional() - remote_updated_at?: string; + remote_updated_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the credit note record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the credit note record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/expense/services/expense.service.ts b/packages/api/src/accounting/expense/services/expense.service.ts index 4ee3d01a6..60433414f 100644 --- a/packages/api/src/accounting/expense/services/expense.service.ts +++ b/packages/api/src/accounting/expense/services/expense.service.ts @@ -1,20 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; import { UnifiedAccountingExpenseInput, UnifiedAccountingExpenseOutput, } from '../types/model.unified'; - -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalExpenseOutput } from '@@core/utils/types/original/original.accounting'; - -import { IExpenseService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class ExpenseService { @@ -36,18 +31,172 @@ export class ExpenseService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addExpense(unifiedExpenseData, linkedUserId); + + const savedExpense = await this.prisma.acc_expenses.create({ + data: { + id_acc_expense: uuidv4(), + ...unifiedExpenseData, + total_amount: unifiedExpenseData.total_amount + ? Number(unifiedExpenseData.total_amount) + : null, + sub_total: unifiedExpenseData.sub_total + ? Number(unifiedExpenseData.sub_total) + : null, + total_tax_amount: unifiedExpenseData.total_tax_amount + ? Number(unifiedExpenseData.total_tax_amount) + : null, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + }, + }); + + // Save line items + if (unifiedExpenseData.line_items) { + await Promise.all( + unifiedExpenseData.line_items.map(async (lineItem) => { + await this.prisma.acc_expense_lines.create({ + data: { + id_acc_expense_line: uuidv4(), + id_acc_expense: savedExpense.id_acc_expense, + ...lineItem, + net_amount: lineItem.net_amount + ? Number(lineItem.net_amount) + : null, + created_at: new Date(), + modified_at: new Date(), + id_connection: connection_id, + }, + }); + }), + ); + } + + const result: UnifiedAccountingExpenseOutput = { + ...savedExpense, + currency: savedExpense.currency as CurrencyCode, + id: savedExpense.id_acc_expense, + total_amount: savedExpense.total_amount + ? Number(savedExpense.total_amount) + : undefined, + sub_total: savedExpense.sub_total + ? Number(savedExpense.sub_total) + : undefined, + total_tax_amount: savedExpense.total_tax_amount + ? Number(savedExpense.total_tax_amount) + : undefined, + line_items: unifiedExpenseData.line_items, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getExpense( - id_expenseing_expense: string, + id_acc_expense: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const expense = await this.prisma.acc_expenses.findUnique({ + where: { id_acc_expense: id_acc_expense }, + }); + + if (!expense) { + throw new Error(`Expense with ID ${id_acc_expense} not found.`); + } + + const lineItems = await this.prisma.acc_expense_lines.findMany({ + where: { id_acc_expense: id_acc_expense }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: expense.id_acc_expense }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedExpense: UnifiedAccountingExpenseOutput = { + id: expense.id_acc_expense, + transaction_date: expense.transaction_date, + total_amount: expense.total_amount + ? Number(expense.total_amount) + : undefined, + sub_total: expense.sub_total ? Number(expense.sub_total) : undefined, + total_tax_amount: expense.total_tax_amount + ? Number(expense.total_tax_amount) + : undefined, + currency: expense.currency as CurrencyCode, + exchange_rate: expense.exchange_rate, + memo: expense.memo, + account_id: expense.id_acc_account, + contact_id: expense.id_acc_contact, + company_info_id: expense.id_acc_company_info, + tracking_categories: expense.tracking_categories, + field_mappings: field_mappings, + remote_id: expense.remote_id, + remote_created_at: expense.remote_created_at, + created_at: expense.created_at, + modified_at: expense.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_expense_line, + net_amount: item.net_amount ? Number(item.net_amount) : undefined, + currency: item.currency as CurrencyCode, + description: item.description, + exchange_rate: item.exchange_rate, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: expense.id_acc_expense }, + }); + unifiedExpense.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.expense.pull', + method: 'GET', + url: '/accounting/expense', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedExpense; + } catch (error) { + throw error; + } } async getExpenses( @@ -58,7 +207,113 @@ export class ExpenseService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingExpenseOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const expenses = await this.prisma.acc_expenses.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_expense: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = expenses.length > limit; + if (hasNextPage) expenses.pop(); + + const unifiedExpenses = await Promise.all( + expenses.map(async (expense) => { + const lineItems = await this.prisma.acc_expense_lines.findMany({ + where: { id_acc_expense: expense.id_acc_expense }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: expense.id_acc_expense }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedExpense: UnifiedAccountingExpenseOutput = { + id: expense.id_acc_expense, + transaction_date: expense.transaction_date, + total_amount: expense.total_amount + ? Number(expense.total_amount) + : undefined, + sub_total: expense.sub_total + ? Number(expense.sub_total) + : undefined, + total_tax_amount: expense.total_tax_amount + ? Number(expense.total_tax_amount) + : undefined, + currency: expense.currency as CurrencyCode, + exchange_rate: expense.exchange_rate, + memo: expense.memo, + account_id: expense.id_acc_account, + contact_id: expense.id_acc_contact, + company_info_id: expense.id_acc_company_info, + tracking_categories: expense.tracking_categories, + field_mappings: field_mappings, + remote_id: expense.remote_id, + remote_created_at: expense.remote_created_at, + created_at: expense.created_at, + modified_at: expense.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_expense_line, + net_amount: item.net_amount ? Number(item.net_amount) : undefined, + currency: item.currency as CurrencyCode, + description: item.description, + exchange_rate: item.exchange_rate, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: expense.id_acc_expense }, + }); + unifiedExpense.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedExpense; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.expense.pull', + method: 'GET', + url: '/accounting/expenses', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedExpenses, + next_cursor: hasNextPage + ? expenses[expenses.length - 1].id_acc_expense + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/expense/sync/sync.service.ts b/packages/api/src/accounting/expense/sync/sync.service.ts index b0d257ede..b302d6eb4 100644 --- a/packages/api/src/accounting/expense/sync/sync.service.ts +++ b/packages/api/src/accounting/expense/sync/sync.service.ts @@ -1,16 +1,24 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalExpenseOutput } from '@@core/utils/types/original/original.accounting'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_expenses as AccExpense } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingExpenseOutput } from '../types/model.unified'; import { IExpenseService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { + LineItem, + UnifiedAccountingExpenseOutput, +} from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,24 +28,211 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'expense', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting expenses...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IExpenseService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingExpenseOutput, + OriginalExpenseOutput, + IExpenseService + >(integrationId, linkedUserId, 'accounting', 'expense', service, []); + } catch (error) { + throw error; + } } - saveToDb( + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + expenses: UnifiedAccountingExpenseOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const expenseResults: AccExpense[] = []; + + for (let i = 0; i < expenses.length; i++) { + const expense = expenses[i]; + const originId = expense.remote_id; + + let existingExpense = await this.prisma.acc_expenses.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const expenseData = { + transaction_date: expense.transaction_date + ? new Date(expense.transaction_date) + : null, + total_amount: expense.total_amount + ? Number(expense.total_amount) + : null, + sub_total: expense.sub_total ? Number(expense.sub_total) : null, + total_tax_amount: expense.total_tax_amount + ? Number(expense.total_tax_amount) + : null, + currency: expense.currency as CurrencyCode, + exchange_rate: expense.exchange_rate, + memo: expense.memo, + id_acc_account: expense.account_id, + id_acc_contact: expense.contact_id, + id_acc_company_info: expense.company_info_id, + tracking_categories: expense.tracking_categories, + remote_id: originId, + remote_created_at: expense.remote_created_at, + modified_at: new Date(), + }; + + if (existingExpense) { + existingExpense = await this.prisma.acc_expenses.update({ + where: { id_acc_expense: existingExpense.id_acc_expense }, + data: expenseData, + }); + } else { + existingExpense = await this.prisma.acc_expenses.create({ + data: { + ...expenseData, + id_acc_expense: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + expenseResults.push(existingExpense); + + // Process field mappings + await this.ingestService.processFieldMappings( + expense.field_mappings, + existingExpense.id_acc_expense, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingExpense.id_acc_expense, + remote_data[i], + ); + + // Handle line items + if (expense.line_items && expense.line_items.length > 0) { + await this.processExpenseLineItems( + existingExpense.id_acc_expense, + expense.line_items, + connection_id, + ); + } + } + + return expenseResults; + } catch (error) { + throw error; + } } - // Additional methods and logic + private async processExpenseLineItems( + expenseId: string, + lineItems: LineItem[], + connectionId: string, + ): Promise { + for (const lineItem of lineItems) { + const lineItemData = { + id_acc_expense: expenseId, + remote_id: lineItem.remote_id, + net_amount: lineItem.net_amount ? Number(lineItem.net_amount) : null, + currency: lineItem.currency as CurrencyCode, + description: lineItem.description, + exchange_rate: lineItem.exchange_rate, + modified_at: new Date(), + id_connection: connectionId, + }; + + const existingLineItem = await this.prisma.acc_expense_lines.findFirst({ + where: { + remote_id: lineItem.remote_id, + id_acc_expense: expenseId, + }, + }); + + if (existingLineItem) { + await this.prisma.acc_expense_lines.update({ + where: { + id_acc_expense_line: existingLineItem.id_acc_expense_line, + }, + data: lineItemData, + }); + } else { + await this.prisma.acc_expense_lines.create({ + data: { + ...lineItemData, + id_acc_expense_line: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing line items that are not in the current set + const currentRemoteIds = lineItems.map((item) => item.remote_id); + await this.prisma.acc_expense_lines.deleteMany({ + where: { + id_acc_expense: expenseId, + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); + } } diff --git a/packages/api/src/accounting/expense/types/index.ts b/packages/api/src/accounting/expense/types/index.ts index a6326fe16..d4de9fe9b 100644 --- a/packages/api/src/accounting/expense/types/index.ts +++ b/packages/api/src/accounting/expense/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedAccountingExpenseInput, UnifiedAccountingExpenseOutput } from './model.unified'; +import { + UnifiedAccountingExpenseInput, + UnifiedAccountingExpenseOutput, +} from './model.unified'; import { OriginalExpenseOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IExpenseService { addExpense( @@ -9,10 +13,7 @@ export interface IExpenseService { linkedUserId: string, ): Promise>; - syncExpenses( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IExpenseMapper { diff --git a/packages/api/src/accounting/expense/types/model.unified.ts b/packages/api/src/accounting/expense/types/model.unified.ts index 25d0a8558..9d1537f3c 100644 --- a/packages/api/src/accounting/expense/types/model.unified.ts +++ b/packages/api/src/accounting/expense/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -8,17 +9,86 @@ import { IsArray, } from 'class-validator'; -// todo: expense line -export class UnifiedAccountingExpenseInput { +export class LineItem { + @ApiPropertyOptional({ + type: Number, + example: 5000, + nullable: true, + description: 'The net amount of the line item in cents', + }) + @IsNumber() + @IsOptional() + net_amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + enum: CurrencyCode, + nullable: true, + description: 'The currency of the line item', + }) + @IsString() + @IsOptional() + currency?: CurrencyCode; + + @ApiPropertyOptional({ + type: String, + example: 'Office supplies', + nullable: true, + description: 'Description of the line item', + }) + @IsString() + @IsOptional() + description?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.0', + nullable: true, + description: 'The exchange rate for the line item', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + @ApiPropertyOptional({ type: String, + example: 'line_item_1234', + nullable: true, + description: 'The remote ID of the line item', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The created date of the line item', + }) + @IsDateString() + @IsOptional() + created_at?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The last modified date of the line item', + }) + @IsDateString() + @IsOptional() + modified_at?: Date; +} +export class UnifiedAccountingExpenseInput { + @ApiPropertyOptional({ + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date of the expense transaction', }) @IsDateString() @IsOptional() - transaction_date?: string; + transaction_date?: Date; @ApiPropertyOptional({ type: Number, @@ -53,12 +123,13 @@ export class UnifiedAccountingExpenseInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the expense', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -112,15 +183,24 @@ export class UnifiedAccountingExpenseInput { @ApiPropertyOptional({ type: [String], - example: ['Project A', 'Department B'], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], nullable: true, - description: 'The tracking categories associated with the expense', + description: + 'The UUIDs of the tracking categories associated with the expense', }) @IsArray() @IsString({ each: true }) @IsOptional() tracking_categories?: string[]; + @ApiPropertyOptional({ + type: [LineItem], + description: 'The line items associated with this expense', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; + @ApiPropertyOptional({ type: Object, example: { @@ -171,32 +251,32 @@ export class UnifiedAccountingExpenseOutput extends UnifiedAccountingExpenseInpu remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date when the expense was created in the remote system', }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the expense record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the expense record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/incomestatement/services/incomestatement.service.ts b/packages/api/src/accounting/incomestatement/services/incomestatement.service.ts index bf10f8326..ed3a4f3e6 100644 --- a/packages/api/src/accounting/incomestatement/services/incomestatement.service.ts +++ b/packages/api/src/accounting/incomestatement/services/incomestatement.service.ts @@ -2,19 +2,15 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; import { throwTypedError } from '@@core/utils/errors'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { UnifiedAccountingIncomestatementInput, UnifiedAccountingIncomestatementOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalIncomeStatementOutput } from '@@core/utils/types/original/original.accounting'; - -import { IIncomeStatementService } from '../types'; @Injectable() export class IncomeStatementService { @@ -29,14 +25,90 @@ export class IncomeStatementService { } async getIncomeStatement( - id_incomestatementing_incomestatement: string, + id_acc_income_statement: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const incomeStatement = + await this.prisma.acc_income_statements.findUnique({ + where: { id_acc_income_statement: id_acc_income_statement }, + }); + + if (!incomeStatement) { + throw new Error( + `Income statement with ID ${id_acc_income_statement} not found.`, + ); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: incomeStatement.id_acc_income_statement, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedIncomeStatement: UnifiedAccountingIncomestatementOutput = { + id: incomeStatement.id_acc_income_statement, + name: incomeStatement.name, + currency: incomeStatement.currency as CurrencyCode, + start_period: incomeStatement.start_period, + end_period: incomeStatement.end_period, + gross_profit: incomeStatement.gross_profit + ? Number(incomeStatement.gross_profit) + : undefined, + net_operating_income: incomeStatement.net_operating_income + ? Number(incomeStatement.net_operating_income) + : undefined, + net_income: incomeStatement.net_income + ? Number(incomeStatement.net_income) + : undefined, + field_mappings: field_mappings, + remote_id: incomeStatement.remote_id, + created_at: incomeStatement.created_at, + modified_at: incomeStatement.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: incomeStatement.id_acc_income_statement, + }, + }); + unifiedIncomeStatement.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.income_statement.pull', + method: 'GET', + url: '/accounting/income_statement', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedIncomeStatement; + } catch (error) { + throw error; + } } async getIncomeStatements( @@ -47,7 +119,102 @@ export class IncomeStatementService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingIncomestatementOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const incomeStatements = await this.prisma.acc_income_statements.findMany( + { + take: limit + 1, + cursor: cursor ? { id_acc_income_statement: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }, + ); + + const hasNextPage = incomeStatements.length > limit; + if (hasNextPage) incomeStatements.pop(); + + const unifiedIncomeStatements = await Promise.all( + incomeStatements.map(async (incomeStatement) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: incomeStatement.id_acc_income_statement, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedIncomeStatement: UnifiedAccountingIncomestatementOutput = + { + id: incomeStatement.id_acc_income_statement, + name: incomeStatement.name, + currency: incomeStatement.currency as CurrencyCode, + start_period: incomeStatement.start_period, + end_period: incomeStatement.end_period, + gross_profit: incomeStatement.gross_profit + ? Number(incomeStatement.gross_profit) + : undefined, + net_operating_income: incomeStatement.net_operating_income + ? Number(incomeStatement.net_operating_income) + : undefined, + net_income: incomeStatement.net_income + ? Number(incomeStatement.net_income) + : undefined, + field_mappings: field_mappings, + remote_id: incomeStatement.remote_id, + created_at: incomeStatement.created_at, + modified_at: incomeStatement.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: incomeStatement.id_acc_income_statement, + }, + }); + unifiedIncomeStatement.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedIncomeStatement; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.income_statement.pull', + method: 'GET', + url: '/accounting/income_statements', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedIncomeStatements, + next_cursor: hasNextPage + ? incomeStatements[incomeStatements.length - 1] + .id_acc_income_statement + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/incomestatement/sync/sync.service.ts b/packages/api/src/accounting/incomestatement/sync/sync.service.ts index 4391aa1f7..3e4415bb7 100644 --- a/packages/api/src/accounting/incomestatement/sync/sync.service.ts +++ b/packages/api/src/accounting/incomestatement/sync/sync.service.ts @@ -1,9 +1,8 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ApiResponse, CurrencyCode } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingIncomestatementOutput } from '../types/model.unified'; import { IIncomeStatementService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_income_statements as AccIncomeStatement } from '@prisma/client'; +import { OriginalIncomeStatementOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,23 +25,156 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'income_statement', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed } - saveToDb( + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting income statements...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IIncomeStatementService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingIncomestatementOutput, + OriginalIncomeStatementOutput, + IIncomeStatementService + >( + integrationId, + linkedUserId, + 'accounting', + 'income_statement', + service, + [], + ); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + incomeStatements: UnifiedAccountingIncomestatementOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const incomeStatementResults: AccIncomeStatement[] = []; - // Additional methods and logic + for (let i = 0; i < incomeStatements.length; i++) { + const incomeStatement = incomeStatements[i]; + const originId = incomeStatement.remote_id; + + let existingIncomeStatement = + await this.prisma.acc_income_statements.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const incomeStatementData = { + name: incomeStatement.name, + currency: incomeStatement.currency as CurrencyCode, + start_period: incomeStatement.start_period, + end_period: incomeStatement.end_period, + gross_profit: incomeStatement.gross_profit + ? Number(incomeStatement.gross_profit) + : null, + net_operating_income: incomeStatement.net_operating_income + ? Number(incomeStatement.net_operating_income) + : null, + net_income: incomeStatement.net_income + ? Number(incomeStatement.net_income) + : null, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingIncomeStatement) { + existingIncomeStatement = + await this.prisma.acc_income_statements.update({ + where: { + id_acc_income_statement: + existingIncomeStatement.id_acc_income_statement, + }, + data: incomeStatementData, + }); + } else { + existingIncomeStatement = + await this.prisma.acc_income_statements.create({ + data: { + ...incomeStatementData, + id_acc_income_statement: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + incomeStatementResults.push(existingIncomeStatement); + + // Process field mappings + await this.ingestService.processFieldMappings( + incomeStatement.field_mappings, + existingIncomeStatement.id_acc_income_statement, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingIncomeStatement.id_acc_income_statement, + remote_data[i], + ); + } + + return incomeStatementResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/accounting/incomestatement/types/index.ts b/packages/api/src/accounting/incomestatement/types/index.ts index 3d0fef96e..e22d33cc8 100644 --- a/packages/api/src/accounting/incomestatement/types/index.ts +++ b/packages/api/src/accounting/incomestatement/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalIncomeStatementOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IIncomeStatementService { addIncomeStatement( @@ -12,10 +13,7 @@ export interface IIncomeStatementService { linkedUserId: string, ): Promise>; - syncIncomeStatements( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IIncomeStatementMapper { @@ -34,5 +32,8 @@ export interface IIncomeStatementMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + | UnifiedAccountingIncomestatementOutput + | UnifiedAccountingIncomestatementOutput[] + >; } diff --git a/packages/api/src/accounting/incomestatement/types/model.unified.ts b/packages/api/src/accounting/incomestatement/types/model.unified.ts index 46eb641bc..18123d79d 100644 --- a/packages/api/src/accounting/incomestatement/types/model.unified.ts +++ b/packages/api/src/accounting/incomestatement/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -21,32 +22,33 @@ export class UnifiedAccountingIncomestatementInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency used in the income statement', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-04-01T00:00:00Z', nullable: true, description: 'The start date of the period covered by the income statement', }) @IsDateString() @IsOptional() - start_period?: string; + start_period?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-30T23:59:59Z', nullable: true, description: 'The end date of the period covered by the income statement', }) @IsDateString() @IsOptional() - end_period?: string; + end_period?: Date; @ApiPropertyOptional({ type: Number, @@ -129,22 +131,22 @@ export class UnifiedAccountingIncomestatementOutput extends UnifiedAccountingInc remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the income statement record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the income statement record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/invoice/services/invoice.service.ts b/packages/api/src/accounting/invoice/services/invoice.service.ts index 565c493c6..ac846a2d5 100644 --- a/packages/api/src/accounting/invoice/services/invoice.service.ts +++ b/packages/api/src/accounting/invoice/services/invoice.service.ts @@ -1,20 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; import { UnifiedAccountingInvoiceInput, UnifiedAccountingInvoiceOutput, } from '../types/model.unified'; - -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalInvoiceOutput } from '@@core/utils/types/original/original.accounting'; - -import { IInvoiceService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class InvoiceService { @@ -36,18 +31,203 @@ export class InvoiceService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addInvoice(unifiedInvoiceData, linkedUserId); + + const savedInvoice = await this.prisma.acc_invoices.create({ + data: { + id_acc_invoice: uuidv4(), + ...unifiedInvoiceData, + total_discount: unifiedInvoiceData.total_discount + ? Number(unifiedInvoiceData.total_discount) + : null, + sub_total: unifiedInvoiceData.sub_total + ? Number(unifiedInvoiceData.sub_total) + : null, + total_tax_amount: unifiedInvoiceData.total_tax_amount + ? Number(unifiedInvoiceData.total_tax_amount) + : null, + total_amount: unifiedInvoiceData.total_amount + ? Number(unifiedInvoiceData.total_amount) + : null, + balance: unifiedInvoiceData.balance + ? Number(unifiedInvoiceData.balance) + : null, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + }, + }); + + // Save line items + if (unifiedInvoiceData.line_items) { + await Promise.all( + unifiedInvoiceData.line_items.map(async (lineItem) => { + await this.prisma.acc_invoices_line_items.create({ + data: { + id_acc_invoices_line_item: uuidv4(), + id_acc_invoice: savedInvoice.id_acc_invoice, + id_acc_item: uuidv4(), + ...lineItem, + unit_price: lineItem.unit_price + ? Number(lineItem.unit_price) + : null, + quantity: lineItem.quantity ? Number(lineItem.quantity) : null, + total_amount: lineItem.total_amount + ? Number(lineItem.total_amount) + : null, + created_at: new Date(), + modified_at: new Date(), + id_connection: connection_id, + }, + }); + }), + ); + } + + const result: UnifiedAccountingInvoiceOutput = { + ...savedInvoice, + currency: savedInvoice.currency as CurrencyCode, + id: savedInvoice.id_acc_invoice, + total_discount: savedInvoice.total_discount + ? Number(savedInvoice.total_discount) + : undefined, + sub_total: savedInvoice.sub_total + ? Number(savedInvoice.sub_total) + : undefined, + total_tax_amount: savedInvoice.total_tax_amount + ? Number(savedInvoice.total_tax_amount) + : undefined, + total_amount: savedInvoice.total_amount + ? Number(savedInvoice.total_amount) + : undefined, + balance: savedInvoice.balance + ? Number(savedInvoice.balance) + : undefined, + line_items: unifiedInvoiceData.line_items, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getInvoice( - id_invoiceing_invoice: string, + id_acc_invoice: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const invoice = await this.prisma.acc_invoices.findUnique({ + where: { id_acc_invoice: id_acc_invoice }, + }); + + if (!invoice) { + throw new Error(`Invoice with ID ${id_acc_invoice} not found.`); + } + + const lineItems = await this.prisma.acc_invoices_line_items.findMany({ + where: { id_acc_invoice: id_acc_invoice }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: invoice.id_acc_invoice }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedInvoice: UnifiedAccountingInvoiceOutput = { + id: invoice.id_acc_invoice, + type: invoice.type, + number: invoice.number, + issue_date: invoice.issue_date, + due_date: invoice.due_date, + paid_on_date: invoice.paid_on_date, + memo: invoice.memo, + currency: invoice.currency as CurrencyCode, + exchange_rate: invoice.exchange_rate, + total_discount: invoice.total_discount + ? Number(invoice.total_discount) + : undefined, + sub_total: invoice.sub_total ? Number(invoice.sub_total) : undefined, + status: invoice.status, + total_tax_amount: invoice.total_tax_amount + ? Number(invoice.total_tax_amount) + : undefined, + total_amount: invoice.total_amount + ? Number(invoice.total_amount) + : undefined, + balance: invoice.balance ? Number(invoice.balance) : undefined, + contact_id: invoice.id_acc_contact, + accounting_period_id: invoice.id_acc_accounting_period, + tracking_categories: invoice.tracking_categories, + field_mappings: field_mappings, + remote_id: invoice.remote_id, + remote_updated_at: invoice.remote_updated_at, + created_at: invoice.created_at, + modified_at: invoice.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_invoices_line_item, + description: item.description, + unit_price: item.unit_price ? Number(item.unit_price) : undefined, + quantity: item.quantity ? Number(item.quantity) : undefined, + total_amount: item.total_amount + ? Number(item.total_amount) + : undefined, + currency: item.currency as CurrencyCode, + exchange_rate: item.exchange_rate, + id_acc_item: item.id_acc_item, + acc_tracking_categories: item.acc_tracking_categories, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: invoice.id_acc_invoice }, + }); + unifiedInvoice.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.invoice.pull', + method: 'GET', + url: '/accounting/invoice', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedInvoice; + } catch (error) { + throw error; + } } async getInvoices( @@ -58,7 +238,127 @@ export class InvoiceService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingInvoiceOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const invoices = await this.prisma.acc_invoices.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_invoice: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = invoices.length > limit; + if (hasNextPage) invoices.pop(); + + const unifiedInvoices = await Promise.all( + invoices.map(async (invoice) => { + const lineItems = await this.prisma.acc_invoices_line_items.findMany({ + where: { id_acc_invoice: invoice.id_acc_invoice }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: invoice.id_acc_invoice }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedInvoice: UnifiedAccountingInvoiceOutput = { + id: invoice.id_acc_invoice, + type: invoice.type, + number: invoice.number, + issue_date: invoice.issue_date, + due_date: invoice.due_date, + paid_on_date: invoice.paid_on_date, + memo: invoice.memo, + currency: invoice.currency as CurrencyCode, + exchange_rate: invoice.exchange_rate, + total_discount: invoice.total_discount + ? Number(invoice.total_discount) + : undefined, + sub_total: invoice.sub_total + ? Number(invoice.sub_total) + : undefined, + status: invoice.status, + total_tax_amount: invoice.total_tax_amount + ? Number(invoice.total_tax_amount) + : undefined, + total_amount: invoice.total_amount + ? Number(invoice.total_amount) + : undefined, + balance: invoice.balance ? Number(invoice.balance) : undefined, + contact_id: invoice.id_acc_contact, + accounting_period_id: invoice.id_acc_accounting_period, + tracking_categories: invoice.tracking_categories, + field_mappings: field_mappings, + remote_id: invoice.remote_id, + remote_updated_at: invoice.remote_updated_at, + created_at: invoice.created_at, + modified_at: invoice.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_invoices_line_item, + description: item.description, + unit_price: item.unit_price ? Number(item.unit_price) : undefined, + quantity: item.quantity ? Number(item.quantity) : undefined, + total_amount: item.total_amount + ? Number(item.total_amount) + : undefined, + currency: item.currency as CurrencyCode, + exchange_rate: item.exchange_rate, + id_acc_item: item.id_acc_item, + acc_tracking_categories: item.acc_tracking_categories, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: invoice.id_acc_invoice }, + }); + unifiedInvoice.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedInvoice; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.invoice.pull', + method: 'GET', + url: '/accounting/invoices', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedInvoices, + next_cursor: hasNextPage + ? invoices[invoices.length - 1].id_acc_invoice + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/invoice/sync/sync.service.ts b/packages/api/src/accounting/invoice/sync/sync.service.ts index f4c245a60..c3fa05443 100644 --- a/packages/api/src/accounting/invoice/sync/sync.service.ts +++ b/packages/api/src/accounting/invoice/sync/sync.service.ts @@ -1,16 +1,24 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalInvoiceOutput } from '@@core/utils/types/original/original.accounting'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_invoices as AccInvoice } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingInvoiceOutput } from '../types/model.unified'; import { IInvoiceService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { + LineItem, + UnifiedAccountingInvoiceOutput, +} from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,22 +28,225 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'invoice', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting invoices...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IInvoiceService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingInvoiceOutput, + OriginalInvoiceOutput, + IInvoiceService + >(integrationId, linkedUserId, 'accounting', 'invoice', service, []); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + invoices: UnifiedAccountingInvoiceOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const invoiceResults: AccInvoice[] = []; + + for (let i = 0; i < invoices.length; i++) { + const invoice = invoices[i]; + const originId = invoice.remote_id; + + let existingInvoice = await this.prisma.acc_invoices.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const invoiceData = { + type: invoice.type, + number: invoice.number, + issue_date: invoice.issue_date, + due_date: invoice.due_date, + paid_on_date: invoice.paid_on_date, + memo: invoice.memo, + currency: invoice.currency as CurrencyCode, + exchange_rate: invoice.exchange_rate, + total_discount: invoice.total_discount + ? Number(invoice.total_discount) + : null, + sub_total: invoice.sub_total ? Number(invoice.sub_total) : null, + status: invoice.status, + total_tax_amount: invoice.total_tax_amount + ? Number(invoice.total_tax_amount) + : null, + total_amount: invoice.total_amount + ? Number(invoice.total_amount) + : null, + balance: invoice.balance ? Number(invoice.balance) : null, + remote_updated_at: invoice.remote_updated_at, + remote_id: originId, + id_acc_contact: invoice.contact_id, + id_acc_accounting_period: invoice.accounting_period_id, + tracking_categories: invoice.tracking_categories, + modified_at: new Date(), + }; + + if (existingInvoice) { + existingInvoice = await this.prisma.acc_invoices.update({ + where: { id_acc_invoice: existingInvoice.id_acc_invoice }, + data: invoiceData, + }); + } else { + existingInvoice = await this.prisma.acc_invoices.create({ + data: { + ...invoiceData, + id_acc_invoice: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + invoiceResults.push(existingInvoice); + + // Process field mappings + await this.ingestService.processFieldMappings( + invoice.field_mappings, + existingInvoice.id_acc_invoice, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingInvoice.id_acc_invoice, + remote_data[i], + ); + + // Handle line items + if (invoice.line_items && invoice.line_items.length > 0) { + await this.processInvoiceLineItems( + existingInvoice.id_acc_invoice, + invoice.line_items, + connection_id, + ); + } + } + + return invoiceResults; + } catch (error) { + throw error; + } + } + + private async processInvoiceLineItems( + invoiceId: string, + lineItems: LineItem[], + connectionId: string, + ): Promise { + for (const lineItem of lineItems) { + const lineItemData = { + description: lineItem.description, + unit_price: lineItem.unit_price ? Number(lineItem.unit_price) : null, + quantity: lineItem.quantity ? Number(lineItem.quantity) : null, + total_amount: lineItem.total_amount + ? Number(lineItem.total_amount) + : null, + currency: lineItem.currency as CurrencyCode, + exchange_rate: lineItem.exchange_rate, + id_acc_invoice: invoiceId, + id_acc_item: lineItem.item_id, + acc_tracking_categories: lineItem.tracking_categories, + remote_id: lineItem.remote_id, + modified_at: new Date(), + id_connection: connectionId, + }; + + const existingLineItem = + await this.prisma.acc_invoices_line_items.findFirst({ + where: { + remote_id: lineItem.remote_id, + id_acc_invoice: invoiceId, + }, + }); + + if (existingLineItem) { + await this.prisma.acc_invoices_line_items.update({ + where: { + id_acc_invoices_line_item: + existingLineItem.id_acc_invoices_line_item, + }, + data: lineItemData, + }); + } else { + await this.prisma.acc_invoices_line_items.create({ + data: { + ...lineItemData, + id_acc_invoices_line_item: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing line items that are not in the current set + const currentRemoteIds = lineItems.map((item) => item.remote_id); + await this.prisma.acc_invoices_line_items.deleteMany({ + where: { + id_acc_invoice: invoiceId, + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); } - // Additional methods and logic } diff --git a/packages/api/src/accounting/invoice/types/index.ts b/packages/api/src/accounting/invoice/types/index.ts index dd800275f..cddb0c418 100644 --- a/packages/api/src/accounting/invoice/types/index.ts +++ b/packages/api/src/accounting/invoice/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedAccountingInvoiceInput, UnifiedAccountingInvoiceOutput } from './model.unified'; +import { + UnifiedAccountingInvoiceInput, + UnifiedAccountingInvoiceOutput, +} from './model.unified'; import { OriginalInvoiceOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IInvoiceService { addInvoice( @@ -9,10 +13,7 @@ export interface IInvoiceService { linkedUserId: string, ): Promise>; - syncInvoices( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IInvoiceMapper { diff --git a/packages/api/src/accounting/invoice/types/model.unified.ts b/packages/api/src/accounting/invoice/types/model.unified.ts index 62e6d7ac3..ce0625651 100644 --- a/packages/api/src/accounting/invoice/types/model.unified.ts +++ b/packages/api/src/accounting/invoice/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -8,6 +9,119 @@ import { IsArray, } from 'class-validator'; +export class LineItem { + @ApiPropertyOptional({ + type: String, + example: 'Product description', + nullable: true, + description: 'Description of the line item', + }) + @IsString() + @IsOptional() + description?: string; + + @ApiPropertyOptional({ + type: Number, + example: 1000, + nullable: true, + description: 'The unit price of the item in cents', + }) + @IsNumber() + @IsOptional() + unit_price?: number; + + @ApiPropertyOptional({ + type: Number, + example: 2, + nullable: true, + description: 'The quantity of the item', + }) + @IsNumber() + @IsOptional() + quantity?: number; + + @ApiPropertyOptional({ + type: Number, + example: 2000, + nullable: true, + description: 'The total amount for the line item in cents', + }) + @IsNumber() + @IsOptional() + total_amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + enum: CurrencyCode, + nullable: true, + description: 'The currency of the line item', + }) + @IsString() + @IsOptional() + currency?: CurrencyCode; + + @ApiPropertyOptional({ + type: String, + example: '1.0', + nullable: true, + description: 'The exchange rate for the line item', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated item', + }) + @IsUUID() + @IsOptional() + item_id?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: + 'The UUIDs of the tracking categories associated with the line item', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: 'line_item_1234', + nullable: true, + description: 'The remote ID of the line item', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The created date of the line item', + }) + @IsDateString() + @IsOptional() + created_at?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The last modified date of the line item', + }) + @IsDateString() + @IsOptional() + modified_at?: Date; +} + export class UnifiedAccountingInvoiceInput { @ApiPropertyOptional({ type: String, @@ -30,34 +144,34 @@ export class UnifiedAccountingInvoiceInput { number?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date the invoice was issued', }) @IsDateString() @IsOptional() - issue_date?: string; + issue_date?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-07-15T12:00:00Z', nullable: true, description: 'The due date of the invoice', }) @IsDateString() @IsOptional() - due_date?: string; + due_date?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-07-10T12:00:00Z', nullable: true, description: 'The date the invoice was paid', }) @IsDateString() @IsOptional() - paid_on_date?: string; + paid_on_date?: Date; @ApiPropertyOptional({ type: String, @@ -72,12 +186,13 @@ export class UnifiedAccountingInvoiceInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the invoice', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -167,19 +282,31 @@ export class UnifiedAccountingInvoiceInput { }) @IsUUID() @IsOptional() - accounting_period_id?: string; + accounting_period_id?: string; // todo @ApiPropertyOptional({ type: [String], - example: ['Project A', 'Department B'], + example: [ + '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + ], nullable: true, - description: 'The tracking categories associated with the invoice', + description: + 'The UUIDs of the tracking categories associated with the invoice', }) @IsArray() @IsString({ each: true }) @IsOptional() tracking_categories?: string[]; + @ApiPropertyOptional({ + type: [LineItem], + description: 'The line items associated with this invoice', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; + @ApiPropertyOptional({ type: Object, example: { @@ -230,7 +357,7 @@ export class UnifiedAccountingInvoiceOutput extends UnifiedAccountingInvoiceInpu remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -238,25 +365,25 @@ export class UnifiedAccountingInvoiceOutput extends UnifiedAccountingInvoiceInpu }) @IsDateString() @IsOptional() - remote_updated_at?: string; + remote_updated_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the invoice record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the invoice record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/item/services/item.service.ts b/packages/api/src/accounting/item/services/item.service.ts index f9e00132a..e53a8be21 100644 --- a/packages/api/src/accounting/item/services/item.service.ts +++ b/packages/api/src/accounting/item/services/item.service.ts @@ -5,13 +5,12 @@ import { v4 as uuidv4 } from 'uuid'; import { ApiResponse } from '@@core/utils/types'; import { throwTypedError } from '@@core/utils/errors'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingItemInput, UnifiedAccountingItemOutput } from '../types/model.unified'; - +import { + UnifiedAccountingItemInput, + UnifiedAccountingItemOutput, +} from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalItemOutput } from '@@core/utils/types/original/original.accounting'; - -import { IItemService } from '../types'; @Injectable() export class ItemService { @@ -26,16 +25,81 @@ export class ItemService { } async getItem( - id_iteming_item: string, + id_acc_item: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; - } + try { + const item = await this.prisma.acc_items.findUnique({ + where: { id_acc_item: id_acc_item }, + }); + + if (!item) { + throw new Error(`Item with ID ${id_acc_item} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: item.id_acc_item }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedItem: UnifiedAccountingItemOutput = { + id: item.id_acc_item, + name: item.name, + status: item.status, + unit_price: item.unit_price ? Number(item.unit_price) : undefined, + purchase_price: item.purchase_price + ? Number(item.purchase_price) + : undefined, + sales_account: item.sales_account, + purchase_account: item.purchase_account, + company_info_id: item.id_acc_company_info, + field_mappings: field_mappings, + remote_id: item.remote_id, + remote_updated_at: item.remote_updated_at, + created_at: item.created_at, + modified_at: item.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: item.id_acc_item }, + }); + unifiedItem.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.item.pull', + method: 'GET', + url: '/accounting/item', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + return unifiedItem; + } catch (error) { + throw error; + } + } async getItems( connectionId: string, projectId: string, @@ -44,7 +108,89 @@ export class ItemService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingItemOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const items = await this.prisma.acc_items.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_item: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = items.length > limit; + if (hasNextPage) items.pop(); + + const unifiedItems = await Promise.all( + items.map(async (item) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: item.id_acc_item }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedItem: UnifiedAccountingItemOutput = { + id: item.id_acc_item, + name: item.name, + status: item.status, + unit_price: item.unit_price ? Number(item.unit_price) : undefined, + purchase_price: item.purchase_price + ? Number(item.purchase_price) + : undefined, + sales_account: item.sales_account, + purchase_account: item.purchase_account, + company_info_id: item.id_acc_company_info, + field_mappings: field_mappings, + remote_id: item.remote_id, + remote_updated_at: item.remote_updated_at, + created_at: item.created_at, + modified_at: item.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: item.id_acc_item }, + }); + unifiedItem.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedItem; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.item.pull', + method: 'GET', + url: '/accounting/items', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedItems, + next_cursor: hasNextPage ? items[items.length - 1].id_acc_item : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/item/sync/sync.service.ts b/packages/api/src/accounting/item/sync/sync.service.ts index 3e5573cf6..a6df008da 100644 --- a/packages/api/src/accounting/item/sync/sync.service.ts +++ b/packages/api/src/accounting/item/sync/sync.service.ts @@ -1,7 +1,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingItemOutput } from '../types/model.unified'; import { IItemService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_items as AccItem } from '@prisma/client'; +import { OriginalItemOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,23 +25,140 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'item', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed } - saveToDb( + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting items...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IItemService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingItemOutput, + OriginalItemOutput, + IItemService + >(integrationId, linkedUserId, 'accounting', 'item', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + items: UnifiedAccountingItemOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const itemResults: AccItem[] = []; - // Additional methods and logic + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const originId = item.remote_id; + + let existingItem = await this.prisma.acc_items.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const itemData = { + name: item.name, + status: item.status, + unit_price: item.unit_price ? Number(item.unit_price) : null, + purchase_price: item.purchase_price + ? Number(item.purchase_price) + : null, + remote_updated_at: item.remote_updated_at, + remote_id: originId, + sales_account: item.sales_account, + purchase_account: item.purchase_account, + id_acc_company_info: item.company_info_id, + modified_at: new Date(), + }; + + if (existingItem) { + existingItem = await this.prisma.acc_items.update({ + where: { id_acc_item: existingItem.id_acc_item }, + data: itemData, + }); + } else { + existingItem = await this.prisma.acc_items.create({ + data: { + ...itemData, + id_acc_item: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + itemResults.push(existingItem); + + // Process field mappings + await this.ingestService.processFieldMappings( + item.field_mappings, + existingItem.id_acc_item, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingItem.id_acc_item, + remote_data[i], + ); + } + + return itemResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/accounting/item/types/index.ts b/packages/api/src/accounting/item/types/index.ts index dd67857d8..a9f79296d 100644 --- a/packages/api/src/accounting/item/types/index.ts +++ b/packages/api/src/accounting/item/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedAccountingItemInput, UnifiedAccountingItemOutput } from './model.unified'; +import { + UnifiedAccountingItemInput, + UnifiedAccountingItemOutput, +} from './model.unified'; import { OriginalItemOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IItemService { addItem( @@ -9,10 +13,7 @@ export interface IItemService { linkedUserId: string, ): Promise>; - syncItems( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IItemMapper { diff --git a/packages/api/src/accounting/item/types/model.unified.ts b/packages/api/src/accounting/item/types/model.unified.ts index ecf6d22d0..013d547b8 100644 --- a/packages/api/src/accounting/item/types/model.unified.ts +++ b/packages/api/src/accounting/item/types/model.unified.ts @@ -76,7 +76,7 @@ export class UnifiedAccountingItemInput { }) @IsUUID() @IsOptional() - id_acc_company_info?: string; + company_info_id?: string; @ApiPropertyOptional({ type: Object, @@ -114,14 +114,14 @@ export class UnifiedAccountingItemOutput extends UnifiedAccountingItemInput { remote_id?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date when the item was last updated in the remote system', }) @IsDateString() @IsOptional() - remote_updated_at?: string; + remote_updated_at?: Date; @ApiPropertyOptional({ type: Object, @@ -137,22 +137,22 @@ export class UnifiedAccountingItemOutput extends UnifiedAccountingItemInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the accounting item record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the accounting item record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/journalentry/services/journalentry.service.ts b/packages/api/src/accounting/journalentry/services/journalentry.service.ts index 43f636714..dd2562544 100644 --- a/packages/api/src/accounting/journalentry/services/journalentry.service.ts +++ b/packages/api/src/accounting/journalentry/services/journalentry.service.ts @@ -1,20 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; import { UnifiedAccountingJournalentryInput, UnifiedAccountingJournalentryOutput, } from '../types/model.unified'; - -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalJournalEntryOutput } from '@@core/utils/types/original/original.accounting'; - -import { IJournalEntryService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class JournalEntryService { @@ -36,18 +31,158 @@ export class JournalEntryService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addJournalEntry( + unifiedJournalEntryData, + linkedUserId, + ); + + const savedJournalEntry = await this.prisma.acc_journal_entries.create({ + data: { + id_acc_journal_entry: uuidv4(), + ...unifiedJournalEntryData, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + }, + }); + + // Save line items + if (unifiedJournalEntryData.line_items) { + await Promise.all( + unifiedJournalEntryData.line_items.map(async (lineItem) => { + await this.prisma.acc_journal_entries_lines.create({ + data: { + id_acc_journal_entries_line: uuidv4(), + id_acc_journal_entry: savedJournalEntry.id_acc_journal_entry, + ...lineItem, + net_amount: lineItem.net_amount + ? Number(lineItem.net_amount) + : null, + created_at: new Date(), + modified_at: new Date(), + }, + }); + }), + ); + } + + const result: UnifiedAccountingJournalentryOutput = { + ...savedJournalEntry, + currency: savedJournalEntry.currency as CurrencyCode, + id: savedJournalEntry.id_acc_journal_entry, + line_items: unifiedJournalEntryData.line_items, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getJournalEntry( - id_journalentrying_journalentry: string, + id_acc_journal_entry: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const journalEntry = await this.prisma.acc_journal_entries.findUnique({ + where: { id_acc_journal_entry: id_acc_journal_entry }, + }); + + if (!journalEntry) { + throw new Error( + `Journal entry with ID ${id_acc_journal_entry} not found.`, + ); + } + + const lineItems = await this.prisma.acc_journal_entries_lines.findMany({ + where: { id_acc_journal_entry: id_acc_journal_entry }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: journalEntry.id_acc_journal_entry }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedJournalEntry: UnifiedAccountingJournalentryOutput = { + id: journalEntry.id_acc_journal_entry, + transaction_date: journalEntry.transaction_date, + payments: journalEntry.payments, + applied_payments: journalEntry.applied_payments, + memo: journalEntry.memo, + currency: journalEntry.currency as CurrencyCode, + exchange_rate: journalEntry.exchange_rate, + id_acc_company_info: journalEntry.id_acc_company_info, + journal_number: journalEntry.journal_number, + tracking_categories: journalEntry.tracking_categories, + id_acc_accounting_period: journalEntry.id_acc_accounting_period, + posting_status: journalEntry.posting_status, + field_mappings: field_mappings, + remote_id: journalEntry.remote_id, + remote_created_at: journalEntry.remote_created_at, + remote_modiified_at: journalEntry.remote_modiified_at, + created_at: journalEntry.created_at, + modified_at: journalEntry.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_journal_entries_line, + net_amount: item.net_amount ? Number(item.net_amount) : undefined, + tracking_categories: item.tracking_categories, + currency: item.currency as CurrencyCode, + description: item.description, + company: item.company, + contact: item.contact, + exchange_rate: item.exchange_rate, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: journalEntry.id_acc_journal_entry }, + }); + unifiedJournalEntry.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.journal_entry.pull', + method: 'GET', + url: '/accounting/journal_entry', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedJournalEntry; + } catch (error) { + throw error; + } } async getJournalEntrys( @@ -58,7 +193,114 @@ export class JournalEntryService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingJournalentryOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const journalEntries = await this.prisma.acc_journal_entries.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_journal_entry: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = journalEntries.length > limit; + if (hasNextPage) journalEntries.pop(); + + const unifiedJournalEntries = await Promise.all( + journalEntries.map(async (journalEntry) => { + const lineItems = + await this.prisma.acc_journal_entries_lines.findMany({ + where: { + id_acc_journal_entry: journalEntry.id_acc_journal_entry, + }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: journalEntry.id_acc_journal_entry }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedJournalEntry: UnifiedAccountingJournalentryOutput = { + id: journalEntry.id_acc_journal_entry, + transaction_date: journalEntry.transaction_date, + payments: journalEntry.payments, + applied_payments: journalEntry.applied_payments, + memo: journalEntry.memo, + currency: journalEntry.currency as CurrencyCode, + exchange_rate: journalEntry.exchange_rate, + id_acc_company_info: journalEntry.id_acc_company_info, + journal_number: journalEntry.journal_number, + tracking_categories: journalEntry.tracking_categories, + id_acc_accounting_period: journalEntry.id_acc_accounting_period, + posting_status: journalEntry.posting_status, + field_mappings: field_mappings, + remote_id: journalEntry.remote_id, + remote_created_at: journalEntry.remote_created_at, + remote_modiified_at: journalEntry.remote_modiified_at, + created_at: journalEntry.created_at, + modified_at: journalEntry.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_journal_entries_line, + net_amount: item.net_amount ? Number(item.net_amount) : undefined, + tracking_categories: item.tracking_categories, + currency: item.currency as CurrencyCode, + description: item.description, + company: item.company, + contact: item.contact, + exchange_rate: item.exchange_rate, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: journalEntry.id_acc_journal_entry }, + }); + unifiedJournalEntry.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedJournalEntry; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.journal_entry.pull', + method: 'GET', + url: '/accounting/journal_entries', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedJournalEntries, + next_cursor: hasNextPage + ? journalEntries[journalEntries.length - 1].id_acc_journal_entry + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/journalentry/sync/sync.service.ts b/packages/api/src/accounting/journalentry/sync/sync.service.ts index 9cfd866c3..8c5da919b 100644 --- a/packages/api/src/accounting/journalentry/sync/sync.service.ts +++ b/packages/api/src/accounting/journalentry/sync/sync.service.ts @@ -1,16 +1,24 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalJournalEntryOutput } from '@@core/utils/types/original/original.accounting'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_journal_entries as AccJournalEntry } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingJournalentryOutput } from '../types/model.unified'; import { IJournalEntryService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { + LineItem, + UnifiedAccountingJournalentryOutput, +} from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,23 +28,218 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'journal_entry', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting journal entries...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IJournalEntryService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingJournalentryOutput, + OriginalJournalEntryOutput, + IJournalEntryService + >( + integrationId, + linkedUserId, + 'accounting', + 'journal_entry', + service, + [], + ); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + journalEntries: UnifiedAccountingJournalentryOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const journalEntryResults: AccJournalEntry[] = []; + + for (let i = 0; i < journalEntries.length; i++) { + const journalEntry = journalEntries[i]; + const originId = journalEntry.remote_id; + + let existingJournalEntry = + await this.prisma.acc_journal_entries.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const journalEntryData = { + transaction_date: journalEntry.transaction_date, + payments: journalEntry.payments, + applied_payments: journalEntry.applied_payments, + memo: journalEntry.memo, + currency: journalEntry.currency as CurrencyCode, + exchange_rate: journalEntry.exchange_rate, + id_acc_company_info: journalEntry.id_acc_company_info, + journal_number: journalEntry.journal_number, + tracking_categories: journalEntry.tracking_categories, + id_acc_accounting_period: journalEntry.id_acc_accounting_period, + posting_status: journalEntry.posting_status, + remote_created_at: journalEntry.remote_created_at, + remote_modiified_at: journalEntry.remote_modiified_at, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingJournalEntry) { + existingJournalEntry = await this.prisma.acc_journal_entries.update({ + where: { + id_acc_journal_entry: existingJournalEntry.id_acc_journal_entry, + }, + data: journalEntryData, + }); + } else { + existingJournalEntry = await this.prisma.acc_journal_entries.create({ + data: { + ...journalEntryData, + id_acc_journal_entry: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + journalEntryResults.push(existingJournalEntry); + + // Process field mappings + await this.ingestService.processFieldMappings( + journalEntry.field_mappings, + existingJournalEntry.id_acc_journal_entry, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingJournalEntry.id_acc_journal_entry, + remote_data[i], + ); + + // Handle line items + if (journalEntry.line_items && journalEntry.line_items.length > 0) { + await this.processJournalEntryLineItems( + existingJournalEntry.id_acc_journal_entry, + journalEntry.line_items, + ); + } + } + + return journalEntryResults; + } catch (error) { + throw error; + } } - // Additional methods and logic + private async processJournalEntryLineItems( + journalEntryId: string, + lineItems: LineItem[], + ): Promise { + for (const lineItem of lineItems) { + const lineItemData = { + net_amount: lineItem.net_amount ? Number(lineItem.net_amount) : null, + tracking_categories: lineItem.tracking_categories, + currency: lineItem.currency as CurrencyCode, + description: lineItem.description, + company: lineItem.company, + contact: lineItem.contact, + exchange_rate: lineItem.exchange_rate, + remote_id: lineItem.remote_id, + modified_at: new Date(), + id_acc_journal_entry: journalEntryId, + }; + + const existingLineItem = + await this.prisma.acc_journal_entries_lines.findFirst({ + where: { + remote_id: lineItem.remote_id, + id_acc_journal_entry: journalEntryId, + }, + }); + + if (existingLineItem) { + await this.prisma.acc_journal_entries_lines.update({ + where: { + id_acc_journal_entries_line: + existingLineItem.id_acc_journal_entries_line, + }, + data: lineItemData, + }); + } else { + await this.prisma.acc_journal_entries_lines.create({ + data: { + ...lineItemData, + id_acc_journal_entries_line: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing line items that are not in the current set + const currentRemoteIds = lineItems.map((item) => item.remote_id); + await this.prisma.acc_journal_entries_lines.deleteMany({ + where: { + id_acc_journal_entry: journalEntryId, + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); + } } diff --git a/packages/api/src/accounting/journalentry/types/index.ts b/packages/api/src/accounting/journalentry/types/index.ts index 2c2171a75..c6779f2d1 100644 --- a/packages/api/src/accounting/journalentry/types/index.ts +++ b/packages/api/src/accounting/journalentry/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalJournalEntryOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IJournalEntryService { addJournalEntry( @@ -12,10 +13,7 @@ export interface IJournalEntryService { linkedUserId: string, ): Promise>; - syncJournalEntrys( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IJournalEntryMapper { @@ -34,5 +32,7 @@ export interface IJournalEntryMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedAccountingJournalentryOutput | UnifiedAccountingJournalentryOutput[] + >; } diff --git a/packages/api/src/accounting/journalentry/types/model.unified.ts b/packages/api/src/accounting/journalentry/types/model.unified.ts index d8ab47c88..36a1bdb06 100644 --- a/packages/api/src/accounting/journalentry/types/model.unified.ts +++ b/packages/api/src/accounting/journalentry/types/model.unified.ts @@ -1,22 +1,127 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, - IsOptional, IsString, IsDateString, IsArray, + IsOptional, + IsNumber, } from 'class-validator'; -export class UnifiedAccountingJournalentryInput { +export class LineItem { + @ApiPropertyOptional({ + type: Number, + example: 10000, + nullable: true, + description: 'The net amount of the line item in cents', + }) + @IsNumber() + @IsOptional() + net_amount?: number; + + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: + 'The UUIDs of the tracking categories associated with the line item', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + enum: CurrencyCode, + nullable: true, + description: 'The currency of the line item', + }) + @IsString() + @IsOptional() + currency?: CurrencyCode; + + @ApiPropertyOptional({ + type: String, + example: 'Office supplies expense', + nullable: true, + description: 'Description of the line item', + }) + @IsString() + @IsOptional() + description?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company', + }) + @IsUUID() + @IsOptional() + company?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated contact', + }) + @IsUUID() + @IsOptional() + contact?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.2', + nullable: true, + description: 'The exchange rate applied to the line item', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + @ApiPropertyOptional({ type: String, + example: 'line_item_1234', + nullable: true, + description: 'The remote ID of the line item', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The created date of the line item', + }) + @IsDateString() + @IsOptional() + created_at?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The last modified date of the line item', + }) + @IsDateString() + @IsOptional() + modified_at?: Date; +} + +export class UnifiedAccountingJournalentryInput { + @ApiPropertyOptional({ + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date of the transaction', }) @IsDateString() @IsOptional() - transaction_date?: string; + transaction_date?: Date; @ApiPropertyOptional({ type: [String], @@ -53,12 +158,13 @@ export class UnifiedAccountingJournalentryInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the journal entry', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -91,9 +197,10 @@ export class UnifiedAccountingJournalentryInput { @ApiPropertyOptional({ type: [String], - example: ['Category1', 'Category2'], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], nullable: true, - description: 'The tracking categories associated with the journal entry', + description: + 'The UUIDs of the tracking categories associated with the journal entry', }) @IsArray() @IsString({ each: true }) @@ -120,6 +227,14 @@ export class UnifiedAccountingJournalentryInput { @IsOptional() posting_status?: string; + @ApiPropertyOptional({ + type: [LineItem], + description: 'The line items associated with this journal entry', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; + @ApiPropertyOptional({ type: Object, example: { @@ -156,7 +271,7 @@ export class UnifiedAccountingJournalentryOutput extends UnifiedAccountingJourna remote_id: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -164,10 +279,10 @@ export class UnifiedAccountingJournalentryOutput extends UnifiedAccountingJourna }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -175,7 +290,7 @@ export class UnifiedAccountingJournalentryOutput extends UnifiedAccountingJourna }) @IsDateString() @IsOptional() - remote_modiified_at?: string; + remote_modiified_at?: Date; @ApiPropertyOptional({ type: Object, @@ -192,22 +307,22 @@ export class UnifiedAccountingJournalentryOutput extends UnifiedAccountingJourna remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the journal entry record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the journal entry record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/payment/services/payment.service.ts b/packages/api/src/accounting/payment/services/payment.service.ts index 10e07f30c..56f04b382 100644 --- a/packages/api/src/accounting/payment/services/payment.service.ts +++ b/packages/api/src/accounting/payment/services/payment.service.ts @@ -1,20 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; import { UnifiedAccountingPaymentInput, UnifiedAccountingPaymentOutput, } from '../types/model.unified'; - -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalPaymentOutput } from '@@core/utils/types/original/original.accounting'; - -import { IPaymentService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class PaymentService { @@ -36,18 +31,160 @@ export class PaymentService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addPayment(unifiedPaymentData, linkedUserId); + + const savedPayment = await this.prisma.acc_payments.create({ + data: { + id_acc_payment: uuidv4(), + ...unifiedPaymentData, + total_amount: unifiedPaymentData.total_amount + ? Number(unifiedPaymentData.total_amount) + : null, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + }, + }); + + // Save line items + if (unifiedPaymentData.line_items) { + await Promise.all( + unifiedPaymentData.line_items.map(async (lineItem) => { + await this.prisma.acc_payments_line_items.create({ + data: { + acc_payments_line_item: uuidv4(), + id_acc_payment: savedPayment.id_acc_payment, + ...lineItem, + applied_amount: lineItem.applied_amount + ? Number(lineItem.applied_amount) + : null, + created_at: new Date(), + modified_at: new Date(), + id_connection: connection_id, + }, + }); + }), + ); + } + + const result: UnifiedAccountingPaymentOutput = { + ...savedPayment, + currency: savedPayment.currency as CurrencyCode, + id: savedPayment.id_acc_payment, + total_amount: savedPayment.total_amount + ? Number(savedPayment.total_amount) + : undefined, + line_items: unifiedPaymentData.line_items, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getPayment( - id_paymenting_payment: string, + id_acc_payment: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const payment = await this.prisma.acc_payments.findUnique({ + where: { id_acc_payment: id_acc_payment }, + }); + + if (!payment) { + throw new Error(`Payment with ID ${id_acc_payment} not found.`); + } + + const lineItems = await this.prisma.acc_payments_line_items.findMany({ + where: { id_acc_payment: id_acc_payment }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: payment.id_acc_payment }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPayment: UnifiedAccountingPaymentOutput = { + id: payment.id_acc_payment, + invoice_id: payment.id_acc_invoice, + transaction_date: payment.transaction_date, + contact_id: payment.id_acc_contact, + account_id: payment.id_acc_account, + currency: payment.currency as CurrencyCode, + exchange_rate: payment.exchange_rate, + total_amount: payment.total_amount + ? Number(payment.total_amount) + : undefined, + type: payment.type, + company_info_id: payment.id_acc_company_info, + accounting_period_id: payment.id_acc_accounting_period, + tracking_categories: payment.tracking_categories, + field_mappings: field_mappings, + remote_id: payment.remote_id, + remote_updated_at: payment.remote_updated_at, + created_at: payment.created_at, + modified_at: payment.modified_at, + line_items: lineItems.map((item) => ({ + id: item.acc_payments_line_item, + applied_amount: item.applied_amount + ? Number(item.applied_amount) + : undefined, + applied_date: item.applied_date, + related_object_id: item.related_object_id, + related_object_type: item.related_object_type, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: payment.id_acc_payment }, + }); + unifiedPayment.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.payment.pull', + method: 'GET', + url: '/accounting/payment', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedPayment; + } catch (error) { + throw error; + } } async getPayments( @@ -58,7 +195,111 @@ export class PaymentService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingPaymentOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const payments = await this.prisma.acc_payments.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_payment: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = payments.length > limit; + if (hasNextPage) payments.pop(); + + const unifiedPayments = await Promise.all( + payments.map(async (payment) => { + const lineItems = await this.prisma.acc_payments_line_items.findMany({ + where: { id_acc_payment: payment.id_acc_payment }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: payment.id_acc_payment }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPayment: UnifiedAccountingPaymentOutput = { + id: payment.id_acc_payment, + invoice_id: payment.id_acc_invoice, + transaction_date: payment.transaction_date, + contact_id: payment.id_acc_contact, + account_id: payment.id_acc_account, + currency: payment.currency as CurrencyCode, + exchange_rate: payment.exchange_rate, + total_amount: payment.total_amount + ? Number(payment.total_amount) + : undefined, + type: payment.type, + company_info_id: payment.id_acc_company_info, + accounting_period_id: payment.id_acc_accounting_period, + tracking_categories: payment.tracking_categories, + field_mappings: field_mappings, + remote_id: payment.remote_id, + remote_updated_at: payment.remote_updated_at, + created_at: payment.created_at, + modified_at: payment.modified_at, + line_items: lineItems.map((item) => ({ + id: item.acc_payments_line_item, + applied_amount: item.applied_amount + ? Number(item.applied_amount) + : undefined, + applied_date: item.applied_date, + related_object_id: item.related_object_id, + related_object_type: item.related_object_type, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: payment.id_acc_payment }, + }); + unifiedPayment.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedPayment; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.payment.pull', + method: 'GET', + url: '/accounting/payments', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedPayments, + next_cursor: hasNextPage + ? payments[payments.length - 1].id_acc_payment + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/payment/sync/sync.service.ts b/packages/api/src/accounting/payment/sync/sync.service.ts index 1a34b3669..cf1da5205 100644 --- a/packages/api/src/accounting/payment/sync/sync.service.ts +++ b/packages/api/src/accounting/payment/sync/sync.service.ts @@ -1,16 +1,24 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalPaymentOutput } from '@@core/utils/types/original/original.accounting'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_payments as AccPayment } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingPaymentOutput } from '../types/model.unified'; import { IPaymentService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { + LineItem, + UnifiedAccountingPaymentOutput, +} from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,23 +28,210 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'payment', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting payments...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IPaymentService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingPaymentOutput, + OriginalPaymentOutput, + IPaymentService + >(integrationId, linkedUserId, 'accounting', 'payment', service, []); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + payments: UnifiedAccountingPaymentOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const paymentResults: AccPayment[] = []; + + for (let i = 0; i < payments.length; i++) { + const payment = payments[i]; + const originId = payment.remote_id; + + let existingPayment = await this.prisma.acc_payments.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const paymentData = { + id_acc_invoice: payment.invoice_id, + transaction_date: payment.transaction_date, + id_acc_contact: payment.contact_id, + id_acc_account: payment.account_id, + currency: payment.currency as CurrencyCode, + exchange_rate: payment.exchange_rate, + total_amount: payment.total_amount + ? Number(payment.total_amount) + : null, + type: payment.type, + remote_updated_at: payment.remote_updated_at, + id_acc_company_info: payment.company_info_id, + id_acc_accounting_period: payment.accounting_period_id, + tracking_categories: payment.tracking_categories, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingPayment) { + existingPayment = await this.prisma.acc_payments.update({ + where: { id_acc_payment: existingPayment.id_acc_payment }, + data: paymentData, + }); + } else { + existingPayment = await this.prisma.acc_payments.create({ + data: { + ...paymentData, + id_acc_payment: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + paymentResults.push(existingPayment); + + // Process field mappings + await this.ingestService.processFieldMappings( + payment.field_mappings, + existingPayment.id_acc_payment, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingPayment.id_acc_payment, + remote_data[i], + ); + + // Handle line items + if (payment.line_items && payment.line_items.length > 0) { + await this.processPaymentLineItems( + existingPayment.id_acc_payment, + payment.line_items, + connection_id, + ); + } + } + + return paymentResults; + } catch (error) { + throw error; + } } - // Additional methods and logic + private async processPaymentLineItems( + paymentId: string, + lineItems: LineItem[], + connectionId: string, + ): Promise { + for (const lineItem of lineItems) { + const lineItemData = { + id_acc_payment: paymentId, + applied_amount: lineItem.applied_amount + ? Number(lineItem.applied_amount) + : null, + applied_date: lineItem.applied_date, + related_object_id: lineItem.related_object_id, + related_object_type: lineItem.related_object_type, + remote_id: lineItem.remote_id, + modified_at: new Date(), + id_connection: connectionId, + }; + + const existingLineItem = + await this.prisma.acc_payments_line_items.findFirst({ + where: { + remote_id: lineItem.remote_id, + id_acc_payment: paymentId, + }, + }); + + if (existingLineItem) { + await this.prisma.acc_payments_line_items.update({ + where: { + acc_payments_line_item: existingLineItem.acc_payments_line_item, + }, + data: lineItemData, + }); + } else { + await this.prisma.acc_payments_line_items.create({ + data: { + ...lineItemData, + acc_payments_line_item: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing line items that are not in the current set + const currentRemoteIds = lineItems.map((item) => item.remote_id); + await this.prisma.acc_payments_line_items.deleteMany({ + where: { + id_acc_payment: paymentId, + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); + } } diff --git a/packages/api/src/accounting/payment/types/index.ts b/packages/api/src/accounting/payment/types/index.ts index 4d1b0ef89..c76e30747 100644 --- a/packages/api/src/accounting/payment/types/index.ts +++ b/packages/api/src/accounting/payment/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedAccountingPaymentInput, UnifiedAccountingPaymentOutput } from './model.unified'; +import { + UnifiedAccountingPaymentInput, + UnifiedAccountingPaymentOutput, +} from './model.unified'; import { OriginalPaymentOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IPaymentService { addPayment( @@ -9,10 +13,7 @@ export interface IPaymentService { linkedUserId: string, ): Promise>; - syncPayments( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IPaymentMapper { diff --git a/packages/api/src/accounting/payment/types/model.unified.ts b/packages/api/src/accounting/payment/types/model.unified.ts index 66c1bd3a8..aa85513dd 100644 --- a/packages/api/src/accounting/payment/types/model.unified.ts +++ b/packages/api/src/accounting/payment/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -8,6 +9,76 @@ import { IsArray, } from 'class-validator'; +export class LineItem { + @ApiPropertyOptional({ + type: Number, + example: 5000, + nullable: true, + description: 'The applied amount in cents', + }) + @IsNumber() + @IsOptional() + applied_amount?: number; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + nullable: true, + description: 'The date when the amount was applied', + }) + @IsDateString() + @IsOptional() + applied_date?: Date; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the related object (e.g., invoice)', + }) + @IsUUID() + @IsOptional() + related_object_id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'invoice', + nullable: true, + description: 'The type of the related object', + }) + @IsString() + @IsOptional() + related_object_type?: string; + + @ApiPropertyOptional({ + type: String, + example: 'line_item_1234', + nullable: true, + description: 'The remote ID of the line item', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The created date of the line item', + }) + @IsDateString() + @IsOptional() + created_at?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The last modified date of the line item', + }) + @IsDateString() + @IsOptional() + modified_at?: Date; +} + export class UnifiedAccountingPaymentInput { @ApiPropertyOptional({ type: String, @@ -17,17 +88,17 @@ export class UnifiedAccountingPaymentInput { }) @IsUUID() @IsOptional() - id_acc_invoice?: string; + invoice_id?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date of the transaction', }) @IsDateString() @IsOptional() - transaction_date?: string; + transaction_date?: Date; @ApiPropertyOptional({ type: String, @@ -37,7 +108,7 @@ export class UnifiedAccountingPaymentInput { }) @IsUUID() @IsOptional() - id_acc_contact?: string; + contact_id?: string; @ApiPropertyOptional({ type: String, @@ -47,17 +118,18 @@ export class UnifiedAccountingPaymentInput { }) @IsUUID() @IsOptional() - id_acc_account?: string; + account_id?: string; @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the payment', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -97,7 +169,7 @@ export class UnifiedAccountingPaymentInput { }) @IsUUID() @IsOptional() - id_acc_company_info?: string; + company_info_id?: string; @ApiPropertyOptional({ type: String, @@ -107,19 +179,28 @@ export class UnifiedAccountingPaymentInput { }) @IsUUID() @IsOptional() - id_acc_accounting_period?: string; + accounting_period_id?: string; @ApiPropertyOptional({ type: [String], - example: ['Category1', 'Category2'], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], nullable: true, - description: 'The tracking categories associated with the payment', + description: + 'The UUIDs of the tracking categories associated with the payment', }) @IsArray() @IsString({ each: true }) @IsOptional() tracking_categories?: string[]; + @ApiPropertyOptional({ + type: [LineItem], + description: 'The line items associated with this payment', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; + @ApiPropertyOptional({ type: Object, example: { @@ -156,7 +237,7 @@ export class UnifiedAccountingPaymentOutput extends UnifiedAccountingPaymentInpu remote_id?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -164,7 +245,7 @@ export class UnifiedAccountingPaymentOutput extends UnifiedAccountingPaymentInpu }) @IsDateString() @IsOptional() - remote_updated_at?: string; + remote_updated_at?: Date; @ApiPropertyOptional({ type: Object, @@ -181,22 +262,22 @@ export class UnifiedAccountingPaymentOutput extends UnifiedAccountingPaymentInpu remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the payment record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the payment record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/phonenumber/services/phonenumber.service.ts b/packages/api/src/accounting/phonenumber/services/phonenumber.service.ts index 7f943a277..6bf7f5882 100644 --- a/packages/api/src/accounting/phonenumber/services/phonenumber.service.ts +++ b/packages/api/src/accounting/phonenumber/services/phonenumber.service.ts @@ -9,12 +9,8 @@ import { UnifiedAccountingPhonenumberInput, UnifiedAccountingPhonenumberOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalPhoneNumberOutput } from '@@core/utils/types/original/original.accounting'; - -import { IPhoneNumberService } from '../types'; @Injectable() export class PhoneNumberService { @@ -29,14 +25,75 @@ export class PhoneNumberService { } async getPhoneNumber( - id_phonenumbering_phonenumber: string, + id_acc_phone_number: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const phoneNumber = await this.prisma.acc_phone_numbers.findUnique({ + where: { id_acc_phone_number: id_acc_phone_number }, + }); + + if (!phoneNumber) { + throw new Error( + `Phone number with ID ${id_acc_phone_number} not found.`, + ); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: phoneNumber.id_acc_phone_number }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPhoneNumber: UnifiedAccountingPhonenumberOutput = { + id: phoneNumber.id_acc_phone_number, + number: phoneNumber.number, + type: phoneNumber.type, + company_info_id: phoneNumber.id_acc_company_info, + contact_id: phoneNumber.id_acc_contact, + field_mappings: field_mappings, + created_at: phoneNumber.created_at, + modified_at: phoneNumber.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: phoneNumber.id_acc_phone_number }, + }); + unifiedPhoneNumber.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.phone_number.pull', + method: 'GET', + url: '/accounting/phone_number', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedPhoneNumber; + } catch (error) { + throw error; + } } async getPhoneNumbers( @@ -47,7 +104,84 @@ export class PhoneNumberService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingPhonenumberOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const phoneNumbers = await this.prisma.acc_phone_numbers.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_phone_number: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = phoneNumbers.length > limit; + if (hasNextPage) phoneNumbers.pop(); + + const unifiedPhoneNumbers = await Promise.all( + phoneNumbers.map(async (phoneNumber) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: phoneNumber.id_acc_phone_number }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPhoneNumber: UnifiedAccountingPhonenumberOutput = { + id: phoneNumber.id_acc_phone_number, + number: phoneNumber.number, + type: phoneNumber.type, + company_info_id: phoneNumber.id_acc_company_info, + contact_id: phoneNumber.id_acc_contact, + field_mappings: field_mappings, + created_at: phoneNumber.created_at, + modified_at: phoneNumber.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: phoneNumber.id_acc_phone_number }, + }); + unifiedPhoneNumber.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedPhoneNumber; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.phone_number.pull', + method: 'GET', + url: '/accounting/phone_numbers', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedPhoneNumbers, + next_cursor: hasNextPage + ? phoneNumbers[phoneNumbers.length - 1].id_acc_phone_number + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/phonenumber/sync/sync.service.ts b/packages/api/src/accounting/phonenumber/sync/sync.service.ts index 5d4ddd4ee..2d1e73807 100644 --- a/packages/api/src/accounting/phonenumber/sync/sync.service.ts +++ b/packages/api/src/accounting/phonenumber/sync/sync.service.ts @@ -1,7 +1,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingPhonenumberOutput } from '../types/model.unified'; import { IPhoneNumberService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_phone_numbers as AccPhoneNumber } from '@prisma/client'; +import { OriginalPhoneNumberOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,23 +25,137 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'phone_number', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting phone numbers...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } } - saveToDb( + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IPhoneNumberService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingPhonenumberOutput, + OriginalPhoneNumberOutput, + IPhoneNumberService + >(integrationId, linkedUserId, 'accounting', 'phone_number', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + phoneNumbers: UnifiedAccountingPhonenumberOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const phoneNumberResults: AccPhoneNumber[] = []; + + for (let i = 0; i < phoneNumbers.length; i++) { + const phoneNumber = phoneNumbers[i]; + const originId = phoneNumber.remote_id; + + let existingPhoneNumber = await this.prisma.acc_phone_numbers.findFirst( + { + where: { + remote_id: originId, + id_connection: connection_id, + }, + }, + ); + + const phoneNumberData = { + number: phoneNumber.number, + type: phoneNumber.type, + id_acc_company_info: phoneNumber.company_info_id, + id_acc_contact: phoneNumber.contact_id, + modified_at: new Date(), + }; + + if (existingPhoneNumber) { + existingPhoneNumber = await this.prisma.acc_phone_numbers.update({ + where: { + id_acc_phone_number: existingPhoneNumber.id_acc_phone_number, + }, + data: phoneNumberData, + }); + } else { + existingPhoneNumber = await this.prisma.acc_phone_numbers.create({ + data: { + ...phoneNumberData, + id_acc_phone_number: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + phoneNumberResults.push(existingPhoneNumber); + + // Process field mappings + await this.ingestService.processFieldMappings( + phoneNumber.field_mappings, + existingPhoneNumber.id_acc_phone_number, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingPhoneNumber.id_acc_phone_number, + remote_data[i], + ); + } + + return phoneNumberResults; + } catch (error) { + throw error; + } } - // Additional methods and logic } diff --git a/packages/api/src/accounting/phonenumber/types/index.ts b/packages/api/src/accounting/phonenumber/types/index.ts index 74525dd7c..c2c962a38 100644 --- a/packages/api/src/accounting/phonenumber/types/index.ts +++ b/packages/api/src/accounting/phonenumber/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalPhoneNumberOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IPhoneNumberService { addPhoneNumber( @@ -12,10 +13,7 @@ export interface IPhoneNumberService { linkedUserId: string, ): Promise>; - syncPhoneNumbers( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IPhoneNumberMapper { @@ -34,5 +32,7 @@ export interface IPhoneNumberMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedAccountingPhonenumberOutput | UnifiedAccountingPhonenumberOutput[] + >; } diff --git a/packages/api/src/accounting/phonenumber/types/model.unified.ts b/packages/api/src/accounting/phonenumber/types/model.unified.ts index 59b54aaaf..fe8cfedd8 100644 --- a/packages/api/src/accounting/phonenumber/types/model.unified.ts +++ b/packages/api/src/accounting/phonenumber/types/model.unified.ts @@ -30,7 +30,7 @@ export class UnifiedAccountingPhonenumberInput { }) @IsUUID() @IsOptional() - id_acc_company_info?: string; + company_info_id?: string; @ApiPropertyOptional({ type: String, @@ -39,7 +39,7 @@ export class UnifiedAccountingPhonenumberInput { description: 'The UUID of the associated contact', }) @IsUUID() - id_acc_contact: string; + contact_id: string; @ApiPropertyOptional({ type: Object, @@ -92,22 +92,22 @@ export class UnifiedAccountingPhonenumberOutput extends UnifiedAccountingPhonenu remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the phone number record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the phone number record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/purchaseorder/services/purchaseorder.service.ts b/packages/api/src/accounting/purchaseorder/services/purchaseorder.service.ts index cbc0c7273..d40c9b4aa 100644 --- a/packages/api/src/accounting/purchaseorder/services/purchaseorder.service.ts +++ b/packages/api/src/accounting/purchaseorder/services/purchaseorder.service.ts @@ -1,20 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; import { UnifiedAccountingPurchaseorderInput, UnifiedAccountingPurchaseorderOutput, } from '../types/model.unified'; - -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalPurchaseOrderOutput } from '@@core/utils/types/original/original.accounting'; - -import { IPurchaseOrderService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class PurchaseOrderService { @@ -36,18 +31,182 @@ export class PurchaseOrderService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addPurchaseOrder( + unifiedPurchaseOrderData, + linkedUserId, + ); + + const savedPurchaseOrder = await this.prisma.acc_purchase_orders.create({ + data: { + id_acc_purchase_order: uuidv4(), + ...unifiedPurchaseOrderData, + total_amount: unifiedPurchaseOrderData.total_amount + ? Number(unifiedPurchaseOrderData.total_amount) + : null, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + }, + }); + + // Save line items + if (unifiedPurchaseOrderData.line_items) { + await Promise.all( + unifiedPurchaseOrderData.line_items.map(async (lineItem) => { + await this.prisma.acc_purchase_orders_line_items.create({ + data: { + id_acc_purchase_orders_line_item: uuidv4(), + id_acc_purchase_order: savedPurchaseOrder.id_acc_purchase_order, + ...lineItem, + unit_price: lineItem.unit_price + ? Number(lineItem.unit_price) + : null, + quantity: lineItem.quantity ? Number(lineItem.quantity) : null, + tax_amount: lineItem.tax_amount + ? Number(lineItem.tax_amount) + : null, + total_line_amount: lineItem.total_line_amount + ? Number(lineItem.total_line_amount) + : null, + created_at: new Date(), + modified_at: new Date(), + }, + }); + }), + ); + } + + const result: UnifiedAccountingPurchaseorderOutput = { + ...savedPurchaseOrder, + currency: savedPurchaseOrder.currency as CurrencyCode, + id: savedPurchaseOrder.id_acc_purchase_order, + total_amount: savedPurchaseOrder.total_amount + ? Number(savedPurchaseOrder.total_amount) + : undefined, + line_items: unifiedPurchaseOrderData.line_items, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getPurchaseOrder( - id_purchaseordering_purchaseorder: string, + id_acc_purchase_order: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const purchaseOrder = await this.prisma.acc_purchase_orders.findUnique({ + where: { id_acc_purchase_order: id_acc_purchase_order }, + }); + + if (!purchaseOrder) { + throw new Error( + `Purchase order with ID ${id_acc_purchase_order} not found.`, + ); + } + + const lineItems = + await this.prisma.acc_purchase_orders_line_items.findMany({ + where: { id_acc_purchase_order: id_acc_purchase_order }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: purchaseOrder.id_acc_purchase_order }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPurchaseOrder: UnifiedAccountingPurchaseorderOutput = { + id: purchaseOrder.id_acc_purchase_order, + status: purchaseOrder.status, + issue_date: purchaseOrder.issue_date, + purchase_order_number: purchaseOrder.purchase_order_number, + delivery_date: purchaseOrder.delivery_date, + delivery_address: purchaseOrder.delivery_address, + customer: purchaseOrder.customer, + vendor: purchaseOrder.vendor, + memo: purchaseOrder.memo, + company_id: purchaseOrder.company, + total_amount: purchaseOrder.total_amount + ? Number(purchaseOrder.total_amount) + : undefined, + currency: purchaseOrder.currency as CurrencyCode, + exchange_rate: purchaseOrder.exchange_rate, + tracking_categories: purchaseOrder.tracking_categories, + accounting_period_id: purchaseOrder.id_acc_accounting_period, + field_mappings: field_mappings, + remote_id: purchaseOrder.remote_id, + remote_created_at: purchaseOrder.remote_created_at, + remote_updated_at: purchaseOrder.remote_updated_at, + created_at: purchaseOrder.created_at, + modified_at: purchaseOrder.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_purchase_orders_line_item, + description: item.description, + unit_price: item.unit_price ? Number(item.unit_price) : undefined, + quantity: item.quantity ? Number(item.quantity) : undefined, + tracking_categories: item.tracking_categories, + tax_amount: item.tax_amount ? Number(item.tax_amount) : undefined, + total_line_amount: item.total_line_amount + ? Number(item.total_line_amount) + : undefined, + currency: item.currency as CurrencyCode, + exchange_rate: item.exchange_rate, + id_acc_account: item.id_acc_account, + id_acc_company: item.id_acc_company, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: purchaseOrder.id_acc_purchase_order }, + }); + unifiedPurchaseOrder.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.purchase_order.pull', + method: 'GET', + url: '/accounting/purchase_order', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedPurchaseOrder; + } catch (error) { + throw error; + } } async getPurchaseOrders( @@ -58,7 +217,128 @@ export class PurchaseOrderService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingPurchaseorderOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const purchaseOrders = await this.prisma.acc_purchase_orders.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_purchase_order: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = purchaseOrders.length > limit; + if (hasNextPage) purchaseOrders.pop(); + + const unifiedPurchaseOrders = await Promise.all( + purchaseOrders.map(async (purchaseOrder) => { + const lineItems = + await this.prisma.acc_purchase_orders_line_items.findMany({ + where: { + id_acc_purchase_order: purchaseOrder.id_acc_purchase_order, + }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: purchaseOrder.id_acc_purchase_order, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPurchaseOrder: UnifiedAccountingPurchaseorderOutput = { + id: purchaseOrder.id_acc_purchase_order, + status: purchaseOrder.status, + issue_date: purchaseOrder.issue_date, + purchase_order_number: purchaseOrder.purchase_order_number, + delivery_date: purchaseOrder.delivery_date, + delivery_address: purchaseOrder.delivery_address, + customer: purchaseOrder.customer, + vendor: purchaseOrder.vendor, + memo: purchaseOrder.memo, + company_id: purchaseOrder.company, + total_amount: purchaseOrder.total_amount + ? Number(purchaseOrder.total_amount) + : undefined, + currency: purchaseOrder.currency as CurrencyCode, + exchange_rate: purchaseOrder.exchange_rate, + tracking_categories: purchaseOrder.tracking_categories, + accounting_period_id: purchaseOrder.id_acc_accounting_period, + field_mappings: field_mappings, + remote_id: purchaseOrder.remote_id, + remote_created_at: purchaseOrder.remote_created_at, + remote_updated_at: purchaseOrder.remote_updated_at, + created_at: purchaseOrder.created_at, + modified_at: purchaseOrder.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_purchase_orders_line_item, + description: item.description, + unit_price: item.unit_price ? Number(item.unit_price) : undefined, + quantity: item.quantity ? Number(item.quantity) : undefined, + tracking_categories: item.tracking_categories, + tax_amount: item.tax_amount ? Number(item.tax_amount) : undefined, + total_line_amount: item.total_line_amount + ? Number(item.total_line_amount) + : undefined, + currency: item.currency as CurrencyCode, + exchange_rate: item.exchange_rate, + id_acc_account: item.id_acc_account, + id_acc_company: item.id_acc_company, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: purchaseOrder.id_acc_purchase_order, + }, + }); + unifiedPurchaseOrder.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedPurchaseOrder; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.purchase_order.pull', + method: 'GET', + url: '/accounting/purchase_orders', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedPurchaseOrders, + next_cursor: hasNextPage + ? purchaseOrders[purchaseOrders.length - 1].id_acc_purchase_order + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/purchaseorder/sync/sync.service.ts b/packages/api/src/accounting/purchaseorder/sync/sync.service.ts index 5bb870aac..f5e75eeb3 100644 --- a/packages/api/src/accounting/purchaseorder/sync/sync.service.ts +++ b/packages/api/src/accounting/purchaseorder/sync/sync.service.ts @@ -1,16 +1,24 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalPurchaseOrderOutput } from '@@core/utils/types/original/original.accounting'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_purchase_orders as AccPurchaseOrder } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingPurchaseorderOutput } from '../types/model.unified'; import { IPurchaseOrderService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { + LineItem, + UnifiedAccountingPurchaseorderOutput, +} from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,22 +28,229 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'purchase_order', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting purchase orders...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IPurchaseOrderService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingPurchaseorderOutput, + OriginalPurchaseOrderOutput, + IPurchaseOrderService + >( + integrationId, + linkedUserId, + 'accounting', + 'purchase_order', + service, + [], + ); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + purchaseOrders: UnifiedAccountingPurchaseorderOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const purchaseOrderResults: AccPurchaseOrder[] = []; + + for (let i = 0; i < purchaseOrders.length; i++) { + const purchaseOrder = purchaseOrders[i]; + const originId = purchaseOrder.remote_id; + + let existingPurchaseOrder = + await this.prisma.acc_purchase_orders.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const purchaseOrderData = { + status: purchaseOrder.status, + issue_date: purchaseOrder.issue_date, + purchase_order_number: purchaseOrder.purchase_order_number, + delivery_date: purchaseOrder.delivery_date, + delivery_address: purchaseOrder.delivery_address, + customer: purchaseOrder.customer, + vendor: purchaseOrder.vendor, + memo: purchaseOrder.memo, + company: purchaseOrder.company_id, + total_amount: purchaseOrder.total_amount + ? Number(purchaseOrder.total_amount) + : null, + currency: purchaseOrder.currency as CurrencyCode, + exchange_rate: purchaseOrder.exchange_rate, + tracking_categories: purchaseOrder.tracking_categories, + remote_created_at: purchaseOrder.remote_created_at, + remote_updated_at: purchaseOrder.remote_updated_at, + id_acc_accounting_period: purchaseOrder.accounting_period_id, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingPurchaseOrder) { + existingPurchaseOrder = await this.prisma.acc_purchase_orders.update({ + where: { + id_acc_purchase_order: + existingPurchaseOrder.id_acc_purchase_order, + }, + data: purchaseOrderData, + }); + } else { + existingPurchaseOrder = await this.prisma.acc_purchase_orders.create({ + data: { + ...purchaseOrderData, + id_acc_purchase_order: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + purchaseOrderResults.push(existingPurchaseOrder); + + // Process field mappings + await this.ingestService.processFieldMappings( + purchaseOrder.field_mappings, + existingPurchaseOrder.id_acc_purchase_order, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingPurchaseOrder.id_acc_purchase_order, + remote_data[i], + ); + + // Handle line items + if (purchaseOrder.line_items && purchaseOrder.line_items.length > 0) { + await this.processPurchaseOrderLineItems( + existingPurchaseOrder.id_acc_purchase_order, + purchaseOrder.line_items, + ); + } + } + + return purchaseOrderResults; + } catch (error) { + throw error; + } + } + + private async processPurchaseOrderLineItems( + purchaseOrderId: string, + lineItems: LineItem[], + ): Promise { + for (const lineItem of lineItems) { + const lineItemData = { + description: lineItem.description, + unit_price: lineItem.unit_price ? Number(lineItem.unit_price) : null, + quantity: lineItem.quantity ? Number(lineItem.quantity) : null, + tracking_categories: lineItem.tracking_categories || [], + tax_amount: lineItem.tax_amount ? Number(lineItem.tax_amount) : null, + total_line_amount: lineItem.total_line_amount + ? Number(lineItem.total_line_amount) + : null, + currency: lineItem.currency as CurrencyCode, + exchange_rate: lineItem.exchange_rate, + id_acc_account: lineItem.account_id, + id_acc_company: lineItem.company_id, + remote_id: lineItem.remote_id, + modified_at: new Date(), + id_acc_purchase_order: purchaseOrderId, + }; + + const existingLineItem = + await this.prisma.acc_purchase_orders_line_items.findFirst({ + where: { + remote_id: lineItem.remote_id, + id_acc_purchase_order: purchaseOrderId, + }, + }); + + if (existingLineItem) { + await this.prisma.acc_purchase_orders_line_items.update({ + where: { + id_acc_purchase_orders_line_item: + existingLineItem.id_acc_purchase_orders_line_item, + }, + data: lineItemData, + }); + } else { + await this.prisma.acc_purchase_orders_line_items.create({ + data: { + ...lineItemData, + id_acc_purchase_orders_line_item: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing line items that are not in the current set + const currentRemoteIds = lineItems.map((item) => item.remote_id); + await this.prisma.acc_purchase_orders_line_items.deleteMany({ + where: { + id_acc_purchase_order: purchaseOrderId, + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); } - // Additional methods and logic } diff --git a/packages/api/src/accounting/purchaseorder/types/index.ts b/packages/api/src/accounting/purchaseorder/types/index.ts index 396b042f6..d90499a97 100644 --- a/packages/api/src/accounting/purchaseorder/types/index.ts +++ b/packages/api/src/accounting/purchaseorder/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalPurchaseOrderOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IPurchaseOrderService { addPurchaseOrder( @@ -12,10 +13,7 @@ export interface IPurchaseOrderService { linkedUserId: string, ): Promise>; - syncPurchaseOrders( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IPurchaseOrderMapper { @@ -34,5 +32,8 @@ export interface IPurchaseOrderMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + | UnifiedAccountingPurchaseorderOutput + | UnifiedAccountingPurchaseorderOutput[] + >; } diff --git a/packages/api/src/accounting/purchaseorder/types/model.unified.ts b/packages/api/src/accounting/purchaseorder/types/model.unified.ts index 117ce9802..8d7de7efd 100644 --- a/packages/api/src/accounting/purchaseorder/types/model.unified.ts +++ b/packages/api/src/accounting/purchaseorder/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -8,6 +9,139 @@ import { IsArray, } from 'class-validator'; +export class LineItem { + @ApiPropertyOptional({ + type: String, + example: 'Item description', + nullable: true, + description: 'Description of the line item', + }) + @IsString() + @IsOptional() + description?: string; + + @ApiPropertyOptional({ + type: Number, + example: 1000, + nullable: true, + description: 'The unit price of the item in cents', + }) + @IsNumber() + @IsOptional() + unit_price?: number; + + @ApiPropertyOptional({ + type: Number, + example: 5, + nullable: true, + description: 'The quantity of the item', + }) + @IsNumber() + @IsOptional() + quantity?: number; + + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: + 'The UUIDs of the tracking categories associated with the line item', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: Number, + example: 500, + nullable: true, + description: 'The tax amount for the line item in cents', + }) + @IsNumber() + @IsOptional() + tax_amount?: number; + + @ApiPropertyOptional({ + type: Number, + example: 5500, + nullable: true, + description: 'The total amount for the line item in cents', + }) + @IsNumber() + @IsOptional() + total_line_amount?: number; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + nullable: true, + enum: CurrencyCode, + description: 'The currency of the line item', + }) + @IsString() + @IsOptional() + currency?: CurrencyCode; + + @ApiPropertyOptional({ + type: String, + example: '1.0', + nullable: true, + description: 'The exchange rate for the line item', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated account', + }) + @IsUUID() + @IsOptional() + account_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company', + }) + @IsUUID() + @IsOptional() + company_id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'remote_line_item_id_1234', + nullable: true, + description: 'The remote ID of the line item', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The created date of the line item', + }) + @IsDateString() + @IsOptional() + created_at?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The last modified date of the line item', + }) + @IsDateString() + @IsOptional() + modified_at?: Date; +} + export class UnifiedAccountingPurchaseorderInput { @ApiPropertyOptional({ type: String, @@ -20,14 +154,14 @@ export class UnifiedAccountingPurchaseorderInput { status?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The issue date of the purchase order', }) @IsDateString() @IsOptional() - issue_date?: string; + issue_date?: Date; @ApiPropertyOptional({ type: String, @@ -40,14 +174,14 @@ export class UnifiedAccountingPurchaseorderInput { purchase_order_number?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-07-15T12:00:00Z', nullable: true, description: 'The delivery date for the purchase order', }) @IsDateString() @IsOptional() - delivery_date?: string; + delivery_date?: Date; @ApiPropertyOptional({ type: String, @@ -97,7 +231,7 @@ export class UnifiedAccountingPurchaseorderInput { }) @IsUUID() @IsOptional() - company?: string; + company_id?: string; @ApiPropertyOptional({ type: Number, @@ -112,12 +246,13 @@ export class UnifiedAccountingPurchaseorderInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the purchase order', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -131,9 +266,10 @@ export class UnifiedAccountingPurchaseorderInput { @ApiPropertyOptional({ type: [String], - example: ['Category1', 'Category2'], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], nullable: true, - description: 'The tracking categories associated with the purchase order', + description: + 'The UUIDs of the tracking categories associated with the purchase order', }) @IsArray() @IsString({ each: true }) @@ -148,7 +284,15 @@ export class UnifiedAccountingPurchaseorderInput { }) @IsUUID() @IsOptional() - id_acc_accounting_period?: string; + accounting_period_id?: string; + + @ApiPropertyOptional({ + type: [LineItem], + description: 'The line items associated with this purchase order', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; @ApiPropertyOptional({ type: Object, @@ -187,7 +331,7 @@ export class UnifiedAccountingPurchaseorderOutput extends UnifiedAccountingPurch remote_id?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -195,10 +339,10 @@ export class UnifiedAccountingPurchaseorderOutput extends UnifiedAccountingPurch }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -206,7 +350,7 @@ export class UnifiedAccountingPurchaseorderOutput extends UnifiedAccountingPurch }) @IsDateString() @IsOptional() - remote_updated_at?: string; + remote_updated_at?: Date; @ApiPropertyOptional({ type: Object, @@ -223,22 +367,22 @@ export class UnifiedAccountingPurchaseorderOutput extends UnifiedAccountingPurch remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the purchase order record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the purchase order record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/taxrate/services/taxrate.service.ts b/packages/api/src/accounting/taxrate/services/taxrate.service.ts index cbff3b7d7..d0478dfed 100644 --- a/packages/api/src/accounting/taxrate/services/taxrate.service.ts +++ b/packages/api/src/accounting/taxrate/services/taxrate.service.ts @@ -1,20 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedAccountingTaxrateInput, - UnifiedAccountingTaxrateOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedAccountingTaxrateOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalTaxRateOutput } from '@@core/utils/types/original/original.accounting'; - -import { ITaxRateService } from '../types'; @Injectable() export class TaxRateService { @@ -29,14 +20,78 @@ export class TaxRateService { } async getTaxRate( - id_taxrateing_taxrate: string, + id_acc_tax_rate: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const taxRate = await this.prisma.acc_tax_rates.findUnique({ + where: { id_acc_tax_rate: id_acc_tax_rate }, + }); + + if (!taxRate) { + throw new Error(`Tax rate with ID ${id_acc_tax_rate} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: taxRate.id_acc_tax_rate }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTaxRate: UnifiedAccountingTaxrateOutput = { + id: taxRate.id_acc_tax_rate, + description: taxRate.description, + total_tax_ratge: taxRate.total_tax_ratge + ? Number(taxRate.total_tax_ratge) + : undefined, + effective_tax_rate: taxRate.effective_tax_rate + ? Number(taxRate.effective_tax_rate) + : undefined, + company_id: taxRate.company, + field_mappings: field_mappings, + remote_id: taxRate.remote_id, + created_at: taxRate.created_at, + modified_at: taxRate.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: taxRate.id_acc_tax_rate }, + }); + unifiedTaxRate.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.tax_rate.pull', + method: 'GET', + url: '/accounting/tax_rate', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedTaxRate; + } catch (error) { + throw error; + } } async getTaxRates( @@ -47,7 +102,89 @@ export class TaxRateService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingTaxrateOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const taxRates = await this.prisma.acc_tax_rates.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_tax_rate: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = taxRates.length > limit; + if (hasNextPage) taxRates.pop(); + + const unifiedTaxRates = await Promise.all( + taxRates.map(async (taxRate) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: taxRate.id_acc_tax_rate }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTaxRate: UnifiedAccountingTaxrateOutput = { + id: taxRate.id_acc_tax_rate, + description: taxRate.description, + total_tax_ratge: taxRate.total_tax_ratge + ? Number(taxRate.total_tax_ratge) + : undefined, + effective_tax_rate: taxRate.effective_tax_rate + ? Number(taxRate.effective_tax_rate) + : undefined, + company_id: taxRate.company, + field_mappings: field_mappings, + remote_id: taxRate.remote_id, + created_at: taxRate.created_at, + modified_at: taxRate.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: taxRate.id_acc_tax_rate }, + }); + unifiedTaxRate.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedTaxRate; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.tax_rate.pull', + method: 'GET', + url: '/accounting/tax_rates', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedTaxRates, + next_cursor: hasNextPage + ? taxRates[taxRates.length - 1].id_acc_tax_rate + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/taxrate/sync/sync.service.ts b/packages/api/src/accounting/taxrate/sync/sync.service.ts index 4de53339a..9c737358d 100644 --- a/packages/api/src/accounting/taxrate/sync/sync.service.ts +++ b/packages/api/src/accounting/taxrate/sync/sync.service.ts @@ -1,7 +1,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingTaxrateOutput } from '../types/model.unified'; import { ITaxRateService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_tax_rates as AccTaxRate } from '@prisma/client'; +import { OriginalTaxRateOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,22 +25,138 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'tax_rate', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting tax rates...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } } - saveToDb( + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ITaxRateService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingTaxrateOutput, + OriginalTaxRateOutput, + ITaxRateService + >(integrationId, linkedUserId, 'accounting', 'tax_rate', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + taxRates: UnifiedAccountingTaxrateOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const taxRateResults: AccTaxRate[] = []; + + for (let i = 0; i < taxRates.length; i++) { + const taxRate = taxRates[i]; + const originId = taxRate.remote_id; + + let existingTaxRate = await this.prisma.acc_tax_rates.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const taxRateData = { + description: taxRate.description, + total_tax_ratge: taxRate.total_tax_ratge + ? Number(taxRate.total_tax_ratge) + : null, + effective_tax_rate: taxRate.effective_tax_rate + ? Number(taxRate.effective_tax_rate) + : null, + company: taxRate.company_id, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingTaxRate) { + existingTaxRate = await this.prisma.acc_tax_rates.update({ + where: { id_acc_tax_rate: existingTaxRate.id_acc_tax_rate }, + data: taxRateData, + }); + } else { + existingTaxRate = await this.prisma.acc_tax_rates.create({ + data: { + ...taxRateData, + id_acc_tax_rate: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + taxRateResults.push(existingTaxRate); + + // Process field mappings + await this.ingestService.processFieldMappings( + taxRate.field_mappings, + existingTaxRate.id_acc_tax_rate, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingTaxRate.id_acc_tax_rate, + remote_data[i], + ); + } + + return taxRateResults; + } catch (error) { + throw error; + } } - // Additional methods and logic } diff --git a/packages/api/src/accounting/taxrate/types/index.ts b/packages/api/src/accounting/taxrate/types/index.ts index e56e41e65..2e0b8104c 100644 --- a/packages/api/src/accounting/taxrate/types/index.ts +++ b/packages/api/src/accounting/taxrate/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedAccountingTaxrateInput, UnifiedAccountingTaxrateOutput } from './model.unified'; +import { + UnifiedAccountingTaxrateInput, + UnifiedAccountingTaxrateOutput, +} from './model.unified'; import { OriginalTaxRateOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ITaxRateService { addTaxRate( @@ -9,10 +13,7 @@ export interface ITaxRateService { linkedUserId: string, ): Promise>; - syncTaxRates( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ITaxRateMapper { diff --git a/packages/api/src/accounting/taxrate/types/model.unified.ts b/packages/api/src/accounting/taxrate/types/model.unified.ts index 1cdbe6327..ae3da9762 100644 --- a/packages/api/src/accounting/taxrate/types/model.unified.ts +++ b/packages/api/src/accounting/taxrate/types/model.unified.ts @@ -46,7 +46,7 @@ export class UnifiedAccountingTaxrateInput { }) @IsUUID() @IsOptional() - company?: string; + company_id?: string; @ApiPropertyOptional({ type: Object, @@ -99,22 +99,22 @@ export class UnifiedAccountingTaxrateOutput extends UnifiedAccountingTaxrateInpu remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the tax rate record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the tax rate record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/trackingcategory/services/trackingcategory.service.ts b/packages/api/src/accounting/trackingcategory/services/trackingcategory.service.ts index c46282a5c..82cd563f6 100644 --- a/packages/api/src/accounting/trackingcategory/services/trackingcategory.service.ts +++ b/packages/api/src/accounting/trackingcategory/services/trackingcategory.service.ts @@ -1,20 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedAccountingTrackingcategoryInput, - UnifiedAccountingTrackingcategoryOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedAccountingTrackingcategoryOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalTrackingCategoryOutput } from '@@core/utils/types/original/original.accounting'; - -import { ITrackingCategoryService } from '../types'; @Injectable() export class TrackingCategoryService { @@ -29,17 +20,84 @@ export class TrackingCategoryService { } async getTrackingCategory( - id_trackingcategorying_trackingcategory: string, + id_acc_tracking_category: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const trackingCategory = + await this.prisma.acc_tracking_categories.findUnique({ + where: { id_acc_tracking_category: id_acc_tracking_category }, + }); + + if (!trackingCategory) { + throw new Error( + `Tracking category with ID ${id_acc_tracking_category} not found.`, + ); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: trackingCategory.id_acc_tracking_category, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTrackingCategory: UnifiedAccountingTrackingcategoryOutput = { + id: trackingCategory.id_acc_tracking_category, + name: trackingCategory.name, + status: trackingCategory.status, + category_type: trackingCategory.category_type, + parent_category: trackingCategory.parent_category, + field_mappings: field_mappings, + remote_id: trackingCategory.remote_id, + created_at: trackingCategory.created_at, + modified_at: trackingCategory.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: trackingCategory.id_acc_tracking_category, + }, + }); + unifiedTrackingCategory.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.tracking_category.pull', + method: 'GET', + url: '/accounting/tracking_category', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedTrackingCategory; + } catch (error) { + throw error; + } } - async getTrackingCategorys( + async getTrackingCategories( connectionId: string, projectId: string, integrationId: string, @@ -47,7 +105,92 @@ export class TrackingCategoryService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingTrackingcategoryOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const trackingCategories = + await this.prisma.acc_tracking_categories.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_tracking_category: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = trackingCategories.length > limit; + if (hasNextPage) trackingCategories.pop(); + + const unifiedTrackingCategories = await Promise.all( + trackingCategories.map(async (trackingCategory) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: trackingCategory.id_acc_tracking_category, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTrackingCategory: UnifiedAccountingTrackingcategoryOutput = + { + id: trackingCategory.id_acc_tracking_category, + name: trackingCategory.name, + status: trackingCategory.status, + category_type: trackingCategory.category_type, + parent_category: trackingCategory.parent_category, + field_mappings: field_mappings, + remote_id: trackingCategory.remote_id, + created_at: trackingCategory.created_at, + modified_at: trackingCategory.modified_at, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: trackingCategory.id_acc_tracking_category, + }, + }); + unifiedTrackingCategory.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedTrackingCategory; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.tracking_category.pull', + method: 'GET', + url: '/accounting/tracking_categories', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedTrackingCategories, + next_cursor: hasNextPage + ? trackingCategories[trackingCategories.length - 1] + .id_acc_tracking_category + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/trackingcategory/sync/sync.service.ts b/packages/api/src/accounting/trackingcategory/sync/sync.service.ts index 128d54610..9ddc21bea 100644 --- a/packages/api/src/accounting/trackingcategory/sync/sync.service.ts +++ b/packages/api/src/accounting/trackingcategory/sync/sync.service.ts @@ -1,7 +1,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedAccountingTrackingcategoryOutput } from '../types/model.unified'; import { ITrackingCategoryService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_tracking_categories as AccTrackingCategory } from '@prisma/client'; +import { OriginalTrackingCategoryOutput } from '@@core/utils/types/original/original.accounting'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,22 +25,147 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'tracking_category', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting tracking categories...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } } - saveToDb( + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ITrackingCategoryService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingTrackingcategoryOutput, + OriginalTrackingCategoryOutput, + ITrackingCategoryService + >( + integrationId, + linkedUserId, + 'accounting', + 'tracking_category', + service, + [], + ); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + trackingCategories: UnifiedAccountingTrackingcategoryOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const trackingCategoryResults: AccTrackingCategory[] = []; + + for (let i = 0; i < trackingCategories.length; i++) { + const trackingCategory = trackingCategories[i]; + const originId = trackingCategory.remote_id; + + let existingTrackingCategory = + await this.prisma.acc_tracking_categories.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const trackingCategoryData = { + name: trackingCategory.name, + status: trackingCategory.status, + category_type: trackingCategory.category_type, + parent_category: trackingCategory.parent_category, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingTrackingCategory) { + existingTrackingCategory = + await this.prisma.acc_tracking_categories.update({ + where: { + id_acc_tracking_category: + existingTrackingCategory.id_acc_tracking_category, + }, + data: trackingCategoryData, + }); + } else { + existingTrackingCategory = + await this.prisma.acc_tracking_categories.create({ + data: { + ...trackingCategoryData, + id_acc_tracking_category: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + trackingCategoryResults.push(existingTrackingCategory); + + // Process field mappings + await this.ingestService.processFieldMappings( + trackingCategory.field_mappings, + existingTrackingCategory.id_acc_tracking_category, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingTrackingCategory.id_acc_tracking_category, + remote_data[i], + ); + } + + return trackingCategoryResults; + } catch (error) { + throw error; + } } - // Additional methods and logic } diff --git a/packages/api/src/accounting/trackingcategory/trackingcategory.controller.ts b/packages/api/src/accounting/trackingcategory/trackingcategory.controller.ts index 14586690c..7de858c37 100644 --- a/packages/api/src/accounting/trackingcategory/trackingcategory.controller.ts +++ b/packages/api/src/accounting/trackingcategory/trackingcategory.controller.ts @@ -28,8 +28,10 @@ import { import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; import { QueryDto } from '@@core/utils/dtos/query.dto'; -import { ApiGetCustomResponse, ApiPaginatedResponse } from '@@core/utils/dtos/openapi.respone.dto'; - +import { + ApiGetCustomResponse, + ApiPaginatedResponse, +} from '@@core/utils/dtos/openapi.respone.dto'; @ApiTags('accounting/trackingcategories') @Controller('accounting/trackingcategories') @@ -65,7 +67,7 @@ export class TrackingCategoryController { connection_token, ); const { remote_data, limit, cursor } = query; - return this.trackingcategoryService.getTrackingCategorys( + return this.trackingcategoryService.getTrackingCategories( connectionId, projectId, remoteSource, diff --git a/packages/api/src/accounting/trackingcategory/types/index.ts b/packages/api/src/accounting/trackingcategory/types/index.ts index f45b7cc1a..1f118badc 100644 --- a/packages/api/src/accounting/trackingcategory/types/index.ts +++ b/packages/api/src/accounting/trackingcategory/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalTrackingCategoryOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ITrackingCategoryService { addTrackingCategory( @@ -12,10 +13,7 @@ export interface ITrackingCategoryService { linkedUserId: string, ): Promise>; - syncTrackingCategorys( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ITrackingCategoryMapper { @@ -34,5 +32,8 @@ export interface ITrackingCategoryMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + | UnifiedAccountingTrackingcategoryOutput + | UnifiedAccountingTrackingcategoryOutput[] + >; } diff --git a/packages/api/src/accounting/trackingcategory/types/model.unified.ts b/packages/api/src/accounting/trackingcategory/types/model.unified.ts index 6f0c8ec9b..99d21a0fe 100644 --- a/packages/api/src/accounting/trackingcategory/types/model.unified.ts +++ b/packages/api/src/accounting/trackingcategory/types/model.unified.ts @@ -93,22 +93,22 @@ export class UnifiedAccountingTrackingcategoryOutput extends UnifiedAccountingTr remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the tracking category record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the tracking category record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; } diff --git a/packages/api/src/accounting/transaction/services/transaction.service.ts b/packages/api/src/accounting/transaction/services/transaction.service.ts index 6fc38d448..ce87caf26 100644 --- a/packages/api/src/accounting/transaction/services/transaction.service.ts +++ b/packages/api/src/accounting/transaction/services/transaction.service.ts @@ -1,20 +1,12 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedAccountingTransactionInput, - UnifiedAccountingTransactionOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedAccountingTransactionOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalTransactionOutput } from '@@core/utils/types/original/original.accounting'; - -import { ITransactionService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class TransactionService { @@ -29,14 +21,103 @@ export class TransactionService { } async getTransaction( - id_transactioning_transaction: string, + id_acc_transaction: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const transaction = await this.prisma.acc_transactions.findUnique({ + where: { id_acc_transaction: id_acc_transaction }, + }); + + if (!transaction) { + throw new Error(`Transaction with ID ${id_acc_transaction} not found.`); + } + + const lineItems = await this.prisma.acc_transactions_lines_items.findMany( + { + where: { id_acc_transaction: id_acc_transaction }, + }, + ); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: transaction.id_acc_transaction }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTransaction: UnifiedAccountingTransactionOutput = { + id: transaction.id_acc_transaction, + transaction_type: transaction.transaction_type, + number: transaction.number ? Number(transaction.number) : undefined, + transaction_date: transaction.transaction_date, + total_amount: transaction.total_amount, + exchange_rate: transaction.exchange_rate, + currency: transaction.currency as CurrencyCode, + tracking_categories: transaction.tracking_categories, + account_id: transaction.id_acc_account, + contact_id: transaction.id_acc_contact, + company_info_id: transaction.id_acc_company_info, + accounting_period_id: transaction.id_acc_accounting_period, + field_mappings: field_mappings, + remote_id: transaction.remote_id, + created_at: transaction.created_at, + modified_at: transaction.modified_at, + line_items: lineItems.map((item) => ({ + memo: item.memo, + unit_price: item.unit_price, + quantity: item.quantity, + total_line_amount: item.total_line_amount, + id_acc_tax_rate: item.id_acc_tax_rate, + currency: item.currency as CurrencyCode, + exchange_rate: item.exchange_rate, + tracking_categories: item.tracking_categories, + id_acc_company_info: item.id_acc_company_info, + id_acc_item: item.id_acc_item, + id_acc_account: item.id_acc_account, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: transaction.id_acc_transaction }, + }); + unifiedTransaction.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.transaction.pull', + method: 'GET', + url: '/accounting/transaction', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedTransaction; + } catch (error) { + throw error; + } } async getTransactions( @@ -47,7 +128,114 @@ export class TransactionService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingTransactionOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const transactions = await this.prisma.acc_transactions.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_transaction: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = transactions.length > limit; + if (hasNextPage) transactions.pop(); + + const unifiedTransactions = await Promise.all( + transactions.map(async (transaction) => { + const lineItems = + await this.prisma.acc_transactions_lines_items.findMany({ + where: { id_acc_transaction: transaction.id_acc_transaction }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: transaction.id_acc_transaction }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTransaction: UnifiedAccountingTransactionOutput = { + id: transaction.id_acc_transaction, + transaction_type: transaction.transaction_type, + number: transaction.number ? Number(transaction.number) : undefined, + transaction_date: transaction.transaction_date, + total_amount: transaction.total_amount, + exchange_rate: transaction.exchange_rate, + currency: transaction.currency as CurrencyCode, + tracking_categories: transaction.tracking_categories, + account_id: transaction.id_acc_account, + contact_id: transaction.id_acc_contact, + company_info_id: transaction.id_acc_company_info, + accounting_period_id: transaction.id_acc_accounting_period, + field_mappings: field_mappings, + remote_id: transaction.remote_id, + created_at: transaction.created_at, + modified_at: transaction.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_transactions_lines_item, + memo: item.memo, + unit_price: item.unit_price, + quantity: item.quantity, + total_line_amount: item.total_line_amount, + id_acc_tax_rate: item.id_acc_tax_rate, + currency: item.currency as CurrencyCode, + exchange_rate: item.exchange_rate, + tracking_categories: item.tracking_categories, + id_acc_company_info: item.id_acc_company_info, + id_acc_item: item.id_acc_item, + id_acc_account: item.id_acc_account, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: transaction.id_acc_transaction }, + }); + unifiedTransaction.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedTransaction; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.transaction.pull', + method: 'GET', + url: '/accounting/transactions', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedTransactions, + next_cursor: hasNextPage + ? transactions[transactions.length - 1].id_acc_transaction + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/transaction/sync/sync.service.ts b/packages/api/src/accounting/transaction/sync/sync.service.ts index c63c23b73..13f6d92a4 100644 --- a/packages/api/src/accounting/transaction/sync/sync.service.ts +++ b/packages/api/src/accounting/transaction/sync/sync.service.ts @@ -1,16 +1,24 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalTransactionOutput } from '@@core/utils/types/original/original.accounting'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_transactions as AccTransaction } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingTransactionOutput } from '../types/model.unified'; import { ITransactionService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { + LineItem, + UnifiedAccountingTransactionOutput, +} from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,23 +28,212 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'transaction', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting transactions...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ITransactionService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingTransactionOutput, + OriginalTransactionOutput, + ITransactionService + >(integrationId, linkedUserId, 'accounting', 'transaction', service, []); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + transactions: UnifiedAccountingTransactionOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const transactionResults: AccTransaction[] = []; + + for (let i = 0; i < transactions.length; i++) { + const transaction = transactions[i]; + const originId = transaction.remote_id; + + let existingTransaction = await this.prisma.acc_transactions.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const transactionData = { + transaction_type: transaction.transaction_type, + number: transaction.number ? Number(transaction.number) : null, + transaction_date: transaction.transaction_date, + total_amount: transaction.total_amount, + exchange_rate: transaction.exchange_rate, + currency: transaction.currency as CurrencyCode, + tracking_categories: transaction.tracking_categories || [], + id_acc_account: transaction.account_id, + id_acc_contact: transaction.contact_id, + id_acc_company_info: transaction.company_info_id, + id_acc_accounting_period: transaction.accounting_period_id, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingTransaction) { + existingTransaction = await this.prisma.acc_transactions.update({ + where: { + id_acc_transaction: existingTransaction.id_acc_transaction, + }, + data: transactionData, + }); + } else { + existingTransaction = await this.prisma.acc_transactions.create({ + data: { + ...transactionData, + id_acc_transaction: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + transactionResults.push(existingTransaction); + + // Process field mappings + await this.ingestService.processFieldMappings( + transaction.field_mappings, + existingTransaction.id_acc_transaction, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingTransaction.id_acc_transaction, + remote_data[i], + ); + + // Handle line items (acc_transactions_lines_items) + if (transaction.line_items && transaction.line_items.length > 0) { + await this.processLineItems( + existingTransaction.id_acc_transaction, + transaction.line_items, + ); + } + } + + return transactionResults; + } catch (error) { + throw error; + } } - // Additional methods and logic + private async processLineItems( + transactionId: string, + lineItems: LineItem[], + ): Promise { + for (const lineItem of lineItems) { + const lineItemData = { + memo: lineItem.memo, + unit_price: lineItem.unit_price, + quantity: lineItem.quantity, + total_line_amount: lineItem.total_line_amount, + tax_rate_id: lineItem.tax_rate_id, + currency: lineItem.currency as CurrencyCode, + exchange_rate: lineItem.exchange_rate, + tracking_categories: lineItem.tracking_categories || [], + id_acc_company_info: lineItem.company_info_id, + id_acc_item: lineItem.item_id, + id_acc_account: lineItem.account_id, + remote_id: lineItem.remote_id, + modified_at: new Date(), + id_acc_transaction: transactionId, + }; + + const existingLineItem = + await this.prisma.acc_transactions_lines_items.findFirst({ + where: { + remote_id: lineItem.remote_id, + id_acc_transaction: transactionId, + }, + }); + + if (existingLineItem) { + await this.prisma.acc_transactions_lines_items.update({ + where: { + id_acc_transactions_lines_item: + existingLineItem.id_acc_transactions_lines_item, + }, + data: lineItemData, + }); + } else { + await this.prisma.acc_transactions_lines_items.create({ + data: { + ...lineItemData, + id_acc_transactions_lines_item: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing line items that are not in the current set + const currentRemoteIds = lineItems.map((item) => item.remote_id); + await this.prisma.acc_transactions_lines_items.deleteMany({ + where: { + id_acc_transaction: transactionId, + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); + } } diff --git a/packages/api/src/accounting/transaction/types/index.ts b/packages/api/src/accounting/transaction/types/index.ts index ef214f1d0..56a15eaaf 100644 --- a/packages/api/src/accounting/transaction/types/index.ts +++ b/packages/api/src/accounting/transaction/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalTransactionOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ITransactionService { addTransaction( @@ -12,10 +13,7 @@ export interface ITransactionService { linkedUserId: string, ): Promise>; - syncTransactions( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ITransactionMapper { @@ -34,5 +32,7 @@ export interface ITransactionMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedAccountingTransactionOutput | UnifiedAccountingTransactionOutput[] + >; } diff --git a/packages/api/src/accounting/transaction/types/model.unified.ts b/packages/api/src/accounting/transaction/types/model.unified.ts index 31ac3b963..27e5aae97 100644 --- a/packages/api/src/accounting/transaction/types/model.unified.ts +++ b/packages/api/src/accounting/transaction/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -8,6 +9,157 @@ import { IsArray, } from 'class-validator'; +export class LineItem { + @ApiPropertyOptional({ + type: String, + example: 'Product description', + nullable: true, + description: 'Memo or description for the line item', + }) + @IsString() + @IsOptional() + memo?: string; + + @ApiPropertyOptional({ + type: String, + example: '10.99', + nullable: true, + description: 'Unit price of the item', + }) + @IsString() + @IsOptional() + unit_price?: string; + + @ApiPropertyOptional({ + type: String, + example: '2', + nullable: true, + description: 'Quantity of the item', + }) + @IsString() + @IsOptional() + quantity?: string; + + @ApiPropertyOptional({ + type: String, + example: '21.98', + nullable: true, + description: 'Total amount for the line item', + }) + @IsString() + @IsOptional() + total_line_amount?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated tax rate', + }) + @IsUUID() + @IsOptional() + tax_rate_id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'USD', + enum: CurrencyCode, + nullable: true, + description: 'The currency of the line item', + }) + @IsString() + @IsOptional() + currency?: CurrencyCode; + + @ApiPropertyOptional({ + type: String, + example: '1.0', + nullable: true, + description: 'The exchange rate for the line item', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: + 'The UUIDs of tracking categories associated with the line item', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + company_info_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated item', + }) + @IsUUID() + @IsOptional() + item_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated account', + }) + @IsUUID() + @IsOptional() + account_id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'remote_line_item_id_1234', + nullable: true, + description: 'The remote ID of the line item', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The created date of the line item', + }) + @IsDateString() + created_at: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The last modified date of the line item', + }) + @IsDateString() + modified_at: Date; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated transaction', + }) + @IsUUID() + @IsOptional() + transaction_id?: string; +} + export class UnifiedAccountingTransactionInput { @ApiPropertyOptional({ type: String, @@ -30,14 +182,14 @@ export class UnifiedAccountingTransactionInput { number?: number; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date of the transaction', }) @IsDateString() @IsOptional() - transaction_date?: string; + transaction_date?: Date; @ApiPropertyOptional({ type: String, @@ -62,19 +214,20 @@ export class UnifiedAccountingTransactionInput { @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the transaction', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: [String], example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], nullable: true, description: - 'The UUID of tracking categories associated with the transaction', + 'The UUIDs of the tracking categories associated with the transaction', }) @IsArray() @IsString({ each: true }) @@ -89,7 +242,7 @@ export class UnifiedAccountingTransactionInput { }) @IsUUID() @IsOptional() - id_acc_account?: string; + account_id?: string; @ApiPropertyOptional({ type: String, @@ -99,7 +252,7 @@ export class UnifiedAccountingTransactionInput { }) @IsUUID() @IsOptional() - id_acc_contact?: string; + contact_id?: string; @ApiPropertyOptional({ type: String, @@ -109,7 +262,7 @@ export class UnifiedAccountingTransactionInput { }) @IsUUID() @IsOptional() - id_acc_company_info?: string; + company_info_id?: string; @ApiPropertyOptional({ type: String, @@ -119,7 +272,28 @@ export class UnifiedAccountingTransactionInput { }) @IsUUID() @IsOptional() - id_acc_accounting_period?: string; + accounting_period_id?: string; + + @ApiPropertyOptional({ + type: [LineItem], + description: 'The line items associated with this transaction', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; } export class UnifiedAccountingTransactionOutput extends UnifiedAccountingTransactionInput { @@ -143,25 +317,39 @@ export class UnifiedAccountingTransactionOutput extends UnifiedAccountingTransac remote_id: string; // Required field @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: false, description: 'The created date of the transaction', }) @IsDateString() - created_at: string; // Required field + created_at: Date; // Required field @ApiPropertyOptional({ - type: String, + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the tracking category in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; + + @ApiPropertyOptional({ + type: Date, example: '2024-06-15T12:00:00Z', nullable: false, description: 'The last modified date of the transaction', }) @IsDateString() - modified_at: string; // Required field + modified_at: Date; // Required field @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -169,5 +357,5 @@ export class UnifiedAccountingTransactionOutput extends UnifiedAccountingTransac }) @IsDateString() @IsOptional() - remote_updated_at?: string; + remote_updated_at?: Date; } diff --git a/packages/api/src/accounting/vendorcredit/services/vendorcredit.service.ts b/packages/api/src/accounting/vendorcredit/services/vendorcredit.service.ts index 4f2bd2d82..838ac5bff 100644 --- a/packages/api/src/accounting/vendorcredit/services/vendorcredit.service.ts +++ b/packages/api/src/accounting/vendorcredit/services/vendorcredit.service.ts @@ -1,20 +1,12 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedAccountingVendorcreditInput, - UnifiedAccountingVendorcreditOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedAccountingVendorcreditOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalVendorCreditOutput } from '@@core/utils/types/original/original.accounting'; - -import { IVendorCreditService } from '../types'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class VendorCreditService { @@ -29,14 +21,100 @@ export class VendorCreditService { } async getVendorCredit( - id_vendorcrediting_vendorcredit: string, + id_acc_vendor_credit: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const vendorCredit = await this.prisma.acc_vendor_credits.findUnique({ + where: { id_acc_vendor_credit: id_acc_vendor_credit }, + }); + + if (!vendorCredit) { + throw new Error( + `Vendor credit with ID ${id_acc_vendor_credit} not found.`, + ); + } + + const lineItems = await this.prisma.acc_vendor_credit_lines.findMany({ + where: { id_acc_vendor_credit: id_acc_vendor_credit }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: vendorCredit.id_acc_vendor_credit }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedVendorCredit: UnifiedAccountingVendorcreditOutput = { + id: vendorCredit.id_acc_vendor_credit, + number: vendorCredit.number, + transaction_date: vendorCredit.transaction_date, + vendor: vendorCredit.vendor, + total_amount: vendorCredit.total_amount + ? Number(vendorCredit.total_amount) + : undefined, + currency: vendorCredit.currency as CurrencyCode, + exchange_rate: vendorCredit.exchange_rate, + company_id: vendorCredit.company, + tracking_categories: vendorCredit.tracking_categories, + accounting_period_id: vendorCredit.accounting_period, + field_mappings: field_mappings, + remote_id: vendorCredit.remote_id, + created_at: vendorCredit.created_at.toISOString(), + modified_at: vendorCredit.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_vendor_credit_line, + net_amount: item.net_amount ? item.net_amount.toString() : undefined, + tracking_categories: item.tracking_categories, + description: item.description, + id_acc_account: item.id_acc_account, + exchange_rate: item.exchange_rate, + id_acc_company_info: item.id_acc_company_info, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + id_acc_vendor_credit: item.id_acc_vendor_credit, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: vendorCredit.id_acc_vendor_credit }, + }); + unifiedVendorCredit.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.vendor_credit.pull', + method: 'GET', + url: '/accounting/vendor_credit', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedVendorCredit; + } catch (error) { + throw error; + } } async getVendorCredits( @@ -47,7 +125,111 @@ export class VendorCreditService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedAccountingVendorcreditOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const vendorCredits = await this.prisma.acc_vendor_credits.findMany({ + take: limit + 1, + cursor: cursor ? { id_acc_vendor_credit: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = vendorCredits.length > limit; + if (hasNextPage) vendorCredits.pop(); + + const unifiedVendorCredits = await Promise.all( + vendorCredits.map(async (vendorCredit) => { + const lineItems = await this.prisma.acc_vendor_credit_lines.findMany({ + where: { id_acc_vendor_credit: vendorCredit.id_acc_vendor_credit }, + }); + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: vendorCredit.id_acc_vendor_credit }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedVendorCredit: UnifiedAccountingVendorcreditOutput = { + id: vendorCredit.id_acc_vendor_credit, + number: vendorCredit.number, + transaction_date: vendorCredit.transaction_date, + vendor: vendorCredit.vendor, + total_amount: vendorCredit.total_amount + ? Number(vendorCredit.total_amount) + : undefined, + currency: vendorCredit.currency as CurrencyCode as CurrencyCode, + exchange_rate: vendorCredit.exchange_rate, + company_id: vendorCredit.company, + tracking_categories: vendorCredit.tracking_categories, + accounting_period_id: vendorCredit.accounting_period, + field_mappings: field_mappings, + remote_id: vendorCredit.remote_id, + created_at: vendorCredit.created_at.toISOString(), + modified_at: vendorCredit.modified_at, + line_items: lineItems.map((item) => ({ + id: item.id_acc_vendor_credit_line, + net_amount: item.net_amount + ? item.net_amount.toString() + : undefined, + tracking_categories: item.tracking_categories, + description: item.description, + id_acc_account: item.id_acc_account, + exchange_rate: item.exchange_rate, + id_acc_company_info: item.id_acc_company_info, + remote_id: item.remote_id, + created_at: item.created_at, + modified_at: item.modified_at, + id_acc_vendor_credit: item.id_acc_vendor_credit, + })), + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: vendorCredit.id_acc_vendor_credit }, + }); + unifiedVendorCredit.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedVendorCredit; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'accounting.vendor_credit.pull', + method: 'GET', + url: '/accounting/vendor_credits', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedVendorCredits, + next_cursor: hasNextPage + ? vendorCredits[vendorCredits.length - 1].id_acc_vendor_credit + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/accounting/vendorcredit/sync/sync.service.ts b/packages/api/src/accounting/vendorcredit/sync/sync.service.ts index c7b7fa339..c55697a27 100644 --- a/packages/api/src/accounting/vendorcredit/sync/sync.service.ts +++ b/packages/api/src/accounting/vendorcredit/sync/sync.service.ts @@ -1,16 +1,24 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalVendorCreditOutput } from '@@core/utils/types/original/original.accounting'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ACCOUNTING_PROVIDERS } from '@panora/shared'; +import { acc_vendor_credits as AccVendorCredit } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedAccountingVendorcreditOutput } from '../types/model.unified'; import { IVendorCreditService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { + UnifiedAccountingVendorcreditOutput, + LineItem, +} from '../types/model.unified'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,22 +28,215 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('accounting', 'vendor_credit', this); } async onModuleInit() { - // Initialization logic + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing accounting vendor credits...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of ACCOUNTING_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IVendorCreditService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedAccountingVendorcreditOutput, + OriginalVendorCreditOutput, + IVendorCreditService + >( + integrationId, + linkedUserId, + 'accounting', + 'vendor_credit', + service, + [], + ); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + vendorCredits: UnifiedAccountingVendorcreditOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const vendorCreditResults: AccVendorCredit[] = []; + + for (let i = 0; i < vendorCredits.length; i++) { + const vendorCredit = vendorCredits[i]; + const originId = vendorCredit.remote_id; + + let existingVendorCredit = + await this.prisma.acc_vendor_credits.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const vendorCreditData = { + number: vendorCredit.number, + transaction_date: vendorCredit.transaction_date, + vendor: vendorCredit.vendor, + total_amount: vendorCredit.total_amount + ? Number(vendorCredit.total_amount) + : null, + currency: vendorCredit.currency as CurrencyCode, + exchange_rate: vendorCredit.exchange_rate, + id_acc_company: vendorCredit.company_id, + tracking_categories: vendorCredit.tracking_categories || [], + id_acc_accounting_period: vendorCredit.accounting_period_id, + remote_id: originId, + modified_at: new Date(), + }; + + if (existingVendorCredit) { + existingVendorCredit = await this.prisma.acc_vendor_credits.update({ + where: { + id_acc_vendor_credit: existingVendorCredit.id_acc_vendor_credit, + }, + data: vendorCreditData, + }); + } else { + existingVendorCredit = await this.prisma.acc_vendor_credits.create({ + data: { + ...vendorCreditData, + id_acc_vendor_credit: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + vendorCreditResults.push(existingVendorCredit); + + // Process field mappings + await this.ingestService.processFieldMappings( + vendorCredit.field_mappings, + existingVendorCredit.id_acc_vendor_credit, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingVendorCredit.id_acc_vendor_credit, + remote_data[i], + ); + + // Handle line items + if (vendorCredit.line_items && vendorCredit.line_items.length > 0) { + await this.processVendorCreditLineItems( + existingVendorCredit.id_acc_vendor_credit, + vendorCredit.line_items, + ); + } + } + + return vendorCreditResults; + } catch (error) { + throw error; + } + } + + private async processVendorCreditLineItems( + vendorCreditId: string, + lineItems: LineItem[], + ): Promise { + for (const lineItem of lineItems) { + const lineItemData = { + net_amount: lineItem.net_amount ? Number(lineItem.net_amount) : null, + tracking_categories: lineItem.tracking_categories || [], + description: lineItem.description, + id_acc_account: lineItem.account_id, + exchange_rate: lineItem.exchange_rate, + id_acc_company_info: lineItem.company_info_id, + remote_id: lineItem.remote_id, + modified_at: new Date(), + id_acc_vendor_credit: vendorCreditId, + }; + + const existingLineItem = + await this.prisma.acc_vendor_credit_lines.findFirst({ + where: { + remote_id: lineItem.remote_id, + id_acc_vendor_credit: vendorCreditId, + }, + }); + + if (existingLineItem) { + await this.prisma.acc_vendor_credit_lines.update({ + where: { + id_acc_vendor_credit_line: + existingLineItem.id_acc_vendor_credit_line, + }, + data: lineItemData, + }); + } else { + await this.prisma.acc_vendor_credit_lines.create({ + data: { + ...lineItemData, + id_acc_vendor_credit_line: uuidv4(), + created_at: new Date(), + }, + }); + } + } + + // Remove any existing line items that are not in the current set + const currentRemoteIds = lineItems.map((item) => item.remote_id); + await this.prisma.acc_vendor_credit_lines.deleteMany({ + where: { + id_acc_vendor_credit: vendorCreditId, + remote_id: { + notIn: currentRemoteIds, + }, + }, + }); } - // Additional methods and logic } diff --git a/packages/api/src/accounting/vendorcredit/types/index.ts b/packages/api/src/accounting/vendorcredit/types/index.ts index 6bd22a4e2..529526306 100644 --- a/packages/api/src/accounting/vendorcredit/types/index.ts +++ b/packages/api/src/accounting/vendorcredit/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalVendorCreditOutput } from '@@core/utils/types/original/original.accounting'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IVendorCreditService { addVendorCredit( @@ -12,10 +13,7 @@ export interface IVendorCreditService { linkedUserId: string, ): Promise>; - syncVendorCredits( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IVendorCreditMapper { @@ -34,5 +32,7 @@ export interface IVendorCreditMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedAccountingVendorcreditOutput | UnifiedAccountingVendorcreditOutput[] + >; } diff --git a/packages/api/src/accounting/vendorcredit/types/model.unified.ts b/packages/api/src/accounting/vendorcredit/types/model.unified.ts index ccdf49f13..980918563 100644 --- a/packages/api/src/accounting/vendorcredit/types/model.unified.ts +++ b/packages/api/src/accounting/vendorcredit/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -8,6 +9,106 @@ import { IsArray, } from 'class-validator'; +export class LineItem { + @ApiPropertyOptional({ + type: String, + example: '100', + nullable: true, + description: 'The net amount of the line item', + }) + @IsString() + @IsOptional() + net_amount?: string; + + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: + 'The UUIDs of the tracking categories associated with the line item', + }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tracking_categories?: string[]; + + @ApiPropertyOptional({ + type: String, + example: 'Office supplies', + nullable: true, + description: 'Description of the line item', + }) + @IsString() + @IsOptional() + description?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated account', + }) + @IsUUID() + @IsOptional() + account_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '1.0', + nullable: true, + description: 'The exchange rate for the line item', + }) + @IsString() + @IsOptional() + exchange_rate?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated company info', + }) + @IsUUID() + @IsOptional() + company_info_id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'remote_line_item_id_1234', + nullable: true, + description: 'The remote ID of the line item', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The created date of the line item', + }) + @IsDateString() + created_at: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-06-15T12:00:00Z', + description: 'The last modified date of the line item', + }) + @IsDateString() + modified_at: Date; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated vendor credit', + }) + @IsUUID() + @IsOptional() + vendor_credit_id?: string; +} + export class UnifiedAccountingVendorcreditInput { @ApiPropertyOptional({ type: String, @@ -20,14 +121,14 @@ export class UnifiedAccountingVendorcreditInput { number?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The date of the transaction', }) @IsDateString() @IsOptional() - transaction_date?: string; + transaction_date?: Date; @ApiPropertyOptional({ type: String, @@ -53,11 +154,12 @@ export class UnifiedAccountingVendorcreditInput { type: String, example: 'USD', nullable: true, + enum: CurrencyCode, description: 'The currency of the vendor credit', }) @IsString() @IsOptional() - currency?: string; + currency?: CurrencyCode; @ApiPropertyOptional({ type: String, @@ -77,14 +179,14 @@ export class UnifiedAccountingVendorcreditInput { }) @IsUUID() @IsOptional() - company?: string; + company_id?: string; @ApiPropertyOptional({ type: [String], example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], nullable: true, description: - 'The UUID of tracking categories associated with the vendor credit', + 'The UUID of the tracking categories associated with the vendor credit', }) @IsArray() @IsString({ each: true }) @@ -99,7 +201,28 @@ export class UnifiedAccountingVendorcreditInput { }) @IsUUID() @IsOptional() - accounting_period?: string; + accounting_period_id?: string; + + @ApiPropertyOptional({ + type: [LineItem], + description: 'The line items associated with this vendor credit', + }) + @IsArray() + @IsOptional() + line_items?: LineItem[]; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; } export class UnifiedAccountingVendorcreditOutput extends UnifiedAccountingVendorcreditInput { @@ -124,7 +247,7 @@ export class UnifiedAccountingVendorcreditOutput extends UnifiedAccountingVendor remote_id?: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: false, description: 'The created date of the vendor credit', @@ -133,16 +256,16 @@ export class UnifiedAccountingVendorcreditOutput extends UnifiedAccountingVendor created_at: string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: false, description: 'The last modified date of the vendor credit', }) @IsDateString() - modified_at: string; + modified_at: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -150,5 +273,19 @@ export class UnifiedAccountingVendorcreditOutput extends UnifiedAccountingVendor }) @IsDateString() @IsOptional() - remote_updated_at?: string; + remote_updated_at?: Date; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the vendor credit in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; } diff --git a/packages/api/src/crm/company/services/company.service.ts b/packages/api/src/crm/company/services/company.service.ts index e7fe42ea2..675822c32 100644 --- a/packages/api/src/crm/company/services/company.service.ts +++ b/packages/api/src/crm/company/services/company.service.ts @@ -526,7 +526,7 @@ export class CompanyService { // Convert the map to an array of objects // Convert the map to an object -const field_mappings = Object.fromEntries(fieldMappingsMap); + const field_mappings = Object.fromEntries(fieldMappingsMap); // Transform to UnifiedCrmCompanyOutput format return { diff --git a/packages/api/src/crm/company/sync/sync.service.ts b/packages/api/src/crm/company/sync/sync.service.ts index 7a4cb0a20..189e10d6f 100644 --- a/packages/api/src/crm/company/sync/sync.service.ts +++ b/packages/api/src/crm/company/sync/sync.service.ts @@ -53,12 +53,12 @@ export class SyncService implements OnModuleInit, IBaseSync { this.logger.log(`Syncing companies....`); const users = user_id ? [ - await this.prisma.users.findUnique({ - where: { - id_user: user_id, - }, - }), - ] + await this.prisma.users.findUnique({ + where: { + id_user: user_id, + }, + }), + ] : await this.prisma.users.findMany(); if (users && users.length > 0) { for (const user of users) { @@ -108,7 +108,9 @@ export class SyncService implements OnModuleInit, IBaseSync { const service: ICompanyService = this.serviceRegistry.getService(integrationId); if (!service) { - this.logger.log(`No service found in {vertical:crm, commonObject: company} for integration ID: ${integrationId}`); + this.logger.log( + `No service found in {vertical:crm, commonObject: company} for integration ID: ${integrationId}`, + ); return; } diff --git a/packages/api/src/hris/@lib/@types/index.ts b/packages/api/src/hris/@lib/@types/index.ts index 8ca678053..152fe0763 100644 --- a/packages/api/src/hris/@lib/@types/index.ts +++ b/packages/api/src/hris/@lib/@types/index.ts @@ -67,6 +67,11 @@ import { UnifiedHrisTimeoffbalanceInput, UnifiedHrisTimeoffbalanceOutput, } from '@hris/timeoffbalance/types/model.unified'; +import { ITimesheetentryService } from '@hris/timesheetentry/types'; +import { + UnifiedHrisTimesheetEntryInput, + UnifiedHrisTimesheetEntryOutput, +} from '@hris/timesheetentry/types/model.unified'; export enum HrisObject { bankinfo = 'bankinfo', @@ -83,6 +88,7 @@ export enum HrisObject { payrollrun = 'payrollrun', timeoff = 'timeoff', timeoffbalance = 'timeoffbalance', + timesheetentry = 'timesheetentry', } export type UnifiedHris = @@ -113,7 +119,9 @@ export type UnifiedHris = | UnifiedHrisLocationInput | UnifiedHrisLocationOutput | UnifiedHrisPaygroupInput - | UnifiedHrisPaygroupOutput; + | UnifiedHrisPaygroupOutput + | UnifiedHrisTimesheetEntryInput + | UnifiedHrisTimesheetEntryOutput; export type IHrisService = | IBankInfoService @@ -128,4 +136,5 @@ export type IHrisService = | ITimeoffBalanceService | IPayrollRunService | IPayGroupService - | ILocationService; + | ILocationService + | ITimesheetentryService; diff --git a/packages/api/src/hris/@lib/@utils/index.ts b/packages/api/src/hris/@lib/@utils/index.ts new file mode 100644 index 000000000..23b09c261 --- /dev/null +++ b/packages/api/src/hris/@lib/@utils/index.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; + +@Injectable() +export class Utils { + constructor(private readonly prisma: PrismaService) {} + + async getEmployeeUuidFromRemoteId(id: string, connection_id: string) { + try { + const res = await this.prisma.hris_employees.findFirst({ + where: { + remote_id: id, + id_connection: connection_id, + }, + }); + if (!res) return; + return res.id_hris_employee; + } catch (error) { + throw error; + } + } + + async getCompanyUuidFromRemoteId(id: string, connection_id: string) { + try { + const res = await this.prisma.hris_companies.findFirst({ + where: { + remote_id: id, + id_connection: connection_id, + }, + }); + if (!res) return; + return res.id_hris_company; + } catch (error) { + throw error; + } + } + + async getEmployerBenefitUuidFromRemoteId(id: string, connection_id: string) { + try { + const res = await this.prisma.hris_employer_benefits.findFirst({ + where: { + remote_id: id, + id_connection: connection_id, + }, + }); + if (!res) return; + return res.id_hris_employer_benefit; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/bankinfo/services/bankinfo.service.ts b/packages/api/src/hris/bankinfo/services/bankinfo.service.ts index 0c6ff3879..87de1f944 100644 --- a/packages/api/src/hris/bankinfo/services/bankinfo.service.ts +++ b/packages/api/src/hris/bankinfo/services/bankinfo.service.ts @@ -3,9 +3,9 @@ import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { Injectable } from '@nestjs/common'; import { UnifiedHrisBankinfoOutput } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class BankInfoService { @@ -20,14 +20,79 @@ export class BankInfoService { } async getBankinfo( - id_bankinfoing_bankinfo: string, + id_hris_bank_info: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const bankInfo = await this.prisma.hris_bank_infos.findUnique({ + where: { id_hris_bank_info: id_hris_bank_info }, + }); + + if (!bankInfo) { + throw new Error(`Bank info with ID ${id_hris_bank_info} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: bankInfo.id_hris_bank_info }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedBankInfo: UnifiedHrisBankinfoOutput = { + id: bankInfo.id_hris_bank_info, + account_type: bankInfo.account_type, + bank_name: bankInfo.bank_name, + account_number: bankInfo.account_number, + routing_number: bankInfo.routing_number, + employee_id: bankInfo.id_hris_employee, + field_mappings: field_mappings, + remote_id: bankInfo.remote_id, + remote_created_at: bankInfo.remote_created_at, + created_at: bankInfo.created_at, + modified_at: bankInfo.modified_at, + remote_was_deleted: bankInfo.remote_was_deleted, + }; + + const res: UnifiedHrisBankinfoOutput = unifiedBankInfo; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: bankInfo.id_hris_bank_info }, + }); + res.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.bankinfo.pull', + method: 'GET', + url: '/hris/bankinfo', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return res; + } catch (error) { + throw error; + } } async getBankinfos( @@ -38,7 +103,88 @@ export class BankInfoService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisBankinfoOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const bankInfos = await this.prisma.hris_bank_infos.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_bank_info: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = bankInfos.length > limit; + if (hasNextPage) bankInfos.pop(); + + const unifiedBankInfos = await Promise.all( + bankInfos.map(async (bankInfo) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: bankInfo.id_hris_bank_info }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedBankInfo: UnifiedHrisBankinfoOutput = { + id: bankInfo.id_hris_bank_info, + account_type: bankInfo.account_type, + bank_name: bankInfo.bank_name, + account_number: bankInfo.account_number, + routing_number: bankInfo.routing_number, + employee_id: bankInfo.id_hris_employee, + field_mappings: field_mappings, + remote_id: bankInfo.remote_id, + remote_created_at: bankInfo.remote_created_at, + created_at: bankInfo.created_at, + modified_at: bankInfo.modified_at, + remote_was_deleted: bankInfo.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: bankInfo.id_hris_bank_info }, + }); + unifiedBankInfo.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedBankInfo; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.bankinfo.pull', + method: 'GET', + url: '/hris/bankinfos', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedBankInfos, + next_cursor: hasNextPage + ? bankInfos[bankInfos.length - 1].id_hris_bank_info + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/bankinfo/sync/sync.processor.ts b/packages/api/src/hris/bankinfo/sync/sync.processor.ts new file mode 100644 index 000000000..b61deb815 --- /dev/null +++ b/packages/api/src/hris/bankinfo/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-bankinfos') + async handleSyncCustomers(job: Job) { + try { + console.log(`Processing queue -> hris-sync-bankinfos ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris bank infos', error); + } + } +} diff --git a/packages/api/src/hris/bankinfo/sync/sync.service.ts b/packages/api/src/hris/bankinfo/sync/sync.service.ts index fd5017577..df1aa1f6c 100644 --- a/packages/api/src/hris/bankinfo/sync/sync.service.ts +++ b/packages/api/src/hris/bankinfo/sync/sync.service.ts @@ -2,12 +2,19 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { UnifiedHrisBankinfoOutput } from '../types/model.unified'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_bank_infos as HrisBankInfo } from '@prisma/client'; +import { OriginalBankInfoOutput } from '@@core/utils/types/original/original.hris'; +import { IBankInfoService } from '../types'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -17,23 +24,139 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'bankinfo', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing bank infos...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IBankInfoService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisBankinfoOutput, + OriginalBankInfoOutput, + IBankInfoService + >(integrationId, linkedUserId, 'hris', 'bankinfo', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + bankInfos: UnifiedHrisBankinfoOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const bankInfoResults: HrisBankInfo[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < bankInfos.length; i++) { + const bankInfo = bankInfos[i]; + const originId = bankInfo.remote_id; + + let existingBankInfo = await this.prisma.hris_bank_infos.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const bankInfoData = { + account_type: bankInfo.account_type, + bank_name: bankInfo.bank_name, + account_number: bankInfo.account_number, + routing_number: bankInfo.routing_number, + id_hris_employee: bankInfo.employee_id, + remote_id: originId, + remote_created_at: bankInfo.remote_created_at + ? new Date(bankInfo.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: bankInfo.remote_was_deleted, + }; - // Additional methods and logic + if (existingBankInfo) { + existingBankInfo = await this.prisma.hris_bank_infos.update({ + where: { id_hris_bank_info: existingBankInfo.id_hris_bank_info }, + data: bankInfoData, + }); + } else { + existingBankInfo = await this.prisma.hris_bank_infos.create({ + data: { + ...bankInfoData, + id_hris_bank_info: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + bankInfoResults.push(existingBankInfo); + + // Process field mappings + await this.ingestService.processFieldMappings( + bankInfo.field_mappings, + existingBankInfo.id_hris_bank_info, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingBankInfo.id_hris_bank_info, + remote_data[i], + ); + } + + return bankInfoResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/bankinfo/types/index.ts b/packages/api/src/hris/bankinfo/types/index.ts index 0433850ea..bf5581db7 100644 --- a/packages/api/src/hris/bankinfo/types/index.ts +++ b/packages/api/src/hris/bankinfo/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedHrisBankinfoInput, UnifiedHrisBankinfoOutput } from './model.unified'; +import { + UnifiedHrisBankinfoInput, + UnifiedHrisBankinfoOutput, +} from './model.unified'; import { OriginalBankInfoOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IBankInfoService { addBankinfo( @@ -9,10 +13,7 @@ export interface IBankInfoService { linkedUserId: string, ): Promise>; - syncBankinfos( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IBankinfoMapper { diff --git a/packages/api/src/hris/bankinfo/types/model.unified.ts b/packages/api/src/hris/bankinfo/types/model.unified.ts index 3b5f1103d..e3a7cbe3b 100644 --- a/packages/api/src/hris/bankinfo/types/model.unified.ts +++ b/packages/api/src/hris/bankinfo/types/model.unified.ts @@ -7,16 +7,19 @@ import { IsBoolean, } from 'class-validator'; +export type AccountType = 'SAVINGS' | 'CHECKING'; + export class UnifiedHrisBankinfoInput { @ApiPropertyOptional({ type: String, - example: 'checking', + example: 'CHECKING', + enum: ['SAVINGS', 'CHECKING'], nullable: true, description: 'The type of the bank account', }) @IsString() @IsOptional() - account_type?: string; + account_type?: AccountType | string; @ApiPropertyOptional({ type: String, @@ -91,7 +94,7 @@ export class UnifiedHrisBankinfoOutput extends UnifiedHrisBankinfoInput { }) @IsString() @IsOptional() - remote_id: string; + remote_id?: string; @ApiPropertyOptional({ type: Object, @@ -105,10 +108,10 @@ export class UnifiedHrisBankinfoOutput extends UnifiedHrisBankinfoInput { 'The remote data of the bank info in the context of the 3rd Party', }) @IsOptional() - remote_data: Record; + remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -116,25 +119,25 @@ export class UnifiedHrisBankinfoOutput extends UnifiedHrisBankinfoInput { }) @IsDateString() @IsOptional() - remote_created_at: string; + remote_created_at: Date; @ApiProperty({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the bank info record', }) @IsDateString() - created_at: string; + created_at: Date; @ApiProperty({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the bank info record', }) @IsDateString() - modified_at: string; + modified_at: Date; @ApiProperty({ type: Boolean, diff --git a/packages/api/src/hris/benefit/benefit.module.ts b/packages/api/src/hris/benefit/benefit.module.ts index 181f4f8b4..6dd0b8eb4 100644 --- a/packages/api/src/hris/benefit/benefit.module.ts +++ b/packages/api/src/hris/benefit/benefit.module.ts @@ -1,35 +1,26 @@ import { Module } from '@nestjs/common'; import { BenefitController } from './benefit.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { BenefitService } from './services/benefit.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { GustoService } from './services/gusto'; +import { GustoBenefitMapper } from './services/gusto/mappers'; @Module({ controllers: [BenefitController], providers: [ BenefitService, - SyncService, WebhookService, - ServiceRegistry, - IngestDataService, CoreUnification, - + GustoBenefitMapper, /* PROVIDERS SERVICES */ + GustoService, ], exports: [SyncService], }) diff --git a/packages/api/src/hris/benefit/services/benefit.service.ts b/packages/api/src/hris/benefit/services/benefit.service.ts index 786985bd5..f98e3422f 100644 --- a/packages/api/src/hris/benefit/services/benefit.service.ts +++ b/packages/api/src/hris/benefit/services/benefit.service.ts @@ -1,20 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedHrisBenefitInput, - UnifiedHrisBenefitOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedHrisBenefitOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalBenefitOutput } from '@@core/utils/types/original/original.hris'; - -import { IBenefitService } from '../types'; @Injectable() export class BenefitService { @@ -29,14 +20,79 @@ export class BenefitService { } async getBenefit( - id_benefiting_benefit: string, + id_hris_benefit: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const benefit = await this.prisma.hris_benefits.findUnique({ + where: { id_hris_benefit: id_hris_benefit }, + }); + + if (!benefit) { + throw new Error(`Benefit with ID ${id_hris_benefit} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: benefit.id_hris_benefit }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedBenefit: UnifiedHrisBenefitOutput = { + id: benefit.id_hris_benefit, + provider_name: benefit.provider_name, + employee_id: benefit.id_hris_employee, + employee_contribution: Number(benefit.employee_contribution), + company_contribution: Number(benefit.company_contribution), + start_date: benefit.start_date, + end_date: benefit.end_date, + employer_benefit_id: benefit.id_hris_employer_benefit, + field_mappings: field_mappings, + remote_id: benefit.remote_id, + remote_created_at: benefit.remote_created_at, + created_at: benefit.created_at, + modified_at: benefit.modified_at, + remote_was_deleted: benefit.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: benefit.id_hris_benefit }, + }); + unifiedBenefit.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.benefit.pull', + method: 'GET', + url: '/hris/benefit', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedBenefit; + } catch (error) { + throw error; + } } async getBenefits( @@ -47,7 +103,90 @@ export class BenefitService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisBenefitOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const benefits = await this.prisma.hris_benefits.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_benefit: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = benefits.length > limit; + if (hasNextPage) benefits.pop(); + + const unifiedBenefits = await Promise.all( + benefits.map(async (benefit) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: benefit.id_hris_benefit }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedBenefit: UnifiedHrisBenefitOutput = { + id: benefit.id_hris_benefit, + provider_name: benefit.provider_name, + employee_id: benefit.id_hris_employee, + employee_contribution: Number(benefit.employee_contribution), + company_contribution: Number(benefit.company_contribution), + start_date: benefit.start_date, + end_date: benefit.end_date, + employer_benefit_id: benefit.id_hris_employer_benefit, + field_mappings: field_mappings, + remote_id: benefit.remote_id, + remote_created_at: benefit.remote_created_at, + created_at: benefit.created_at, + modified_at: benefit.modified_at, + remote_was_deleted: benefit.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: benefit.id_hris_benefit }, + }); + unifiedBenefit.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedBenefit; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.benefit.pull', + method: 'GET', + url: '/hris/benefits', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedBenefits, + next_cursor: hasNextPage + ? benefits[benefits.length - 1].id_hris_benefit + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/benefit/services/gusto/index.ts b/packages/api/src/hris/benefit/services/gusto/index.ts new file mode 100644 index 000000000..7686d0d78 --- /dev/null +++ b/packages/api/src/hris/benefit/services/gusto/index.ts @@ -0,0 +1,72 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { HrisObject } from '@hris/@lib/@types'; +import { IBenefitService } from '@hris/benefit/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { GustoBenefitOutput } from './types'; + +@Injectable() +export class GustoService implements IBenefitService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + HrisObject.benefit.toUpperCase() + ':' + GustoService.name, + ); + this.registry.registerService('gusto', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId, id_employee } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'gusto', + vertical: 'hris', + }, + }); + + const employee = await this.prisma.hris_employees.findUnique({ + where: { + id_hris_employee: id_employee as string, + }, + select: { + remote_id: true, + }, + }); + + const resp = await axios.get( + `${connection.account_url}/v1/employees/${employee.remote_id}/employee_benefits`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + this.logger.log(`Synced gusto benefits !`); + + return { + data: resp.data, + message: 'Gusto benefits retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/benefit/services/gusto/mappers.ts b/packages/api/src/hris/benefit/services/gusto/mappers.ts new file mode 100644 index 000000000..6552a5fc5 --- /dev/null +++ b/packages/api/src/hris/benefit/services/gusto/mappers.ts @@ -0,0 +1,93 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { Injectable } from '@nestjs/common'; +import { GustoBenefitOutput } from './types'; +import { + UnifiedHrisBenefitInput, + UnifiedHrisBenefitOutput, +} from '@hris/benefit/types/model.unified'; +import { IBenefitMapper } from '@hris/benefit/types'; +import { Utils } from '@hris/@lib/@utils'; + +@Injectable() +export class GustoBenefitMapper implements IBenefitMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private ingestService: IngestDataService, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService('hris', 'benefit', 'gusto', this); + } + + async desunify( + source: UnifiedHrisBenefitInput, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return; + } + + async unify( + source: GustoBenefitOutput | GustoBenefitOutput[], + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + if (!Array.isArray(source)) { + return this.mapSingleBenefitToUnified( + source, + connectionId, + customFieldMappings, + ); + } + return Promise.all( + source.map((benefit) => + this.mapSingleBenefitToUnified( + benefit, + connectionId, + customFieldMappings, + ), + ), + ); + } + + private async mapSingleBenefitToUnified( + benefit: GustoBenefitOutput, + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + const opts: any = {}; + + if (benefit.employee_uuid) { + const employee_id = await this.utils.getEmployeeUuidFromRemoteId( + benefit.employee_uuid, + connectionId, + ); + if (employee_id) { + opts.employee_id = employee_id; + } + } + if (benefit.company_benefit_uuid) { + const id = await this.utils.getEmployerBenefitUuidFromRemoteId( + benefit.company_benefit_uuid, + connectionId, + ); + if (id) { + opts.employer_benefit_id = id; + } + } + + return { + remote_id: benefit.uuid || null, + remote_data: benefit, + ...opts, + employee_contribution: benefit.employee_deduction + ? parseFloat(benefit.employee_deduction) + : null, + company_contribution: benefit.company_contribution + ? parseFloat(benefit.company_contribution) + : null, + remote_was_deleted: null, + }; + } +} diff --git a/packages/api/src/hris/benefit/services/gusto/types.ts b/packages/api/src/hris/benefit/services/gusto/types.ts new file mode 100644 index 000000000..1f923b5a6 --- /dev/null +++ b/packages/api/src/hris/benefit/services/gusto/types.ts @@ -0,0 +1,28 @@ +export type GustoBenefitOutput = Partial<{ + version: string; + employee_uuid: string; + company_benefit_uuid: string; + active: boolean; + uuid: string; + employee_deduction: string; + company_contribution: string; + employee_deduction_annual_maximum: string; + company_contribution_annual_maximum: string; + limit_option: string; + deduct_as_percentage: boolean; + contribute_as_percentage: boolean; + catch_up: boolean; + coverage_amount: string; + contribution: { + type: 'amount' | 'percentage' | 'tiered'; + value: + | string + | number + | Array<{ threshold: number; amount: string | number }>; + }; + deduction_reduces_taxable_income: + | 'unset' + | 'reduces_taxable_income' + | 'does_not_reduce_taxable_income'; + coverage_salary_multiplier: string; +}>; diff --git a/packages/api/src/hris/benefit/sync/sync.processor.ts b/packages/api/src/hris/benefit/sync/sync.processor.ts new file mode 100644 index 000000000..920612a27 --- /dev/null +++ b/packages/api/src/hris/benefit/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-benefits') + async handleSyncBenefits(job: Job) { + try { + console.log(`Processing queue -> hris-sync-benefits ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris benefits', error); + } + } +} diff --git a/packages/api/src/hris/benefit/sync/sync.service.ts b/packages/api/src/hris/benefit/sync/sync.service.ts index 507b88c3e..6ede672db 100644 --- a/packages/api/src/hris/benefit/sync/sync.service.ts +++ b/packages/api/src/hris/benefit/sync/sync.service.ts @@ -1,15 +1,20 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalBenefitOutput } from '@@core/utils/types/original/original.hris'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_benefits as HrisBenefit } from '@prisma/client'; import { v4 as uuidv4 } from 'uuid'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedHrisBenefitOutput } from '../types/model.unified'; import { IBenefitService } from '../types'; -import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { UnifiedHrisBenefitOutput } from '../types/model.unified'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +24,145 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'benefit', this); + } + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing benefits...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IBenefitService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisBenefitOutput, + OriginalBenefitOutput, + IBenefitService + >(integrationId, linkedUserId, 'hris', 'benefit', service, []); + } catch (error) { + throw error; + } } - saveToDb( + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + benefits: UnifiedHrisBenefitOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const benefitResults: HrisBenefit[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < benefits.length; i++) { + const benefit = benefits[i]; + const originId = benefit.remote_id; - // Additional methods and logic + let existingBenefit = await this.prisma.hris_benefits.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const benefitData = { + provider_name: benefit.provider_name, + id_hris_employee: benefit.employee_id, + employee_contribution: benefit.employee_contribution + ? BigInt(benefit.employee_contribution) + : null, + company_contribution: benefit.company_contribution + ? BigInt(benefit.company_contribution) + : null, + start_date: benefit.start_date ? new Date(benefit.start_date) : null, + end_date: benefit.end_date ? new Date(benefit.end_date) : null, + id_hris_employer_benefit: benefit.employer_benefit_id, + remote_id: originId, + remote_created_at: benefit.remote_created_at + ? new Date(benefit.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: benefit.remote_was_deleted || false, + }; + + if (existingBenefit) { + existingBenefit = await this.prisma.hris_benefits.update({ + where: { id_hris_benefit: existingBenefit.id_hris_benefit }, + data: benefitData, + }); + } else { + existingBenefit = await this.prisma.hris_benefits.create({ + data: { + ...benefitData, + id_hris_benefit: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + benefitResults.push(existingBenefit); + + // Process field mappings + await this.ingestService.processFieldMappings( + benefit.field_mappings, + existingBenefit.id_hris_benefit, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingBenefit.id_hris_benefit, + remote_data[i], + ); + } + + return benefitResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/benefit/types/index.ts b/packages/api/src/hris/benefit/types/index.ts index d636c676a..d17fe41a9 100644 --- a/packages/api/src/hris/benefit/types/index.ts +++ b/packages/api/src/hris/benefit/types/index.ts @@ -1,18 +1,19 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedHrisBenefitInput, UnifiedHrisBenefitOutput } from './model.unified'; +import { + UnifiedHrisBenefitInput, + UnifiedHrisBenefitOutput, +} from './model.unified'; import { OriginalBenefitOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IBenefitService { - addBenefit( + addBenefit?( benefitData: DesunifyReturnType, linkedUserId: string, ): Promise>; - syncBenefits( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IBenefitMapper { diff --git a/packages/api/src/hris/benefit/types/model.unified.ts b/packages/api/src/hris/benefit/types/model.unified.ts index f348f6e07..5c2e16f94 100644 --- a/packages/api/src/hris/benefit/types/model.unified.ts +++ b/packages/api/src/hris/benefit/types/model.unified.ts @@ -49,24 +49,24 @@ export class UnifiedHrisBenefitInput { company_contribution?: number; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-01-01T00:00:00Z', nullable: true, description: 'The start date of the benefit', }) @IsDateString() @IsOptional() - start_date?: string; + start_date?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-12-31T23:59:59Z', nullable: true, description: 'The end date of the benefit', }) @IsDateString() @IsOptional() - end_date?: string; + end_date?: Date; @ApiPropertyOptional({ type: String, @@ -128,7 +128,7 @@ export class UnifiedHrisBenefitOutput extends UnifiedHrisBenefitInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -136,27 +136,27 @@ export class UnifiedHrisBenefitOutput extends UnifiedHrisBenefitInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the benefit record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the benefit record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/company/company.controller.ts b/packages/api/src/hris/company/company.controller.ts index 835ff7bd1..779ef9db6 100644 --- a/packages/api/src/hris/company/company.controller.ts +++ b/packages/api/src/hris/company/company.controller.ts @@ -1,36 +1,28 @@ +import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { Controller, - Post, - Body, - Query, Get, - Patch, - Param, Headers, + Param, + Query, UseGuards, } from '@nestjs/common'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { - ApiBody, + ApiHeader, ApiOperation, ApiParam, ApiQuery, ApiTags, - ApiHeader, - //ApiKeyAuth, } from '@nestjs/swagger'; - -import { UnifiedHrisCompanyOutput } from './types/model.unified'; -import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; -import { CompanyService } from './services/company.service'; -import { QueryDto } from '@@core/utils/dtos/query.dto'; +import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiGetCustomResponse, ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; -import { query } from 'express'; - +import { QueryDto } from '@@core/utils/dtos/query.dto'; +import { CompanyService } from './services/company.service'; +import { UnifiedHrisCompanyOutput } from './types/model.unified'; @ApiTags('hris/companies') @Controller('hris/companies') diff --git a/packages/api/src/hris/company/company.module.ts b/packages/api/src/hris/company/company.module.ts index bc2b03682..a5aaabb66 100644 --- a/packages/api/src/hris/company/company.module.ts +++ b/packages/api/src/hris/company/company.module.ts @@ -1,35 +1,26 @@ import { Module } from '@nestjs/common'; import { CompanyController } from './company.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { CompanyService } from './services/company.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { GustoCompanyMapper } from './services/gusto/mappers'; +import { GustoService } from './services/gusto'; @Module({ controllers: [CompanyController], providers: [ CompanyService, CoreUnification, - SyncService, - WebhookService, - ServiceRegistry, - IngestDataService, + GustoCompanyMapper, /* PROVIDERS SERVICES */ + GustoService, ], exports: [SyncService], }) diff --git a/packages/api/src/hris/company/services/company.service.ts b/packages/api/src/hris/company/services/company.service.ts index 3fdee9a57..d435c4fc0 100644 --- a/packages/api/src/hris/company/services/company.service.ts +++ b/packages/api/src/hris/company/services/company.service.ts @@ -1,16 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedHrisCompanyOutput } from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedHrisCompanyOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalCompanyOutput } from '@@core/utils/types/original/original.hris'; - -import { ICompanyService } from '../types'; @Injectable() export class CompanyService { @@ -25,14 +20,76 @@ export class CompanyService { } async getCompany( - id_companying_company: string, + id_hris_company: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const company = await this.prisma.hris_companies.findUnique({ + where: { id_hris_company: id_hris_company }, + }); + + if (!company) { + throw new Error(`Company with ID ${id_hris_company} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: company.id_hris_company }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedCompany: UnifiedHrisCompanyOutput = { + id: company.id_hris_company, + legal_name: company.legal_name, + display_name: company.display_name, + locations: company.locations, + eins: company.eins, + field_mappings: field_mappings, + remote_id: company.remote_id, + remote_created_at: company.remote_created_at, + created_at: company.created_at, + modified_at: company.modified_at, + remote_was_deleted: company.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: company.id_hris_company }, + }); + unifiedCompany.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.company.pull', + method: 'GET', + url: '/hris/company', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedCompany; + } catch (error) { + throw error; + } } async getCompanies( @@ -43,7 +100,87 @@ export class CompanyService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisCompanyOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const companies = await this.prisma.hris_companies.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_company: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = companies.length > limit; + if (hasNextPage) companies.pop(); + + const unifiedCompanies = await Promise.all( + companies.map(async (company) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: company.id_hris_company }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedCompany: UnifiedHrisCompanyOutput = { + id: company.id_hris_company, + legal_name: company.legal_name, + display_name: company.display_name, + eins: company.eins, + locations: company.locations, + field_mappings: field_mappings, + remote_id: company.remote_id, + remote_created_at: company.remote_created_at, + created_at: company.created_at, + modified_at: company.modified_at, + remote_was_deleted: company.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: company.id_hris_company }, + }); + unifiedCompany.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedCompany; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.company.pull', + method: 'GET', + url: '/hris/companies', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedCompanies, + next_cursor: hasNextPage + ? companies[companies.length - 1].id_hris_company + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/company/services/gusto/index.ts b/packages/api/src/hris/company/services/gusto/index.ts new file mode 100644 index 000000000..bfc8bfa99 --- /dev/null +++ b/packages/api/src/hris/company/services/gusto/index.ts @@ -0,0 +1,81 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { HrisObject } from '@hris/@lib/@types'; +import { ICompanyService } from '@hris/company/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { GustoCompanyOutput } from './types'; +import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; +import { OriginalCompanyOutput } from '@@core/utils/types/original/original.hris'; + +@Injectable() +export class GustoService implements ICompanyService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + HrisObject.company.toUpperCase() + ':' + GustoService.name, + ); + this.registry.registerService('gusto', this); + } + + addCompany( + companyData: DesunifyReturnType, + linkedUserId: string, + ): Promise> { + throw new Error('Method not implemented.'); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'gusto', + vertical: 'hris', + }, + }); + + const resp = await axios.get(`${connection.account_url}/v1/token_info`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }); + const company_uuid = resp.data.resource.uuid; + const resp_ = await axios.get( + `${connection.account_url}/v1/companies/${company_uuid}`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + this.logger.log(`Synced gusto companys !`); + + return { + data: [resp_.data], + message: 'Gusto companys retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/company/services/gusto/mappers.ts b/packages/api/src/hris/company/services/gusto/mappers.ts new file mode 100644 index 000000000..2c12f51d5 --- /dev/null +++ b/packages/api/src/hris/company/services/gusto/mappers.ts @@ -0,0 +1,119 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { Injectable } from '@nestjs/common'; +import { GustoCompanyOutput } from './types'; +import { + UnifiedHrisCompanyInput, + UnifiedHrisCompanyOutput, +} from '@hris/company/types/model.unified'; +import { ICompanyMapper } from '@hris/company/types'; +import { Utils } from '@hris/@lib/@utils'; +import { UnifiedHrisLocationOutput } from '@hris/location/types/model.unified'; +import { GustoLocationOutput } from '@hris/location/services/gusto/types'; +import { HrisObject } from '@hris/@lib/@types'; + +@Injectable() +export class GustoCompanyMapper implements ICompanyMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private ingestService: IngestDataService, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService('hris', 'company', 'gusto', this); + } + + async desunify( + source: UnifiedHrisCompanyInput, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return; + } + + async unify( + source: GustoCompanyOutput | GustoCompanyOutput[], + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + if (!Array.isArray(source)) { + return this.mapSingleCompanyToUnified( + source, + connectionId, + customFieldMappings, + ); + } + return Promise.all( + source.map((company) => + this.mapSingleCompanyToUnified( + company, + connectionId, + customFieldMappings, + ), + ), + ); + } + + private async mapSingleCompanyToUnified( + company: GustoCompanyOutput, + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + const opts: any = {}; + if (company.locations && company.locations.length > 0) { + const locations = await this.ingestService.ingestData< + UnifiedHrisLocationOutput, + GustoLocationOutput + >( + company.locations, + 'gusto', + connectionId, + 'hris', + HrisObject.location, + [], + ); + if (locations) { + opts.locations = locations; + } + } + return { + remote_id: company.uuid || null, + legal_name: company.name || null, + display_name: company.trade_name || null, + eins: company.ein ? [company.ein] : [], + remote_data: company, + ...opts, + }; + } + + private mapEntityType(entityType?: string): string | undefined { + switch (entityType) { + case 'C-Corporation': + return 'C-Corporation'; + case 'S-Corporation': + return 'S-Corporation'; + case 'Sole proprietor': + return 'Sole proprietor'; + case 'LLC': + return 'LLC'; + case 'LLP': + return 'LLP'; + case 'Limited partnership': + return 'Limited partnership'; + case 'Co-ownership': + return 'Co-ownership'; + case 'Association': + return 'Association'; + case 'Trusteeship': + return 'Trusteeship'; + case 'General partnership': + return 'General partnership'; + case 'Joint venture': + return 'Joint venture'; + case 'Non-Profit': + return 'Non-Profit'; + default: + return undefined; + } + } +} diff --git a/packages/api/src/hris/company/services/gusto/types.ts b/packages/api/src/hris/company/services/gusto/types.ts new file mode 100644 index 000000000..3bfa8539d --- /dev/null +++ b/packages/api/src/hris/company/services/gusto/types.ts @@ -0,0 +1,76 @@ +export type GustoCompanyOutput = Partial<{ + ein: string; // The Federal Employer Identification Number of the company. + entity_type: + | 'C-Corporation' + | 'S-Corporation' + | 'Sole proprietor' + | 'LLC' + | 'LLP' + | 'Limited partnership' + | 'Co-ownership' + | 'Association' + | 'Trusteeship' + | 'General partnership' + | 'Joint venture' + | 'Non-Profit'; // The tax payer type of the company. + tier: + | 'simple' + | 'plus' + | 'premium' + | 'core' + | 'complete' + | 'concierge' + | 'contractor_only' + | 'basic' + | null; // The Gusto product tier of the company. + is_suspended: boolean; // Whether or not the company is suspended in Gusto. + company_status: 'Approved' | 'Not Approved' | 'Suspended'; // The status of the company in Gusto. + uuid: string; // A unique identifier of the company in Gusto. + name: string; // The name of the company. + slug: string; // The slug of the name of the company. + trade_name: string; // The trade name of the company. + is_partner_managed: boolean; // Whether the company is fully managed by a partner via the API + pay_schedule_type: + | 'single' + | 'hourly_salaried' + | 'by_employee' + | 'by_department'; // The pay schedule assignment type. + join_date: string; // Company's first invoiceable event date + funding_type: 'ach' | 'reverse_wire' | 'wire_in' | 'brex'; // Company's default funding type + locations: Array
; // The locations of the company, with status + compensations: { + hourly: CompensationRate[]; // The available hourly compensation rates for the company. + fixed: CompensationRate[]; // The available fixed compensation rates for the company. + }; + paid_time_off: PaidTimeOff[]; // The available types of paid time off for the company. + primary_signatory: Person; // The primary signatory of the company. + primary_payroll_admin: Omit; // The primary payroll admin of the company. +}>; + +type Address = { + street_1: string; + street_2: string | null; + city: string; + state: string; + zip: string; + country: string; // Defaults to USA +}; + +type CompensationRate = { + name: string; + multiple?: number; // For hourly compensation + fixed?: number; // For fixed compensation +}; + +type PaidTimeOff = { + name: string; +}; + +type Person = { + first_name: string; + middle_initial?: string; + last_name: string; + phone: string; + email: string; + home_address?: Address; +}; diff --git a/packages/api/src/hris/company/sync/sync.processor.ts b/packages/api/src/hris/company/sync/sync.processor.ts new file mode 100644 index 000000000..e228206f7 --- /dev/null +++ b/packages/api/src/hris/company/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-companies') + async handleSyncCompanies(job: Job) { + try { + console.log(`Processing queue -> hris-sync-companies ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris companies', error); + } + } +} diff --git a/packages/api/src/hris/company/sync/sync.service.ts b/packages/api/src/hris/company/sync/sync.service.ts index 8c859fad2..a5026d099 100644 --- a/packages/api/src/hris/company/sync/sync.service.ts +++ b/packages/api/src/hris/company/sync/sync.service.ts @@ -10,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisCompanyOutput } from '../types/model.unified'; import { ICompanyService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_companies as HrisCompany } from '@prisma/client'; +import { OriginalCompanyOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +25,138 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'company', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing companies...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ICompanyService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisCompanyOutput, + OriginalCompanyOutput, + ICompanyService + >(integrationId, linkedUserId, 'hris', 'company', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + companies: UnifiedHrisCompanyOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const companyResults: HrisCompany[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < companies.length; i++) { + const company = companies[i]; + const originId = company.remote_id; + + let existingCompany = await this.prisma.hris_companies.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const companyData = { + legal_name: company.legal_name, + display_name: company.display_name, + eins: company.eins || [], + locations: company.locations, + remote_id: originId, + remote_created_at: company.remote_created_at + ? new Date(company.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: company.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingCompany) { + existingCompany = await this.prisma.hris_companies.update({ + where: { id_hris_company: existingCompany.id_hris_company }, + data: companyData, + }); + } else { + existingCompany = await this.prisma.hris_companies.create({ + data: { + ...companyData, + id_hris_company: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + companyResults.push(existingCompany); + + // Process field mappings + await this.ingestService.processFieldMappings( + company.field_mappings, + existingCompany.id_hris_company, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingCompany.id_hris_company, + remote_data[i], + ); + } + + return companyResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/company/types/index.ts b/packages/api/src/hris/company/types/index.ts index 1533f6255..2c5260695 100644 --- a/packages/api/src/hris/company/types/index.ts +++ b/packages/api/src/hris/company/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalCompanyOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ICompanyService { addCompany( @@ -12,10 +13,7 @@ export interface ICompanyService { linkedUserId: string, ): Promise>; - syncCompanys( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ICompanyMapper { diff --git a/packages/api/src/hris/company/types/model.unified.ts b/packages/api/src/hris/company/types/model.unified.ts index ed68a960e..038a0a441 100644 --- a/packages/api/src/hris/company/types/model.unified.ts +++ b/packages/api/src/hris/company/types/model.unified.ts @@ -19,6 +19,16 @@ export class UnifiedHrisCompanyInput { @IsOptional() legal_name?: string; + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: 'UUIDs of the of the Location associated with the company', + }) + @IsString() + @IsOptional() + locations?: string[]; + @ApiPropertyOptional({ type: String, example: 'Acme Corp', @@ -90,7 +100,7 @@ export class UnifiedHrisCompanyOutput extends UnifiedHrisCompanyInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -98,27 +108,27 @@ export class UnifiedHrisCompanyOutput extends UnifiedHrisCompanyInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the company record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the company record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/dependent/services/dependent.service.ts b/packages/api/src/hris/dependent/services/dependent.service.ts index 035b8fc4c..378b22dc7 100644 --- a/packages/api/src/hris/dependent/services/dependent.service.ts +++ b/packages/api/src/hris/dependent/services/dependent.service.ts @@ -1,20 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedHrisDependentInput, - UnifiedHrisDependentOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedHrisDependentOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalDependentOutput } from '@@core/utils/types/original/original.hris'; - -import { IDependentService } from '../types'; @Injectable() export class DependentService { @@ -29,14 +20,83 @@ export class DependentService { } async getDependent( - id_dependenting_dependent: string, + id_hris_dependent: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const dependent = await this.prisma.hris_dependents.findUnique({ + where: { id_hris_dependents: id_hris_dependent }, + }); + + if (!dependent) { + throw new Error(`Dependent with ID ${id_hris_dependent} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: dependent.id_hris_dependents }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedDependent: UnifiedHrisDependentOutput = { + id: dependent.id_hris_dependents, + first_name: dependent.first_name, + last_name: dependent.last_name, + middle_name: dependent.middle_name, + relationship: dependent.relationship, + date_of_birth: dependent.date_of_birth, + gender: dependent.gender, + phone_number: dependent.phone_number, + home_location: dependent.home_location, + is_student: dependent.is_student, + ssn: dependent.ssn, + employee_id: dependent.id_hris_employee, + field_mappings: field_mappings, + remote_id: dependent.remote_id, + remote_created_at: dependent.remote_created_at, + created_at: dependent.created_at, + modified_at: dependent.modified_at, + remote_was_deleted: dependent.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: dependent.id_hris_dependents }, + }); + unifiedDependent.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.dependent.pull', + method: 'GET', + url: '/hris/dependent', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedDependent; + } catch (error) { + throw error; + } } async getDependents( @@ -47,7 +107,94 @@ export class DependentService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisDependentOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const dependents = await this.prisma.hris_dependents.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_dependents: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = dependents.length > limit; + if (hasNextPage) dependents.pop(); + + const unifiedDependents = await Promise.all( + dependents.map(async (dependent) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: dependent.id_hris_dependents }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedDependent: UnifiedHrisDependentOutput = { + id: dependent.id_hris_dependents, + first_name: dependent.first_name, + last_name: dependent.last_name, + middle_name: dependent.middle_name, + relationship: dependent.relationship, + date_of_birth: dependent.date_of_birth, + gender: dependent.gender, + phone_number: dependent.phone_number, + home_location: dependent.home_location, + is_student: dependent.is_student, + ssn: dependent.ssn, + employee_id: dependent.id_hris_employee, + field_mappings: field_mappings, + remote_id: dependent.remote_id, + remote_created_at: dependent.remote_created_at, + created_at: dependent.created_at, + modified_at: dependent.modified_at, + remote_was_deleted: dependent.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: dependent.id_hris_dependents }, + }); + unifiedDependent.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedDependent; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.dependent.pull', + method: 'GET', + url: '/hris/dependents', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedDependents, + next_cursor: hasNextPage + ? dependents[dependents.length - 1].id_hris_dependents + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/dependent/sync/sync.processor.ts b/packages/api/src/hris/dependent/sync/sync.processor.ts new file mode 100644 index 000000000..0eed99e3c --- /dev/null +++ b/packages/api/src/hris/dependent/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-dependents') + async handleSyncCompanies(job: Job) { + try { + console.log(`Processing queue -> hris-sync-dependents ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris dependents', error); + } + } +} diff --git a/packages/api/src/hris/dependent/sync/sync.service.ts b/packages/api/src/hris/dependent/sync/sync.service.ts index ba70774a1..8bacd8f15 100644 --- a/packages/api/src/hris/dependent/sync/sync.service.ts +++ b/packages/api/src/hris/dependent/sync/sync.service.ts @@ -10,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisDependentOutput } from '../types/model.unified'; import { IDependentService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_dependents as HrisDependent } from '@prisma/client'; +import { OriginalDependentOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +25,147 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'dependent', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing dependents...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IDependentService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisDependentOutput, + OriginalDependentOutput, + IDependentService + >(integrationId, linkedUserId, 'hris', 'dependent', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + dependents: UnifiedHrisDependentOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const dependentResults: HrisDependent[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < dependents.length; i++) { + const dependent = dependents[i]; + const originId = dependent.remote_id; + + let existingDependent = await this.prisma.hris_dependents.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const dependentData = { + first_name: dependent.first_name, + last_name: dependent.last_name, + middle_name: dependent.middle_name, + relationship: dependent.relationship, + date_of_birth: dependent.date_of_birth + ? new Date(dependent.date_of_birth) + : null, + gender: dependent.gender, + phone_number: dependent.phone_number, + home_location: dependent.home_location, + is_student: dependent.is_student, + ssn: dependent.ssn, + id_hris_employee: dependent.employee_id, + remote_id: originId, + remote_created_at: dependent.remote_created_at + ? new Date(dependent.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: dependent.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingDependent) { + existingDependent = await this.prisma.hris_dependents.update({ + where: { id_hris_dependents: existingDependent.id_hris_dependents }, + data: dependentData, + }); + } else { + existingDependent = await this.prisma.hris_dependents.create({ + data: { + ...dependentData, + id_hris_dependents: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + dependentResults.push(existingDependent); + + // Process field mappings + await this.ingestService.processFieldMappings( + dependent.field_mappings, + existingDependent.id_hris_dependents, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingDependent.id_hris_dependents, + remote_data[i], + ); + } + + return dependentResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/dependent/types/index.ts b/packages/api/src/hris/dependent/types/index.ts index b31865e8b..5c268bd78 100644 --- a/packages/api/src/hris/dependent/types/index.ts +++ b/packages/api/src/hris/dependent/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedHrisDependentInput, UnifiedHrisDependentOutput } from './model.unified'; +import { + UnifiedHrisDependentInput, + UnifiedHrisDependentOutput, +} from './model.unified'; import { OriginalDependentOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IDependentService { addDependent( @@ -9,10 +13,7 @@ export interface IDependentService { linkedUserId: string, ): Promise>; - syncDependents( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IDependentMapper { diff --git a/packages/api/src/hris/dependent/types/model.unified.ts b/packages/api/src/hris/dependent/types/model.unified.ts index d4a82da1b..70eedd34e 100644 --- a/packages/api/src/hris/dependent/types/model.unified.ts +++ b/packages/api/src/hris/dependent/types/model.unified.ts @@ -7,6 +7,15 @@ import { IsBoolean, } from 'class-validator'; +export type Gender = + | 'MALE' + | 'FEMALE' + | 'NON-BINARY' + | 'OTHER' + | 'PREFER_NOT_TO_DISCLOSE'; + +export type Relationship = 'CHILD' | 'SPOUSE' | 'DOMESTIC_PARTNER'; + export class UnifiedHrisDependentInput { @ApiPropertyOptional({ type: String, @@ -40,33 +49,35 @@ export class UnifiedHrisDependentInput { @ApiPropertyOptional({ type: String, - example: 'Child', + example: 'CHILD', + enum: ['CHILD', 'SPOUSE', 'DOMESTIC_PARTNER'], nullable: true, description: 'The relationship of the dependent to the employee', }) @IsString() @IsOptional() - relationship?: string; + relationship?: Relationship | string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2020-01-01', nullable: true, description: 'The date of birth of the dependent', }) @IsDateString() @IsOptional() - date_of_birth?: string; + date_of_birth?: Date; @ApiPropertyOptional({ type: String, - example: 'Male', + example: 'MALE', + enum: ['MALE', 'FEMALE', 'NON-BINARY', 'OTHER', 'PREFER_NOT_TO_DISCLOSE'], nullable: true, description: 'The gender of the dependent', }) @IsString() @IsOptional() - gender?: string; + gender?: Gender | string; @ApiPropertyOptional({ type: String, @@ -169,7 +180,7 @@ export class UnifiedHrisDependentOutput extends UnifiedHrisDependentInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -177,27 +188,27 @@ export class UnifiedHrisDependentOutput extends UnifiedHrisDependentInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the dependent record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the dependent record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/employee/employee.module.ts b/packages/api/src/hris/employee/employee.module.ts index 38026ec03..1ce66118c 100644 --- a/packages/api/src/hris/employee/employee.module.ts +++ b/packages/api/src/hris/employee/employee.module.ts @@ -1,35 +1,26 @@ import { Module } from '@nestjs/common'; import { EmployeeController } from './employee.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { EmployeeService } from './services/employee.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { GustoEmployeeMapper } from './services/gusto/mappers'; +import { GustoService } from './services/gusto'; @Module({ controllers: [EmployeeController], providers: [ EmployeeService, CoreUnification, - SyncService, - WebhookService, - ServiceRegistry, - IngestDataService, + GustoEmployeeMapper, /* PROVIDERS SERVICES */ + GustoService, ], exports: [SyncService], }) diff --git a/packages/api/src/hris/employee/services/employee.service.ts b/packages/api/src/hris/employee/services/employee.service.ts index 91339c353..9c35cca70 100644 --- a/packages/api/src/hris/employee/services/employee.service.ts +++ b/packages/api/src/hris/employee/services/employee.service.ts @@ -1,20 +1,14 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; import { UnifiedHrisEmployeeInput, UnifiedHrisEmployeeOutput, } from '../types/model.unified'; - -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalEmployeeOutput } from '@@core/utils/types/original/original.hris'; - -import { IEmployeeService } from '../types'; @Injectable() export class EmployeeService { @@ -36,18 +30,127 @@ export class EmployeeService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addEmployee(unifiedEmployeeData, linkedUserId); + + const savedEmployee = await this.prisma.hris_employees.create({ + data: { + id_hris_employee: uuidv4(), + ...unifiedEmployeeData, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + remote_was_deleted: false, + }, + }); + + const result: UnifiedHrisEmployeeOutput = { + ...savedEmployee, + id: savedEmployee.id_hris_employee, + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getEmployee( - id_employeeing_employee: string, + id_hris_employee: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const employee = await this.prisma.hris_employees.findUnique({ + where: { id_hris_employee: id_hris_employee }, + }); + + if (!employee) { + throw new Error(`Employee with ID ${id_hris_employee} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: employee.id_hris_employee }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedEmployee: UnifiedHrisEmployeeOutput = { + id: employee.id_hris_employee, + groups: employee.groups, + employee_number: employee.employee_number, + company_id: employee.id_hris_company, + first_name: employee.first_name, + last_name: employee.last_name, + preferred_name: employee.preferred_name, + display_full_name: employee.display_full_name, + username: employee.username, + work_email: employee.work_email, + personal_email: employee.personal_email, + mobile_phone_number: employee.mobile_phone_number, + employments: employee.employments, + ssn: employee.ssn, + locations: employee.locations, + manager_id: employee.manager, + gender: employee.gender, + ethnicity: employee.ethnicity, + marital_status: employee.marital_status, + date_of_birth: employee.date_of_birth, + start_date: employee.start_date, + employment_status: employee.employment_status, + termination_date: employee.termination_date, + avatar_url: employee.avatar_url, + field_mappings: field_mappings, + remote_id: employee.remote_id, + remote_created_at: employee.remote_created_at, + created_at: employee.created_at, + modified_at: employee.modified_at, + remote_was_deleted: employee.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: employee.id_hris_employee }, + }); + unifiedEmployee.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.employee.pull', + method: 'GET', + url: '/hris/employee', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedEmployee; + } catch (error) { + throw error; + } } async getEmployees( @@ -58,7 +161,102 @@ export class EmployeeService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisEmployeeOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const employees = await this.prisma.hris_employees.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_employee: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = employees.length > limit; + if (hasNextPage) employees.pop(); + + const unifiedEmployees = await Promise.all( + employees.map(async (employee) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: employee.id_hris_employee }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedEmployee: UnifiedHrisEmployeeOutput = { + id: employee.id_hris_employee, + groups: employee.groups, + employee_number: employee.employee_number, + company_id: employee.id_hris_company, + first_name: employee.first_name, + last_name: employee.last_name, + preferred_name: employee.preferred_name, + display_full_name: employee.display_full_name, + username: employee.username, + locations: employee.locations, + manager_id: employee.manager, + work_email: employee.work_email, + personal_email: employee.personal_email, + mobile_phone_number: employee.mobile_phone_number, + employments: employee.employments, + ssn: employee.ssn, + gender: employee.gender, + ethnicity: employee.ethnicity, + marital_status: employee.marital_status, + date_of_birth: employee.date_of_birth, + start_date: employee.start_date, + employment_status: employee.employment_status, + termination_date: employee.termination_date, + avatar_url: employee.avatar_url, + field_mappings: field_mappings, + remote_id: employee.remote_id, + remote_created_at: employee.remote_created_at, + }; + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: employee.id_hris_employee }, + }); + unifiedEmployee.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedEmployee; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.employee.pull', + method: 'GET', + url: '/hris/employees', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedEmployees, + next_cursor: hasNextPage + ? employees[employees.length - 1].id_hris_employee + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/employee/services/gusto/index.ts b/packages/api/src/hris/employee/services/gusto/index.ts new file mode 100644 index 000000000..f2a416eb3 --- /dev/null +++ b/packages/api/src/hris/employee/services/gusto/index.ts @@ -0,0 +1,72 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { HrisObject } from '@hris/@lib/@types'; +import { IEmployeeService } from '@hris/employee/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { GustoEmployeeOutput } from './types'; + +@Injectable() +export class GustoService implements IEmployeeService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + HrisObject.employee.toUpperCase() + ':' + GustoService.name, + ); + this.registry.registerService('gusto', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId, company_id } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'gusto', + vertical: 'hris', + }, + }); + + const company = await this.prisma.hris_companies.findUnique({ + where: { + id_hris_company: company_id as string, + }, + select: { + remote_id: true, + }, + }); + + const resp = await axios.get( + `${connection.account_url}/v1/companies/${company.remote_id}/employees`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + this.logger.log(`Synced gusto employees !`); + + return { + data: resp.data, + message: 'Gusto employees retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/employee/services/gusto/mappers.ts b/packages/api/src/hris/employee/services/gusto/mappers.ts new file mode 100644 index 000000000..03114adc8 --- /dev/null +++ b/packages/api/src/hris/employee/services/gusto/mappers.ts @@ -0,0 +1,139 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { Injectable } from '@nestjs/common'; +import { GustoEmployeeOutput } from './types'; +import { + UnifiedHrisEmployeeInput, + UnifiedHrisEmployeeOutput, +} from '@hris/employee/types/model.unified'; +import { IEmployeeMapper } from '@hris/employee/types'; +import { Utils } from '@hris/@lib/@utils'; +import { Job } from 'bull'; +import { HrisObject, TicketingObject } from '@panora/shared'; +import { ZendeskTagOutput } from '@ticketing/tag/services/zendesk/types'; +import axios from 'axios'; +import { UnifiedHrisLocationOutput } from '@hris/location/types/model.unified'; +import { UnifiedHrisEmploymentOutput } from '@hris/employment/types/model.unified'; +import { GustoEmploymentOutput } from '@hris/employment/services/gusto/types'; + +@Injectable() +export class GustoEmployeeMapper implements IEmployeeMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private ingestService: IngestDataService, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService('hris', 'employee', 'gusto', this); + } + + async desunify( + source: UnifiedHrisEmployeeInput, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return; + } + + async unify( + source: GustoEmployeeOutput | GustoEmployeeOutput[], + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + if (!Array.isArray(source)) { + return this.mapSingleEmployeeToUnified( + source, + connectionId, + customFieldMappings, + ); + } + return Promise.all( + source.map((employee) => + this.mapSingleEmployeeToUnified( + employee, + connectionId, + customFieldMappings, + ), + ), + ); + } + + private async mapSingleEmployeeToUnified( + employee: GustoEmployeeOutput, + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + const opts: any = {}; + if (employee.company_uuid) { + const company_id = await this.utils.getCompanyUuidFromRemoteId( + employee.company_uuid, + connectionId, + ); + if (company_id) { + opts.company_id = company_id; + } + } + if (employee.manager_uuid) { + const manager_id = await this.utils.getEmployeeUuidFromRemoteId( + employee.manager_uuid, + connectionId, + ); + if (manager_id) { + opts.manager_id = manager_id; + } + } + + if (employee.jobs) { + const compensationObjects = employee.jobs.map((job) => { + const compensation = + job.compensations.find( + (compensation) => + compensation.uuid === job.current_compensation_uuid, + ) || null; + + return { + ...compensation, + title: job.title, + }; + }); + const employments = await this.ingestService.ingestData< + UnifiedHrisEmploymentOutput, + GustoEmploymentOutput + >( + compensationObjects, + 'gusto', + connectionId, + 'hris', + HrisObject.employment, + [], + ); + if (employments) { + opts.employments = employments; + } + } + + const primaryJob = employee.jobs.find((job) => job.primary); + + return { + remote_id: employee.uuid, + remote_data: employee, + first_name: employee.first_name, + last_name: employee.last_name, + preferred_name: employee.preferred_first_name, + display_full_name: `${employee.first_name} ${employee.last_name}`, + work_email: employee.work_email, + personal_email: employee.email, + mobile_phone_number: employee.phone, + start_date: primaryJob ? new Date(primaryJob.hire_date) : null, + termination_date: + employee.terminations.length > 0 + ? new Date(employee.terminations[0].effective_date) + : null, + employment_status: employee.current_employment_status, + date_of_birth: employee.date_of_birth + ? new Date(employee.date_of_birth) + : null, + ...opts, + }; + } +} diff --git a/packages/api/src/hris/employee/services/gusto/types.ts b/packages/api/src/hris/employee/services/gusto/types.ts new file mode 100644 index 000000000..881500b5f --- /dev/null +++ b/packages/api/src/hris/employee/services/gusto/types.ts @@ -0,0 +1,123 @@ +export type GustoEmployeeOutput = { + uuid: string; // The UUID of the employee in Gusto. + first_name: string; // The first name of the employee. + middle_initial: string | null; // The middle initial of the employee. + last_name: string; // The last name of the employee. + email: string | null; // The personal email address of the employee. + company_uuid: string; // The UUID of the company the employee is employed by. + manager_uuid: string; // The UUID of the employee's manager. + version: string; // The current version of the employee. + department: string | null; // The employee's department in the company. + terminated: boolean; // Whether the employee is terminated. + two_percent_shareholder: boolean; // Whether the employee is a two percent shareholder of the company. + onboarded: boolean; // Whether the employee has completed onboarding. + onboarding_status: + | 'onboarding_completed' + | 'admin_onboarding_incomplete' + | 'self_onboarding_pending_invite' + | 'self_onboarding_invited' + | 'self_onboarding_invited_started' + | 'self_onboarding_invited_overdue' + | 'self_onboarding_completed_by_employee' + | 'self_onboarding_awaiting_admin_review'; // The current onboarding status of the employee. + jobs: Job[]; // The jobs held by the employee. + terminations: Termination[]; // The terminations of the employee. + garnishments: Garnishment[]; // The garnishments of the employee. + custom_fields?: CustomField[]; // Custom fields for the employee. + date_of_birth: string | null; // The date of birth of the employee. + has_ssn: boolean; // Indicates whether the employee has an SSN in Gusto. + ssn: string; // Deprecated. This field always returns an empty string. + phone: string; // The phone number of the employee. + preferred_first_name: string; // The preferred first name of the employee. + payment_method: 'Direct Deposit' | 'Check' | null; // The employee's payment method. + work_email: string | null; // The work email address of the employee. + current_employment_status: + | 'full_time' + | 'part_time_under_twenty_hours' + | 'part_time_twenty_plus_hours' + | 'variable' + | 'seasonal' + | null; // The current employment status of the employee. +}; + +type Job = { + uuid: string; // The UUID of the job. + version: string; // The current version of the job. + employee_uuid: string; // The UUID of the employee to which the job belongs. + hire_date: string; // The date when the employee was hired or rehired for the job. + title: string | null; // The title for the job. + primary: boolean; // Whether this is the employee's primary job. + rate: string; // The current compensation rate of the job. + payment_unit: string; // The payment unit of the current compensation for the job. + current_compensation_uuid: string; // The UUID of the current compensation of the job. + two_percent_shareholder: boolean; // Whether the employee owns at least 2% of the company. + state_wc_covered: boolean; // Whether this job is eligible for workers' compensation coverage in the state of Washington (WA). + state_wc_class_code: string; // The risk class code for workers' compensation in Washington state. + compensations: Compensation[]; // The compensations associated with the job. +}; + +type Compensation = { + uuid: string; // The UUID of the compensation in Gusto. + version: string; // The current version of the compensation. + job_uuid: string; // The UUID of the job to which the compensation belongs. + rate: string; // The dollar amount paid per payment unit. + payment_unit: 'Hour' | 'Week' | 'Month' | 'Year' | 'Paycheck'; // The unit accompanying the compensation rate. + flsa_status: + | 'Exempt' + | 'Salaried Nonexempt' + | 'Nonexempt' + | 'Owner' + | 'Commission Only Exempt' + | 'Commission Only Nonexempt'; // The FLSA status for this compensation. + effective_date: string; // The effective date for this compensation. + adjust_for_minimum_wage: boolean; // Indicates if the compensation could be adjusted to minimum wage during payroll calculation. + eligible_paid_time_off: EligiblePaidTimeOff[]; // The available types of paid time off for the compensation. +}; + +type EligiblePaidTimeOff = { + name: string; // The name of the paid time off type. + policy_name: string; // The name of the time off policy. + policy_uuid: string; // The UUID of the time off policy. + accrual_unit: string; // The unit the PTO type is accrued in. + accrual_rate: string; // The number of accrual units accrued per accrual period. + accrual_method: string; // The accrual method of the time off policy. + accrual_period: string; // The frequency at which the PTO type is accrued. + accrual_balance: string; // The number of accrual units accrued. + maximum_accrual_balance: string | null; // The maximum number of accrual units allowed. + paid_at_termination: boolean; // Whether the accrual balance is paid to the employee upon termination. +}; + +type Termination = { + uuid: string; // The UUID of the termination object. + version: string; // The current version of the termination. + employee_uuid: string; // The UUID of the employee to which this termination is attached. + active: boolean; // Whether the employee's termination has gone into effect. + cancelable: boolean; // Whether the employee's termination is cancelable. + effective_date: string; // The employee's last day of work. + run_termination_payroll: boolean; // Whether the employee should receive their final wages via an off-cycle payroll. +}; + +type Garnishment = { + uuid: string; // The UUID of the garnishment in Gusto. + version: string; // The current version of the garnishment. + employee_uuid: string; // The UUID of the employee to which this garnishment belongs. + active: boolean; // Whether or not this garnishment is currently active. + amount: string; // The amount of the garnishment. + description: string; // The description of the garnishment. + court_ordered: boolean; // Whether the garnishment is court ordered. + times: number | null; // The number of times to apply the garnishment. + recurring: boolean; // Whether the garnishment should recur indefinitely. + annual_maximum: string | null; // The maximum deduction per annum. + pay_period_maximum: string | null; // The maximum deduction per pay period. + deduct_as_percentage: boolean; // Whether the amount should be treated as a percentage to be deducted per pay period. +}; + +type CustomField = { + id: string; // The ID of the custom field. + company_custom_field_id: string; // The ID of the company custom field. + name: string; // The name of the custom field. + type: 'text' | 'currency' | 'number' | 'date' | 'radio'; // Input type for the custom field. + description: string; // The description of the custom field. + value: string; // The value of the custom field. + selection_options: string[] | null; // An array of options for fields of type radio. +}; diff --git a/packages/api/src/hris/employee/sync/sync.processor.ts b/packages/api/src/hris/employee/sync/sync.processor.ts new file mode 100644 index 000000000..2e11413d2 --- /dev/null +++ b/packages/api/src/hris/employee/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-employees') + async handleSyncEmployees(job: Job) { + try { + console.log(`Processing queue -> hris-sync-employees ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris employees', error); + } + } +} diff --git a/packages/api/src/hris/employee/sync/sync.service.ts b/packages/api/src/hris/employee/sync/sync.service.ts index ba2698842..8ff6cf57b 100644 --- a/packages/api/src/hris/employee/sync/sync.service.ts +++ b/packages/api/src/hris/employee/sync/sync.service.ts @@ -10,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisEmployeeOutput } from '../types/model.unified'; import { IEmployeeService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_employees as HrisEmployee } from '@prisma/client'; +import { OriginalEmployeeOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +25,163 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'employee', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing employees...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IEmployeeService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisEmployeeOutput, + OriginalEmployeeOutput, + IEmployeeService + >(integrationId, linkedUserId, 'hris', 'employee', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + employees: UnifiedHrisEmployeeOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const employeeResults: HrisEmployee[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < employees.length; i++) { + const employee = employees[i]; + const originId = employee.remote_id; + + let existingEmployee = await this.prisma.hris_employees.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const employeeData = { + groups: employee.groups || [], + employee_number: employee.employee_number, + id_hris_company: employee.company_id, + first_name: employee.first_name, + last_name: employee.last_name, + preferred_name: employee.preferred_name, + display_full_name: employee.display_full_name, + locations: employee.locations, + username: employee.username, + work_email: employee.work_email, + personal_email: employee.personal_email, + mobile_phone_number: employee.mobile_phone_number, + employments: employee.employments || [], + ssn: employee.ssn, + gender: employee.gender, + manager_id: employee.manager_id, + ethnicity: employee.ethnicity, + marital_status: employee.marital_status, + date_of_birth: employee.date_of_birth + ? new Date(employee.date_of_birth) + : null, + start_date: employee.start_date + ? new Date(employee.start_date) + : null, + employment_status: employee.employment_status, + termination_date: employee.termination_date + ? new Date(employee.termination_date) + : null, + avatar_url: employee.avatar_url, + remote_id: originId, + remote_created_at: employee.remote_created_at + ? new Date(employee.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: employee.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingEmployee) { + existingEmployee = await this.prisma.hris_employees.update({ + where: { id_hris_employee: existingEmployee.id_hris_employee }, + data: employeeData, + }); + } else { + existingEmployee = await this.prisma.hris_employees.create({ + data: { + ...employeeData, + id_hris_employee: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + employeeResults.push(existingEmployee); + + // Process field mappings + await this.ingestService.processFieldMappings( + employee.field_mappings, + existingEmployee.id_hris_employee, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingEmployee.id_hris_employee, + remote_data[i], + ); + } + + return employeeResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/employee/types/index.ts b/packages/api/src/hris/employee/types/index.ts index 582b158b1..fc44e8583 100644 --- a/packages/api/src/hris/employee/types/index.ts +++ b/packages/api/src/hris/employee/types/index.ts @@ -1,18 +1,19 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedHrisEmployeeInput, UnifiedHrisEmployeeOutput } from './model.unified'; +import { + UnifiedHrisEmployeeInput, + UnifiedHrisEmployeeOutput, +} from './model.unified'; import { OriginalEmployeeOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IEmployeeService { - addEmployee( + addEmployee?( employeeData: DesunifyReturnType, linkedUserId: string, ): Promise>; - syncEmployees( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IEmployeeMapper { diff --git a/packages/api/src/hris/employee/types/model.unified.ts b/packages/api/src/hris/employee/types/model.unified.ts index 8d395c64c..a7c6e8239 100644 --- a/packages/api/src/hris/employee/types/model.unified.ts +++ b/packages/api/src/hris/employee/types/model.unified.ts @@ -9,6 +9,32 @@ import { IsUrl, } from 'class-validator'; +export type Gender = + | 'MALE' + | 'FEMALE' + | 'NON-BINARY' + | 'OTHER' + | 'PREFER_NOT_TO_DISCLOSE'; + +export type Ethnicity = + | 'AMERICAN_INDIAN_OR_ALASKA_NATIVE' + | 'ASIAN_OR_INDIAN_SUBCONTINENT' + | 'BLACK_OR_AFRICAN_AMERICAN' + | 'HISPANIC_OR_LATINO' + | 'NATIVE_HAWAIIAN_OR_OTHER_PACIFIC_ISLANDER' + | 'TWO_OR_MORE_RACES' + | 'WHITE' + | 'PREFER_NOT_TO_DISCLOSE'; + +export type MartialStatus = + | 'SINGLE' + | 'MARRIED_FILING_JOINTLY' + | 'MARRIED_FILING_SEPARATELY' + | 'HEAD_OF_HOUSEHOLD' + | 'QUALIFYING_WIDOW_OR_WIDOWER_WITH_DEPENDENT_CHILD'; + +export type EmploymentStatus = 'ACTIVE' | 'PENDING' | 'INACTIVE'; + export class UnifiedHrisEmployeeInput { @ApiPropertyOptional({ type: [String], @@ -21,6 +47,16 @@ export class UnifiedHrisEmployeeInput { @IsOptional() groups?: string[]; + @ApiPropertyOptional({ + type: [String], + example: ['801f9ede-c698-4e66-a7fc-48d19eebaa4f'], + nullable: true, + description: 'UUIDs of the of the Location associated with the company', + }) + @IsString() + @IsOptional() + locations?: string[]; + @ApiPropertyOptional({ type: String, example: 'EMP001', @@ -123,7 +159,10 @@ export class UnifiedHrisEmployeeInput { @ApiPropertyOptional({ type: [String], - example: ['Employment1', 'Employment2'], + example: [ + '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + ], nullable: true, description: 'The employments of the employee', }) @@ -144,73 +183,92 @@ export class UnifiedHrisEmployeeInput { @ApiPropertyOptional({ type: String, - example: 'Male', + example: 'MALE', + enum: ['MALE', 'FEMALE', 'NON-BINARY', 'OTHER', 'PREFER_NOT_TO_DISCLOSE'], nullable: true, description: 'The gender of the employee', }) @IsString() @IsOptional() - gender?: string; + gender?: Gender | string; @ApiPropertyOptional({ type: String, - example: 'Caucasian', + example: 'AMERICAN_INDIAN_OR_ALASKA_NATIVE', + enum: [ + 'AMERICAN_INDIAN_OR_ALASKA_NATIVE', + 'ASIAN_OR_INDIAN_SUBCONTINENT', + 'BLACK_OR_AFRICAN_AMERICAN', + 'HISPANIC_OR_LATINO', + 'NATIVE_HAWAIIAN_OR_OTHER_PACIFIC_ISLANDER', + 'TWO_OR_MORE_RACES', + 'WHITE', + 'PREFER_NOT_TO_DISCLOSE', + ], nullable: true, description: 'The ethnicity of the employee', }) @IsString() @IsOptional() - ethnicity?: string; + ethnicity?: Ethnicity | string; @ApiPropertyOptional({ type: String, example: 'Married', + enum: [ + 'SINGLE', + 'MARRIED_FILING_JOINTLY', + 'MARRIED_FILING_SEPARATELY', + 'HEAD_OF_HOUSEHOLD', + 'QUALIFYING_WIDOW_OR_WIDOWER_WITH_DEPENDENT_CHILD', + ], nullable: true, description: 'The marital status of the employee', }) @IsString() @IsOptional() - marital_status?: string; + marital_status?: MartialStatus | string; @ApiPropertyOptional({ - type: String, + type: Date, example: '1990-01-01', nullable: true, description: 'The date of birth of the employee', }) @IsDateString() @IsOptional() - date_of_birth?: string; + date_of_birth?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2020-01-01', nullable: true, description: 'The start date of the employee', }) @IsDateString() @IsOptional() - start_date?: string; + start_date?: Date; @ApiPropertyOptional({ type: String, - example: 'Active', + example: 'ACTIVE', + enum: ['ACTIVE', 'PENDING', 'INACTIVE'], nullable: true, description: 'The employment status of the employee', }) @IsString() @IsOptional() - employment_status?: string; + employment_status?: EmploymentStatus | string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2025-01-01', nullable: true, description: 'The termination date of the employee', }) @IsDateString() @IsOptional() - termination_date?: string; + termination_date?: Date; @ApiPropertyOptional({ type: String, @@ -222,6 +280,16 @@ export class UnifiedHrisEmployeeInput { @IsOptional() avatar_url?: string; + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'UUID of the manager (employee) of the employee', + }) + @IsUrl() + @IsOptional() + manager_id?: string; + @ApiPropertyOptional({ type: Object, example: { @@ -273,7 +341,7 @@ export class UnifiedHrisEmployeeOutput extends UnifiedHrisEmployeeInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -281,27 +349,27 @@ export class UnifiedHrisEmployeeOutput extends UnifiedHrisEmployeeInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the employee record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the employee record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/employeepayrollrun/services/employeepayrollrun.service.ts b/packages/api/src/hris/employeepayrollrun/services/employeepayrollrun.service.ts index cb9080481..4b0ef884a 100644 --- a/packages/api/src/hris/employeepayrollrun/services/employeepayrollrun.service.ts +++ b/packages/api/src/hris/employeepayrollrun/services/employeepayrollrun.service.ts @@ -1,20 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedHrisEmployeepayrollrunInput, - UnifiedHrisEmployeepayrollrunOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedHrisEmployeepayrollrunOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalEmployeePayrollRunOutput } from '@@core/utils/types/original/original.hris'; - -import { IEmployeePayrollRunService } from '../types'; @Injectable() export class EmployeePayrollRunService { @@ -27,15 +18,110 @@ export class EmployeePayrollRunService { ) { this.logger.setContext(EmployeePayrollRunService.name); } + async getEmployeePayrollRun( - id_employeepayrollruning_employeepayrollrun: string, + id_hris_employee_payroll_run: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const employeePayrollRun = + await this.prisma.hris_employee_payroll_runs.findUnique({ + where: { id_hris_employee_payroll_run: id_hris_employee_payroll_run }, + include: { + hris_employee_payroll_runs_deductions: true, + hris_employee_payroll_runs_earnings: true, + hris_employee_payroll_runs_taxes: true, + }, + }); + + if (!employeePayrollRun) { + throw new Error( + `Employee Payroll Run with ID ${id_hris_employee_payroll_run} not found.`, + ); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: employeePayrollRun.id_hris_employee_payroll_run, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedEmployeePayrollRun: UnifiedHrisEmployeepayrollrunOutput = { + id: employeePayrollRun.id_hris_employee_payroll_run, + employee_id: employeePayrollRun.id_hris_employee, + payroll_run_id: employeePayrollRun.id_hris_payroll_run, + gross_pay: Number(employeePayrollRun.gross_pay), + net_pay: Number(employeePayrollRun.net_pay), + start_date: employeePayrollRun.start_date, + end_date: employeePayrollRun.end_date, + check_date: employeePayrollRun.check_date, + deductions: + employeePayrollRun.hris_employee_payroll_runs_deductions.map((d) => ({ + name: d.name, + employee_deduction: Number(d.employee_deduction), + company_deduction: Number(d.company_deduction), + })), + earnings: employeePayrollRun.hris_employee_payroll_runs_earnings.map( + (e) => ({ + amount: Number(e.amount), + type: e.type, + }), + ), + taxes: employeePayrollRun.hris_employee_payroll_runs_taxes.map((t) => ({ + name: t.name, + amount: Number(t.amount), + employer_tax: t.employer_tax, + })), + field_mappings: field_mappings, + remote_id: employeePayrollRun.remote_id, + remote_created_at: employeePayrollRun.remote_created_at, + created_at: employeePayrollRun.created_at, + modified_at: employeePayrollRun.modified_at, + remote_was_deleted: employeePayrollRun.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: employeePayrollRun.id_hris_employee_payroll_run, + }, + }); + unifiedEmployeePayrollRun.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.employee_payroll_run.pull', + method: 'GET', + url: '/hris/employee_payroll_run', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedEmployeePayrollRun; + } catch (error) { + throw error; + } } async getEmployeePayrollRuns( @@ -46,7 +132,126 @@ export class EmployeePayrollRunService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisEmployeepayrollrunOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const employeePayrollRuns = + await this.prisma.hris_employee_payroll_runs.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_employee_payroll_run: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + include: { + hris_employee_payroll_runs_deductions: true, + hris_employee_payroll_runs_earnings: true, + hris_employee_payroll_runs_taxes: true, + }, + }); + + const hasNextPage = employeePayrollRuns.length > limit; + if (hasNextPage) employeePayrollRuns.pop(); + + const unifiedEmployeePayrollRuns = await Promise.all( + employeePayrollRuns.map(async (employeePayrollRun) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: + employeePayrollRun.id_hris_employee_payroll_run, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedEmployeePayrollRun: UnifiedHrisEmployeepayrollrunOutput = + { + id: employeePayrollRun.id_hris_employee_payroll_run, + employee_id: employeePayrollRun.id_hris_employee, + payroll_run_id: employeePayrollRun.id_hris_payroll_run, + gross_pay: Number(employeePayrollRun.gross_pay), + net_pay: Number(employeePayrollRun.net_pay), + start_date: employeePayrollRun.start_date, + end_date: employeePayrollRun.end_date, + check_date: employeePayrollRun.check_date, + deductions: + employeePayrollRun.hris_employee_payroll_runs_deductions.map( + (d) => ({ + name: d.name, + employee_deduction: Number(d.employee_deduction), + company_deduction: Number(d.company_deduction), + }), + ), + earnings: + employeePayrollRun.hris_employee_payroll_runs_earnings.map( + (e) => ({ + amount: Number(e.amount), + type: e.type, + }), + ), + taxes: employeePayrollRun.hris_employee_payroll_runs_taxes.map( + (t) => ({ + name: t.name, + amount: Number(t.amount), + employer_tax: t.employer_tax, + }), + ), + field_mappings: field_mappings, + remote_id: employeePayrollRun.remote_id, + remote_created_at: employeePayrollRun.remote_created_at, + created_at: employeePayrollRun.created_at, + modified_at: employeePayrollRun.modified_at, + remote_was_deleted: employeePayrollRun.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: + employeePayrollRun.id_hris_employee_payroll_run, + }, + }); + unifiedEmployeePayrollRun.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedEmployeePayrollRun; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.employee_payroll_run.pull', + method: 'GET', + url: '/hris/employee_payroll_runs', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedEmployeePayrollRuns, + next_cursor: hasNextPage + ? employeePayrollRuns[employeePayrollRuns.length - 1] + .id_hris_employee_payroll_run + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/employeepayrollrun/sync/sync.processor.ts b/packages/api/src/hris/employeepayrollrun/sync/sync.processor.ts new file mode 100644 index 000000000..445b661fd --- /dev/null +++ b/packages/api/src/hris/employeepayrollrun/sync/sync.processor.ts @@ -0,0 +1,21 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-employeepayrollruns') + async handleSyncEmployeePayrollRuns(job: Job) { + try { + console.log( + `Processing queue -> hris-sync-employeepayrollruns ${job.id}`, + ); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris employee payroll runs', error); + } + } +} diff --git a/packages/api/src/hris/employeepayrollrun/sync/sync.service.ts b/packages/api/src/hris/employeepayrollrun/sync/sync.service.ts index 59f56dc72..ffb0b043d 100644 --- a/packages/api/src/hris/employeepayrollrun/sync/sync.service.ts +++ b/packages/api/src/hris/employeepayrollrun/sync/sync.service.ts @@ -10,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisEmployeepayrollrunOutput } from '../types/model.unified'; import { IEmployeePayrollRunService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_employee_payroll_runs as HrisEmployeePayrollRun } from '@prisma/client'; +import { OriginalEmployeePayrollRunOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +25,199 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'employee_payroll_run', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing employee payroll runs...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IEmployeePayrollRunService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisEmployeepayrollrunOutput, + OriginalEmployeePayrollRunOutput, + IEmployeePayrollRunService + >( + integrationId, + linkedUserId, + 'hris', + 'employee_payroll_run', + service, + [], + ); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + employeePayrollRuns: UnifiedHrisEmployeepayrollrunOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); + ): Promise { + try { + const employeePayrollRunResults: HrisEmployeePayrollRun[] = []; + + for (let i = 0; i < employeePayrollRuns.length; i++) { + const employeePayrollRun = employeePayrollRuns[i]; + const originId = employeePayrollRun.remote_id; + + let existingEmployeePayrollRun = + await this.prisma.hris_employee_payroll_runs.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const employeePayrollRunData = { + id_hris_employee: employeePayrollRun.employee_id, + id_hris_payroll_run: employeePayrollRun.payroll_run_id, + gross_pay: employeePayrollRun.gross_pay + ? BigInt(employeePayrollRun.gross_pay) + : null, + net_pay: employeePayrollRun.net_pay + ? BigInt(employeePayrollRun.net_pay) + : null, + start_date: employeePayrollRun.start_date + ? new Date(employeePayrollRun.start_date) + : null, + end_date: employeePayrollRun.end_date + ? new Date(employeePayrollRun.end_date) + : null, + check_date: employeePayrollRun.check_date + ? new Date(employeePayrollRun.check_date) + : null, + remote_id: originId, + remote_created_at: employeePayrollRun.remote_created_at + ? new Date(employeePayrollRun.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: employeePayrollRun.remote_was_deleted || false, + }; + + if (existingEmployeePayrollRun) { + existingEmployeePayrollRun = + await this.prisma.hris_employee_payroll_runs.update({ + where: { + id_hris_employee_payroll_run: + existingEmployeePayrollRun.id_hris_employee_payroll_run, + }, + data: employeePayrollRunData, + }); + } else { + existingEmployeePayrollRun = + await this.prisma.hris_employee_payroll_runs.create({ + data: { + ...employeePayrollRunData, + id_hris_employee_payroll_run: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + employeePayrollRunResults.push(existingEmployeePayrollRun); + + // Process field mappings + await this.ingestService.processFieldMappings( + employeePayrollRun.field_mappings, + existingEmployeePayrollRun.id_hris_employee_payroll_run, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingEmployeePayrollRun.id_hris_employee_payroll_run, + remote_data[i], + ); + + // Process deductions, earnings, and taxes + await this.processDeductions( + existingEmployeePayrollRun.id_hris_employee_payroll_run, + employeePayrollRun.deductions, + ); + await this.processEarnings( + existingEmployeePayrollRun.id_hris_employee_payroll_run, + employeePayrollRun.earnings, + ); + await this.processTaxes( + existingEmployeePayrollRun.id_hris_employee_payroll_run, + employeePayrollRun.taxes, + ); + } + + return employeePayrollRunResults; + } catch (error) { + throw error; + } } - async onModuleInit() { - // Initialization logic + private async processDeductions( + id_hris_employee_payroll_run: string, + deductions: any[], + ) { + // Implementation for processing deductions + } + + private async processEarnings( + id_hris_employee_payroll_run: string, + earnings: any[], + ) { + // Implementation for processing earnings } - // Additional methods and logic + private async processTaxes( + id_hris_employee_payroll_run: string, + taxes: any[], + ) { + // Implementation for processing taxes + } } diff --git a/packages/api/src/hris/employeepayrollrun/types/index.ts b/packages/api/src/hris/employeepayrollrun/types/index.ts index 79d61d703..2949b6c28 100644 --- a/packages/api/src/hris/employeepayrollrun/types/index.ts +++ b/packages/api/src/hris/employeepayrollrun/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalEmployeePayrollRunOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IEmployeePayrollRunService { addEmployeePayrollRun( @@ -12,9 +13,8 @@ export interface IEmployeePayrollRunService { linkedUserId: string, ): Promise>; - syncEmployeePayrollRuns( - linkedUserId: string, - custom_properties?: string[], + sync( + data: SyncParam, ): Promise>; } diff --git a/packages/api/src/hris/employeepayrollrun/types/model.unified.ts b/packages/api/src/hris/employeepayrollrun/types/model.unified.ts index 617647e04..6d41b69b9 100644 --- a/packages/api/src/hris/employeepayrollrun/types/model.unified.ts +++ b/packages/api/src/hris/employeepayrollrun/types/model.unified.ts @@ -137,34 +137,34 @@ export class UnifiedHrisEmployeepayrollrunInput { net_pay?: number; @ApiPropertyOptional({ - type: String, + type: Date, example: '2023-01-01T00:00:00Z', nullable: true, description: 'The start date of the pay period', }) @IsDateString() @IsOptional() - start_date?: string; + start_date?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2023-01-15T23:59:59Z', nullable: true, description: 'The end date of the pay period', }) @IsDateString() @IsOptional() - end_date?: string; + end_date?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2023-01-20T00:00:00Z', nullable: true, description: 'The date the check was issued', }) @IsDateString() @IsOptional() - check_date?: string; + check_date?: Date; @ApiPropertyOptional({ type: [DeductionItem], @@ -244,7 +244,7 @@ export class UnifiedHrisEmployeepayrollrunOutput extends UnifiedHrisEmployeepayr remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -252,27 +252,27 @@ export class UnifiedHrisEmployeepayrollrunOutput extends UnifiedHrisEmployeepayr }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the employee payroll run record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the employee payroll run record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/employerbenefit/employerbenefit.controller.ts b/packages/api/src/hris/employerbenefit/employerbenefit.controller.ts index c4a5a6eec..90eb74233 100644 --- a/packages/api/src/hris/employerbenefit/employerbenefit.controller.ts +++ b/packages/api/src/hris/employerbenefit/employerbenefit.controller.ts @@ -1,38 +1,29 @@ +import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { Controller, - Post, - Body, - Query, Get, - Patch, - Param, Headers, + Param, + Query, UseGuards, } from '@nestjs/common'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { - ApiBody, + ApiHeader, ApiOperation, ApiParam, ApiQuery, ApiTags, - ApiHeader, - //ApiKeyAuth, } from '@nestjs/swagger'; -import { EmployerBenefitService } from './services/employerbenefit.service'; -import { - UnifiedHrisEmployerbenefitInput, - UnifiedHrisEmployerbenefitOutput, -} from './types/model.unified'; -import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; -import { QueryDto } from '@@core/utils/dtos/query.dto'; +import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiGetCustomResponse, ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - +import { QueryDto } from '@@core/utils/dtos/query.dto'; +import { EmployerBenefitService } from './services/employerbenefit.service'; +import { UnifiedHrisEmployerbenefitOutput } from './types/model.unified'; @ApiTags('hris/employerbenefits') @Controller('hris/employerbenefits') diff --git a/packages/api/src/hris/employerbenefit/employerbenefit.module.ts b/packages/api/src/hris/employerbenefit/employerbenefit.module.ts index 5d178e717..984e0b462 100644 --- a/packages/api/src/hris/employerbenefit/employerbenefit.module.ts +++ b/packages/api/src/hris/employerbenefit/employerbenefit.module.ts @@ -1,34 +1,26 @@ import { Module } from '@nestjs/common'; import { EmployerBenefitController } from './employerbenefit.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { EmployerBenefitService } from './services/employerbenefit.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { GustoEmployerbenefitMapper } from './services/gusto/mappers'; +import { GustoService } from './services/gusto'; @Module({ controllers: [EmployerBenefitController], providers: [ EmployerBenefitService, CoreUnification, - SyncService, WebhookService, - ServiceRegistry, - IngestDataService, + GustoEmployerbenefitMapper, /* PROVIDERS SERVICES */ + GustoService, ], exports: [SyncService], }) diff --git a/packages/api/src/hris/employerbenefit/services/employerbenefit.service.ts b/packages/api/src/hris/employerbenefit/services/employerbenefit.service.ts index 4f90ecd6c..fedd3cb88 100644 --- a/packages/api/src/hris/employerbenefit/services/employerbenefit.service.ts +++ b/packages/api/src/hris/employerbenefit/services/employerbenefit.service.ts @@ -9,12 +9,8 @@ import { UnifiedHrisEmployerbenefitInput, UnifiedHrisEmployerbenefitOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalEmployerBenefitOutput } from '@@core/utils/types/original/original.hris'; - -import { IEmployerBenefitService } from '../types'; @Injectable() export class EmployerBenefitService { @@ -29,14 +25,83 @@ export class EmployerBenefitService { } async getEmployerBenefit( - id_employerbenefiting_employerbenefit: string, + id_hris_employer_benefit: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const employerBenefit = + await this.prisma.hris_employer_benefits.findUnique({ + where: { id_hris_employer_benefit: id_hris_employer_benefit }, + }); + + if (!employerBenefit) { + throw new Error( + `Employer Benefit with ID ${id_hris_employer_benefit} not found.`, + ); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: employerBenefit.id_hris_employer_benefit, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedEmployerBenefit: UnifiedHrisEmployerbenefitOutput = { + id: employerBenefit.id_hris_employer_benefit, + benefit_plan_type: employerBenefit.benefit_plan_type, + name: employerBenefit.name, + description: employerBenefit.description, + deduction_code: employerBenefit.deduction_code, + field_mappings: field_mappings, + remote_id: employerBenefit.remote_id, + remote_created_at: employerBenefit.remote_created_at, + created_at: employerBenefit.created_at, + modified_at: employerBenefit.modified_at, + remote_was_deleted: employerBenefit.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: employerBenefit.id_hris_employer_benefit, + }, + }); + unifiedEmployerBenefit.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.employer_benefit.pull', + method: 'GET', + url: '/hris/employer_benefit', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedEmployerBenefit; + } catch (error) { + throw error; + } } async getEmployerBenefits( @@ -47,7 +112,93 @@ export class EmployerBenefitService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisEmployerbenefitOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const employerBenefits = + await this.prisma.hris_employer_benefits.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_employer_benefit: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = employerBenefits.length > limit; + if (hasNextPage) employerBenefits.pop(); + + const unifiedEmployerBenefits = await Promise.all( + employerBenefits.map(async (employerBenefit) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: employerBenefit.id_hris_employer_benefit, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedEmployerBenefit: UnifiedHrisEmployerbenefitOutput = { + id: employerBenefit.id_hris_employer_benefit, + benefit_plan_type: employerBenefit.benefit_plan_type, + name: employerBenefit.name, + description: employerBenefit.description, + deduction_code: employerBenefit.deduction_code, + field_mappings: field_mappings, + remote_id: employerBenefit.remote_id, + remote_created_at: employerBenefit.remote_created_at, + created_at: employerBenefit.created_at, + modified_at: employerBenefit.modified_at, + remote_was_deleted: employerBenefit.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: employerBenefit.id_hris_employer_benefit, + }, + }); + unifiedEmployerBenefit.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedEmployerBenefit; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.employer_benefit.pull', + method: 'GET', + url: '/hris/employer_benefits', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedEmployerBenefits, + next_cursor: hasNextPage + ? employerBenefits[employerBenefits.length - 1] + .id_hris_employer_benefit + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/employerbenefit/services/gusto/index.ts b/packages/api/src/hris/employerbenefit/services/gusto/index.ts new file mode 100644 index 000000000..2024d7b4a --- /dev/null +++ b/packages/api/src/hris/employerbenefit/services/gusto/index.ts @@ -0,0 +1,96 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { HrisObject } from '@hris/@lib/@types'; +import { IEmployerBenefitService } from '@hris/employerbenefit/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { GustoEmployerbenefitOutput } from './types'; + +@Injectable() +export class GustoService implements IEmployerBenefitService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + HrisObject.employerbenefit.toUpperCase() + ':' + GustoService.name, + ); + this.registry.registerService('gusto', this); + } + + async sync( + data: SyncParam, + ): Promise> { + try { + const { linkedUserId, id_company } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'gusto', + vertical: 'hris', + }, + }); + + const company = await this.prisma.hris_companies.findUnique({ + where: { + id_hris_company: id_company as string, + }, + select: { + remote_id: true, + }, + }); + + const resp = await axios.get( + `${connection.account_url}/v1/companies/${company.remote_id}/company_benefits`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + + const resp_ = await axios.get(`${connection.account_url}/v1/benefits`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }); + + const res = []; + for (const employerBenefit of resp.data) { + const pick = resp_.data.filter( + (item) => item.benefit_type == employerBenefit.benefit_type, + ); + res.push({ + ...employerBenefit, + category: pick.category, + name: pick.name, + }); + } + + this.logger.log(`Synced gusto employerbenefits !`); + + return { + data: res, + message: 'Gusto employerbenefits retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/employerbenefit/services/gusto/mappers.ts b/packages/api/src/hris/employerbenefit/services/gusto/mappers.ts new file mode 100644 index 000000000..3d2c4d07f --- /dev/null +++ b/packages/api/src/hris/employerbenefit/services/gusto/mappers.ts @@ -0,0 +1,90 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { Injectable } from '@nestjs/common'; +import { GustoCategory, GustoEmployerbenefitOutput } from './types'; +import { + BenefitPlanType, + UnifiedHrisEmployerbenefitInput, + UnifiedHrisEmployerbenefitOutput, +} from '@hris/employerbenefit/types/model.unified'; +import { IEmployerBenefitMapper } from '@hris/employerbenefit/types'; +import { Utils } from '@hris/@lib/@utils'; + +@Injectable() +export class GustoEmployerbenefitMapper implements IEmployerBenefitMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private ingestService: IngestDataService, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService( + 'hris', + 'employerbenefit', + 'gusto', + this, + ); + } + + async desunify( + source: UnifiedHrisEmployerbenefitInput, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return; + } + + async unify( + source: GustoEmployerbenefitOutput | GustoEmployerbenefitOutput[], + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise< + UnifiedHrisEmployerbenefitOutput | UnifiedHrisEmployerbenefitOutput[] + > { + if (!Array.isArray(source)) { + return this.mapSingleEmployerbenefitToUnified( + source, + connectionId, + customFieldMappings, + ); + } + return Promise.all( + source.map((employerbenefit) => + this.mapSingleEmployerbenefitToUnified( + employerbenefit, + connectionId, + customFieldMappings, + ), + ), + ); + } + + private async mapSingleEmployerbenefitToUnified( + employerbenefit: GustoEmployerbenefitOutput, + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return { + remote_id: employerbenefit.uuid || null, + remote_data: employerbenefit, + benefit_plan_type: this.mapGustoBenefitToPanora(employerbenefit.category), + name: employerbenefit.name, + description: employerbenefit.description, + }; + } + + mapGustoBenefitToPanora( + category: GustoCategory | string, + ): BenefitPlanType | string { + switch (category) { + case 'Health': + return 'MEDICAL'; + case 'Savings and Retirement': + return 'RETIREMENT'; + case 'Other': + return 'OTHER'; + default: + return category; + } + } +} diff --git a/packages/api/src/hris/employerbenefit/services/gusto/types.ts b/packages/api/src/hris/employerbenefit/services/gusto/types.ts new file mode 100644 index 000000000..6834a7094 --- /dev/null +++ b/packages/api/src/hris/employerbenefit/services/gusto/types.ts @@ -0,0 +1,20 @@ +export type GustoEmployerbenefitOutput = Partial<{ + uuid: string; + version: string; + company_uuid: string; + benefit_type: number; + active: boolean; + description: string; + deletable: boolean; + supports_percentage_amounts: boolean; + responsible_for_employer_taxes: boolean; + responsible_for_employee_w2: boolean; + category: string; + name: string; +}>; + +export type GustoCategory = + | 'Health' + | 'Savings and Retirement' + | 'Transportation' + | 'Other'; diff --git a/packages/api/src/hris/employerbenefit/sync/sync.processor.ts b/packages/api/src/hris/employerbenefit/sync/sync.processor.ts new file mode 100644 index 000000000..b42fed452 --- /dev/null +++ b/packages/api/src/hris/employerbenefit/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-employerbenefits') + async handleSyncEmployerBenefits(job: Job) { + try { + console.log(`Processing queue -> hris-sync-employerbenefits ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris employer benefits', error); + } + } +} diff --git a/packages/api/src/hris/employerbenefit/sync/sync.service.ts b/packages/api/src/hris/employerbenefit/sync/sync.service.ts index 9953cb218..e6702c113 100644 --- a/packages/api/src/hris/employerbenefit/sync/sync.service.ts +++ b/packages/api/src/hris/employerbenefit/sync/sync.service.ts @@ -10,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisEmployerbenefitOutput } from '../types/model.unified'; import { IEmployerBenefitService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_employer_benefits as HrisEmployerBenefit } from '@prisma/client'; +import { OriginalEmployerBenefitOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +25,144 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'employer_benefit', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing employer benefits...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IEmployerBenefitService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisEmployerbenefitOutput, + OriginalEmployerBenefitOutput, + IEmployerBenefitService + >(integrationId, linkedUserId, 'hris', 'employer_benefit', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + employerBenefits: UnifiedHrisEmployerbenefitOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const employerBenefitResults: HrisEmployerBenefit[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < employerBenefits.length; i++) { + const employerBenefit = employerBenefits[i]; + const originId = employerBenefit.remote_id; + + let existingEmployerBenefit = + await this.prisma.hris_employer_benefits.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const employerBenefitData = { + benefit_plan_type: employerBenefit.benefit_plan_type, + name: employerBenefit.name, + description: employerBenefit.description, + deduction_code: employerBenefit.deduction_code, + remote_id: originId, + remote_created_at: employerBenefit.remote_created_at + ? new Date(employerBenefit.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: employerBenefit.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingEmployerBenefit) { + existingEmployerBenefit = + await this.prisma.hris_employer_benefits.update({ + where: { + id_hris_employer_benefit: + existingEmployerBenefit.id_hris_employer_benefit, + }, + data: employerBenefitData, + }); + } else { + existingEmployerBenefit = + await this.prisma.hris_employer_benefits.create({ + data: { + ...employerBenefitData, + id_hris_employer_benefit: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + employerBenefitResults.push(existingEmployerBenefit); + + // Process field mappings + await this.ingestService.processFieldMappings( + employerBenefit.field_mappings, + existingEmployerBenefit.id_hris_employer_benefit, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingEmployerBenefit.id_hris_employer_benefit, + remote_data[i], + ); + } + + return employerBenefitResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/employerbenefit/types/index.ts b/packages/api/src/hris/employerbenefit/types/index.ts index bf453af84..af9533a28 100644 --- a/packages/api/src/hris/employerbenefit/types/index.ts +++ b/packages/api/src/hris/employerbenefit/types/index.ts @@ -5,17 +5,10 @@ import { } from './model.unified'; import { OriginalEmployerBenefitOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IEmployerBenefitService { - addEmployerBenefit( - employerbenefitData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - - syncEmployerBenefits( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IEmployerBenefitMapper { @@ -34,5 +27,7 @@ export interface IEmployerBenefitMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedHrisEmployerbenefitOutput | UnifiedHrisEmployerbenefitOutput[] + >; } diff --git a/packages/api/src/hris/employerbenefit/types/model.unified.ts b/packages/api/src/hris/employerbenefit/types/model.unified.ts index 9c83f3fe2..3f280edb6 100644 --- a/packages/api/src/hris/employerbenefit/types/model.unified.ts +++ b/packages/api/src/hris/employerbenefit/types/model.unified.ts @@ -1,22 +1,29 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; import { - IsUUID, + IsBoolean, + IsDateString, IsOptional, IsString, - IsDateString, - IsBoolean, + IsUUID, } from 'class-validator'; +export type BenefitPlanType = + | 'MEDICAL' + | 'HEALTH_SAVINGS' + | 'INSURANCE' + | 'RETIREMENT' + | 'OTHER'; export class UnifiedHrisEmployerbenefitInput { @ApiPropertyOptional({ type: String, example: 'Health Insurance', + enum: ['MEDICAL', 'HEALTH_SAVINGS', 'INSURANCE', 'RETIREMENT', 'OTHER'], nullable: true, description: 'The type of the benefit plan', }) @IsString() @IsOptional() - benefit_plan_type?: string; + benefit_plan_type?: BenefitPlanType | string; @ApiPropertyOptional({ type: String, @@ -99,7 +106,7 @@ export class UnifiedHrisEmployerbenefitOutput extends UnifiedHrisEmployerbenefit remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -107,27 +114,27 @@ export class UnifiedHrisEmployerbenefitOutput extends UnifiedHrisEmployerbenefit }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the employer benefit record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the employer benefit record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/employment/employment.module.ts b/packages/api/src/hris/employment/employment.module.ts index 0748b4d4a..780507515 100644 --- a/packages/api/src/hris/employment/employment.module.ts +++ b/packages/api/src/hris/employment/employment.module.ts @@ -1,33 +1,25 @@ import { Module } from '@nestjs/common'; import { EmploymentController } from './employment.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { EmploymentService } from './services/employment.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { SyncService } from './sync/sync.service'; -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { GustoEmploymentMapper } from './services/gusto/mappers'; @Module({ controllers: [EmploymentController], providers: [ EmploymentService, CoreUnification, - SyncService, WebhookService, - ServiceRegistry, - IngestDataService, + GustoEmploymentMapper, /* PROVIDERS SERVICES */ ], exports: [SyncService], diff --git a/packages/api/src/hris/employment/services/employment.service.ts b/packages/api/src/hris/employment/services/employment.service.ts index 18f290524..0b9adb891 100644 --- a/packages/api/src/hris/employment/services/employment.service.ts +++ b/packages/api/src/hris/employment/services/employment.service.ts @@ -1,20 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedHrisEmploymentInput, - UnifiedHrisEmploymentOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedHrisEmploymentOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalEmploymentOutput } from '@@core/utils/types/original/original.hris'; - -import { IEmploymentService } from '../types'; @Injectable() export class EmploymentService { @@ -29,14 +20,84 @@ export class EmploymentService { } async getEmployment( - id_employmenting_employment: string, + id_hris_employment: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const employment = await this.prisma.hris_employments.findUnique({ + where: { id_hris_employment: id_hris_employment }, + }); + + if (!employment) { + throw new Error(`Employment with ID ${id_hris_employment} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: employment.id_hris_employment, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedEmployment: UnifiedHrisEmploymentOutput = { + id: employment.id_hris_employment, + job_title: employment.job_title, + pay_rate: Number(employment.pay_rate), + pay_period: employment.pay_period, + pay_frequency: employment.pay_frequency, + pay_currency: employment.pay_currency, + flsa_status: employment.flsa_status, + effective_date: employment.effective_date, + employment_type: employment.employment_type, + field_mappings: field_mappings, + remote_id: employment.remote_id, + remote_created_at: employment.remote_created_at, + created_at: employment.created_at, + modified_at: employment.modified_at, + remote_was_deleted: employment.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: employment.id_hris_employment, + }, + }); + unifiedEmployment.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.employment.pull', + method: 'GET', + url: '/hris/employment', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedEmployment; + } catch (error) { + throw error; + } } async getEmployments( @@ -47,7 +108,95 @@ export class EmploymentService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisEmploymentOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const employments = await this.prisma.hris_employments.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_employment: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = employments.length > limit; + if (hasNextPage) employments.pop(); + + const unifiedEmployments = await Promise.all( + employments.map(async (employment) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: employment.id_hris_employment, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedEmployment: UnifiedHrisEmploymentOutput = { + id: employment.id_hris_employment, + job_title: employment.job_title, + pay_rate: Number(employment.pay_rate), + pay_period: employment.pay_period, + pay_frequency: employment.pay_frequency, + pay_currency: employment.pay_currency, + flsa_status: employment.flsa_status, + effective_date: employment.effective_date, + employment_type: employment.employment_type, + field_mappings: field_mappings, + remote_id: employment.remote_id, + remote_created_at: employment.remote_created_at, + created_at: employment.created_at, + modified_at: employment.modified_at, + remote_was_deleted: employment.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: employment.id_hris_employment, + }, + }); + unifiedEmployment.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedEmployment; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.employment.pull', + method: 'GET', + url: '/hris/employments', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedEmployments, + next_cursor: hasNextPage + ? employments[employments.length - 1].id_hris_employment + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/employment/services/gusto/mappers.ts b/packages/api/src/hris/employment/services/gusto/mappers.ts new file mode 100644 index 000000000..af9fd3366 --- /dev/null +++ b/packages/api/src/hris/employment/services/gusto/mappers.ts @@ -0,0 +1,92 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { Utils } from '@hris/@lib/@utils'; +import { IEmploymentMapper } from '@hris/employment/types'; +import { + FlsaStatus, + UnifiedHrisEmploymentInput, + UnifiedHrisEmploymentOutput, +} from '@hris/employment/types/model.unified'; +import { Injectable } from '@nestjs/common'; +import { GustoEmploymentOutput } from './types'; + +@Injectable() +export class GustoEmploymentMapper implements IEmploymentMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private ingestService: IngestDataService, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService('hris', 'employment', 'gusto', this); + } + + async desunify( + source: UnifiedHrisEmploymentInput, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return; + } + + async unify( + source: GustoEmploymentOutput | GustoEmploymentOutput[], + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + if (!Array.isArray(source)) { + return this.mapSingleEmploymentToUnified( + source, + connectionId, + customFieldMappings, + ); + } + return Promise.all( + source.map((employment) => + this.mapSingleEmploymentToUnified( + employment, + connectionId, + customFieldMappings, + ), + ), + ); + } + + private async mapSingleEmploymentToUnified( + employment: GustoEmploymentOutput, + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return { + remote_id: employment.uuid, + remote_data: employment, + effective_date: new Date(employment.effective_date), + job_title: employment.title, + pay_rate: Number(employment.rate), + flsa_status: this.mapFlsaStatusToPanora(employment.flsa_status), + }; + } + + mapFlsaStatusToPanora( + str: + | 'Exempt' + | 'Salaried Nonexempt' + | 'Nonexempt' + | 'Owner' + | 'Commission Only Exempt' + | 'Commission Only Nonexempt', + ): FlsaStatus | string { + switch (str) { + case 'Exempt': + return 'EXEMPT'; + case 'Salaried Nonexempt': + return 'SALARIED_NONEXEMPT'; + case 'Nonexempt': + return 'NONEXEMPT'; + case 'Owner': + return 'OWNER'; + default: + return str; + } + } +} diff --git a/packages/api/src/hris/employment/services/gusto/types.ts b/packages/api/src/hris/employment/services/gusto/types.ts new file mode 100644 index 000000000..9cc0b0112 --- /dev/null +++ b/packages/api/src/hris/employment/services/gusto/types.ts @@ -0,0 +1,31 @@ +export type GustoEmploymentOutput = Partial<{ + uuid: string; // The UUID of the compensation in Gusto. + version: string; // The current version of the compensation. + job_uuid: string; // The UUID of the job to which the compensation belongs. + title: string; + rate: string; // The dollar amount paid per payment unit. + payment_unit: 'Hour' | 'Week' | 'Month' | 'Year' | 'Paycheck'; // The unit accompanying the compensation rate. + flsa_status: + | 'Exempt' + | 'Salaried Nonexempt' + | 'Nonexempt' + | 'Owner' + | 'Commission Only Exempt' + | 'Commission Only Nonexempt'; // The FLSA status for this compensation. + effective_date: string; // The effective date for this compensation. + adjust_for_minimum_wage: boolean; // Indicates if the compensation could be adjusted to minimum wage during payroll calculation. + eligible_paid_time_off: EligiblePaidTimeOff[]; // The available types of paid time off for the compensation. +}>; + +type EligiblePaidTimeOff = { + name: string; // The name of the paid time off type. + policy_name: string; // The name of the time off policy. + policy_uuid: string; // The UUID of the time off policy. + accrual_unit: string; // The unit the PTO type is accrued in. + accrual_rate: string; // The number of accrual units accrued per accrual period. + accrual_method: string; // The accrual method of the time off policy. + accrual_period: string; // The frequency at which the PTO type is accrued. + accrual_balance: string; // The number of accrual units accrued. + maximum_accrual_balance: string | null; // The maximum number of accrual units allowed. + paid_at_termination: boolean; // Whether the accrual balance is paid to the employee upon termination. +}; diff --git a/packages/api/src/hris/employment/sync/sync.processor.ts b/packages/api/src/hris/employment/sync/sync.processor.ts new file mode 100644 index 000000000..0f7562dfe --- /dev/null +++ b/packages/api/src/hris/employment/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-employments') + async handleSyncEmployments(job: Job) { + try { + console.log(`Processing queue -> hris-sync-employments ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris employments', error); + } + } +} diff --git a/packages/api/src/hris/employment/sync/sync.service.ts b/packages/api/src/hris/employment/sync/sync.service.ts index a43745982..0a383ddb3 100644 --- a/packages/api/src/hris/employment/sync/sync.service.ts +++ b/packages/api/src/hris/employment/sync/sync.service.ts @@ -2,7 +2,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -10,6 +9,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisEmploymentOutput } from '../types/model.unified'; import { IEmploymentService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_employments as HrisEmployment } from '@prisma/client'; +import { OriginalEmploymentOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +24,146 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'employment', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing employments...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IEmploymentService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisEmploymentOutput, + OriginalEmploymentOutput, + IEmploymentService + >(integrationId, linkedUserId, 'hris', 'employment', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + employments: UnifiedHrisEmploymentOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const employmentResults: HrisEmployment[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < employments.length; i++) { + const employment = employments[i]; + const originId = employment.remote_id; + + let existingEmployment = await this.prisma.hris_employments.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const employmentData = { + job_title: employment.job_title, + pay_rate: employment.pay_rate ? BigInt(employment.pay_rate) : null, + pay_period: employment.pay_period, + pay_frequency: employment.pay_frequency, + pay_currency: employment.pay_currency, + flsa_status: employment.flsa_status, + effective_date: employment.effective_date + ? new Date(employment.effective_date) + : null, + employment_type: employment.employment_type, + remote_id: originId, + remote_created_at: employment.remote_created_at + ? new Date(employment.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: employment.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingEmployment) { + existingEmployment = await this.prisma.hris_employments.update({ + where: { + id_hris_employment: existingEmployment.id_hris_employment, + }, + data: employmentData, + }); + } else { + existingEmployment = await this.prisma.hris_employments.create({ + data: { + ...employmentData, + id_hris_employment: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + employmentResults.push(existingEmployment); + + // Process field mappings + await this.ingestService.processFieldMappings( + employment.field_mappings, + existingEmployment.id_hris_employment, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingEmployment.id_hris_employment, + remote_data[i], + ); + } + + return employmentResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/employment/types/index.ts b/packages/api/src/hris/employment/types/index.ts index b88d11e85..480e0bca8 100644 --- a/packages/api/src/hris/employment/types/index.ts +++ b/packages/api/src/hris/employment/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalEmploymentOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IEmploymentService { addEmployment( @@ -12,10 +13,7 @@ export interface IEmploymentService { linkedUserId: string, ): Promise>; - syncEmployments( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IEmploymentMapper { diff --git a/packages/api/src/hris/employment/types/model.unified.ts b/packages/api/src/hris/employment/types/model.unified.ts index a2c1f90c1..aef4d7031 100644 --- a/packages/api/src/hris/employment/types/model.unified.ts +++ b/packages/api/src/hris/employment/types/model.unified.ts @@ -1,3 +1,4 @@ +import { CurrencyCode } from '@@core/utils/types'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsUUID, @@ -8,6 +9,41 @@ import { IsBoolean, } from 'class-validator'; +export type FlsaStatus = + | 'EXEMPT' + | 'SALARIED_NONEXEMPT' + | 'NONEXEMPT' + | 'OWNER'; + +export type EmploymentType = + | 'FULL_TIME' + | 'PART_TIME' + | 'INTERN' + | 'CONTRACTOR' + | 'FREELANCE'; + +export type PayFrequency = + | 'WEEKLY' + | 'BIWEEKLY' + | 'MONTHLY' + | 'QUARTERLY' + | 'SEMIANNUALLY' + | 'ANNUALLY' + | 'THIRTEEN-MONTHLY' + | 'PRO_RATA' + | 'SEMIMONTHLY'; + +export type PayPeriod = + | 'HOUR' + | 'DAY' + | 'WEEK' + | 'EVERY_TWO_WEEKS' + | 'SEMIMONTHLY' + | 'MONTH' + | 'QUARTER' + | 'EVERY_SIX_MONTHS' + | 'YEAR'; + export class UnifiedHrisEmploymentInput { @ApiPropertyOptional({ type: String, @@ -31,63 +67,88 @@ export class UnifiedHrisEmploymentInput { @ApiPropertyOptional({ type: String, - example: 'Monthly', + example: 'MONTHLY', + enum: [ + 'HOUR', + 'DAY', + 'WEEK', + 'EVERY_TWO_WEEKS', + 'SEMIMONTHLY', + 'MONTH', + 'QUARTER', + 'EVERY_SIX_MONTHS', + 'YEAR', + ], nullable: true, description: 'The pay period of the employment', }) @IsString() @IsOptional() - pay_period?: string; + pay_period?: PayPeriod | string; @ApiPropertyOptional({ type: String, - example: 'Bi-weekly', + example: 'WEEKLY', + enum: [ + 'WEEKLY', + 'BIWEEKLY', + 'MONTHLY', + 'QUARTERLY', + 'SEMIANNUALLY', + 'ANNUALLY', + 'THIRTEEN-MONTHLY', + 'PRO_RATA', + 'SEMIMONTHLY', + ], nullable: true, description: 'The pay frequency of the employment', }) @IsString() @IsOptional() - pay_frequency?: string; + pay_frequency?: PayFrequency | string; @ApiPropertyOptional({ type: String, example: 'USD', + enum: CurrencyCode, nullable: true, description: 'The currency of the pay', }) @IsString() @IsOptional() - pay_currency?: string; + pay_currency?: CurrencyCode; @ApiPropertyOptional({ type: String, - example: 'Exempt', + example: 'EXEMPT', + enum: ['EXEMPT', 'SALARIED_NONEXEMPT', 'NONEXEMPT', 'OWNER'], nullable: true, description: 'The FLSA status of the employment', }) @IsString() @IsOptional() - flsa_status?: string; + flsa_status?: FlsaStatus | string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2023-01-01', nullable: true, description: 'The effective date of the employment', }) @IsDateString() @IsOptional() - effective_date?: string; + effective_date?: Date; @ApiPropertyOptional({ type: String, - example: 'Full-time', + example: 'FULL_TIME', + enum: ['FULL_TIME', 'PART_TIME', 'INTERN', 'CONTRACTOR', 'FREELANCE'], nullable: true, description: 'The type of employment', }) @IsString() @IsOptional() - employment_type?: string; + employment_type?: EmploymentType | string; @ApiPropertyOptional({ type: String, @@ -160,7 +221,7 @@ export class UnifiedHrisEmploymentOutput extends UnifiedHrisEmploymentInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -168,27 +229,27 @@ export class UnifiedHrisEmploymentOutput extends UnifiedHrisEmploymentInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the employment record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the employment record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/group/group.module.ts b/packages/api/src/hris/group/group.module.ts index 8b6d96546..e126e5188 100644 --- a/packages/api/src/hris/group/group.module.ts +++ b/packages/api/src/hris/group/group.module.ts @@ -1,35 +1,26 @@ import { Module } from '@nestjs/common'; import { GroupController } from './group.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { GroupService } from './services/group.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { GustoGroupMapper } from './services/gusto/mappers'; +import { GustoService } from './services/gusto'; @Module({ controllers: [GroupController], providers: [ GroupService, - SyncService, WebhookService, - CoreUnification, - ServiceRegistry, - IngestDataService, + GustoGroupMapper, /* PROVIDERS SERVICES */ + GustoService, ], exports: [SyncService], }) diff --git a/packages/api/src/hris/group/services/group.service.ts b/packages/api/src/hris/group/services/group.service.ts index b45ae7029..378b9af64 100644 --- a/packages/api/src/hris/group/services/group.service.ts +++ b/packages/api/src/hris/group/services/group.service.ts @@ -2,16 +2,13 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { UnifiedHrisGroupInput, UnifiedHrisGroupOutput } from '../types/model.unified'; - +import { + UnifiedHrisGroupInput, + UnifiedHrisGroupOutput, +} from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalGroupOutput } from '@@core/utils/types/original/original.hris'; - -import { IGroupService } from '../types'; @Injectable() export class GroupService { @@ -26,14 +23,79 @@ export class GroupService { } async getGroup( - id_grouping_group: string, + id_hris_group: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const group = await this.prisma.hris_groups.findUnique({ + where: { id_hris_group: id_hris_group }, + }); + + if (!group) { + throw new Error(`Group with ID ${id_hris_group} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: group.id_hris_group, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedGroup: UnifiedHrisGroupOutput = { + id: group.id_hris_group, + parent_group: group.parent_group, + name: group.name, + type: group.type, + field_mappings: field_mappings, + remote_id: group.remote_id, + remote_created_at: group.remote_created_at, + created_at: group.created_at, + modified_at: group.modified_at, + remote_was_deleted: group.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: group.id_hris_group, + }, + }); + unifiedGroup.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.group.pull', + method: 'GET', + url: '/hris/group', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedGroup; + } catch (error) { + throw error; + } } async getGroups( @@ -44,7 +106,90 @@ export class GroupService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisGroupOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const groups = await this.prisma.hris_groups.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_group: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = groups.length > limit; + if (hasNextPage) groups.pop(); + + const unifiedGroups = await Promise.all( + groups.map(async (group) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: group.id_hris_group, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedGroup: UnifiedHrisGroupOutput = { + id: group.id_hris_group, + parent_group: group.parent_group, + name: group.name, + type: group.type, + field_mappings: field_mappings, + remote_id: group.remote_id, + remote_created_at: group.remote_created_at, + created_at: group.created_at, + modified_at: group.modified_at, + remote_was_deleted: group.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: group.id_hris_group, + }, + }); + unifiedGroup.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedGroup; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.group.pull', + method: 'GET', + url: '/hris/groups', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedGroups, + next_cursor: hasNextPage + ? groups[groups.length - 1].id_hris_group + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/group/services/gusto/index.ts b/packages/api/src/hris/group/services/gusto/index.ts new file mode 100644 index 000000000..a2741d417 --- /dev/null +++ b/packages/api/src/hris/group/services/gusto/index.ts @@ -0,0 +1,72 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { HrisObject } from '@hris/@lib/@types'; +import { IGroupService } from '@hris/group/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { GustoGroupOutput } from './types'; + +@Injectable() +export class GustoService implements IGroupService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + HrisObject.group.toUpperCase() + ':' + GustoService.name, + ); + this.registry.registerService('gusto', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId, id_company } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'gusto', + vertical: 'hris', + }, + }); + + const company = await this.prisma.hris_companies.findUnique({ + where: { + id_hris_company: id_company as string, + }, + select: { + remote_id: true, + }, + }); + + const resp = await axios.get( + `${connection.account_url}/v1/companies/${company.remote_id}/departments`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + this.logger.log(`Synced gusto groups !`); + + return { + data: resp.data, + message: 'Gusto groups retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/group/services/gusto/mappers.ts b/packages/api/src/hris/group/services/gusto/mappers.ts new file mode 100644 index 000000000..b41edfe47 --- /dev/null +++ b/packages/api/src/hris/group/services/gusto/mappers.ts @@ -0,0 +1,61 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { Injectable } from '@nestjs/common'; +import { GustoGroupOutput } from './types'; +import { + UnifiedHrisGroupInput, + UnifiedHrisGroupOutput, +} from '@hris/group/types/model.unified'; +import { IGroupMapper } from '@hris/group/types'; +import { Utils } from '@hris/@lib/@utils'; + +@Injectable() +export class GustoGroupMapper implements IGroupMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private ingestService: IngestDataService, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService('hris', 'group', 'gusto', this); + } + + async desunify( + source: UnifiedHrisGroupInput, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return; + } + + async unify( + source: GustoGroupOutput | GustoGroupOutput[], + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + if (!Array.isArray(source)) { + return this.mapSingleGroupToUnified( + source, + connectionId, + customFieldMappings, + ); + } + return Promise.all( + source.map((group) => + this.mapSingleGroupToUnified(group, connectionId, customFieldMappings), + ), + ); + } + + private async mapSingleGroupToUnified( + group: GustoGroupOutput, + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return { + remote_id: group.uuid || null, + remote_data: group, + name: group.title, + }; + } +} diff --git a/packages/api/src/hris/group/services/gusto/types.ts b/packages/api/src/hris/group/services/gusto/types.ts new file mode 100644 index 000000000..b1b3bdaac --- /dev/null +++ b/packages/api/src/hris/group/services/gusto/types.ts @@ -0,0 +1,12 @@ +export type GustoGroupOutput = Partial<{ + uuid: string; + company_uuid: string; + title: string; + version: string; + employees: [ + { + uuid: string; + }, + ]; + contractors: any[]; +}>; diff --git a/packages/api/src/hris/group/sync/sync.processor.ts b/packages/api/src/hris/group/sync/sync.processor.ts new file mode 100644 index 000000000..aecfb6a70 --- /dev/null +++ b/packages/api/src/hris/group/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-groups') + async handleSyncGroups(job: Job) { + try { + console.log(`Processing queue -> hris-sync-groups ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris groups', error); + } + } +} diff --git a/packages/api/src/hris/group/sync/sync.service.ts b/packages/api/src/hris/group/sync/sync.service.ts index d616ce3ce..d4aab37a1 100644 --- a/packages/api/src/hris/group/sync/sync.service.ts +++ b/packages/api/src/hris/group/sync/sync.service.ts @@ -2,7 +2,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -10,6 +9,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisGroupOutput } from '../types/model.unified'; import { IGroupService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_groups as HrisGroup } from '@prisma/client'; +import { OriginalGroupOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +24,139 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'group', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing groups...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IGroupService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisGroupOutput, + OriginalGroupOutput, + IGroupService + >(integrationId, linkedUserId, 'hris', 'group', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + groups: UnifiedHrisGroupOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const groupResults: HrisGroup[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + const originId = group.remote_id; + + let existingGroup = await this.prisma.hris_groups.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const groupData = { + parent_group: group.parent_group, + name: group.name, + type: group.type, + remote_id: originId, + remote_created_at: group.remote_created_at + ? new Date(group.remote_created_at) + : new Date(), + modified_at: new Date(), + remote_was_deleted: group.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingGroup) { + existingGroup = await this.prisma.hris_groups.update({ + where: { + id_hris_group: existingGroup.id_hris_group, + }, + data: groupData, + }); + } else { + existingGroup = await this.prisma.hris_groups.create({ + data: { + ...groupData, + id_hris_group: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + groupResults.push(existingGroup); + + // Process field mappings + await this.ingestService.processFieldMappings( + group.field_mappings, + existingGroup.id_hris_group, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingGroup.id_hris_group, + remote_data[i], + ); + } + + return groupResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/group/types/index.ts b/packages/api/src/hris/group/types/index.ts index 2ed5a1257..b34ea68bd 100644 --- a/packages/api/src/hris/group/types/index.ts +++ b/packages/api/src/hris/group/types/index.ts @@ -2,17 +2,10 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; import { UnifiedHrisGroupInput, UnifiedHrisGroupOutput } from './model.unified'; import { OriginalGroupOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IGroupService { - addGroup( - groupData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - - syncGroups( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IGroupMapper { diff --git a/packages/api/src/hris/group/types/model.unified.ts b/packages/api/src/hris/group/types/model.unified.ts index 8170a26a0..6ce238ae6 100644 --- a/packages/api/src/hris/group/types/model.unified.ts +++ b/packages/api/src/hris/group/types/model.unified.ts @@ -7,6 +7,12 @@ import { IsBoolean, } from 'class-validator'; +export type Type = + | 'TEAM' + | 'DEPARTMENT' + | 'COST_CENTER' + | 'BUSINESS_UNIT' + | 'GROUP'; export class UnifiedHrisGroupInput { @ApiPropertyOptional({ type: String, @@ -30,13 +36,14 @@ export class UnifiedHrisGroupInput { @ApiPropertyOptional({ type: String, - example: 'Department', + example: 'DEPARTMENT', + enum: ['TEAM', 'DEPARTMENT', 'COST_CENTER', 'BUSINESS_UNIT', 'GROUP'], nullable: true, description: 'The type of the group', }) @IsString() @IsOptional() - type?: string; + type?: Type | string; @ApiPropertyOptional({ type: Object, @@ -87,34 +94,34 @@ export class UnifiedHrisGroupOutput extends UnifiedHrisGroupInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The date when the group was created in the 3rd party system', }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the group record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the group record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/hris.module.ts b/packages/api/src/hris/hris.module.ts index 46c9effdc..b7522a13c 100644 --- a/packages/api/src/hris/hris.module.ts +++ b/packages/api/src/hris/hris.module.ts @@ -13,6 +13,7 @@ import { PayGroupModule } from './paygroup/paygroup.module'; import { PayrollRunModule } from './payrollrun/payrollrun.module'; import { TimeoffModule } from './timeoff/timeoff.module'; import { TimeoffBalanceModule } from './timeoffbalance/timeoffbalance.module'; +import { TimesheetentryModule } from './timesheetentry/timesheetentry.module'; @Module({ exports: [ @@ -30,6 +31,7 @@ import { TimeoffBalanceModule } from './timeoffbalance/timeoffbalance.module'; PayrollRunModule, TimeoffModule, TimeoffBalanceModule, + TimesheetentryModule, ], imports: [ BankInfoModule, @@ -46,6 +48,7 @@ import { TimeoffBalanceModule } from './timeoffbalance/timeoffbalance.module'; PayrollRunModule, TimeoffModule, TimeoffBalanceModule, + TimesheetentryModule, ], }) export class HrisModule {} diff --git a/packages/api/src/hris/location/services/gusto/index.ts b/packages/api/src/hris/location/services/gusto/index.ts new file mode 100644 index 000000000..6138824d3 --- /dev/null +++ b/packages/api/src/hris/location/services/gusto/index.ts @@ -0,0 +1,101 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { HrisObject } from '@hris/@lib/@types'; +import { ILocationService } from '@hris/location/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { GustoLocationOutput } from './types'; + +@Injectable() +export class GustoService implements ILocationService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private env: EnvironmentService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + HrisObject.location.toUpperCase() + ':' + GustoService.name, + ); + this.registry.registerService('gusto', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId, id_employee } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'gusto', + vertical: 'hris', + }, + }); + + const employee = await this.prisma.hris_employees.findUnique({ + where: { + id_hris_employee: id_employee as string, + }, + select: { + remote_id: true, + }, + }); + + const resp = await axios.get( + `${connection.account_url}/v1/employees/${employee.remote_id}/home_addresses`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + + const resp_ = await axios.get( + `${connection.account_url}/v1/employees/${employee.remote_id}/work_addresses`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + this.logger.log(`Synced gusto locations !`); + + const resp_home = resp.data.map((add) => { + return { + ...add, + type: 'HOME', + }; + }); + + let resp_work; + if (resp_.data) { + resp_work = resp_.data.map((add) => { + return { + ...add, + type: 'WORK', + }; + }); + } + + return { + data: resp.data.map, + message: 'Gusto locations retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/location/services/gusto/mappers.ts b/packages/api/src/hris/location/services/gusto/mappers.ts new file mode 100644 index 000000000..511e7a4a4 --- /dev/null +++ b/packages/api/src/hris/location/services/gusto/mappers.ts @@ -0,0 +1,93 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { Injectable } from '@nestjs/common'; +import { GustoLocationOutput } from './types'; +import { + UnifiedHrisLocationInput, + UnifiedHrisLocationOutput, +} from '@hris/location/types/model.unified'; +import { ILocationMapper } from '@hris/location/types'; +import { Utils } from '@hris/@lib/@utils'; + +@Injectable() +export class GustoLocationMapper implements ILocationMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private ingestService: IngestDataService, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService('hris', 'location', 'gusto', this); + } + + async desunify( + source: UnifiedHrisLocationInput, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + return; + } + + async unify( + source: GustoLocationOutput | GustoLocationOutput[], + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + if (!Array.isArray(source)) { + return this.mapSingleLocationToUnified( + source, + connectionId, + customFieldMappings, + ); + } + return Promise.all( + source.map((location) => + this.mapSingleLocationToUnified( + location, + connectionId, + customFieldMappings, + ), + ), + ); + } + + private async mapSingleLocationToUnified( + location: GustoLocationOutput, + connectionId: string, + customFieldMappings?: { slug: string; remote_id: string }[], + ): Promise { + const opts: any = {}; + + if (location.employee_uuid) { + const employee_id = await this.utils.getEmployeeUuidFromRemoteId( + location.employee_uuid, + connectionId, + ); + if (employee_id) { + opts.employee_id = employee_id; + } + } + if (location.company_location_uuid) { + const id = await this.utils.getEmployerLocationUuidFromRemoteId( + location.company_location_uuid, + connectionId, + ); + if (id) { + opts.employer_location_id = id; + } + } + + return { + remote_id: location.uuid || null, + remote_data: location, + ...opts, + employee_contribution: location.employee_deduction + ? parseFloat(location.employee_deduction) + : null, + company_contribution: location.company_contribution + ? parseFloat(location.company_contribution) + : null, + remote_was_deleted: null, + }; + } +} diff --git a/packages/api/src/hris/location/services/gusto/types.ts b/packages/api/src/hris/location/services/gusto/types.ts new file mode 100644 index 000000000..041cc4c07 --- /dev/null +++ b/packages/api/src/hris/location/services/gusto/types.ts @@ -0,0 +1,9 @@ +export type GustoLocationOutput = { + street_1: string; + street_2: string; + city: string; + state: string; + zip: string; + country: string; + active: boolean; +}; diff --git a/packages/api/src/hris/location/services/location.service.ts b/packages/api/src/hris/location/services/location.service.ts index 457541b11..113e99b1a 100644 --- a/packages/api/src/hris/location/services/location.service.ts +++ b/packages/api/src/hris/location/services/location.service.ts @@ -2,19 +2,13 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { UnifiedHrisLocationInput, UnifiedHrisLocationOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalLocationOutput } from '@@core/utils/types/original/original.hris'; - -import { ILocationService } from '../types'; @Injectable() export class LocationService { @@ -29,14 +23,85 @@ export class LocationService { } async getLocation( - id_locationing_location: string, + id_hris_location: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const location = await this.prisma.hris_locations.findUnique({ + where: { id_hris_location: id_hris_location }, + }); + + if (!location) { + throw new Error(`Location with ID ${id_hris_location} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: location.id_hris_location, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedLocation: UnifiedHrisLocationOutput = { + id: location.id_hris_location, + name: location.name, + phone_number: location.phone_number, + street_1: location.street_1, + street_2: location.street_2, + city: location.city, + state: location.state, + zip_code: location.zip_code, + country: location.country, + location_type: location.location_type, + field_mappings: field_mappings, + remote_id: location.remote_id, + remote_created_at: location.remote_created_at, + created_at: location.created_at, + modified_at: location.modified_at, + remote_was_deleted: location.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: location.id_hris_location, + }, + }); + unifiedLocation.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.location.pull', + method: 'GET', + url: '/hris/location', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedLocation; + } catch (error) { + throw error; + } } async getLocations( @@ -47,7 +112,96 @@ export class LocationService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisLocationOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const locations = await this.prisma.hris_locations.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_location: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = locations.length > limit; + if (hasNextPage) locations.pop(); + + const unifiedLocations = await Promise.all( + locations.map(async (location) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: location.id_hris_location, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedLocation: UnifiedHrisLocationOutput = { + id: location.id_hris_location, + name: location.name, + phone_number: location.phone_number, + street_1: location.street_1, + street_2: location.street_2, + city: location.city, + state: location.state, + zip_code: location.zip_code, + country: location.country, + location_type: location.location_type, + field_mappings: field_mappings, + remote_id: location.remote_id, + remote_created_at: location.remote_created_at, + created_at: location.created_at, + modified_at: location.modified_at, + remote_was_deleted: location.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: location.id_hris_location, + }, + }); + unifiedLocation.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedLocation; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.location.pull', + method: 'GET', + url: '/hris/locations', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedLocations, + next_cursor: hasNextPage + ? locations[locations.length - 1].id_hris_location + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/location/sync/sync.processor.ts b/packages/api/src/hris/location/sync/sync.processor.ts new file mode 100644 index 000000000..8352a99e4 --- /dev/null +++ b/packages/api/src/hris/location/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-locations') + async handleSyncLocations(job: Job) { + try { + console.log(`Processing queue -> hris-sync-locations ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris locations', error); + } + } +} diff --git a/packages/api/src/hris/location/sync/sync.service.ts b/packages/api/src/hris/location/sync/sync.service.ts index 8ea2af121..9fba5d7fd 100644 --- a/packages/api/src/hris/location/sync/sync.service.ts +++ b/packages/api/src/hris/location/sync/sync.service.ts @@ -2,7 +2,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -10,6 +9,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisLocationOutput } from '../types/model.unified'; import { ILocationService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_locations as HrisLocation } from '@prisma/client'; +import { OriginalLocationOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +24,145 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'location', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing locations...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ILocationService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisLocationOutput, + OriginalLocationOutput, + ILocationService + >(integrationId, linkedUserId, 'hris', 'location', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + locations: UnifiedHrisLocationOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const locationResults: HrisLocation[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < locations.length; i++) { + const location = locations[i]; + const originId = location.remote_id; + + let existingLocation = await this.prisma.hris_locations.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const locationData = { + name: location.name, + phone_number: location.phone_number, + street_1: location.street_1, + street_2: location.street_2, + city: location.city, + state: location.state, + zip_code: location.zip_code, + country: location.country, + location_type: location.location_type, + remote_id: originId, + remote_created_at: location.remote_created_at + ? new Date(location.remote_created_at) + : new Date(), + modified_at: new Date(), + remote_was_deleted: location.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingLocation) { + existingLocation = await this.prisma.hris_locations.update({ + where: { + id_hris_location: existingLocation.id_hris_location, + }, + data: locationData, + }); + } else { + existingLocation = await this.prisma.hris_locations.create({ + data: { + ...locationData, + id_hris_location: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + locationResults.push(existingLocation); + + // Process field mappings + await this.ingestService.processFieldMappings( + location.field_mappings, + existingLocation.id_hris_location, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingLocation.id_hris_location, + remote_data[i], + ); + } + + return locationResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/location/types/index.ts b/packages/api/src/hris/location/types/index.ts index 74daa5cc4..3cd608bda 100644 --- a/packages/api/src/hris/location/types/index.ts +++ b/packages/api/src/hris/location/types/index.ts @@ -1,18 +1,19 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedHrisLocationInput, UnifiedHrisLocationOutput } from './model.unified'; +import { + UnifiedHrisLocationInput, + UnifiedHrisLocationOutput, +} from './model.unified'; import { OriginalLocationOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ILocationService { - addLocation( + addLocation?( locationData: DesunifyReturnType, linkedUserId: string, ): Promise>; - syncLocations( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ILocationMapper { diff --git a/packages/api/src/hris/location/types/model.unified.ts b/packages/api/src/hris/location/types/model.unified.ts index 629470a19..416f15bda 100644 --- a/packages/api/src/hris/location/types/model.unified.ts +++ b/packages/api/src/hris/location/types/model.unified.ts @@ -5,8 +5,10 @@ import { IsString, IsDateString, IsBoolean, + IsIn, } from 'class-validator'; +export type LocationType = 'WORK' | 'HOME'; export class UnifiedHrisLocationInput { @ApiPropertyOptional({ type: String, @@ -90,13 +92,15 @@ export class UnifiedHrisLocationInput { @ApiPropertyOptional({ type: String, - example: 'Office', + example: 'WORK', + enum: ['WORK', 'HOME'], nullable: true, description: 'The type of the location', }) @IsString() + @IsIn(['WORK', 'HOME']) @IsOptional() - location_type?: string; + location_type?: LocationType | string; @ApiPropertyOptional({ type: Object, @@ -149,7 +153,7 @@ export class UnifiedHrisLocationOutput extends UnifiedHrisLocationInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -157,27 +161,27 @@ export class UnifiedHrisLocationOutput extends UnifiedHrisLocationInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the location record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the location record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/paygroup/services/paygroup.service.ts b/packages/api/src/hris/paygroup/services/paygroup.service.ts index ab4713949..b21213494 100644 --- a/packages/api/src/hris/paygroup/services/paygroup.service.ts +++ b/packages/api/src/hris/paygroup/services/paygroup.service.ts @@ -2,19 +2,13 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { UnifiedHrisPaygroupInput, UnifiedHrisPaygroupOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalPayGroupOutput } from '@@core/utils/types/original/original.hris'; - -import { IPayGroupService } from '../types'; @Injectable() export class PayGroupService { @@ -29,14 +23,77 @@ export class PayGroupService { } async getPayGroup( - id_paygrouping_paygroup: string, + id_hris_pay_group: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const paygroup = await this.prisma.hris_pay_groups.findUnique({ + where: { id_hris_pay_group: id_hris_pay_group }, + }); + + if (!paygroup) { + throw new Error(`PayGroup with ID ${id_hris_pay_group} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: paygroup.id_hris_pay_group, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPayGroup: UnifiedHrisPaygroupOutput = { + id: paygroup.id_hris_pay_group, + pay_group_name: paygroup.pay_group_name, + field_mappings: field_mappings, + remote_id: paygroup.remote_id, + remote_created_at: paygroup.remote_created_at, + created_at: paygroup.created_at, + modified_at: paygroup.modified_at, + remote_was_deleted: paygroup.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: paygroup.id_hris_pay_group, + }, + }); + unifiedPayGroup.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.paygroup.pull', + method: 'GET', + url: '/hris/paygroup', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedPayGroup; + } catch (error) { + throw error; + } } async getPayGroups( @@ -47,7 +104,88 @@ export class PayGroupService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisPaygroupOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const paygroups = await this.prisma.hris_pay_groups.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_pay_group: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = paygroups.length > limit; + if (hasNextPage) paygroups.pop(); + + const unifiedPayGroups = await Promise.all( + paygroups.map(async (paygroup) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: paygroup.id_hris_pay_group, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPayGroup: UnifiedHrisPaygroupOutput = { + id: paygroup.id_hris_pay_group, + pay_group_name: paygroup.pay_group_name, + field_mappings: field_mappings, + remote_id: paygroup.remote_id, + remote_created_at: paygroup.remote_created_at, + created_at: paygroup.created_at, + modified_at: paygroup.modified_at, + remote_was_deleted: paygroup.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: paygroup.id_hris_pay_group, + }, + }); + unifiedPayGroup.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedPayGroup; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.paygroup.pull', + method: 'GET', + url: '/hris/paygroups', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedPayGroups, + next_cursor: hasNextPage + ? paygroups[paygroups.length - 1].id_hris_pay_group + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/paygroup/sync/sync.processor.ts b/packages/api/src/hris/paygroup/sync/sync.processor.ts new file mode 100644 index 000000000..cfd0640ba --- /dev/null +++ b/packages/api/src/hris/paygroup/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-paygroups') + async handleSyncPayGroups(job: Job) { + try { + console.log(`Processing queue -> hris-sync-paygroups ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris pay groups', error); + } + } +} diff --git a/packages/api/src/hris/paygroup/sync/sync.service.ts b/packages/api/src/hris/paygroup/sync/sync.service.ts index 28e050121..169ce2556 100644 --- a/packages/api/src/hris/paygroup/sync/sync.service.ts +++ b/packages/api/src/hris/paygroup/sync/sync.service.ts @@ -2,7 +2,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -10,6 +9,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisPaygroupOutput } from '../types/model.unified'; import { IPayGroupService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_pay_groups as HrisPayGroup } from '@prisma/client'; +import { OriginalPayGroupOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +24,137 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'paygroup', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing paygroups...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IPayGroupService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisPaygroupOutput, + OriginalPayGroupOutput, + IPayGroupService + >(integrationId, linkedUserId, 'hris', 'paygroup', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + paygroups: UnifiedHrisPaygroupOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const paygroupResults: HrisPayGroup[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < paygroups.length; i++) { + const paygroup = paygroups[i]; + const originId = paygroup.remote_id; + + let existingPayGroup = await this.prisma.hris_pay_groups.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const paygroupData = { + pay_group_name: paygroup.pay_group_name, + remote_id: originId, + remote_created_at: paygroup.remote_created_at + ? new Date(paygroup.remote_created_at) + : new Date(), + modified_at: new Date(), + remote_was_deleted: paygroup.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingPayGroup) { + existingPayGroup = await this.prisma.hris_pay_groups.update({ + where: { + id_hris_pay_group: existingPayGroup.id_hris_pay_group, + }, + data: paygroupData, + }); + } else { + existingPayGroup = await this.prisma.hris_pay_groups.create({ + data: { + ...paygroupData, + id_hris_pay_group: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + paygroupResults.push(existingPayGroup); + + // Process field mappings + await this.ingestService.processFieldMappings( + paygroup.field_mappings, + existingPayGroup.id_hris_pay_group, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingPayGroup.id_hris_pay_group, + remote_data[i], + ); + } + + return paygroupResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/paygroup/types/index.ts b/packages/api/src/hris/paygroup/types/index.ts index e3f361b31..d2cc3cf90 100644 --- a/packages/api/src/hris/paygroup/types/index.ts +++ b/packages/api/src/hris/paygroup/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedHrisPaygroupInput, UnifiedHrisPaygroupOutput } from './model.unified'; +import { + UnifiedHrisPaygroupInput, + UnifiedHrisPaygroupOutput, +} from './model.unified'; import { OriginalPayGroupOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IPayGroupService { addPayGroup( @@ -9,10 +13,7 @@ export interface IPayGroupService { linkedUserId: string, ): Promise>; - syncPayGroups( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IPayGroupMapper { diff --git a/packages/api/src/hris/paygroup/types/model.unified.ts b/packages/api/src/hris/paygroup/types/model.unified.ts index 780169de2..cb8c35487 100644 --- a/packages/api/src/hris/paygroup/types/model.unified.ts +++ b/packages/api/src/hris/paygroup/types/model.unified.ts @@ -69,7 +69,7 @@ export class UnifiedHrisPaygroupOutput extends UnifiedHrisPaygroupInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -77,27 +77,27 @@ export class UnifiedHrisPaygroupOutput extends UnifiedHrisPaygroupInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the pay group record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the pay group record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/payrollrun/services/payrollrun.service.ts b/packages/api/src/hris/payrollrun/services/payrollrun.service.ts index 7f0d145c0..3c66e6ff1 100644 --- a/packages/api/src/hris/payrollrun/services/payrollrun.service.ts +++ b/packages/api/src/hris/payrollrun/services/payrollrun.service.ts @@ -1,20 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedHrisPayrollrunInput, - UnifiedHrisPayrollrunOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedHrisPayrollrunOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalPayrollRunOutput } from '@@core/utils/types/original/original.hris'; - -import { IPayrollRunService } from '../types'; @Injectable() export class PayrollRunService { @@ -29,14 +20,81 @@ export class PayrollRunService { } async getPayrollRun( - id_payrollruning_payrollrun: string, + id_hris_payroll_run: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const payrollRun = await this.prisma.hris_payroll_runs.findUnique({ + where: { id_hris_payroll_run: id_hris_payroll_run }, + }); + + if (!payrollRun) { + throw new Error(`PayrollRun with ID ${id_hris_payroll_run} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: payrollRun.id_hris_payroll_run, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPayrollRun: UnifiedHrisPayrollrunOutput = { + id: payrollRun.id_hris_payroll_run, + run_state: payrollRun.run_state, + run_type: payrollRun.run_type, + start_date: payrollRun.start_date, + end_date: payrollRun.end_date, + check_date: payrollRun.check_date, + field_mappings: field_mappings, + remote_id: payrollRun.remote_id, + remote_created_at: payrollRun.remote_created_at, + created_at: payrollRun.created_at, + modified_at: payrollRun.modified_at, + remote_was_deleted: payrollRun.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: payrollRun.id_hris_payroll_run, + }, + }); + unifiedPayrollRun.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.payrollrun.pull', + method: 'GET', + url: '/hris/payrollrun', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedPayrollRun; + } catch (error) { + throw error; + } } async getPayrollRuns( @@ -47,7 +105,92 @@ export class PayrollRunService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisPayrollrunOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const payrollRuns = await this.prisma.hris_payroll_runs.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_payroll_run: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = payrollRuns.length > limit; + if (hasNextPage) payrollRuns.pop(); + + const unifiedPayrollRuns = await Promise.all( + payrollRuns.map(async (payrollRun) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: payrollRun.id_hris_payroll_run, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedPayrollRun: UnifiedHrisPayrollrunOutput = { + id: payrollRun.id_hris_payroll_run, + run_state: payrollRun.run_state, + run_type: payrollRun.run_type, + start_date: payrollRun.start_date, + end_date: payrollRun.end_date, + check_date: payrollRun.check_date, + field_mappings: field_mappings, + remote_id: payrollRun.remote_id, + remote_created_at: payrollRun.remote_created_at, + created_at: payrollRun.created_at, + modified_at: payrollRun.modified_at, + remote_was_deleted: payrollRun.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: payrollRun.id_hris_payroll_run, + }, + }); + unifiedPayrollRun.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedPayrollRun; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.payrollrun.pull', + method: 'GET', + url: '/hris/payrollruns', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedPayrollRuns, + next_cursor: hasNextPage + ? payrollRuns[payrollRuns.length - 1].id_hris_payroll_run + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/payrollrun/sync/sync.processor.ts b/packages/api/src/hris/payrollrun/sync/sync.processor.ts new file mode 100644 index 000000000..088f45634 --- /dev/null +++ b/packages/api/src/hris/payrollrun/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-payrollruns') + async handleSyncCompanies(job: Job) { + try { + console.log(`Processing queue -> hris-sync-payrollruns ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris payrollruns', error); + } + } +} diff --git a/packages/api/src/hris/payrollrun/sync/sync.service.ts b/packages/api/src/hris/payrollrun/sync/sync.service.ts index 943e06001..923773d7b 100644 --- a/packages/api/src/hris/payrollrun/sync/sync.service.ts +++ b/packages/api/src/hris/payrollrun/sync/sync.service.ts @@ -2,7 +2,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -10,6 +9,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisPayrollrunOutput } from '../types/model.unified'; import { IPayrollRunService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_payroll_runs as HrisPayrollRun } from '@prisma/client'; +import { OriginalPayrollRunOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +24,145 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'payrollrun', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing payroll runs...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: IPayrollRunService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisPayrollrunOutput, + OriginalPayrollRunOutput, + IPayrollRunService + >(integrationId, linkedUserId, 'hris', 'payrollrun', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + payrollRuns: UnifiedHrisPayrollrunOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const payrollRunResults: HrisPayrollRun[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < payrollRuns.length; i++) { + const payrollRun = payrollRuns[i]; + const originId = payrollRun.remote_id; + + let existingPayrollRun = await this.prisma.hris_payroll_runs.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const payrollRunData = { + run_state: payrollRun.run_state, + run_type: payrollRun.run_type, + start_date: payrollRun.start_date + ? new Date(payrollRun.start_date) + : null, + end_date: payrollRun.end_date ? new Date(payrollRun.end_date) : null, + check_date: payrollRun.check_date + ? new Date(payrollRun.check_date) + : null, + remote_id: originId, + remote_created_at: payrollRun.remote_created_at + ? new Date(payrollRun.remote_created_at) + : new Date(), + modified_at: new Date(), + remote_was_deleted: payrollRun.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingPayrollRun) { + existingPayrollRun = await this.prisma.hris_payroll_runs.update({ + where: { + id_hris_payroll_run: existingPayrollRun.id_hris_payroll_run, + }, + data: payrollRunData, + }); + } else { + existingPayrollRun = await this.prisma.hris_payroll_runs.create({ + data: { + ...payrollRunData, + id_hris_payroll_run: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + payrollRunResults.push(existingPayrollRun); + + // Process field mappings + await this.ingestService.processFieldMappings( + payrollRun.field_mappings, + existingPayrollRun.id_hris_payroll_run, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingPayrollRun.id_hris_payroll_run, + remote_data[i], + ); + } + + return payrollRunResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/payrollrun/types/index.ts b/packages/api/src/hris/payrollrun/types/index.ts index 9aebceeb2..a7933d143 100644 --- a/packages/api/src/hris/payrollrun/types/index.ts +++ b/packages/api/src/hris/payrollrun/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalPayrollRunOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface IPayrollRunService { addPayrollRun( @@ -12,10 +13,7 @@ export interface IPayrollRunService { linkedUserId: string, ): Promise>; - syncPayrollRuns( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface IPayrollRunMapper { diff --git a/packages/api/src/hris/payrollrun/types/model.unified.ts b/packages/api/src/hris/payrollrun/types/model.unified.ts index dc0a152ad..29a95f9fd 100644 --- a/packages/api/src/hris/payrollrun/types/model.unified.ts +++ b/packages/api/src/hris/payrollrun/types/model.unified.ts @@ -7,56 +7,71 @@ import { IsBoolean, } from 'class-validator'; +export type RunState = 'PAID' | 'DRAFT' | 'APPROVED' | 'FAILED' | 'CLOSE'; +export type RunType = + | 'REGULAR' + | 'OFF_CYCLE' + | 'CORRECTION' + | 'TERMINATION' + | 'SIGN_ON_BONUS'; export class UnifiedHrisPayrollrunInput { @ApiPropertyOptional({ type: String, - example: 'Completed', + example: 'PAID', + enum: ['PAID', 'DRAFT', 'APPROVED', 'FAILED', 'CLOSE'], nullable: true, description: 'The state of the payroll run', }) @IsString() @IsOptional() - run_state?: string; + run_state?: RunState | string; @ApiPropertyOptional({ type: String, - example: 'Regular', + example: 'REGULAR', + enum: [ + 'REGULAR', + 'OFF_CYCLE', + 'CORRECTION', + 'TERMINATION', + 'SIGN_ON_BONUS', + ], nullable: true, description: 'The type of the payroll run', }) @IsString() @IsOptional() - run_type?: string; + run_type?: RunType | string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-01-01T00:00:00Z', nullable: true, description: 'The start date of the payroll run', }) @IsDateString() @IsOptional() - start_date?: string; + start_date?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-01-15T23:59:59Z', nullable: true, description: 'The end date of the payroll run', }) @IsDateString() @IsOptional() - end_date?: string; + end_date?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-01-20T00:00:00Z', nullable: true, description: 'The check date of the payroll run', }) @IsDateString() @IsOptional() - check_date?: string; + check_date?: Date; @ApiPropertyOptional({ type: Object, @@ -109,7 +124,7 @@ export class UnifiedHrisPayrollrunOutput extends UnifiedHrisPayrollrunInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: @@ -117,27 +132,27 @@ export class UnifiedHrisPayrollrunOutput extends UnifiedHrisPayrollrunInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The created date of the payroll run record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-10-01T12:00:00Z', nullable: true, description: 'The last modified date of the payroll run record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/timeoff/services/timeoff.service.ts b/packages/api/src/hris/timeoff/services/timeoff.service.ts index 843085e2f..947f1b834 100644 --- a/packages/api/src/hris/timeoff/services/timeoff.service.ts +++ b/packages/api/src/hris/timeoff/services/timeoff.service.ts @@ -9,12 +9,8 @@ import { UnifiedHrisTimeoffInput, UnifiedHrisTimeoffOutput, } from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalTimeoffOutput } from '@@core/utils/types/original/original.hris'; - -import { ITimeoffService } from '../types'; @Injectable() export class TimeoffService { @@ -36,18 +32,126 @@ export class TimeoffService { linkedUserId: string, remote_data?: boolean, ): Promise { - return; + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addTimeoff(unifiedTimeoffData, linkedUserId); + + const savedTimeOff = await this.prisma.hris_time_off.create({ + data: { + id_hris_time_off: uuidv4(), + ...unifiedTimeoffData, + amount: unifiedTimeoffData.amount + ? BigInt(unifiedTimeoffData.amount) + : null, + start_time: unifiedTimeoffData.start_time + ? new Date(unifiedTimeoffData.start_time) + : null, + end_time: unifiedTimeoffData.end_time + ? new Date(unifiedTimeoffData.end_time) + : null, + remote_id: resp.data.remote_id, + id_connection: connectionId, + created_at: new Date(), + modified_at: new Date(), + remote_created_at: resp.data.remote_created_at + ? new Date(resp.data.remote_created_at) + : null, + remote_was_deleted: false, + }, + }); + + const result: UnifiedHrisTimeoffOutput = { + ...savedTimeOff, + id: savedTimeOff.id_hris_time_off, + amount: Number(savedTimeOff.amount), + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } } async getTimeoff( - id_timeoffing_timeoff: string, + id_hris_time_off: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const timeOff = await this.prisma.hris_time_off.findUnique({ + where: { id_hris_time_off: id_hris_time_off }, + }); + + if (!timeOff) { + throw new Error(`Time off with ID ${id_hris_time_off} not found.`); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: timeOff.id_hris_time_off }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTimeOff: UnifiedHrisTimeoffOutput = { + id: timeOff.id_hris_time_off, + employee: timeOff.employee, + approver: timeOff.approver, + status: timeOff.status, + employee_note: timeOff.employee_note, + units: timeOff.units, + amount: timeOff.amount ? Number(timeOff.amount) : undefined, + request_type: timeOff.request_type, + start_time: timeOff.start_time, + end_time: timeOff.end_time, + field_mappings: field_mappings, + remote_id: timeOff.remote_id, + remote_created_at: timeOff.remote_created_at, + created_at: timeOff.created_at, + modified_at: timeOff.modified_at, + remote_was_deleted: timeOff.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: timeOff.id_hris_time_off }, + }); + unifiedTimeOff.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.time_off.pull', + method: 'GET', + url: '/hris/time_off', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedTimeOff; + } catch (error) { + throw error; + } } async getTimeoffs( @@ -58,7 +162,94 @@ export class TimeoffService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisTimeoffOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const timeOffs = await this.prisma.hris_time_off.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_time_off: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = timeOffs.length > limit; + if (hasNextPage) timeOffs.pop(); + + const unifiedTimeOffs = await Promise.all( + timeOffs.map(async (timeOff) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: timeOff.id_hris_time_off }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTimeOff: UnifiedHrisTimeoffOutput = { + id: timeOff.id_hris_time_off, + employee: timeOff.employee, + approver: timeOff.approver, + status: timeOff.status, + employee_note: timeOff.employee_note, + units: timeOff.units, + amount: timeOff.amount ? Number(timeOff.amount) : undefined, + request_type: timeOff.request_type, + start_time: timeOff.start_time, + end_time: timeOff.end_time, + field_mappings: field_mappings, + remote_id: timeOff.remote_id, + remote_created_at: timeOff.remote_created_at, + created_at: timeOff.created_at, + modified_at: timeOff.modified_at, + remote_was_deleted: timeOff.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: timeOff.id_hris_time_off, + }, + }); + unifiedTimeOff.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedTimeOff; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.timeoff.pull', + method: 'GET', + url: '/hris/timeoffs', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedTimeOffs, + next_cursor: hasNextPage + ? timeOffs[timeOffs.length - 1].id_hris_time_off + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/timeoff/sync/sync.processor.ts b/packages/api/src/hris/timeoff/sync/sync.processor.ts new file mode 100644 index 000000000..229d422a6 --- /dev/null +++ b/packages/api/src/hris/timeoff/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-timeoffs') + async handleSyncTimeOffs(job: Job) { + try { + console.log(`Processing queue -> hris-sync-timeoffs ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris time offs', error); + } + } +} diff --git a/packages/api/src/hris/timeoff/sync/sync.service.ts b/packages/api/src/hris/timeoff/sync/sync.service.ts index 301c000b5..710d8b6fb 100644 --- a/packages/api/src/hris/timeoff/sync/sync.service.ts +++ b/packages/api/src/hris/timeoff/sync/sync.service.ts @@ -10,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisTimeoffOutput } from '../types/model.unified'; import { ITimeoffService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_time_off as HrisTimeOff } from '@prisma/client'; +import { OriginalTimeoffOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -19,23 +25,143 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'time_off', this); } - saveToDb( + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing time off...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ITimeoffService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisTimeoffOutput, + OriginalTimeoffOutput, + ITimeoffService + >(integrationId, linkedUserId, 'hris', 'time_off', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + timeOffs: UnifiedHrisTimeoffOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const timeOffResults: HrisTimeOff[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < timeOffs.length; i++) { + const timeOff = timeOffs[i]; + const originId = timeOff.remote_id; + + let existingTimeOff = await this.prisma.hris_time_off.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const timeOffData = { + employee: timeOff.employee, + approver: timeOff.approver, + status: timeOff.status, + employee_note: timeOff.employee_note, + units: timeOff.units, + amount: timeOff.amount ? BigInt(timeOff.amount) : null, + request_type: timeOff.request_type, + start_time: timeOff.start_time ? new Date(timeOff.start_time) : null, + end_time: timeOff.end_time ? new Date(timeOff.end_time) : null, + remote_id: originId, + remote_created_at: timeOff.remote_created_at + ? new Date(timeOff.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: timeOff.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingTimeOff) { + existingTimeOff = await this.prisma.hris_time_off.update({ + where: { id_hris_time_off: existingTimeOff.id_hris_time_off }, + data: timeOffData, + }); + } else { + existingTimeOff = await this.prisma.hris_time_off.create({ + data: { + ...timeOffData, + id_hris_time_off: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + timeOffResults.push(existingTimeOff); + + // Process field mappings + await this.ingestService.processFieldMappings( + timeOff.field_mappings, + existingTimeOff.id_hris_time_off, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingTimeOff.id_hris_time_off, + remote_data[i], + ); + } + + return timeOffResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/timeoff/types/index.ts b/packages/api/src/hris/timeoff/types/index.ts index 16e954446..e26f0d3ab 100644 --- a/packages/api/src/hris/timeoff/types/index.ts +++ b/packages/api/src/hris/timeoff/types/index.ts @@ -1,7 +1,11 @@ import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { UnifiedHrisTimeoffInput, UnifiedHrisTimeoffOutput } from './model.unified'; +import { + UnifiedHrisTimeoffInput, + UnifiedHrisTimeoffOutput, +} from './model.unified'; import { OriginalTimeoffOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ITimeoffService { addTimeoff( @@ -9,10 +13,7 @@ export interface ITimeoffService { linkedUserId: string, ): Promise>; - syncTimeoffs( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ITimeoffMapper { diff --git a/packages/api/src/hris/timeoff/types/model.unified.ts b/packages/api/src/hris/timeoff/types/model.unified.ts index e822f64bd..ab9a25343 100644 --- a/packages/api/src/hris/timeoff/types/model.unified.ts +++ b/packages/api/src/hris/timeoff/types/model.unified.ts @@ -8,6 +8,20 @@ import { IsBoolean, } from 'class-validator'; +export type Status = + | 'REQUESTED' + | 'APPROVED' + | 'DECLINED' + | 'CANCELLED' + | 'DELETED'; + +export type RequestType = + | 'VACATION' + | 'SICK' + | 'PERSONAL' + | 'JURY_DUTY' + | 'VOLUNTEER' + | 'BEREAVEMENT'; export class UnifiedHrisTimeoffInput { @ApiPropertyOptional({ type: String, @@ -31,13 +45,14 @@ export class UnifiedHrisTimeoffInput { @ApiPropertyOptional({ type: String, - example: 'Approved', + example: 'REQUESTED', + enum: ['REQUESTED', 'APPROVED', 'DECLINED', 'CANCELLED', 'DELETED'], nullable: true, description: 'The status of the time off request', }) @IsString() @IsOptional() - status?: string; + status?: Status | string; @ApiPropertyOptional({ type: String, @@ -51,13 +66,14 @@ export class UnifiedHrisTimeoffInput { @ApiPropertyOptional({ type: String, - example: 'Days', + example: 'DAYS', + enum: ['HOURS', 'DAYS'], nullable: true, description: 'The units used for the time off (e.g., Days, Hours)', }) @IsString() @IsOptional() - units?: string; + units?: 'HOURS' | 'DAYS' | string; @ApiPropertyOptional({ type: Number, @@ -71,33 +87,41 @@ export class UnifiedHrisTimeoffInput { @ApiPropertyOptional({ type: String, - example: 'Vacation', + example: 'VACATION', + enum: [ + 'VACATION', + 'SICK', + 'PERSONAL', + 'JURY_DUTY', + 'VOLUNTEER', + 'BEREAVEMENT', + ], nullable: true, description: 'The type of time off request', }) @IsString() @IsOptional() - request_type?: string; + request_type?: RequestType | string; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-07-01T09:00:00Z', nullable: true, description: 'The start time of the time off', }) @IsDateString() @IsOptional() - start_time?: string; + start_time?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-07-05T17:00:00Z', nullable: true, description: 'The end time of the time off', }) @IsDateString() @IsOptional() - end_time?: string; + end_time?: Date; @ApiPropertyOptional({ type: Object, @@ -150,7 +174,7 @@ export class UnifiedHrisTimeoffOutput extends UnifiedHrisTimeoffInput { remote_data?: Record; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: @@ -158,27 +182,27 @@ export class UnifiedHrisTimeoffOutput extends UnifiedHrisTimeoffInput { }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The created date of the time off record', }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ - type: String, + type: Date, example: '2024-06-15T12:00:00Z', nullable: true, description: 'The last modified date of the time off record', }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/timeoffbalance/services/timeoffbalance.service.ts b/packages/api/src/hris/timeoffbalance/services/timeoffbalance.service.ts index 6a19702e2..33d58c218 100644 --- a/packages/api/src/hris/timeoffbalance/services/timeoffbalance.service.ts +++ b/packages/api/src/hris/timeoffbalance/services/timeoffbalance.service.ts @@ -1,20 +1,11 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; -import { throwTypedError } from '@@core/utils/errors'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { - UnifiedHrisTimeoffbalanceInput, - UnifiedHrisTimeoffbalanceOutput, -} from '../types/model.unified'; - import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { Injectable } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { UnifiedHrisTimeoffbalanceOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; -import { OriginalTimeoffBalanceOutput } from '@@core/utils/types/original/original.hris'; - -import { ITimeoffBalanceService } from '../types'; @Injectable() export class TimeoffBalanceService { @@ -29,14 +20,85 @@ export class TimeoffBalanceService { } async getTimeoffBalance( - id_timeoffbalanceing_timeoffbalance: string, + id_hris_time_off_balance: string, linkedUserId: string, integrationId: string, connectionId: string, projectId: string, remote_data?: boolean, ): Promise { - return; + try { + const timeOffBalance = + await this.prisma.hris_time_off_balances.findUnique({ + where: { id_hris_time_off_balance: id_hris_time_off_balance }, + }); + + if (!timeOffBalance) { + throw new Error( + `Time off balance with ID ${id_hris_time_off_balance} not found.`, + ); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: timeOffBalance.id_hris_time_off_balance, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTimeOffBalance: UnifiedHrisTimeoffbalanceOutput = { + id: timeOffBalance.id_hris_time_off_balance, + balance: timeOffBalance.balance + ? Number(timeOffBalance.balance) + : undefined, + employee_id: timeOffBalance.id_hris_employee, + used: timeOffBalance.used ? Number(timeOffBalance.used) : undefined, + policy_type: timeOffBalance.policy_type, + field_mappings: field_mappings, + remote_id: timeOffBalance.remote_id, + remote_created_at: timeOffBalance.remote_created_at, + created_at: timeOffBalance.created_at, + modified_at: timeOffBalance.modified_at, + remote_was_deleted: timeOffBalance.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: timeOffBalance.id_hris_time_off_balance, + }, + }); + unifiedTimeOffBalance.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.time_off_balance.pull', + method: 'GET', + url: '/hris/time_off_balance', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedTimeOffBalance; + } catch (error) { + throw error; + } } async getTimeoffBalances( @@ -47,7 +109,95 @@ export class TimeoffBalanceService { limit: number, remote_data?: boolean, cursor?: string, - ): Promise { - return; + ): Promise<{ + data: UnifiedHrisTimeoffbalanceOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const timeOffBalances = await this.prisma.hris_time_off_balances.findMany( + { + take: limit + 1, + cursor: cursor ? { id_hris_time_off_balance: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }, + ); + + const hasNextPage = timeOffBalances.length > limit; + if (hasNextPage) timeOffBalances.pop(); + + const unifiedTimeOffBalances = await Promise.all( + timeOffBalances.map(async (timeOffBalance) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: timeOffBalance.id_hris_time_off_balance, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTimeOffBalance: UnifiedHrisTimeoffbalanceOutput = { + id: timeOffBalance.id_hris_time_off_balance, + balance: timeOffBalance.balance + ? Number(timeOffBalance.balance) + : undefined, + employee_id: timeOffBalance.id_hris_employee, + used: timeOffBalance.used ? Number(timeOffBalance.used) : undefined, + policy_type: timeOffBalance.policy_type, + field_mappings: field_mappings, + remote_id: timeOffBalance.remote_id, + remote_created_at: timeOffBalance.remote_created_at, + created_at: timeOffBalance.created_at, + modified_at: timeOffBalance.modified_at, + remote_was_deleted: timeOffBalance.remote_was_deleted, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: timeOffBalance.id_hris_time_off_balance, + }, + }); + unifiedTimeOffBalance.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedTimeOffBalance; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.time_off_balance.pull', + method: 'GET', + url: '/hris/time_off_balances', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedTimeOffBalances, + next_cursor: hasNextPage + ? timeOffBalances[timeOffBalances.length - 1].id_hris_time_off_balance + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } } } diff --git a/packages/api/src/hris/timeoffbalance/sync/sync.processor.ts b/packages/api/src/hris/timeoffbalance/sync/sync.processor.ts new file mode 100644 index 000000000..5cf30623b --- /dev/null +++ b/packages/api/src/hris/timeoffbalance/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-timeoffbalances') + async handleSyncTimeOffBalances(job: Job) { + try { + console.log(`Processing queue -> hris-sync-timeoffbalances ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris time off balances', error); + } + } +} diff --git a/packages/api/src/hris/timeoffbalance/sync/sync.service.ts b/packages/api/src/hris/timeoffbalance/sync/sync.service.ts index da8d4ebb6..eb2988a72 100644 --- a/packages/api/src/hris/timeoffbalance/sync/sync.service.ts +++ b/packages/api/src/hris/timeoffbalance/sync/sync.service.ts @@ -1,7 +1,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; - import { Cron } from '@nestjs/schedule'; import { ApiResponse } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; @@ -11,6 +10,12 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { UnifiedHrisTimeoffbalanceOutput } from '../types/model.unified'; import { ITimeoffBalanceService } from '../types'; import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_time_off_balances as HrisTimeOffBalance } from '@prisma/client'; +import { OriginalTimeoffBalanceOutput } from '@@core/utils/types/original/original.hris'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class SyncService implements OnModuleInit, IBaseSync { @@ -20,23 +25,146 @@ export class SyncService implements OnModuleInit, IBaseSync { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'time_off_balance', this); + } + + async onModuleInit() { + // Initialization logic if needed } - saveToDb( + + @Cron('0 */12 * * *') // every 12 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing time off balances...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ITimeoffBalanceService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisTimeoffbalanceOutput, + OriginalTimeoffBalanceOutput, + ITimeoffBalanceService + >(integrationId, linkedUserId, 'hris', 'time_off_balance', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( connection_id: string, linkedUserId: string, - data: any[], + timeOffBalances: UnifiedHrisTimeoffbalanceOutput[], originSource: string, remote_data: Record[], - ...rest: any - ): Promise { - throw new Error('Method not implemented.'); - } + ): Promise { + try { + const timeOffBalanceResults: HrisTimeOffBalance[] = []; - async onModuleInit() { - // Initialization logic - } + for (let i = 0; i < timeOffBalances.length; i++) { + const timeOffBalance = timeOffBalances[i]; + const originId = timeOffBalance.remote_id; + + let existingTimeOffBalance = + await this.prisma.hris_time_off_balances.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const timeOffBalanceData = { + balance: timeOffBalance.balance + ? BigInt(timeOffBalance.balance) + : null, + id_hris_employee: timeOffBalance.employee_id, + used: timeOffBalance.used ? BigInt(timeOffBalance.used) : null, + policy_type: timeOffBalance.policy_type, + remote_id: originId, + remote_created_at: timeOffBalance.remote_created_at + ? new Date(timeOffBalance.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: timeOffBalance.remote_was_deleted || false, + }; - // Additional methods and logic + if (existingTimeOffBalance) { + existingTimeOffBalance = + await this.prisma.hris_time_off_balances.update({ + where: { + id_hris_time_off_balance: + existingTimeOffBalance.id_hris_time_off_balance, + }, + data: timeOffBalanceData, + }); + } else { + existingTimeOffBalance = + await this.prisma.hris_time_off_balances.create({ + data: { + ...timeOffBalanceData, + id_hris_time_off_balance: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + timeOffBalanceResults.push(existingTimeOffBalance); + + // Process field mappings + await this.ingestService.processFieldMappings( + timeOffBalance.field_mappings, + existingTimeOffBalance.id_hris_time_off_balance, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingTimeOffBalance.id_hris_time_off_balance, + remote_data[i], + ); + } + + return timeOffBalanceResults; + } catch (error) { + throw error; + } + } } diff --git a/packages/api/src/hris/timeoffbalance/types/index.ts b/packages/api/src/hris/timeoffbalance/types/index.ts index a8e19d9b4..32fe6e43d 100644 --- a/packages/api/src/hris/timeoffbalance/types/index.ts +++ b/packages/api/src/hris/timeoffbalance/types/index.ts @@ -5,6 +5,7 @@ import { } from './model.unified'; import { OriginalTimeoffBalanceOutput } from '@@core/utils/types/original/original.hris'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; export interface ITimeoffBalanceService { addTimeoffBalance( @@ -12,10 +13,7 @@ export interface ITimeoffBalanceService { linkedUserId: string, ): Promise>; - syncTimeoffBalances( - linkedUserId: string, - custom_properties?: string[], - ): Promise>; + sync(data: SyncParam): Promise>; } export interface ITimeoffBalanceMapper { @@ -34,5 +32,7 @@ export interface ITimeoffBalanceMapper { slug: string; remote_id: string; }[], - ): Promise; + ): Promise< + UnifiedHrisTimeoffbalanceOutput | UnifiedHrisTimeoffbalanceOutput[] + >; } diff --git a/packages/api/src/hris/timeoffbalance/types/model.unified.ts b/packages/api/src/hris/timeoffbalance/types/model.unified.ts index 266d5634a..ae45a58ae 100644 --- a/packages/api/src/hris/timeoffbalance/types/model.unified.ts +++ b/packages/api/src/hris/timeoffbalance/types/model.unified.ts @@ -8,6 +8,14 @@ import { IsBoolean, } from 'class-validator'; +export type PolicyType = + | 'VACATION' + | 'SICK' + | 'PERSONAL' + | 'JURY_DUTY' + | 'VOLUNTEER' + | 'BEREAVEMENT'; + export class UnifiedHrisTimeoffbalanceInput { @ApiPropertyOptional({ type: Number, @@ -41,13 +49,21 @@ export class UnifiedHrisTimeoffbalanceInput { @ApiPropertyOptional({ type: String, - example: 'Vacation', + example: 'VACATION', + enum: [ + 'VACATION', + 'SICK', + 'PERSONAL', + 'JURY_DUTY', + 'VOLUNTEER', + 'BEREAVEMENT', + ], nullable: true, description: 'The type of time off policy', }) @IsString() @IsOptional() - policy_type?: string; + policy_type?: PolicyType | string; @ApiPropertyOptional({ type: Object, @@ -108,7 +124,7 @@ export class UnifiedHrisTimeoffbalanceOutput extends UnifiedHrisTimeoffbalanceIn }) @IsDateString() @IsOptional() - remote_created_at?: string; + remote_created_at?: Date; @ApiPropertyOptional({ type: String, @@ -118,7 +134,7 @@ export class UnifiedHrisTimeoffbalanceOutput extends UnifiedHrisTimeoffbalanceIn }) @IsDateString() @IsOptional() - created_at?: string; + created_at?: Date; @ApiPropertyOptional({ type: String, @@ -128,7 +144,7 @@ export class UnifiedHrisTimeoffbalanceOutput extends UnifiedHrisTimeoffbalanceIn }) @IsDateString() @IsOptional() - modified_at?: string; + modified_at?: Date; @ApiPropertyOptional({ type: Boolean, diff --git a/packages/api/src/hris/timesheetentry/services/registry.service.ts b/packages/api/src/hris/timesheetentry/services/registry.service.ts new file mode 100644 index 000000000..232f9b894 --- /dev/null +++ b/packages/api/src/hris/timesheetentry/services/registry.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { ITimesheetentryService } from '../types'; + +@Injectable() +export class ServiceRegistry { + private serviceMap: Map; + + constructor() { + this.serviceMap = new Map(); + } + + registerService(serviceKey: string, service: ITimesheetentryService) { + this.serviceMap.set(serviceKey, service); + } + + getService(integrationId: string): ITimesheetentryService { + const service = this.serviceMap.get(integrationId); + if (!service) { + throw new ReferenceError(); + } + return service; + } +} diff --git a/packages/api/src/hris/timesheetentry/services/timesheetentry.service.ts b/packages/api/src/hris/timesheetentry/services/timesheetentry.service.ts new file mode 100644 index 000000000..03f36b9c8 --- /dev/null +++ b/packages/api/src/hris/timesheetentry/services/timesheetentry.service.ts @@ -0,0 +1,262 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { ApiResponse } from '@@core/utils/types'; +import { throwTypedError } from '@@core/utils/errors'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { + UnifiedHrisTimesheetEntryInput, + UnifiedHrisTimesheetEntryOutput, +} from '../types/model.unified'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { ServiceRegistry } from './registry.service'; + +@Injectable() +export class TimesheetentryService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private webhook: WebhookService, + private fieldMappingService: FieldMappingService, + private serviceRegistry: ServiceRegistry, + ) { + this.logger.setContext(TimesheetentryService.name); + } + + async addTimesheetentry( + unifiedTimesheetentryData: UnifiedHrisTimesheetEntryInput, + connection_id: string, + project_id: string, + integrationId: string, + linkedUserId: string, + remote_data?: boolean, + ): Promise { + try { + const service = this.serviceRegistry.getService(integrationId); + const resp = await service.addTimesheetentry( + unifiedTimesheetentryData, + linkedUserId, + ); + + const savedTimesheetEntry = + await this.prisma.hris_timesheet_entries.create({ + data: { + id_hris_timesheet_entry: uuidv4(), + ...unifiedTimesheetentryData, + hours_worked: unifiedTimesheetentryData.hours_worked + ? BigInt(unifiedTimesheetentryData.hours_worked) + : null, + start_time: unifiedTimesheetentryData.start_time + ? new Date(unifiedTimesheetentryData.start_time) + : null, + end_time: unifiedTimesheetentryData.end_time + ? new Date(unifiedTimesheetentryData.end_time) + : null, + remote_id: resp.data.remote_id, + id_connection: connection_id, + created_at: new Date(), + modified_at: new Date(), + remote_created_at: resp.data.remote_created_at + ? new Date(resp.data.remote_created_at) + : null, + remote_was_deleted: false, + }, + }); + + const result: UnifiedHrisTimesheetEntryOutput = { + ...savedTimesheetEntry, + id: savedTimesheetEntry.id_hris_timesheet_entry, + hours_worked: Number(savedTimesheetEntry.hours_worked), + }; + + if (remote_data) { + result.remote_data = resp.data; + } + + return result; + } catch (error) { + throw error; + } + } + + async getTimesheetentry( + id_hris_timesheet_entry: string, + linkedUserId: string, + integrationId: string, + connectionId: string, + projectId: string, + remote_data?: boolean, + ): Promise { + try { + const timesheetEntry = + await this.prisma.hris_timesheet_entries.findUnique({ + where: { id_hris_timesheet_entry: id_hris_timesheet_entry }, + }); + + if (!timesheetEntry) { + throw new Error( + `Timesheet entry with ID ${id_hris_timesheet_entry} not found.`, + ); + } + + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: timesheetEntry.id_hris_timesheet_entry, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTimesheetEntry: UnifiedHrisTimesheetEntryOutput = { + id: timesheetEntry.id_hris_timesheet_entry, + hours_worked: timesheetEntry.hours_worked + ? Number(timesheetEntry.hours_worked) + : undefined, + start_time: timesheetEntry.start_time, + end_time: timesheetEntry.end_time, + employee_id: timesheetEntry.id_hris_employee, + remote_id: timesheetEntry.remote_id, + remote_created_at: timesheetEntry.remote_created_at, + created_at: timesheetEntry.created_at, + modified_at: timesheetEntry.modified_at, + remote_was_deleted: timesheetEntry.remote_was_deleted, + field_mappings: field_mappings, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { ressource_owner_id: timesheetEntry.id_hris_timesheet_entry }, + }); + unifiedTimesheetEntry.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.timesheetentry.pull', + method: 'GET', + url: '/hris/timesheetentry', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return unifiedTimesheetEntry; + } catch (error) { + throw error; + } + } + + async getTimesheetentrys( + connectionId: string, + projectId: string, + integrationId: string, + linkedUserId: string, + limit: number, + remote_data?: boolean, + cursor?: string, + ): Promise<{ + data: UnifiedHrisTimesheetEntryOutput[]; + next_cursor: string | null; + previous_cursor: string | null; + }> { + try { + const timesheetEntries = + await this.prisma.hris_timesheet_entries.findMany({ + take: limit + 1, + cursor: cursor ? { id_hris_timesheet_entry: cursor } : undefined, + where: { id_connection: connectionId }, + orderBy: { created_at: 'asc' }, + }); + + const hasNextPage = timesheetEntries.length > limit; + if (hasNextPage) timesheetEntries.pop(); + + const unifiedTimesheetEntries = await Promise.all( + timesheetEntries.map(async (timesheetEntry) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: timesheetEntry.id_hris_timesheet_entry, + }, + }, + include: { attribute: true }, + }); + + const field_mappings = Object.fromEntries( + values.map((value) => [value.attribute.slug, value.data]), + ); + + const unifiedTimesheetEntry: UnifiedHrisTimesheetEntryOutput = { + id: timesheetEntry.id_hris_timesheet_entry, + hours_worked: timesheetEntry.hours_worked + ? Number(timesheetEntry.hours_worked) + : undefined, + start_time: timesheetEntry.start_time, + end_time: timesheetEntry.end_time, + employee_id: timesheetEntry.id_hris_employee, + remote_id: timesheetEntry.remote_id, + remote_created_at: timesheetEntry.remote_created_at, + created_at: timesheetEntry.created_at, + modified_at: timesheetEntry.modified_at, + remote_was_deleted: timesheetEntry.remote_was_deleted, + field_mappings: field_mappings, + }; + + if (remote_data) { + const remoteDataRecord = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: timesheetEntry.id_hris_timesheet_entry, + }, + }); + unifiedTimesheetEntry.remote_data = remoteDataRecord + ? JSON.parse(remoteDataRecord.data) + : null; + } + + return unifiedTimesheetEntry; + }), + ); + + await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'hris.timesheetentry.pull', + method: 'GET', + url: '/hris/timesheetentrys', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + id_project: projectId, + id_connection: connectionId, + }, + }); + + return { + data: unifiedTimesheetEntries, + next_cursor: hasNextPage + ? timesheetEntries[timesheetEntries.length - 1] + .id_hris_timesheet_entry + : null, + previous_cursor: cursor ?? null, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/timesheetentry/sync/sync.processor.ts b/packages/api/src/hris/timesheetentry/sync/sync.processor.ts new file mode 100644 index 000000000..bb52e81c8 --- /dev/null +++ b/packages/api/src/hris/timesheetentry/sync/sync.processor.ts @@ -0,0 +1,19 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; +import { Queues } from '@@core/@core-services/queues/types'; + +@Processor(Queues.SYNC_JOBS_WORKER) +export class SyncProcessor { + constructor(private syncService: SyncService) {} + + @Process('hris-sync-timesheetentries') + async handleSyncTimesheetentries(job: Job) { + try { + console.log(`Processing queue -> hris-sync-timesheetentries ${job.id}`); + await this.syncService.kickstartSync(); + } catch (error) { + console.error('Error syncing hris timesheetentries', error); + } + } +} diff --git a/packages/api/src/hris/timesheetentry/sync/sync.service.ts b/packages/api/src/hris/timesheetentry/sync/sync.service.ts new file mode 100644 index 000000000..c35ad067c --- /dev/null +++ b/packages/api/src/hris/timesheetentry/sync/sync.service.ts @@ -0,0 +1,173 @@ +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { CoreSyncRegistry } from '@@core/@core-services/registries/core-sync.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { IBaseSync, SyncLinkedUserType } from '@@core/utils/types/interface'; +import { OriginalTimesheetentryOutput } from '@@core/utils/types/original/original.hris'; +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { Cron } from '@nestjs/schedule'; +import { HRIS_PROVIDERS } from '@panora/shared'; +import { hris_timesheet_entries as HrisTimesheetEntry } from '@prisma/client'; +import { v4 as uuidv4 } from 'uuid'; +import { ServiceRegistry } from '../services/registry.service'; +import { ITimesheetentryService } from '../types'; +import { UnifiedHrisTimesheetEntryOutput } from '../types/model.unified'; + +@Injectable() +export class SyncService implements OnModuleInit, IBaseSync { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private webhook: WebhookService, + private fieldMappingService: FieldMappingService, + private serviceRegistry: ServiceRegistry, + private coreUnification: CoreUnification, + private registry: CoreSyncRegistry, + private ingestService: IngestDataService, + ) { + this.logger.setContext(SyncService.name); + this.registry.registerService('hris', 'timesheetentry', this); + } + + async onModuleInit() { + // Initialization logic if needed + } + + @Cron('0 */8 * * *') // every 8 hours + async kickstartSync(user_id?: string) { + try { + this.logger.log('Syncing timesheet entries...'); + const users = user_id + ? [await this.prisma.users.findUnique({ where: { id_user: user_id } })] + : await this.prisma.users.findMany(); + + if (users && users.length > 0) { + for (const user of users) { + const projects = await this.prisma.projects.findMany({ + where: { id_user: user.id_user }, + }); + for (const project of projects) { + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { id_project: project.id_project }, + }); + for (const linkedUser of linkedUsers) { + for (const provider of HRIS_PROVIDERS) { + await this.syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUser.id_linked_user, + }); + } + } + } + } + } + } catch (error) { + throw error; + } + } + + async syncForLinkedUser(param: SyncLinkedUserType) { + try { + const { integrationId, linkedUserId } = param; + const service: ITimesheetentryService = + this.serviceRegistry.getService(integrationId); + if (!service) return; + + await this.ingestService.syncForLinkedUser< + UnifiedHrisTimesheetEntryOutput, + OriginalTimesheetentryOutput, + ITimesheetentryService + >(integrationId, linkedUserId, 'hris', 'timesheetentry', service, []); + } catch (error) { + throw error; + } + } + + async saveToDb( + connection_id: string, + linkedUserId: string, + timesheetEntries: UnifiedHrisTimesheetEntryOutput[], + originSource: string, + remote_data: Record[], + ): Promise { + try { + const timesheetEntryResults: HrisTimesheetEntry[] = []; + + for (let i = 0; i < timesheetEntries.length; i++) { + const timesheetEntry = timesheetEntries[i]; + const originId = timesheetEntry.remote_id; + + let existingTimesheetEntry = + await this.prisma.hris_timesheet_entries.findFirst({ + where: { + remote_id: originId, + id_connection: connection_id, + }, + }); + + const timesheetEntryData = { + hours_worked: timesheetEntry.hours_worked + ? BigInt(timesheetEntry.hours_worked) + : null, + start_time: timesheetEntry.start_time + ? new Date(timesheetEntry.start_time) + : null, + end_time: timesheetEntry.end_time + ? new Date(timesheetEntry.end_time) + : null, + id_hris_employee: timesheetEntry.employee_id, + remote_id: originId, + remote_created_at: timesheetEntry.remote_created_at + ? new Date(timesheetEntry.remote_created_at) + : null, + modified_at: new Date(), + remote_was_deleted: timesheetEntry.remote_was_deleted || false, + }; + + if (existingTimesheetEntry) { + existingTimesheetEntry = + await this.prisma.hris_timesheet_entries.update({ + where: { + id_hris_timesheet_entry: + existingTimesheetEntry.id_hris_timesheet_entry, + }, + data: timesheetEntryData, + }); + } else { + existingTimesheetEntry = + await this.prisma.hris_timesheet_entries.create({ + data: { + ...timesheetEntryData, + id_hris_timesheet_entry: uuidv4(), + created_at: new Date(), + id_connection: connection_id, + }, + }); + } + + timesheetEntryResults.push(existingTimesheetEntry); + + // Process field mappings + await this.ingestService.processFieldMappings( + timesheetEntry.field_mappings, + existingTimesheetEntry.id_hris_timesheet_entry, + originSource, + linkedUserId, + ); + + // Process remote data + await this.ingestService.processRemoteData( + existingTimesheetEntry.id_hris_timesheet_entry, + remote_data[i], + ); + } + + return timesheetEntryResults; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/hris/timesheetentry/timesheetentry.controller.ts b/packages/api/src/hris/timesheetentry/timesheetentry.controller.ts new file mode 100644 index 000000000..1551992ad --- /dev/null +++ b/packages/api/src/hris/timesheetentry/timesheetentry.controller.ts @@ -0,0 +1,175 @@ +import { + Controller, + Post, + Body, + Query, + Get, + Patch, + Param, + Headers, + UseGuards, +} from '@nestjs/common'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { + ApiBody, + ApiOperation, + ApiParam, + ApiQuery, + ApiTags, + ApiHeader, + //ApiKeyAuth, +} from '@nestjs/swagger'; + +import { TimesheetentryService } from './services/timesheetentry.service'; +import { + UnifiedHrisTimesheetEntryInput, + UnifiedHrisTimesheetEntryOutput, +} from './types/model.unified'; +import { ConnectionUtils } from '@@core/connections/@utils'; +import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { QueryDto } from '@@core/utils/dtos/query.dto'; +import { + ApiGetCustomResponse, + ApiPaginatedResponse, + ApiPostCustomResponse, +} from '@@core/utils/dtos/openapi.respone.dto'; + +@ApiTags('hris/timesheetentries') +@Controller('hris/timesheetentries') +export class TimesheetentryController { + constructor( + private readonly timesheetentryService: TimesheetentryService, + private logger: LoggerService, + private connectionUtils: ConnectionUtils, + ) { + this.logger.setContext(TimesheetentryController.name); + } + + @ApiOperation({ + operationId: 'listHrisTimesheetentries', + summary: 'List Timesheetentries', + }) + @ApiHeader({ + name: 'x-connection-token', + required: true, + description: 'The connection token', + example: 'b008e199-eda9-4629-bd41-a01b6195864a', + }) + @ApiPaginatedResponse(UnifiedHrisTimesheetEntryOutput) + @UseGuards(ApiKeyAuthGuard) + @Get() + async getTimesheetentrys( + @Headers('x-connection-token') connection_token: string, + @Query() query: QueryDto, + ) { + try { + const { linkedUserId, remoteSource, connectionId, projectId } = + await this.connectionUtils.getConnectionMetadataFromConnectionToken( + connection_token, + ); + const { remote_data, limit, cursor } = query; + return this.timesheetentryService.getTimesheetentrys( + connectionId, + projectId, + remoteSource, + linkedUserId, + limit, + remote_data, + cursor, + ); + } catch (error) { + throw new Error(error); + } + } + + @ApiOperation({ + operationId: 'retrieveHrisTimesheetentry', + summary: 'Retrieve Timesheetentry', + description: 'Retrieve an Timesheetentry from any connected Hris software', + }) + @ApiParam({ + name: 'id', + required: true, + type: String, + description: 'id of the timesheetentry you want to retrieve.', + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + }) + @ApiQuery({ + name: 'remote_data', + required: false, + type: Boolean, + description: 'Set to true to include data from the original Hris software.', + example: false, + }) + @ApiHeader({ + name: 'x-connection-token', + required: true, + description: 'The connection token', + example: 'b008e199-eda9-4629-bd41-a01b6195864a', + }) + @ApiGetCustomResponse(UnifiedHrisTimesheetEntryOutput) + @UseGuards(ApiKeyAuthGuard) + @Get(':id') + async retrieve( + @Headers('x-connection-token') connection_token: string, + @Param('id') id: string, + @Query('remote_data') remote_data?: boolean, + ) { + const { linkedUserId, remoteSource, connectionId, projectId } = + await this.connectionUtils.getConnectionMetadataFromConnectionToken( + connection_token, + ); + return this.timesheetentryService.getTimesheetentry( + id, + linkedUserId, + remoteSource, + connectionId, + projectId, + remote_data, + ); + } + + @ApiOperation({ + operationId: 'createHrisTimesheetentry', + summary: 'Create Timesheetentrys', + description: 'Create Timesheetentrys in any supported Hris software', + }) + @ApiHeader({ + name: 'x-connection-token', + required: true, + description: 'The connection token', + example: 'b008e199-eda9-4629-bd41-a01b6195864a', + }) + @ApiQuery({ + name: 'remote_data', + required: false, + type: Boolean, + description: 'Set to true to include data from the original Hris software.', + }) + @ApiBody({ type: UnifiedHrisTimesheetEntryInput }) + @ApiPostCustomResponse(UnifiedHrisTimesheetEntryOutput) + @UseGuards(ApiKeyAuthGuard) + @Post() + async addTimesheetentry( + @Body() unifiedTimesheetentryData: UnifiedHrisTimesheetEntryInput, + @Headers('x-connection-token') connection_token: string, + @Query('remote_data') remote_data?: boolean, + ) { + try { + const { linkedUserId, remoteSource, connectionId, projectId } = + await this.connectionUtils.getConnectionMetadataFromConnectionToken( + connection_token, + ); + return this.timesheetentryService.addTimesheetentry( + unifiedTimesheetentryData, + connectionId, + projectId, + remoteSource, + linkedUserId, + remote_data, + ); + } catch (error) { + throw new Error(error); + } + } +} diff --git a/packages/api/src/hris/timesheetentry/timesheetentry.module.ts b/packages/api/src/hris/timesheetentry/timesheetentry.module.ts new file mode 100644 index 000000000..120496854 --- /dev/null +++ b/packages/api/src/hris/timesheetentry/timesheetentry.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; +import { TimesheetentryController } from './timesheetentry.controller'; +import { ServiceRegistry } from './services/registry.service'; +import { TimesheetentryService } from './services/timesheetentry.service'; +import { SyncService } from './sync/sync.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; + +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; + +@Module({ + controllers: [TimesheetentryController], + providers: [ + TimesheetentryService, + CoreUnification, + SyncService, + WebhookService, + ServiceRegistry, + IngestDataService, + /* PROVIDERS SERVICES */ + ], + exports: [SyncService], +}) +export class TimesheetentryModule {} diff --git a/packages/api/src/hris/timesheetentry/types/index.ts b/packages/api/src/hris/timesheetentry/types/index.ts new file mode 100644 index 000000000..136dff390 --- /dev/null +++ b/packages/api/src/hris/timesheetentry/types/index.ts @@ -0,0 +1,38 @@ +import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; +import { + UnifiedHrisTimesheetEntryInput, + UnifiedHrisTimesheetEntryOutput, +} from './model.unified'; +import { OriginalTimesheetentryOutput } from '@@core/utils/types/original/original.hris'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; + +export interface ITimesheetentryService { + addTimesheetentry( + timesheetentryData: DesunifyReturnType, + linkedUserId: string, + ): Promise>; + + sync(data: SyncParam): Promise>; +} + +export interface ITimesheetentryMapper { + desunify( + source: UnifiedHrisTimesheetEntryInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): DesunifyReturnType; + + unify( + source: OriginalTimesheetentryOutput | OriginalTimesheetentryOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise< + UnifiedHrisTimesheetEntryOutput | UnifiedHrisTimesheetEntryOutput[] + >; +} diff --git a/packages/api/src/hris/timesheetentry/types/model.unified.ts b/packages/api/src/hris/timesheetentry/types/model.unified.ts new file mode 100644 index 000000000..5003c64f2 --- /dev/null +++ b/packages/api/src/hris/timesheetentry/types/model.unified.ts @@ -0,0 +1,137 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsUUID, + IsOptional, + IsString, + IsDateString, + IsBoolean, + IsNumber, +} from 'class-validator'; + +export class UnifiedHrisTimesheetEntryInput { + @ApiPropertyOptional({ + type: Number, + example: 40, + nullable: true, + description: 'The number of hours worked', + }) + @IsNumber() + @IsOptional() + hours_worked?: number; + + @ApiPropertyOptional({ + type: Date, + example: '2024-10-01T08:00:00Z', + nullable: true, + description: 'The start time of the timesheet entry', + }) + @IsOptional() + start_time?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-10-01T16:00:00Z', + nullable: true, + description: 'The end time of the timesheet entry', + }) + @IsOptional() + end_time?: Date; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the associated employee', + }) + @IsUUID() + @IsOptional() + employee_id?: string; + + @ApiPropertyOptional({ + type: Boolean, + example: false, + description: + 'Indicates if the timesheet entry was deleted in the remote system', + }) + @IsBoolean() + @IsOptional() + remote_was_deleted?: boolean; + + @ApiPropertyOptional({ + type: Object, + example: { + custom_field_1: 'value1', + custom_field_2: 'value2', + }, + nullable: true, + description: + 'The custom field mappings of the object between the remote 3rd party & Panora', + }) + @IsOptional() + field_mappings?: Record; +} + +export class UnifiedHrisTimesheetEntryOutput extends UnifiedHrisTimesheetEntryInput { + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the timesheet entry record', + }) + @IsUUID() + @IsOptional() + id?: string; + + @ApiPropertyOptional({ + type: String, + example: 'id_1', + nullable: true, + description: 'The remote ID of the timesheet entry', + }) + @IsString() + @IsOptional() + remote_id?: string; + + @ApiPropertyOptional({ + type: Date, + example: '2024-10-01T12:00:00Z', + nullable: true, + description: + 'The date when the timesheet entry was created in the remote system', + }) + @IsDateString() + @IsOptional() + remote_created_at?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-10-01T12:00:00Z', + description: 'The created date of the timesheet entry', + }) + @IsDateString() + @IsOptional() + created_at?: Date; + + @ApiPropertyOptional({ + type: Date, + example: '2024-10-01T12:00:00Z', + description: 'The last modified date of the timesheet entry', + }) + @IsDateString() + @IsOptional() + modified_at?: Date; + + @ApiPropertyOptional({ + type: Object, + example: { + raw_data: { + additional_field: 'some value', + }, + }, + nullable: true, + description: + 'The remote data of the timesheet entry in the context of the 3rd Party', + }) + @IsOptional() + remote_data?: Record; +} diff --git a/packages/api/src/hris/timesheetentry/utils/index.ts b/packages/api/src/hris/timesheetentry/utils/index.ts new file mode 100644 index 000000000..f849788c1 --- /dev/null +++ b/packages/api/src/hris/timesheetentry/utils/index.ts @@ -0,0 +1 @@ +/* PUT ALL UTILS FUNCTIONS USED IN YOUR OBJECT METHODS HERE */ diff --git a/packages/api/src/main.ts b/packages/api/src/main.ts index 5412ba5de..456f471da 100644 --- a/packages/api/src/main.ts +++ b/packages/api/src/main.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; import * as yaml from 'js-yaml'; import { Logger, LoggerErrorInterceptor } from 'nestjs-pino'; import { AppModule } from './app.module'; +import { generatePanoraParamsSpec } from '@@core/utils/decorators/utils'; function addSpeakeasyGroup(document: any) { for (const path in document.paths) { @@ -87,8 +88,11 @@ async function bootstrap() { ], }; document['x-speakeasy-name-override'] = - extendedSpecs['x-speakeasy-name-override']; // Add extended specs + extendedSpecs['x-speakeasy-name-override']; addSpeakeasyGroup(document); + + await generatePanoraParamsSpec(document); + useContainer(app.select(AppModule), { fallbackOnErrors: true }); SwaggerModule.setup('docs', app, document); diff --git a/packages/api/src/ticketing/comment/services/github/mappers.ts b/packages/api/src/ticketing/comment/services/github/mappers.ts index 683eb4d01..b7b0f3fff 100644 --- a/packages/api/src/ticketing/comment/services/github/mappers.ts +++ b/packages/api/src/ticketing/comment/services/github/mappers.ts @@ -7,99 +7,98 @@ import { Utils } from '@ticketing/@lib/@utils'; import { UnifiedTicketingAttachmentOutput } from '@ticketing/attachment/types/model.unified'; import { ICommentMapper } from '@ticketing/comment/types'; import { - UnifiedTicketingCommentInput, - UnifiedTicketingCommentOutput, + UnifiedTicketingCommentInput, + UnifiedTicketingCommentOutput, } from '@ticketing/comment/types/model.unified'; import { GithubCommentInput, GithubCommentOutput } from './types'; @Injectable() export class GithubCommentMapper implements ICommentMapper { - constructor( - private mappersRegistry: MappersRegistry, - private utils: Utils, - private coreUnificationService: CoreUnification, - ) { - this.mappersRegistry.registerService( - 'ticketing', - 'comment', - 'github', - this, - ); - } + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService( + 'ticketing', + 'comment', + 'github', + this, + ); + } - async desunify( - source: UnifiedTicketingCommentInput, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): Promise { - // project_id and issue_id will be extracted and used so We do not need to set user (author) field here + async desunify( + source: UnifiedTicketingCommentInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + // project_id and issue_id will be extracted and used so We do not need to set user (author) field here - // TODO - Add attachments attribute + // TODO - Add attachments attribute - const result: GithubCommentInput = { - body: source.body, - }; - return result; - } + const result: GithubCommentInput = { + body: source.body, + }; + return result; + } - async unify( - source: GithubCommentOutput | GithubCommentOutput[], - connectionId: string, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): Promise { - if (!Array.isArray(source)) { - return await this.mapSingleCommentToUnified( - source, - connectionId, - customFieldMappings, - ); - } - return Promise.all( - source.map((comment) => - this.mapSingleCommentToUnified( - comment, - connectionId, - customFieldMappings, - ), - ), - ); + async unify( + source: GithubCommentOutput | GithubCommentOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + if (!Array.isArray(source)) { + return await this.mapSingleCommentToUnified( + source, + connectionId, + customFieldMappings, + ); } + return Promise.all( + source.map((comment) => + this.mapSingleCommentToUnified( + comment, + connectionId, + customFieldMappings, + ), + ), + ); + } - private async mapSingleCommentToUnified( - comment: GithubCommentOutput, - connectionId: string, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): Promise { - let opts: any = {}; - - // Here Github represent Attachment as URL in body of comment as Markdown so we do not have to store in attachement unified object. + private async mapSingleCommentToUnified( + comment: GithubCommentOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + let opts: any = {}; + // Here Github represent Attachment as URL in body of comment as Markdown so we do not have to store in attachement unified object. - if (comment.user.id) { - const user_id = await this.utils.getUserUuidFromRemoteId( - String(comment.user.id), - connectionId, - ); - if (user_id) { - opts = { ...opts, user_id }; - } - } - // GithubCommentOutput does not contain id of issue that it is assciated - - return { - remote_id: String(comment.id), - remote_data: comment, - body: comment.body || null, - creator_type: 'USER', - ...opts, - }; + if (comment.user.id) { + const user_id = await this.utils.getUserUuidFromRemoteId( + String(comment.user.id), + connectionId, + ); + if (user_id) { + opts = { ...opts, user_id }; + } } + // GithubCommentOutput does not contain id of issue that it is assciated + + return { + remote_id: String(comment.id), + remote_data: comment, + body: comment.body || null, + creator_type: 'USER', + ...opts, + }; + } } diff --git a/packages/api/swagger/swagger-spec.yaml b/packages/api/swagger/swagger-spec.yaml index 4e4daddfa..4aa2386a2 100644 --- a/packages/api/swagger/swagger-spec.yaml +++ b/packages/api/swagger/swagger-spec.yaml @@ -3602,6 +3602,130 @@ paths: $ref: '#/components/schemas/UnifiedHrisTimeoffbalanceOutput' tags: *ref_35 x-speakeasy-group: hris.timeoffbalances + /hris/timesheetentries: + get: + operationId: listHrisTimesheetentries + summary: List Timesheetentries + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisTimesheetEntryOutput' + tags: &ref_36 + - hris/timesheetentries + x-speakeasy-group: hris.timesheetentries + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createHrisTimesheetentry + summary: Create Timesheetentrys + description: Create Timesheetentrys in any supported Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimesheetEntryInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimesheetEntryOutput' + tags: *ref_36 + x-speakeasy-group: hris.timesheetentries + /hris/timesheetentries/{id}: + get: + operationId: retrieveHrisTimesheetentry + summary: Retrieve Timesheetentry + description: Retrieve an Timesheetentry from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the timesheetentry you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimesheetEntryOutput' + tags: *ref_36 + x-speakeasy-group: hris.timesheetentries /marketingautomation/actions: get: operationId: listMarketingautomationAction @@ -3649,7 +3773,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationActionOutput - tags: &ref_36 + tags: &ref_37 - marketingautomation/actions x-speakeasy-group: marketingautomation.actions x-speakeasy-pagination: @@ -3693,7 +3817,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationActionOutput' - tags: *ref_36 + tags: *ref_37 x-speakeasy-group: marketingautomation.actions /marketingautomation/actions/{id}: get: @@ -3730,7 +3854,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationActionOutput' - tags: *ref_36 + tags: *ref_37 x-speakeasy-group: marketingautomation.actions /marketingautomation/automations: get: @@ -3779,7 +3903,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationAutomationOutput - tags: &ref_37 + tags: &ref_38 - marketingautomation/automations x-speakeasy-group: marketingautomation.automations x-speakeasy-pagination: @@ -3824,7 +3948,7 @@ paths: schema: $ref: >- #/components/schemas/UnifiedMarketingautomationAutomationOutput - tags: *ref_37 + tags: *ref_38 x-speakeasy-group: marketingautomation.automations /marketingautomation/automations/{id}: get: @@ -3862,7 +3986,7 @@ paths: schema: $ref: >- #/components/schemas/UnifiedMarketingautomationAutomationOutput - tags: *ref_37 + tags: *ref_38 x-speakeasy-group: marketingautomation.automations /marketingautomation/campaigns: get: @@ -3911,7 +4035,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationCampaignOutput - tags: &ref_38 + tags: &ref_39 - marketingautomation/campaigns x-speakeasy-group: marketingautomation.campaigns x-speakeasy-pagination: @@ -3955,7 +4079,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationCampaignOutput' - tags: *ref_38 + tags: *ref_39 x-speakeasy-group: marketingautomation.campaigns /marketingautomation/campaigns/{id}: get: @@ -3992,7 +4116,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationCampaignOutput' - tags: *ref_38 + tags: *ref_39 x-speakeasy-group: marketingautomation.campaigns /marketingautomation/contacts: get: @@ -4041,7 +4165,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationContactOutput - tags: &ref_39 + tags: &ref_40 - marketingautomation/contacts x-speakeasy-group: marketingautomation.contacts x-speakeasy-pagination: @@ -4085,7 +4209,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationContactOutput' - tags: *ref_39 + tags: *ref_40 x-speakeasy-group: marketingautomation.contacts /marketingautomation/contacts/{id}: get: @@ -4122,7 +4246,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationContactOutput' - tags: *ref_39 + tags: *ref_40 x-speakeasy-group: marketingautomation.contacts /marketingautomation/emails: get: @@ -4171,7 +4295,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationEmailOutput - tags: &ref_40 + tags: &ref_41 - marketingautomation/emails x-speakeasy-group: marketingautomation.emails x-speakeasy-pagination: @@ -4217,7 +4341,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationEmailOutput' - tags: *ref_40 + tags: *ref_41 x-speakeasy-group: marketingautomation.emails /marketingautomation/events: get: @@ -4266,7 +4390,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationEventOutput - tags: &ref_41 + tags: &ref_42 - marketingautomation/events x-speakeasy-group: marketingautomation.events x-speakeasy-pagination: @@ -4312,7 +4436,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationEventOutput' - tags: *ref_41 + tags: *ref_42 x-speakeasy-group: marketingautomation.events /marketingautomation/lists: get: @@ -4361,7 +4485,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationListOutput - tags: &ref_42 + tags: &ref_43 - marketingautomation/lists x-speakeasy-group: marketingautomation.lists x-speakeasy-pagination: @@ -4404,7 +4528,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationListOutput' - tags: *ref_42 + tags: *ref_43 x-speakeasy-group: marketingautomation.lists /marketingautomation/lists/{id}: get: @@ -4441,7 +4565,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationListOutput' - tags: *ref_42 + tags: *ref_43 x-speakeasy-group: marketingautomation.lists /marketingautomation/messages: get: @@ -4490,7 +4614,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationMessageOutput - tags: &ref_43 + tags: &ref_44 - marketingautomation/messages x-speakeasy-group: marketingautomation.messages x-speakeasy-pagination: @@ -4536,7 +4660,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationMessageOutput' - tags: *ref_43 + tags: *ref_44 x-speakeasy-group: marketingautomation.messages /marketingautomation/templates: get: @@ -4585,7 +4709,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationTemplateOutput - tags: &ref_44 + tags: &ref_45 - marketingautomation/templates x-speakeasy-group: marketingautomation.templates x-speakeasy-pagination: @@ -4628,7 +4752,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationTemplateOutput' - tags: *ref_44 + tags: *ref_45 x-speakeasy-group: marketingautomation.templates /marketingautomation/templates/{id}: get: @@ -4665,7 +4789,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationTemplateOutput' - tags: *ref_44 + tags: *ref_45 x-speakeasy-group: marketingautomation.templates /marketingautomation/users: get: @@ -4714,7 +4838,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedMarketingautomationUserOutput - tags: &ref_45 + tags: &ref_46 - marketingautomation/users x-speakeasy-group: marketingautomation.users x-speakeasy-pagination: @@ -4760,7 +4884,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedMarketingautomationUserOutput' - tags: *ref_45 + tags: *ref_46 x-speakeasy-group: marketingautomation.users /ats/activities: get: @@ -4808,7 +4932,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsActivityOutput' - tags: &ref_46 + tags: &ref_47 - ats/activities x-speakeasy-group: ats.activities x-speakeasy-pagination: @@ -4850,7 +4974,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsActivityOutput' - tags: *ref_46 + tags: *ref_47 x-speakeasy-group: ats.activities /ats/activities/{id}: get: @@ -4885,7 +5009,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsActivityOutput' - tags: *ref_46 + tags: *ref_47 x-speakeasy-group: ats.activities /ats/applications: get: @@ -4933,7 +5057,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsApplicationOutput' - tags: &ref_47 + tags: &ref_48 - ats/applications x-speakeasy-group: ats.applications x-speakeasy-pagination: @@ -4975,7 +5099,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsApplicationOutput' - tags: *ref_47 + tags: *ref_48 x-speakeasy-group: ats.applications /ats/applications/{id}: get: @@ -5010,7 +5134,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsApplicationOutput' - tags: *ref_47 + tags: *ref_48 x-speakeasy-group: ats.applications /ats/attachments: get: @@ -5058,7 +5182,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' - tags: &ref_48 + tags: &ref_49 - ats/attachments x-speakeasy-group: ats.attachments x-speakeasy-pagination: @@ -5100,7 +5224,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' - tags: *ref_48 + tags: *ref_49 x-speakeasy-group: ats.attachments /ats/attachments/{id}: get: @@ -5135,7 +5259,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' - tags: *ref_48 + tags: *ref_49 x-speakeasy-group: ats.attachments /ats/candidates: get: @@ -5183,7 +5307,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsCandidateOutput' - tags: &ref_49 + tags: &ref_50 - ats/candidates x-speakeasy-group: ats.candidates x-speakeasy-pagination: @@ -5225,7 +5349,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsCandidateOutput' - tags: *ref_49 + tags: *ref_50 x-speakeasy-group: ats.candidates /ats/candidates/{id}: get: @@ -5260,7 +5384,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsCandidateOutput' - tags: *ref_49 + tags: *ref_50 x-speakeasy-group: ats.candidates /ats/departments: get: @@ -5308,7 +5432,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsDepartmentOutput' - tags: &ref_50 + tags: &ref_51 - ats/departments x-speakeasy-group: ats.departments x-speakeasy-pagination: @@ -5352,7 +5476,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsDepartmentOutput' - tags: *ref_50 + tags: *ref_51 x-speakeasy-group: ats.departments /ats/interviews: get: @@ -5400,7 +5524,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsInterviewOutput' - tags: &ref_51 + tags: &ref_52 - ats/interviews x-speakeasy-group: ats.interviews x-speakeasy-pagination: @@ -5442,7 +5566,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsInterviewOutput' - tags: *ref_51 + tags: *ref_52 x-speakeasy-group: ats.interviews /ats/interviews/{id}: get: @@ -5477,7 +5601,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsInterviewOutput' - tags: *ref_51 + tags: *ref_52 x-speakeasy-group: ats.interviews /ats/jobinterviewstages: get: @@ -5526,7 +5650,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAtsJobinterviewstageOutput - tags: &ref_52 + tags: &ref_53 - ats/jobinterviewstages x-speakeasy-group: ats.jobinterviewstages x-speakeasy-pagination: @@ -5570,7 +5694,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsJobinterviewstageOutput' - tags: *ref_52 + tags: *ref_53 x-speakeasy-group: ats.jobinterviewstages /ats/jobs: get: @@ -5618,7 +5742,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsJobOutput' - tags: &ref_53 + tags: &ref_54 - ats/jobs x-speakeasy-group: ats.jobs x-speakeasy-pagination: @@ -5662,7 +5786,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsJobOutput' - tags: *ref_53 + tags: *ref_54 x-speakeasy-group: ats.jobs /ats/offers: get: @@ -5710,7 +5834,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsOfferOutput' - tags: &ref_54 + tags: &ref_55 - ats/offers x-speakeasy-group: ats.offers x-speakeasy-pagination: @@ -5754,7 +5878,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsOfferOutput' - tags: *ref_54 + tags: *ref_55 x-speakeasy-group: ats.offers /ats/offices: get: @@ -5802,7 +5926,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsOfficeOutput' - tags: &ref_55 + tags: &ref_56 - ats/offices x-speakeasy-group: ats.offices x-speakeasy-pagination: @@ -5846,7 +5970,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsOfficeOutput' - tags: *ref_55 + tags: *ref_56 x-speakeasy-group: ats.offices /ats/rejectreasons: get: @@ -5894,7 +6018,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsRejectreasonOutput' - tags: &ref_56 + tags: &ref_57 - ats/rejectreasons x-speakeasy-group: ats.rejectreasons x-speakeasy-pagination: @@ -5938,7 +6062,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsRejectreasonOutput' - tags: *ref_56 + tags: *ref_57 x-speakeasy-group: ats.rejectreasons /ats/scorecards: get: @@ -5986,7 +6110,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsScorecardOutput' - tags: &ref_57 + tags: &ref_58 - ats/scorecards x-speakeasy-group: ats.scorecards x-speakeasy-pagination: @@ -6030,7 +6154,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsScorecardOutput' - tags: *ref_57 + tags: *ref_58 x-speakeasy-group: ats.scorecards /ats/tags: get: @@ -6078,7 +6202,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsTagOutput' - tags: &ref_58 + tags: &ref_59 - ats/tags x-speakeasy-group: ats.tags x-speakeasy-pagination: @@ -6122,7 +6246,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsTagOutput' - tags: *ref_58 + tags: *ref_59 x-speakeasy-group: ats.tags /ats/users: get: @@ -6170,7 +6294,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsUserOutput' - tags: &ref_59 + tags: &ref_60 - ats/users x-speakeasy-group: ats.users x-speakeasy-pagination: @@ -6214,7 +6338,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsUserOutput' - tags: *ref_59 + tags: *ref_60 x-speakeasy-group: ats.users /ats/eeocs: get: @@ -6262,7 +6386,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAtsEeocsOutput' - tags: &ref_60 + tags: &ref_61 - ats/eeocs x-speakeasy-group: ats.eeocs x-speakeasy-pagination: @@ -6304,7 +6428,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAtsEeocsOutput' - tags: *ref_60 + tags: *ref_61 x-speakeasy-group: ats.eeocs /accounting/accounts: get: @@ -6352,7 +6476,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAccountingAccountOutput' - tags: &ref_61 + tags: &ref_62 - accounting/accounts x-speakeasy-group: accounting.accounts x-speakeasy-pagination: @@ -6394,7 +6518,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingAccountOutput' - tags: *ref_61 + tags: *ref_62 x-speakeasy-group: accounting.accounts /accounting/accounts/{id}: get: @@ -6429,7 +6553,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingAccountOutput' - tags: *ref_61 + tags: *ref_62 x-speakeasy-group: accounting.accounts /accounting/addresses: get: @@ -6477,7 +6601,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAccountingAddressOutput' - tags: &ref_62 + tags: &ref_63 - accounting/addresses x-speakeasy-group: accounting.addresses x-speakeasy-pagination: @@ -6521,7 +6645,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingAddressOutput' - tags: *ref_62 + tags: *ref_63 x-speakeasy-group: accounting.addresses /accounting/attachments: get: @@ -6570,7 +6694,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingAttachmentOutput - tags: &ref_63 + tags: &ref_64 - accounting/attachments x-speakeasy-group: accounting.attachments x-speakeasy-pagination: @@ -6612,7 +6736,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingAttachmentOutput' - tags: *ref_63 + tags: *ref_64 x-speakeasy-group: accounting.attachments /accounting/attachments/{id}: get: @@ -6647,7 +6771,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingAttachmentOutput' - tags: *ref_63 + tags: *ref_64 x-speakeasy-group: accounting.attachments /accounting/balancesheets: get: @@ -6696,7 +6820,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingBalancesheetOutput - tags: &ref_64 + tags: &ref_65 - accounting/balancesheets x-speakeasy-group: accounting.balancesheets x-speakeasy-pagination: @@ -6740,7 +6864,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingBalancesheetOutput' - tags: *ref_64 + tags: *ref_65 x-speakeasy-group: accounting.balancesheets /accounting/cashflowstatements: get: @@ -6789,7 +6913,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingCashflowstatementOutput - tags: &ref_65 + tags: &ref_66 - accounting/cashflowstatements x-speakeasy-group: accounting.cashflowstatements x-speakeasy-pagination: @@ -6833,7 +6957,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingCashflowstatementOutput' - tags: *ref_65 + tags: *ref_66 x-speakeasy-group: accounting.cashflowstatements /accounting/companyinfos: get: @@ -6882,7 +7006,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingCompanyinfoOutput - tags: &ref_66 + tags: &ref_67 - accounting/companyinfos x-speakeasy-group: accounting.companyinfos x-speakeasy-pagination: @@ -6926,7 +7050,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingCompanyinfoOutput' - tags: *ref_66 + tags: *ref_67 x-speakeasy-group: accounting.companyinfos /accounting/contacts: get: @@ -6974,7 +7098,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAccountingContactOutput' - tags: &ref_67 + tags: &ref_68 - accounting/contacts x-speakeasy-group: accounting.contacts x-speakeasy-pagination: @@ -7016,7 +7140,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingContactOutput' - tags: *ref_67 + tags: *ref_68 x-speakeasy-group: accounting.contacts /accounting/contacts/{id}: get: @@ -7051,7 +7175,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingContactOutput' - tags: *ref_67 + tags: *ref_68 x-speakeasy-group: accounting.contacts /accounting/creditnotes: get: @@ -7100,7 +7224,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingCreditnoteOutput - tags: &ref_68 + tags: &ref_69 - accounting/creditnotes x-speakeasy-group: accounting.creditnotes x-speakeasy-pagination: @@ -7144,7 +7268,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingCreditnoteOutput' - tags: *ref_68 + tags: *ref_69 x-speakeasy-group: accounting.creditnotes /accounting/expenses: get: @@ -7192,7 +7316,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAccountingExpenseOutput' - tags: &ref_69 + tags: &ref_70 - accounting/expenses x-speakeasy-group: accounting.expenses x-speakeasy-pagination: @@ -7234,7 +7358,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingExpenseOutput' - tags: *ref_69 + tags: *ref_70 x-speakeasy-group: accounting.expenses /accounting/expenses/{id}: get: @@ -7269,7 +7393,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingExpenseOutput' - tags: *ref_69 + tags: *ref_70 x-speakeasy-group: accounting.expenses /accounting/incomestatements: get: @@ -7318,7 +7442,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingIncomestatementOutput - tags: &ref_70 + tags: &ref_71 - accounting/incomestatements x-speakeasy-group: accounting.incomestatements x-speakeasy-pagination: @@ -7362,7 +7486,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingIncomestatementOutput' - tags: *ref_70 + tags: *ref_71 x-speakeasy-group: accounting.incomestatements /accounting/invoices: get: @@ -7410,7 +7534,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAccountingInvoiceOutput' - tags: &ref_71 + tags: &ref_72 - accounting/invoices x-speakeasy-group: accounting.invoices x-speakeasy-pagination: @@ -7452,7 +7576,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingInvoiceOutput' - tags: *ref_71 + tags: *ref_72 x-speakeasy-group: accounting.invoices /accounting/invoices/{id}: get: @@ -7487,7 +7611,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingInvoiceOutput' - tags: *ref_71 + tags: *ref_72 x-speakeasy-group: accounting.invoices /accounting/items: get: @@ -7535,7 +7659,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAccountingItemOutput' - tags: &ref_72 + tags: &ref_73 - accounting/items x-speakeasy-group: accounting.items x-speakeasy-pagination: @@ -7579,7 +7703,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingItemOutput' - tags: *ref_72 + tags: *ref_73 x-speakeasy-group: accounting.items /accounting/journalentries: get: @@ -7628,7 +7752,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingJournalentryOutput - tags: &ref_73 + tags: &ref_74 - accounting/journalentries x-speakeasy-group: accounting.journalentries x-speakeasy-pagination: @@ -7670,7 +7794,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingJournalentryOutput' - tags: *ref_73 + tags: *ref_74 x-speakeasy-group: accounting.journalentries /accounting/journalentries/{id}: get: @@ -7705,7 +7829,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingJournalentryOutput' - tags: *ref_73 + tags: *ref_74 x-speakeasy-group: accounting.journalentries /accounting/payments: get: @@ -7753,7 +7877,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAccountingPaymentOutput' - tags: &ref_74 + tags: &ref_75 - accounting/payments x-speakeasy-group: accounting.payments x-speakeasy-pagination: @@ -7795,7 +7919,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingPaymentOutput' - tags: *ref_74 + tags: *ref_75 x-speakeasy-group: accounting.payments /accounting/payments/{id}: get: @@ -7830,7 +7954,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingPaymentOutput' - tags: *ref_74 + tags: *ref_75 x-speakeasy-group: accounting.payments /accounting/phonenumbers: get: @@ -7879,7 +8003,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingPhonenumberOutput - tags: &ref_75 + tags: &ref_76 - accounting/phonenumbers x-speakeasy-group: accounting.phonenumbers x-speakeasy-pagination: @@ -7923,7 +8047,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingPhonenumberOutput' - tags: *ref_75 + tags: *ref_76 x-speakeasy-group: accounting.phonenumbers /accounting/purchaseorders: get: @@ -7972,7 +8096,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingPurchaseorderOutput - tags: &ref_76 + tags: &ref_77 - accounting/purchaseorders x-speakeasy-group: accounting.purchaseorders x-speakeasy-pagination: @@ -8014,7 +8138,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingPurchaseorderOutput' - tags: *ref_76 + tags: *ref_77 x-speakeasy-group: accounting.purchaseorders /accounting/purchaseorders/{id}: get: @@ -8049,7 +8173,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingPurchaseorderOutput' - tags: *ref_76 + tags: *ref_77 x-speakeasy-group: accounting.purchaseorders /accounting/taxrates: get: @@ -8097,7 +8221,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedAccountingTaxrateOutput' - tags: &ref_77 + tags: &ref_78 - accounting/taxrates x-speakeasy-group: accounting.taxrates x-speakeasy-pagination: @@ -8141,7 +8265,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingTaxrateOutput' - tags: *ref_77 + tags: *ref_78 x-speakeasy-group: accounting.taxrates /accounting/trackingcategories: get: @@ -8190,7 +8314,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingTrackingcategoryOutput - tags: &ref_78 + tags: &ref_79 - accounting/trackingcategories x-speakeasy-group: accounting.trackingcategories x-speakeasy-pagination: @@ -8234,7 +8358,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingTrackingcategoryOutput' - tags: *ref_78 + tags: *ref_79 x-speakeasy-group: accounting.trackingcategories /accounting/transactions: get: @@ -8283,7 +8407,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingTransactionOutput - tags: &ref_79 + tags: &ref_80 - accounting/transactions x-speakeasy-group: accounting.transactions x-speakeasy-pagination: @@ -8327,7 +8451,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingTransactionOutput' - tags: *ref_79 + tags: *ref_80 x-speakeasy-group: accounting.transactions /accounting/vendorcredits: get: @@ -8376,7 +8500,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedAccountingVendorcreditOutput - tags: &ref_80 + tags: &ref_81 - accounting/vendorcredits x-speakeasy-group: accounting.vendorcredits x-speakeasy-pagination: @@ -8420,7 +8544,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedAccountingVendorcreditOutput' - tags: *ref_80 + tags: *ref_81 x-speakeasy-group: accounting.vendorcredits /filestorage/drives: get: @@ -8468,7 +8592,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedFilestorageDriveOutput' - tags: &ref_81 + tags: &ref_82 - filestorage/drives x-speakeasy-group: filestorage.drives x-speakeasy-pagination: @@ -8512,7 +8636,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedFilestorageDriveOutput' - tags: *ref_81 + tags: *ref_82 x-speakeasy-group: filestorage.drives /filestorage/files: get: @@ -8560,7 +8684,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedFilestorageFileOutput' - tags: &ref_82 + tags: &ref_83 - filestorage/files x-speakeasy-group: filestorage.files x-speakeasy-pagination: @@ -8602,7 +8726,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedFilestorageFileOutput' - tags: *ref_82 + tags: *ref_83 x-speakeasy-group: filestorage.files /filestorage/files/{id}: get: @@ -8637,7 +8761,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedFilestorageFileOutput' - tags: *ref_82 + tags: *ref_83 x-speakeasy-group: filestorage.files /filestorage/folders: get: @@ -8685,7 +8809,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedFilestorageFolderOutput' - tags: &ref_83 + tags: &ref_84 - filestorage/folders x-speakeasy-group: filestorage.folders x-speakeasy-pagination: @@ -8727,7 +8851,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedFilestorageFolderOutput' - tags: *ref_83 + tags: *ref_84 x-speakeasy-group: filestorage.folders /filestorage/folders/{id}: get: @@ -8762,7 +8886,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedFilestorageFolderOutput' - tags: *ref_83 + tags: *ref_84 x-speakeasy-group: filestorage.folders /filestorage/groups: get: @@ -8810,7 +8934,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedFilestorageGroupOutput' - tags: &ref_84 + tags: &ref_85 - filestorage/groups x-speakeasy-group: filestorage.groups x-speakeasy-pagination: @@ -8854,7 +8978,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedFilestorageGroupOutput' - tags: *ref_84 + tags: *ref_85 x-speakeasy-group: filestorage.groups /filestorage/users: get: @@ -8902,7 +9026,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedFilestorageUserOutput' - tags: &ref_85 + tags: &ref_86 - filestorage/users x-speakeasy-group: filestorage.users x-speakeasy-pagination: @@ -8946,7 +9070,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedFilestorageUserOutput' - tags: *ref_85 + tags: *ref_86 x-speakeasy-group: filestorage.users /ecommerce/products: get: @@ -8994,7 +9118,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedEcommerceProductOutput' - tags: &ref_86 + tags: &ref_87 - ecommerce/products x-speakeasy-group: ecommerce.products x-speakeasy-pagination: @@ -9036,7 +9160,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedEcommerceProductOutput' - tags: *ref_86 + tags: *ref_87 x-speakeasy-group: ecommerce.products /ecommerce/products/{id}: get: @@ -9069,7 +9193,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedEcommerceProductOutput' - tags: *ref_86 + tags: *ref_87 x-speakeasy-group: ecommerce.products /ecommerce/orders: get: @@ -9117,7 +9241,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedEcommerceOrderOutput' - tags: &ref_87 + tags: &ref_88 - ecommerce/orders x-speakeasy-group: ecommerce.orders x-speakeasy-pagination: @@ -9159,7 +9283,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedEcommerceOrderOutput' - tags: *ref_87 + tags: *ref_88 x-speakeasy-group: ecommerce.orders /ecommerce/orders/{id}: get: @@ -9192,7 +9316,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedEcommerceOrderOutput' - tags: *ref_87 + tags: *ref_88 x-speakeasy-group: ecommerce.orders /ecommerce/customers: get: @@ -9240,7 +9364,7 @@ paths: type: array items: $ref: '#/components/schemas/UnifiedEcommerceCustomerOutput' - tags: &ref_88 + tags: &ref_89 - ecommerce/customers x-speakeasy-group: ecommerce.customers x-speakeasy-pagination: @@ -9282,7 +9406,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedEcommerceCustomerOutput' - tags: *ref_88 + tags: *ref_89 x-speakeasy-group: ecommerce.customers /ecommerce/fulfillments: get: @@ -9331,7 +9455,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedEcommerceFulfillmentOutput - tags: &ref_89 + tags: &ref_90 - ecommerce/fulfillments x-speakeasy-group: ecommerce.fulfillments x-speakeasy-pagination: @@ -9373,7 +9497,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedEcommerceFulfillmentOutput' - tags: *ref_89 + tags: *ref_90 x-speakeasy-group: ecommerce.fulfillments /ticketing/attachments: get: @@ -9422,7 +9546,7 @@ paths: items: $ref: >- #/components/schemas/UnifiedTicketingAttachmentOutput - tags: &ref_90 + tags: &ref_91 - ticketing/attachments x-speakeasy-group: ticketing.attachments x-speakeasy-pagination: @@ -9463,7 +9587,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedTicketingAttachmentOutput' - tags: *ref_90 + tags: *ref_91 x-speakeasy-group: ticketing.attachments /ticketing/attachments/{id}: get: @@ -9498,7 +9622,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UnifiedTicketingAttachmentOutput' - tags: *ref_90 + tags: *ref_91 x-speakeasy-group: ticketing.attachments info: title: Panora API @@ -9752,7 +9876,7 @@ components: type: string nullable: true example: USER - enum: &ref_120 + enum: &ref_121 - USER - CONTACT description: >- @@ -9779,12 +9903,12 @@ components: specified) attachments: type: array - items: &ref_121 + items: &ref_122 oneOf: - type: string - $ref: '#/components/schemas/UnifiedTicketingAttachmentOutput' nullable: true - example: &ref_122 + example: &ref_123 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f description: The attachements UUIDs tied to the comment required: @@ -9800,7 +9924,7 @@ components: status: type: string example: OPEN - enum: &ref_91 + enum: &ref_92 - OPEN - CLOSED nullable: true @@ -9819,7 +9943,7 @@ components: type: type: string example: BUG - enum: &ref_92 + enum: &ref_93 - BUG - SUBTASK - TASK @@ -9835,21 +9959,21 @@ components: description: The UUID of the parent ticket collections: type: array - items: &ref_93 + items: &ref_94 oneOf: - type: string - $ref: '#/components/schemas/UnifiedTicketingCollectionOutput' - example: &ref_94 + example: &ref_95 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The collection UUIDs the ticket belongs to tags: type: array - items: &ref_95 + items: &ref_96 oneOf: - type: string - $ref: '#/components/schemas/UnifiedTicketingTagOutput' - example: &ref_96 + example: &ref_97 - my_tag - urgent_tag nullable: true @@ -9863,7 +9987,7 @@ components: priority: type: string example: HIGH - enum: &ref_97 + enum: &ref_98 - HIGH - MEDIUM - LOW @@ -9872,7 +9996,7 @@ components: The priority of the ticket. Authorized values are HIGH, MEDIUM or LOW. assigned_to: - example: &ref_98 + example: &ref_99 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The users UUIDs the ticket is assigned to @@ -9880,7 +10004,7 @@ components: items: type: string comment: - example: &ref_99 + example: &ref_100 content: Assigned the issue ! nullable: true description: The comment of the ticket @@ -9898,17 +10022,17 @@ components: description: The UUID of the contact which the ticket belongs to attachments: type: array - items: &ref_100 + items: &ref_101 oneOf: - type: string - $ref: '#/components/schemas/UnifiedTicketingAttachmentInput' - example: &ref_101 + example: &ref_102 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f description: The attachements UUIDs tied to the ticket nullable: true field_mappings: type: object - example: &ref_102 + example: &ref_103 fav_dish: broccoli fav_color: red nullable: true @@ -9961,7 +10085,7 @@ components: status: type: string example: OPEN - enum: *ref_91 + enum: *ref_92 nullable: true description: The status of the ticket. Authorized values are OPEN or CLOSED. description: @@ -9978,7 +10102,7 @@ components: type: type: string example: BUG - enum: *ref_92 + enum: *ref_93 nullable: true description: >- The type of the ticket. Authorized values are PROBLEM, QUESTION, or @@ -9990,14 +10114,14 @@ components: description: The UUID of the parent ticket collections: type: array - items: *ref_93 - example: *ref_94 + items: *ref_94 + example: *ref_95 nullable: true description: The collection UUIDs the ticket belongs to tags: type: array - items: *ref_95 - example: *ref_96 + items: *ref_96 + example: *ref_97 nullable: true description: The tags names of the ticket completed_at: @@ -10009,20 +10133,20 @@ components: priority: type: string example: HIGH - enum: *ref_97 + enum: *ref_98 nullable: true description: >- The priority of the ticket. Authorized values are HIGH, MEDIUM or LOW. assigned_to: - example: *ref_98 + example: *ref_99 nullable: true description: The users UUIDs the ticket is assigned to type: array items: type: string comment: - example: *ref_99 + example: *ref_100 nullable: true description: The comment of the ticket allOf: @@ -10039,13 +10163,13 @@ components: description: The UUID of the contact which the ticket belongs to attachments: type: array - items: *ref_100 - example: *ref_101 + items: *ref_101 + example: *ref_102 description: The attachements UUIDs tied to the ticket nullable: true field_mappings: type: object - example: *ref_102 + example: *ref_103 nullable: true description: >- The custom field mappings of the ticket between the remote 3rd party @@ -10400,7 +10524,7 @@ components: industry: type: string example: ACCOUNTING - enum: &ref_103 + enum: &ref_104 - ACCOUNTING - AIRLINES_AVIATION - ALTERNATIVE_DISPUTE_RESOLUTION @@ -10564,7 +10688,7 @@ components: nullable: true email_addresses: description: The email addresses of the company - example: &ref_104 + example: &ref_105 - email_address: acme@gmail.com email_address_type: WORK nullable: true @@ -10573,7 +10697,7 @@ components: $ref: '#/components/schemas/Email' addresses: description: The addresses of the company - example: &ref_105 + example: &ref_106 - street_1: 5th Avenue city: New York state: NY @@ -10585,7 +10709,7 @@ components: $ref: '#/components/schemas/Address' phone_numbers: description: The phone numbers of the company - example: &ref_106 + example: &ref_107 - phone_number: '+33660606067' phone_type: WORK nullable: true @@ -10594,7 +10718,7 @@ components: $ref: '#/components/schemas/Phone' field_mappings: type: object - example: &ref_107 + example: &ref_108 fav_dish: broccoli fav_color: red description: >- @@ -10643,7 +10767,7 @@ components: industry: type: string example: ACCOUNTING - enum: *ref_103 + enum: *ref_104 description: >- The industry of the company. Authorized values can be found in the Industry enum. @@ -10660,28 +10784,28 @@ components: nullable: true email_addresses: description: The email addresses of the company - example: *ref_104 + example: *ref_105 nullable: true type: array items: $ref: '#/components/schemas/Email' addresses: description: The addresses of the company - example: *ref_105 + example: *ref_106 nullable: true type: array items: $ref: '#/components/schemas/Address' phone_numbers: description: The phone numbers of the company - example: *ref_106 + example: *ref_107 nullable: true type: array items: $ref: '#/components/schemas/Phone' field_mappings: type: object - example: *ref_107 + example: *ref_108 description: >- The custom field mappings of the company between the remote 3rd party & Panora @@ -10705,7 +10829,7 @@ components: email_addresses: nullable: true description: The email addresses of the contact - example: &ref_108 + example: &ref_109 - email: john.doe@example.com type: WORK type: array @@ -10714,7 +10838,7 @@ components: phone_numbers: nullable: true description: The phone numbers of the contact - example: &ref_109 + example: &ref_110 - phone: '1234567890' type: WORK type: array @@ -10723,7 +10847,7 @@ components: addresses: nullable: true description: The addresses of the contact - example: &ref_110 + example: &ref_111 - street: 123 Main St city: Anytown state: CA @@ -10740,7 +10864,7 @@ components: example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f field_mappings: type: object - example: &ref_111 + example: &ref_112 fav_dish: broccoli fav_color: red nullable: true @@ -10797,21 +10921,21 @@ components: email_addresses: nullable: true description: The email addresses of the contact - example: *ref_108 + example: *ref_109 type: array items: $ref: '#/components/schemas/Email' phone_numbers: nullable: true description: The phone numbers of the contact - example: *ref_109 + example: *ref_110 type: array items: $ref: '#/components/schemas/Phone' addresses: nullable: true description: The addresses of the contact - example: *ref_110 + example: *ref_111 type: array items: $ref: '#/components/schemas/Address' @@ -10822,7 +10946,7 @@ components: example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f field_mappings: type: object - example: *ref_111 + example: *ref_112 nullable: true description: >- The custom field mappings of the contact between the remote 3rd @@ -10867,7 +10991,7 @@ components: field_mappings: type: object nullable: true - example: &ref_112 + example: &ref_113 fav_dish: broccoli fav_color: red description: >- @@ -10944,7 +11068,7 @@ components: field_mappings: type: object nullable: true - example: *ref_112 + example: *ref_113 description: >- The custom field mappings of the company between the remote 3rd party & Panora @@ -10965,7 +11089,7 @@ components: type: string nullable: true example: INBOUND - enum: &ref_113 + enum: &ref_114 - INBOUND - OUTBOUND description: >- @@ -10992,7 +11116,7 @@ components: type: string nullable: true example: MEETING - enum: &ref_114 + enum: &ref_115 - EMAIL - CALL - MEETING @@ -11011,7 +11135,7 @@ components: description: The UUID of the company tied to the engagement contacts: nullable: true - example: &ref_115 + example: &ref_116 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f description: The UUIDs of contacts tied to the engagement object type: array @@ -11020,7 +11144,7 @@ components: field_mappings: type: object nullable: true - example: &ref_116 + example: &ref_117 fav_dish: broccoli fav_color: red description: >- @@ -11073,7 +11197,7 @@ components: type: string nullable: true example: INBOUND - enum: *ref_113 + enum: *ref_114 description: >- The direction of the engagement. Authorized values are INBOUND or OUTBOUND @@ -11098,7 +11222,7 @@ components: type: string nullable: true example: MEETING - enum: *ref_114 + enum: *ref_115 description: >- The type of the engagement. Authorized values are EMAIL, CALL or MEETING @@ -11114,7 +11238,7 @@ components: description: The UUID of the company tied to the engagement contacts: nullable: true - example: *ref_115 + example: *ref_116 description: The UUIDs of contacts tied to the engagement object type: array items: @@ -11122,7 +11246,7 @@ components: field_mappings: type: object nullable: true - example: *ref_116 + example: *ref_117 description: >- The custom field mappings of the engagement between the remote 3rd party & Panora @@ -11159,7 +11283,7 @@ components: description: The UUID of the deal tied to the note field_mappings: type: object - example: &ref_117 + example: &ref_118 fav_dish: broccoli fav_color: red nullable: true @@ -11229,7 +11353,7 @@ components: description: The UUID of the deal tied to the note field_mappings: type: object - example: *ref_117 + example: *ref_118 nullable: true description: >- The custom field mappings of the note between the remote 3rd party & @@ -11301,7 +11425,7 @@ components: status: type: string example: PENDING - enum: &ref_118 + enum: &ref_119 - PENDING - COMPLETED description: The status of the task. Authorized values are PENDING, COMPLETED. @@ -11333,7 +11457,7 @@ components: nullable: true field_mappings: type: object - example: &ref_119 + example: &ref_120 fav_dish: broccoli fav_color: red description: >- @@ -11392,7 +11516,7 @@ components: status: type: string example: PENDING - enum: *ref_118 + enum: *ref_119 description: The status of the task. Authorized values are PENDING, COMPLETED. nullable: true due_date: @@ -11422,7 +11546,7 @@ components: nullable: true field_mappings: type: object - example: *ref_119 + example: *ref_120 description: >- The custom field mappings of the task between the remote 3rd party & Panora @@ -11565,7 +11689,7 @@ components: type: string nullable: true example: USER - enum: *ref_120 + enum: *ref_121 description: >- The creator type of the comment. Authorized values are either USER or CONTACT @@ -11590,9 +11714,9 @@ components: specified) attachments: type: array - items: *ref_121 + items: *ref_122 nullable: true - example: *ref_122 + example: *ref_123 description: The attachements UUIDs tied to the comment id: type: string @@ -12274,16 +12398,19 @@ components: nullable: true description: The remote data of the bank info in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the bank info was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the bank info record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -12322,11 +12449,13 @@ components: nullable: true description: The company contribution amount start_date: + format: date-time type: string example: '2024-01-01T00:00:00Z' nullable: true description: The start date of the benefit end_date: + format: date-time type: string example: '2024-12-31T23:59:59Z' nullable: true @@ -12363,16 +12492,19 @@ components: nullable: true description: The remote data of the benefit in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the benefit was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the benefit record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -12431,16 +12563,19 @@ components: nullable: true description: The remote data of the company in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the company was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the company record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -12474,6 +12609,7 @@ components: nullable: true description: The relationship of the dependent to the employee date_of_birth: + format: date-time type: string example: '2020-01-01' nullable: true @@ -12535,16 +12671,19 @@ components: nullable: true description: The remote data of the dependent in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the dependent was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the dependent record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -12627,16 +12766,19 @@ components: nullable: true description: The net pay amount start_date: + format: date-time type: string example: '2023-01-01T00:00:00Z' nullable: true description: The start date of the pay period end_date: + format: date-time type: string example: '2023-01-15T23:59:59Z' nullable: true description: The end date of the pay period check_date: + format: date-time type: string example: '2023-01-20T00:00:00Z' nullable: true @@ -12690,6 +12832,7 @@ components: The remote data of the employee payroll run in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -12697,11 +12840,13 @@ components: The date when the employee payroll run was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the employee payroll run record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -12717,7 +12862,7 @@ components: type: object properties: groups: - example: &ref_123 + example: &ref_124 - Group1 - Group2 nullable: true @@ -12776,7 +12921,7 @@ components: nullable: true description: The mobile phone number of the employee employments: - example: &ref_124 + example: &ref_125 - Employment1 - Employment2 nullable: true @@ -12805,11 +12950,13 @@ components: nullable: true description: The marital status of the employee date_of_birth: + format: date-time type: string example: '1990-01-01' nullable: true description: The date of birth of the employee start_date: + format: date-time type: string example: '2020-01-01' nullable: true @@ -12820,6 +12967,7 @@ components: nullable: true description: The employment status of the employee termination_date: + format: date-time type: string example: '2025-01-01' nullable: true @@ -12831,7 +12979,7 @@ components: description: The URL of the employee's avatar field_mappings: type: object - example: &ref_125 + example: &ref_126 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -12856,16 +13004,19 @@ components: nullable: true description: The remote data of the employee in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the employee was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the employee record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -12879,7 +13030,7 @@ components: type: object properties: groups: - example: *ref_123 + example: *ref_124 nullable: true description: The groups the employee belongs to type: array @@ -12936,7 +13087,7 @@ components: nullable: true description: The mobile phone number of the employee employments: - example: *ref_124 + example: *ref_125 nullable: true description: The employments of the employee type: array @@ -12963,11 +13114,13 @@ components: nullable: true description: The marital status of the employee date_of_birth: + format: date-time type: string example: '1990-01-01' nullable: true description: The date of birth of the employee start_date: + format: date-time type: string example: '2020-01-01' nullable: true @@ -12978,6 +13131,7 @@ components: nullable: true description: The employment status of the employee termination_date: + format: date-time type: string example: '2025-01-01' nullable: true @@ -12989,7 +13143,7 @@ components: description: The URL of the employee's avatar field_mappings: type: object - example: *ref_125 + example: *ref_126 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13048,6 +13202,7 @@ components: The remote data of the employer benefit in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -13055,11 +13210,13 @@ components: The date when the employer benefit was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the employer benefit record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -13103,6 +13260,7 @@ components: nullable: true description: The FLSA status of the employment effective_date: + format: date-time type: string example: '2023-01-01' nullable: true @@ -13149,16 +13307,19 @@ components: nullable: true description: The remote data of the employment in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the employment was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the employment record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -13213,16 +13374,19 @@ components: nullable: true description: The remote data of the group in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the group was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the group record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -13307,16 +13471,19 @@ components: nullable: true description: The remote data of the location in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the location was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the location record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -13361,16 +13528,19 @@ components: nullable: true description: The remote data of the pay group in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the pay group was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the pay group record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -13394,16 +13564,19 @@ components: nullable: true description: The type of the payroll run start_date: + format: date-time type: string example: '2024-01-01T00:00:00Z' nullable: true description: The start date of the payroll run end_date: + format: date-time type: string example: '2024-01-15T23:59:59Z' nullable: true description: The end date of the payroll run check_date: + format: date-time type: string example: '2024-01-20T00:00:00Z' nullable: true @@ -13435,16 +13608,19 @@ components: nullable: true description: The remote data of the payroll run in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The date when the payroll run was created in the 3rd party system created_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true description: The created date of the payroll run record modified_at: + format: date-time type: string example: '2024-10-01T12:00:00Z' nullable: true @@ -13503,18 +13679,20 @@ components: nullable: true description: The type of time off request start_time: + format: date-time type: string example: '2024-07-01T09:00:00Z' nullable: true description: The start time of the time off end_time: + format: date-time type: string example: '2024-07-05T17:00:00Z' nullable: true description: The end time of the time off field_mappings: type: object - example: &ref_126 + example: &ref_127 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -13539,16 +13717,19 @@ components: nullable: true description: The remote data of the time off in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date when the time off was created in the 3rd party system created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the time off record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -13597,18 +13778,20 @@ components: nullable: true description: The type of time off request start_time: + format: date-time type: string example: '2024-07-01T09:00:00Z' nullable: true description: The start time of the time off end_time: + format: date-time type: string example: '2024-07-05T17:00:00Z' nullable: true description: The end time of the time off field_mappings: type: object - example: *ref_126 + example: *ref_127 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13688,18 +13871,127 @@ components: example: false nullable: true description: Indicates if the time off balance was deleted in the remote system - UnifiedMarketingautomationActionOutput: - type: object - properties: {} - UnifiedMarketingautomationActionInput: - type: object - properties: {} - UnifiedMarketingautomationAutomationOutput: + UnifiedHrisTimesheetEntryOutput: type: object - properties: {} - UnifiedMarketingautomationAutomationInput: - type: object - properties: {} + properties: + hours_worked: + type: number + example: 40 + nullable: true + description: The number of hours worked + start_time: + format: date-time + type: string + example: '2024-10-01T08:00:00Z' + nullable: true + description: The start time of the timesheet entry + end_time: + format: date-time + type: string + example: '2024-10-01T16:00:00Z' + nullable: true + description: The end time of the timesheet entry + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + remote_was_deleted: + type: boolean + example: false + description: Indicates if the timesheet entry was deleted in the remote system + field_mappings: + type: object + example: &ref_128 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the timesheet entry record + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the timesheet entry + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the timesheet entry was created in the remote system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the timesheet entry + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The last modified date of the timesheet entry + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the timesheet entry in the context of the 3rd + Party + UnifiedHrisTimesheetEntryInput: + type: object + properties: + hours_worked: + type: number + example: 40 + nullable: true + description: The number of hours worked + start_time: + format: date-time + type: string + example: '2024-10-01T08:00:00Z' + nullable: true + description: The start time of the timesheet entry + end_time: + format: date-time + type: string + example: '2024-10-01T16:00:00Z' + nullable: true + description: The end time of the timesheet entry + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + remote_was_deleted: + type: boolean + example: false + description: Indicates if the timesheet entry was deleted in the remote system + field_mappings: + type: object + example: *ref_128 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedMarketingautomationActionOutput: + type: object + properties: {} + UnifiedMarketingautomationActionInput: + type: object + properties: {} + UnifiedMarketingautomationAutomationOutput: + type: object + properties: {} + UnifiedMarketingautomationAutomationInput: + type: object + properties: {} UnifiedMarketingautomationCampaignOutput: type: object properties: {} @@ -13741,7 +14033,7 @@ components: properties: activity_type: type: string - enum: &ref_127 + enum: &ref_129 - NOTE - EMAIL - OTHER @@ -13760,7 +14052,7 @@ components: description: The body of the activity visibility: type: string - enum: &ref_128 + enum: &ref_130 - ADMIN_ONLY - PUBLIC - PRIVATE @@ -13780,7 +14072,7 @@ components: description: The remote creation date of the activity field_mappings: type: object - example: &ref_129 + example: &ref_131 fav_dish: broccoli fav_color: red additionalProperties: true @@ -13823,7 +14115,7 @@ components: properties: activity_type: type: string - enum: *ref_127 + enum: *ref_129 example: NOTE nullable: true description: The type of activity @@ -13839,7 +14131,7 @@ components: description: The body of the activity visibility: type: string - enum: *ref_128 + enum: *ref_130 example: PUBLIC nullable: true description: The visibility of the activity @@ -13856,7 +14148,7 @@ components: description: The remote creation date of the activity field_mappings: type: object - example: *ref_129 + example: *ref_131 additionalProperties: true nullable: true description: >- @@ -13880,7 +14172,7 @@ components: offers: nullable: true description: The offers UUIDs for the application - example: &ref_130 + example: &ref_132 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f - 12345678-1234-1234-1234-123456789012 type: array @@ -13917,7 +14209,7 @@ components: example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f field_mappings: type: object - example: &ref_131 + example: &ref_133 fav_dish: broccoli fav_color: red additionalProperties: true @@ -13983,7 +14275,7 @@ components: offers: nullable: true description: The offers UUIDs for the application - example: *ref_130 + example: *ref_132 type: array items: type: string @@ -14018,7 +14310,7 @@ components: example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f field_mappings: type: object - example: *ref_131 + example: *ref_133 additionalProperties: true nullable: true description: >- @@ -14040,7 +14332,7 @@ components: attachment_type: type: string example: RESUME - enum: &ref_132 + enum: &ref_134 - RESUME - COVER_LETTER - OFFER_LETTER @@ -14066,7 +14358,7 @@ components: description: The UUID of the candidate field_mappings: type: object - example: &ref_133 + example: &ref_135 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14120,7 +14412,7 @@ components: attachment_type: type: string example: RESUME - enum: *ref_132 + enum: *ref_134 nullable: true description: The type of the file remote_created_at: @@ -14142,7 +14434,7 @@ components: description: The UUID of the candidate field_mappings: type: object - example: *ref_133 + example: *ref_135 additionalProperties: true nullable: true description: >- @@ -14220,37 +14512,37 @@ components: description: The last interaction date with the candidate attachments: type: array - items: &ref_134 + items: &ref_136 oneOf: - type: string - $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' - example: &ref_135 + example: &ref_137 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The attachments UUIDs of the candidate applications: type: array - items: &ref_136 + items: &ref_138 oneOf: - type: string - $ref: '#/components/schemas/UnifiedAtsApplicationOutput' - example: &ref_137 + example: &ref_139 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The applications UUIDs of the candidate tags: type: array - items: &ref_138 + items: &ref_140 oneOf: - type: string - $ref: '#/components/schemas/UnifiedAtsTagOutput' - example: &ref_139 + example: &ref_141 - tag_1 - tag_2 nullable: true description: The tags of the candidate urls: - example: &ref_140 + example: &ref_142 - url: mywebsite.com url_type: WEBSITE nullable: true @@ -14261,7 +14553,7 @@ components: items: $ref: '#/components/schemas/Url' phone_numbers: - example: &ref_141 + example: &ref_143 - phone_number: '+33660688899' phone_type: WORK nullable: true @@ -14270,7 +14562,7 @@ components: items: $ref: '#/components/schemas/Phone' email_addresses: - example: &ref_142 + example: &ref_144 - email_address: joedoe@gmail.com email_address_type: WORK nullable: true @@ -14280,7 +14572,7 @@ components: $ref: '#/components/schemas/Email' field_mappings: type: object - example: &ref_143 + example: &ref_145 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14376,24 +14668,24 @@ components: description: The last interaction date with the candidate attachments: type: array - items: *ref_134 - example: *ref_135 + items: *ref_136 + example: *ref_137 nullable: true description: The attachments UUIDs of the candidate applications: type: array - items: *ref_136 - example: *ref_137 + items: *ref_138 + example: *ref_139 nullable: true description: The applications UUIDs of the candidate tags: type: array - items: *ref_138 - example: *ref_139 + items: *ref_140 + example: *ref_141 nullable: true description: The tags of the candidate urls: - example: *ref_140 + example: *ref_142 nullable: true description: >- The urls of the candidate, possible values for Url type are WEBSITE, @@ -14402,14 +14694,14 @@ components: items: $ref: '#/components/schemas/Url' phone_numbers: - example: *ref_141 + example: *ref_143 nullable: true description: The phone numbers of the candidate type: array items: $ref: '#/components/schemas/Phone' email_addresses: - example: *ref_142 + example: *ref_144 nullable: true description: The email addresses of the candidate type: array @@ -14417,7 +14709,7 @@ components: $ref: '#/components/schemas/Email' field_mappings: type: object - example: *ref_143 + example: *ref_145 additionalProperties: true nullable: true description: >- @@ -14477,7 +14769,7 @@ components: properties: status: type: string - enum: &ref_144 + enum: &ref_146 - SCHEDULED - AWAITING_FEEDBACK - COMPLETED @@ -14500,7 +14792,7 @@ components: nullable: true description: The UUID of the organizer interviewers: - example: &ref_145 + example: &ref_147 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUIDs of the interviewers @@ -14538,7 +14830,7 @@ components: description: The remote modification date of the interview field_mappings: type: object - example: &ref_146 + example: &ref_148 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14581,7 +14873,7 @@ components: properties: status: type: string - enum: *ref_144 + enum: *ref_146 example: SCHEDULED nullable: true description: The status of the interview @@ -14601,7 +14893,7 @@ components: nullable: true description: The UUID of the organizer interviewers: - example: *ref_145 + example: *ref_147 nullable: true description: The UUIDs of the interviewers type: array @@ -14638,7 +14930,7 @@ components: description: The remote modification date of the interview field_mappings: type: object - example: *ref_146 + example: *ref_148 additionalProperties: true nullable: true description: >- @@ -15372,118 +15664,284 @@ components: currency: type: string example: USD - nullable: true - description: The currency of the account - account_number: - type: string - example: '1000' - nullable: true - description: The account number - parent_account: - type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - nullable: true - description: The UUID of the parent account - company_info_id: - type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - nullable: true - description: The UUID of the associated company info - field_mappings: - type: object - example: &ref_147 - custom_field_1: value1 - custom_field_2: value2 - nullable: true - description: >- - The custom field mappings of the object between the remote 3rd party - & Panora - id: - type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - nullable: true - description: The UUID of the account record - remote_id: - type: string - example: account_1234 - nullable: true - description: The remote ID of the account in the context of the 3rd Party - remote_data: - type: object - example: - raw_data: - additional_field: some value - nullable: true - description: The remote data of the account in the context of the 3rd Party - created_at: - type: string - example: '2024-06-15T12:00:00Z' - nullable: true - description: The created date of the account record - modified_at: - type: string - example: '2024-06-15T12:00:00Z' - nullable: true - description: The last modified date of the account record - UnifiedAccountingAccountInput: - type: object - properties: - name: - type: string - example: Cash - nullable: true - description: The name of the account - description: - type: string - example: Main cash account for daily operations - nullable: true - description: A description of the account - classification: - type: string - example: Asset - nullable: true - description: The classification of the account - type: - type: string - example: Current Asset - nullable: true - description: The type of the account - status: - type: string - example: Active - nullable: true - description: The status of the account - current_balance: - type: number - example: 10000 - nullable: true - description: The current balance of the account - currency: - type: string - example: USD - nullable: true - description: The currency of the account - account_number: - type: string - example: '1000' - nullable: true - description: The account number - parent_account: - type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - nullable: true - description: The UUID of the parent account - company_info_id: - type: string - example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f - nullable: true - description: The UUID of the associated company info - field_mappings: - type: object - example: *ref_147 - nullable: true - description: >- - The custom field mappings of the object between the remote 3rd party + enum: &ref_149 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the account + account_number: + type: string + example: '1000' + nullable: true + description: The account number + parent_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent account + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: &ref_150 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the account record + remote_id: + type: string + example: account_1234 + nullable: true + description: The remote ID of the account in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the account in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the account record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the account record + UnifiedAccountingAccountInput: + type: object + properties: + name: + type: string + example: Cash + nullable: true + description: The name of the account + description: + type: string + example: Main cash account for daily operations + nullable: true + description: A description of the account + classification: + type: string + example: Asset + nullable: true + description: The classification of the account + type: + type: string + example: Current Asset + nullable: true + description: The type of the account + status: + type: string + example: Active + nullable: true + description: The status of the account + current_balance: + type: number + example: 10000 + nullable: true + description: The current balance of the account + currency: + type: string + example: USD + enum: *ref_149 + nullable: true + description: The currency of the account + account_number: + type: string + example: '1000' + nullable: true + description: The account number + parent_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent account + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: *ref_150 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party & Panora UnifiedAccountingAddressOutput: type: object @@ -15565,11 +16023,13 @@ components: nullable: true description: The remote data of the address in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the address record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -15594,7 +16054,7 @@ components: description: The UUID of the associated account field_mappings: type: object - example: &ref_148 + example: &ref_151 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -15619,11 +16079,13 @@ components: nullable: true description: The remote data of the attachment in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the attachment record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -15648,11 +16110,60 @@ components: description: The UUID of the associated account field_mappings: type: object - example: *ref_148 + example: *ref_151 nullable: true description: >- The custom field mappings of the object between the remote 3rd party & Panora + LineItem: + type: object + properties: + name: + type: string + example: Net Income + nullable: true + description: The name of the report item + value: + type: number + example: 100000 + nullable: true + description: The value of the report item + type: + type: string + example: Operating Activities + nullable: true + description: The type of the report item + parent_item: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent item + remote_id: + type: string + example: report_item_1234 + nullable: true + description: The remote ID of the report item + remote_generated_at: + format: date-time + type: string + example: '2024-07-01T12:00:00Z' + nullable: true + description: The date when the report item was generated in the remote system + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info object + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + description: The created date of the report item + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + description: The last modified date of the report item UnifiedAccountingBalancesheetOutput: type: object properties: @@ -15664,6 +16175,169 @@ components: currency: type: string example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency used in the balance sheet company_info_id: @@ -15672,6 +16346,7 @@ components: nullable: true description: The UUID of the associated company info date: + format: date-time type: string example: '2024-06-30T23:59:59Z' nullable: true @@ -15710,10 +16385,16 @@ components: items: type: string remote_generated_at: + format: date-time type: string example: '2024-07-01T12:00:00Z' nullable: true description: The date when the balance sheet was generated in the remote system + line_items: + description: The report items associated with this balance sheet + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object example: @@ -15741,11 +16422,13 @@ components: nullable: true description: The remote data of the balance sheet in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the balance sheet record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -15761,6 +16444,169 @@ components: currency: type: string example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency used in the cash flow statement company_id: @@ -15769,11 +16615,13 @@ components: nullable: true description: The UUID of the associated company start_period: + format: date-time type: string example: '2024-04-01T00:00:00Z' nullable: true description: The start date of the period covered by the cash flow statement end_period: + format: date-time type: string example: '2024-06-30T23:59:59Z' nullable: true @@ -15789,12 +16637,18 @@ components: nullable: true description: The cash balance at the end of the period remote_generated_at: + format: date-time type: string example: '2024-07-01T12:00:00Z' nullable: true description: >- The date when the cash flow statement was generated in the remote system + line_items: + description: The report items associated with this cash flow statement + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object example: @@ -15826,11 +16680,13 @@ components: The remote data of the cash flow statement in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the cash flow statement record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -15866,6 +16722,169 @@ components: currency: type: string example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency used by the company urls: @@ -15879,11 +16898,10 @@ components: type: string tracking_categories: example: - - Department - - Project - - Location + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tracking categories used by the company + description: The UUIDs of the tracking categories used by the company type: array items: type: string @@ -15914,16 +16932,19 @@ components: nullable: true description: The remote data of the company info in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date when the company info was created in the remote system created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the company info record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -15965,6 +16986,169 @@ components: type: string example: USD nullable: true + enum: &ref_152 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL description: The currency associated with the contact remote_updated_at: type: string @@ -15978,7 +17162,7 @@ components: description: The UUID of the associated company info field_mappings: type: object - example: &ref_149 + example: &ref_153 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -16003,11 +17187,13 @@ components: nullable: true description: The remote data of the contact in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the contact record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16049,6 +17235,7 @@ components: type: string example: USD nullable: true + enum: *ref_152 description: The currency associated with the contact remote_updated_at: type: string @@ -16062,7 +17249,7 @@ components: description: The UUID of the associated company info field_mappings: type: object - example: *ref_149 + example: *ref_153 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -16071,6 +17258,7 @@ components: type: object properties: transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16112,16 +17300,178 @@ components: description: The remaining credit on the credit note tracking_categories: example: - - Project A - - Department B + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tracking categories associated with the credit note + description: The UUIDs of the tracking categories associated with the credit note type: array items: type: string currency: type: string example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency of the credit note payments: @@ -16174,21 +17524,25 @@ components: nullable: true description: The remote data of the credit note in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date when the credit note was created in the remote system remote_updated_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date when the credit note was last updated in the remote system created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the credit note record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16197,6 +17551,7 @@ components: type: object properties: transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16219,6 +17574,169 @@ components: currency: type: string example: USD + enum: &ref_154 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency of the expense exchange_rate: @@ -16247,17 +17765,21 @@ components: nullable: true description: The UUID of the associated company info tracking_categories: - example: &ref_150 - - Project A - - Department B + example: &ref_155 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tracking categories associated with the expense + description: The UUIDs of the tracking categories associated with the expense type: array items: type: string + line_items: + description: The line items associated with this expense + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_151 + example: &ref_156 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -16282,16 +17804,19 @@ components: nullable: true description: The remote data of the expense in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date when the expense was created in the remote system created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the expense record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16300,6 +17825,7 @@ components: type: object properties: transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16322,6 +17848,7 @@ components: currency: type: string example: USD + enum: *ref_154 nullable: true description: The currency of the expense exchange_rate: @@ -16350,15 +17877,20 @@ components: nullable: true description: The UUID of the associated company info tracking_categories: - example: *ref_150 + example: *ref_155 nullable: true - description: The tracking categories associated with the expense + description: The UUIDs of the tracking categories associated with the expense type: array items: type: string + line_items: + description: The line items associated with this expense + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_151 + example: *ref_156 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -16374,14 +17906,179 @@ components: currency: type: string example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency used in the income statement start_period: + format: date-time type: string example: '2024-04-01T00:00:00Z' nullable: true description: The start date of the period covered by the income statement end_period: + format: date-time type: string example: '2024-06-30T23:59:59Z' nullable: true @@ -16432,11 +18129,13 @@ components: The remote data of the income statement in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the income statement record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16455,16 +18154,19 @@ components: nullable: true description: The invoice number issue_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date the invoice was issued due_date: + format: date-time type: string example: '2024-07-15T12:00:00Z' nullable: true description: The due date of the invoice paid_on_date: + format: date-time type: string example: '2024-07-10T12:00:00Z' nullable: true @@ -16477,6 +18179,169 @@ components: currency: type: string example: USD + enum: &ref_157 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency of the invoice exchange_rate: @@ -16525,17 +18390,22 @@ components: nullable: true description: The UUID of the associated accounting period tracking_categories: - example: &ref_152 - - Project A - - Department B + example: &ref_158 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tracking categories associated with the invoice + description: The UUIDs of the tracking categories associated with the invoice type: array items: type: string + line_items: + description: The line items associated with this invoice + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_153 + example: &ref_159 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -16560,16 +18430,19 @@ components: nullable: true description: The remote data of the invoice in the context of the 3rd Party remote_updated_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date when the invoice was last updated in the remote system created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the invoice record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16588,16 +18461,19 @@ components: nullable: true description: The invoice number issue_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date the invoice was issued due_date: + format: date-time type: string example: '2024-07-15T12:00:00Z' nullable: true description: The due date of the invoice paid_on_date: + format: date-time type: string example: '2024-07-10T12:00:00Z' nullable: true @@ -16610,6 +18486,7 @@ components: currency: type: string example: USD + enum: *ref_157 nullable: true description: The currency of the invoice exchange_rate: @@ -16658,15 +18535,20 @@ components: nullable: true description: The UUID of the associated accounting period tracking_categories: - example: *ref_152 + example: *ref_158 nullable: true - description: The tracking categories associated with the invoice + description: The UUIDs of the tracking categories associated with the invoice type: array items: type: string + line_items: + description: The line items associated with this invoice + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_153 + example: *ref_159 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -16704,7 +18586,7 @@ components: example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated purchase account - id_acc_company_info: + company_info_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true @@ -16729,6 +18611,7 @@ components: nullable: true description: The remote ID of the item in the context of the 3rd Party remote_updated_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16741,11 +18624,13 @@ components: nullable: true description: The remote data of the item in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the accounting item record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16754,12 +18639,13 @@ components: type: object properties: transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date of the transaction payments: - example: &ref_154 + example: &ref_160 - payment1 - payment2 nullable: true @@ -16768,7 +18654,7 @@ components: items: type: string applied_payments: - example: &ref_155 + example: &ref_161 - appliedPayment1 - appliedPayment2 nullable: true @@ -16784,6 +18670,169 @@ components: currency: type: string example: USD + enum: &ref_162 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency of the journal entry exchange_rate: @@ -16802,11 +18851,12 @@ components: nullable: true description: The journal number tracking_categories: - example: &ref_156 - - Category1 - - Category2 + example: &ref_163 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tracking categories associated with the journal entry + description: >- + The UUIDs of the tracking categories associated with the journal + entry type: array items: type: string @@ -16820,9 +18870,14 @@ components: example: Posted nullable: true description: The posting status of the journal entry + line_items: + description: The line items associated with this journal entry + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_157 + example: &ref_164 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -16840,11 +18895,13 @@ components: nullable: false description: The remote ID of the journal entry in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date when the journal entry was created in the remote system remote_modiified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16859,11 +18916,13 @@ components: nullable: true description: The remote data of the journal entry in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the journal entry record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -16872,19 +18931,20 @@ components: type: object properties: transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date of the transaction payments: - example: *ref_154 + example: *ref_160 nullable: true description: The payments associated with the journal entry type: array items: type: string applied_payments: - example: *ref_155 + example: *ref_161 nullable: true description: The applied payments for the journal entry type: array @@ -16898,6 +18958,7 @@ components: currency: type: string example: USD + enum: *ref_162 nullable: true description: The currency of the journal entry exchange_rate: @@ -16916,9 +18977,11 @@ components: nullable: true description: The journal number tracking_categories: - example: *ref_156 + example: *ref_163 nullable: true - description: The tracking categories associated with the journal entry + description: >- + The UUIDs of the tracking categories associated with the journal + entry type: array items: type: string @@ -16932,9 +18995,14 @@ components: example: Posted nullable: true description: The posting status of the journal entry + line_items: + description: The line items associated with this journal entry + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_157 + example: *ref_164 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -16942,22 +19010,23 @@ components: UnifiedAccountingPaymentOutput: type: object properties: - id_acc_invoice: + invoice_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated invoice transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date of the transaction - id_acc_contact: + contact_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated contact - id_acc_account: + account_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true @@ -16965,6 +19034,169 @@ components: currency: type: string example: USD + enum: &ref_165 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency of the payment exchange_rate: @@ -16982,28 +19214,32 @@ components: example: Credit Card nullable: true description: The type of payment - id_acc_company_info: + company_info_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated company info - id_acc_accounting_period: + accounting_period_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated accounting period tracking_categories: - example: &ref_158 - - Category1 - - Category2 + example: &ref_166 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tracking categories associated with the payment + description: The UUIDs of the tracking categories associated with the payment type: array items: type: string + line_items: + description: The line items associated with this payment + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_159 + example: &ref_167 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -17021,6 +19257,7 @@ components: nullable: true description: The remote ID of the payment in the context of the 3rd Party remote_updated_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17033,11 +19270,13 @@ components: nullable: true description: The remote data of the payment in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the payment record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17045,22 +19284,23 @@ components: UnifiedAccountingPaymentInput: type: object properties: - id_acc_invoice: + invoice_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated invoice transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date of the transaction - id_acc_contact: + contact_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated contact - id_acc_account: + account_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true @@ -17068,6 +19308,7 @@ components: currency: type: string example: USD + enum: *ref_165 nullable: true description: The currency of the payment exchange_rate: @@ -17085,26 +19326,31 @@ components: example: Credit Card nullable: true description: The type of payment - id_acc_company_info: + company_info_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated company info - id_acc_accounting_period: + accounting_period_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated accounting period tracking_categories: - example: *ref_158 + example: *ref_166 nullable: true - description: The tracking categories associated with the payment + description: The UUIDs of the tracking categories associated with the payment type: array items: type: string + line_items: + description: The line items associated with this payment + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_159 + example: *ref_167 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -17122,12 +19368,12 @@ components: example: Mobile nullable: true description: The type of phone number - id_acc_company_info: + company_info_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated company info - id_acc_contact: + contact_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: false @@ -17159,11 +19405,13 @@ components: nullable: true description: The remote data of the phone number in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the phone number record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17177,6 +19425,7 @@ components: nullable: true description: The status of the purchase order issue_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17187,6 +19436,7 @@ components: nullable: true description: The purchase order number delivery_date: + format: date-time type: string example: '2024-07-15T12:00:00Z' nullable: true @@ -17211,7 +19461,7 @@ components: example: Purchase order for Q3 inventory nullable: true description: A memo or note for the purchase order - company: + company_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true @@ -17224,6 +19474,169 @@ components: currency: type: string example: USD + enum: &ref_168 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency of the purchase order exchange_rate: @@ -17232,22 +19645,28 @@ components: nullable: true description: The exchange rate applied to the purchase order tracking_categories: - example: &ref_160 - - Category1 - - Category2 + example: &ref_169 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The tracking categories associated with the purchase order + description: >- + The UUIDs of the tracking categories associated with the purchase + order type: array items: type: string - id_acc_accounting_period: + accounting_period_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated accounting period + line_items: + description: The line items associated with this purchase order + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_161 + example: &ref_170 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -17265,11 +19684,13 @@ components: nullable: true description: The remote ID of the purchase order in the context of the 3rd Party remote_created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The date when the purchase order was created in the remote system remote_updated_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17286,11 +19707,13 @@ components: The remote data of the purchase order in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the purchase order record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17304,6 +19727,7 @@ components: nullable: true description: The status of the purchase order issue_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17314,6 +19738,7 @@ components: nullable: true description: The purchase order number delivery_date: + format: date-time type: string example: '2024-07-15T12:00:00Z' nullable: true @@ -17338,7 +19763,7 @@ components: example: Purchase order for Q3 inventory nullable: true description: A memo or note for the purchase order - company: + company_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true @@ -17351,6 +19776,7 @@ components: currency: type: string example: USD + enum: *ref_168 nullable: true description: The currency of the purchase order exchange_rate: @@ -17359,20 +19785,27 @@ components: nullable: true description: The exchange rate applied to the purchase order tracking_categories: - example: *ref_160 + example: *ref_169 nullable: true - description: The tracking categories associated with the purchase order + description: >- + The UUIDs of the tracking categories associated with the purchase + order type: array items: type: string - id_acc_accounting_period: + accounting_period_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated accounting period + line_items: + description: The line items associated with this purchase order + type: array + items: + $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_161 + example: *ref_170 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -17395,7 +19828,7 @@ components: example: 1900 nullable: true description: The effective tax rate in basis points (e.g., 1900 for 19%) - company: + company_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true @@ -17427,11 +19860,13 @@ components: nullable: true description: The remote data of the tax rate in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the tax rate record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17490,11 +19925,13 @@ components: The remote data of the tracking category in the context of the 3rd Party created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: The created date of the tracking category record modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17513,6 +19950,7 @@ components: nullable: true description: The transaction number transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17530,36 +19968,213 @@ components: currency: type: string example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency of the transaction tracking_categories: example: - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of tracking categories associated with the transaction + description: The UUIDs of the tracking categories associated with the transaction type: array items: type: string - id_acc_account: + account_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated account - id_acc_contact: + contact_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated contact - id_acc_company_info: + company_info_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated company info - id_acc_accounting_period: + accounting_period_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated accounting period + line_items: + description: The line items associated with this transaction + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f @@ -17571,16 +20186,28 @@ components: nullable: false description: The remote ID of the transaction created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: false description: The created date of the transaction + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the tracking category in the context of the 3rd + Party modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: false description: The last modified date of the transaction remote_updated_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17594,6 +20221,7 @@ components: nullable: true description: The number of the vendor credit transaction_date: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true @@ -17612,13 +20240,176 @@ components: type: string example: USD nullable: true + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL description: The currency of the vendor credit exchange_rate: type: string example: '1.2' nullable: true description: The exchange rate applied to the vendor credit - company: + company_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true @@ -17627,15 +20418,31 @@ components: example: - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true - description: The UUID of tracking categories associated with the vendor credit + description: >- + The UUID of the tracking categories associated with the vendor + credit type: array items: type: string - accounting_period: + accounting_period_id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUID of the associated accounting period + line_items: + description: The line items associated with this vendor credit + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora id: type: string example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f @@ -17647,22 +20454,32 @@ components: nullable: true description: The remote ID of the vendor credit created_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: false description: The created date of the vendor credit modified_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: false description: The last modified date of the vendor credit remote_updated_at: + format: date-time type: string example: '2024-06-15T12:00:00Z' nullable: true description: >- The date when the vendor credit was last updated in the remote system + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the vendor credit in the context of the 3rd Party UnifiedFilestorageDriveOutput: type: object properties: @@ -17765,7 +20582,7 @@ components: nullable: true field_mappings: type: object - example: &ref_162 + example: &ref_171 fav_dish: broccoli fav_color: red description: >- @@ -17851,7 +20668,7 @@ components: nullable: true field_mappings: type: object - example: *ref_162 + example: *ref_171 description: >- The custom field mappings of the object between the remote 3rd party & Panora @@ -17909,7 +20726,7 @@ components: description: The UUID of the permission tied to the folder field_mappings: type: object - example: &ref_163 + example: &ref_172 fav_dish: broccoli fav_color: red additionalProperties: true @@ -18000,7 +20817,7 @@ components: description: The UUID of the permission tied to the folder field_mappings: type: object - example: *ref_163 + example: *ref_172 additionalProperties: true nullable: true description: >- @@ -18165,13 +20982,13 @@ components: type: string example: ACTIVE nullable: true - enum: &ref_164 + enum: &ref_173 - ARCHIVED - ACTIVE - DRAFT description: The status of the product. Either ACTIVE, DRAFT OR ARCHIVED. images_urls: - example: &ref_165 + example: &ref_174 - https://myproduct/image nullable: true description: The URLs of the product images @@ -18189,7 +21006,7 @@ components: nullable: true description: The vendor of the product variants: - example: &ref_166 + example: &ref_175 - title: teeshirt price: 20 sku: '3' @@ -18201,7 +21018,7 @@ components: items: $ref: '#/components/schemas/Variant' tags: - example: &ref_167 + example: &ref_176 - tag_1 nullable: true description: The tags associated with the product @@ -18210,7 +21027,7 @@ components: type: string field_mappings: type: object - example: &ref_168 + example: &ref_177 fav_dish: broccoli fav_color: red nullable: true @@ -18261,10 +21078,10 @@ components: type: string example: ACTIVE nullable: true - enum: *ref_164 + enum: *ref_173 description: The status of the product. Either ACTIVE, DRAFT OR ARCHIVED. images_urls: - example: *ref_165 + example: *ref_174 nullable: true description: The URLs of the product images type: array @@ -18281,13 +21098,13 @@ components: nullable: true description: The vendor of the product variants: - example: *ref_166 + example: *ref_175 description: The variants of the product type: array items: $ref: '#/components/schemas/Variant' tags: - example: *ref_167 + example: *ref_176 nullable: true description: The tags associated with the product type: array @@ -18295,7 +21112,7 @@ components: type: string field_mappings: type: object - example: *ref_168 + example: *ref_177 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -18322,7 +21139,7 @@ components: type: string nullable: true example: AUD - enum: &ref_169 + enum: &ref_178 - AED - AFN - ALL @@ -18521,11 +21338,11 @@ components: items: type: object nullable: true - example: &ref_170 {} + example: &ref_179 {} description: The items in the order field_mappings: type: object - example: &ref_171 + example: &ref_180 fav_dish: broccoli fav_color: red nullable: true @@ -18581,7 +21398,7 @@ components: type: string nullable: true example: AUD - enum: *ref_169 + enum: *ref_178 description: >- The currency of the order. Authorized value must be of type CurrencyCode (ISO 4217) @@ -18618,11 +21435,11 @@ components: items: type: object nullable: true - example: *ref_170 + example: *ref_179 description: The items in the order field_mappings: type: object - example: *ref_171 + example: *ref_180 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -18799,7 +21616,7 @@ components: field_mappings: type: object nullable: true - example: &ref_172 + example: &ref_181 fav_dish: broccoli fav_color: red description: >- @@ -18871,7 +21688,7 @@ components: field_mappings: type: object nullable: true - example: *ref_172 + example: *ref_181 description: >- The custom field mappings of the attachment between the remote 3rd party & Panora diff --git a/packages/api/variables.MD b/packages/api/variables.MD index 8aaa1ec60..6ca21ab42 100644 --- a/packages/api/variables.MD +++ b/packages/api/variables.MD @@ -113,10 +113,10 @@ | MAILCHIMP_MARKETINGAUTOMATION_CLOUD_CLIENT_SECRET | | | | KLAVIYO_TICKETING_CLOUD_CLIENT_ID | | | | KLAVIYO_TICKETING_CLOUD_CLIENT_SECRET | | | -| NOTION_MANAGEMENT_CLOUD_CLIENT_ID | | | -| NOTION_MANAGEMENT_CLOUD_CLIENT_SECRET | | | -| SLACK_MANAGEMENT_CLOUD_CLIENT_ID | | | -| SLACK_MANAGEMENT_CLOUD_CLIENT_SECRET | | | +| NOTION_PRODUCTIVITY_CLOUD_CLIENT_ID | | | +| NOTION_PRODUCTIVITY_CLOUD_CLIENT_SECRET | | | +| SLACK_PRODUCTIVITY_CLOUD_CLIENT_ID | | | +| SLACK_PRODUCTIVITY_CLOUD_CLIENT_SECRET | | | | GREENHOUSE_ATS_CLOUD_CLIENT_ID | | | | GREENHOUSE_ATS_CLOUD_CLIENT_SECRET | | | | JOBADDER_ATS_CLOUD_CLIENT_ID | | | diff --git a/packages/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index 3021d0eb7..bc44c8c8b 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -59,6 +59,17 @@ export const constructAuthUrl = async ({ projectId, linkedUserId, providerName, if (providerName === 'microsoftdynamicssales') { state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl, resource: additionalParams!.end_user_domain })); } + if (providerName === 'deel') { + const randomState = randomString(); + state = encodeURIComponent(randomState + 'deel_delimiter' + Buffer.from(JSON.stringify({ + projectId, + linkedUserId, + providerName, + vertical, + returnUrl, + resource: additionalParams!.end_user_domain! + })).toString('base64')); + } // console.log('State : ', JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl })); // console.log('encodedRedirect URL : ', encodedRedirectUrl); // const vertical = findConnectorCategory(providerName); @@ -179,6 +190,8 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { let b = `https://${resource}/.default`; b += (' offline_access'); params += `&scope=${encodeURIComponent(b)}`; + } else if (providerName === 'deel') { + params += `&scope=${encodeURIComponent(scopes.replace(/\t/g, ' '))}`; } else { params += `&scope=${encodeURIComponent(scopes)}`; } diff --git a/packages/shared/src/categories.ts b/packages/shared/src/categories.ts index 67630071e..5cb99075e 100644 --- a/packages/shared/src/categories.ts +++ b/packages/shared/src/categories.ts @@ -6,7 +6,7 @@ export enum ConnectorCategory { Ticketing = 'ticketing', MarketingAutomation = 'marketingautomation', FileStorage = 'filestorage', - Management = 'management', + Productivity = 'productivity', Ecommerce = 'ecommerce' } diff --git a/packages/shared/src/connectors/enum.ts b/packages/shared/src/connectors/enum.ts index c9a98c1fb..a66547391 100644 --- a/packages/shared/src/connectors/enum.ts +++ b/packages/shared/src/connectors/enum.ts @@ -9,21 +9,19 @@ export enum CrmConnectors { export enum EcommerceConnectors { SHOPIFY = 'shopify', + WOOCOMMERCE = 'woocommerce', + SQUARESPACE = 'squarespace', + AMAZON = 'amazon' } export enum TicketingConnectors { ZENDESK = 'zendesk', FRONT = 'front', JIRA = 'jira', - GORGIAS = 'gorgias', GITHUB = 'github', GITLAB = 'gitlab' } -export enum AccountingConnectors { - PENNYLANE = 'pennylane', - FRESHBOOKS = 'freshbooks', - CLEARBOOKS = 'clearbooks', - FREEAGENT = 'freeagent', - SAGE = 'sage', +export enum FilestorageConnectors { + BOX = 'box' } diff --git a/packages/shared/src/connectors/index.ts b/packages/shared/src/connectors/index.ts index c7e99e5ea..105ca557a 100644 --- a/packages/shared/src/connectors/index.ts +++ b/packages/shared/src/connectors/index.ts @@ -1,8 +1,8 @@ export const CRM_PROVIDERS = ['zoho', 'zendesk', 'hubspot', 'pipedrive', 'attio', 'close']; export const HRIS_PROVIDERS = []; -export const ATS_PROVIDERS = []; +export const ATS_PROVIDERS = ['ashby']; export const ACCOUNTING_PROVIDERS = []; -export const TICKETING_PROVIDERS = ['zendesk', 'front', 'jira', 'gorgias', 'gitlab', 'github']; +export const TICKETING_PROVIDERS = ['zendesk', 'front', 'jira', 'gitlab', 'github']; export const MARKETINGAUTOMATION_PROVIDERS = []; -export const FILESTORAGE_PROVIDERS = []; -export const ECOMMERCE_PROVIDERS = ['shopify']; +export const FILESTORAGE_PROVIDERS = ['box']; +export const ECOMMERCE_PROVIDERS = ['shopify', 'woocommerce', 'squarespace', 'amazon']; diff --git a/packages/shared/src/connectors/metadata.ts b/packages/shared/src/connectors/metadata.ts index 0184a7d1d..d575b5a5b 100644 --- a/packages/shared/src/connectors/metadata.ts +++ b/packages/shared/src/connectors/metadata.ts @@ -2007,7 +2007,7 @@ export const CONNECTORS_METADATA: ProvidersConfig = { }, logoPath: 'https://asset.brandfetch.io/id4NSNrRnG/idXzwlo3iL.jpeg', description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, + active: true, authStrategy: { strategy: AuthStrategy.oauth2 }, @@ -2087,12 +2087,12 @@ export const CONNECTORS_METADATA: ProvidersConfig = { 'gusto': { urls: { docsUrl: 'https://docs.gusto.com/app-integrations/docs/introduction', - apiUrl: 'https://api.gusto.com/v1', + apiUrl: 'https://api.gusto.com', authBaseUrl: 'https://api.gusto-demo.com/oauth/authorize' }, logoPath: 'https://cdn.runalloy.com/landing/uploads-new/Gusto_Logo_67ca008403.png', description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, + active: true, authStrategy: { strategy: AuthStrategy.oauth2 } @@ -2761,7 +2761,7 @@ export const CONNECTORS_METADATA: ProvidersConfig = { } }, }, - 'management': { + 'productivity': { 'notion': { urls: { docsUrl: 'https://developers.notion.com/docs/getting-started', diff --git a/packages/shared/src/standardObjects.ts b/packages/shared/src/standardObjects.ts index 0c3446222..153d47cb5 100644 --- a/packages/shared/src/standardObjects.ts +++ b/packages/shared/src/standardObjects.ts @@ -2,52 +2,103 @@ export enum CrmObject { company = 'company', contact = 'contact', deal = 'deal', - event = 'event', lead = 'lead', note = 'note', task = 'task', + engagement = 'engagement', + stage = 'stage', user = 'user', } -export enum HrisObject {} +export enum HrisObject { + bankinfo = 'bankinfo', + benefit = 'benefit', + company = 'company', + dependent = 'dependent', + employee = 'employee', + employeepayrollrun = 'employeepayrollrun', + employerbenefit = 'employerbenefit', + employment = 'employment', + group = 'group', + location = 'location', + paygroup = 'paygroup', + payrollrun = 'payrollrun', + timeoff = 'timeoff', + timeoffbalance = 'timeoffbalance', + timesheetentry = 'timesheetentry', +} export enum AtsObject { - activity = 'activity', - application = 'application', - attachment = 'attachment', - candidate = 'candidate', - department = 'department', - eeocs = 'eeocs', - interview = 'interview', - job = 'job', - jobinterviewstage = 'jobinterviewstage', - offer = 'offer', - office = 'office', - rejectreason = 'rejectreason', - scorecard = 'scorecard', - tag = 'tag', - user = 'user' + activity = 'activity', + application = 'application', + attachment = 'attachment', + candidate = 'candidate', + department = 'department', + interview = 'interview', + jobinterviewstage = 'jobinterviewstage', + job = 'job', + offer = 'offer', + office = 'office', + rejectreason = 'rejectreason', + scorecard = 'scorecard', + tag = 'tag', + user = 'user', + eeocs = 'eeocs', } -export enum AccountingObject {} +export enum AccountingObject { + balancesheet = 'balancesheet', + cashflowstatement = 'cashflowstatement', + companyinfo = 'companyinfo', + contact = 'contact', + creditnote = 'creditnote', + expense = 'expense', + incomestatement = 'incomestatement', + invoice = 'invoice', + item = 'item', + journalentry = 'journalentry', + payment = 'payment', + phonenumber = 'phonenumber', + purchaseorder = 'purchaseorder', + taxrate = 'taxrate', + trackingcategory = 'trackingcategory', + transaction = 'transaction', + vendorcredit = 'vendorcredit', + account = 'account', + address = 'address', + attachment = 'attachment', +} export enum EcommerceObject { - order = 'order', - fulfillment = 'fulfillment', - product = 'product', - customer = 'customer', - fulfillmentorders = 'fulfillmentorders' + order = 'order', + fulfillment = 'fulfillment', + product = 'product', + customer = 'customer', + fulfillmentorders = 'fulfillmentorders' } export enum FileStorageObject { - drive = 'drive', file = 'file', folder = 'folder', + permission = 'permission', + drive = 'drive', + sharedlink = 'sharedlink', group = 'group', - user = 'user' + user = 'user', } -export enum MarketingAutomationObject {} +export enum MarketingAutomationObject { + action = 'action', + automation = 'automation', + campaign = 'campaign', + contact = 'contact', + email = 'email', + event = 'event', + list = 'list', + message = 'message', + template = 'template', + user = 'user', +} export enum TicketingObject { ticket = 'ticket', @@ -58,7 +109,7 @@ export enum TicketingObject { account = 'account', tag = 'tag', team = 'team', - collection = 'collection' + collection = 'collection', } // Utility function to prepend prefix to enum values // eslint-disable-next-line @typescript-eslint/no-explicit-any From 965e669d1b2830b1e10f449ac4e6b47976473d7c Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 13 Aug 2024 21:06:58 +0200 Subject: [PATCH 6/8] :bug: Added tests --- packages/api/prisma/schema.prisma | 3 + packages/api/scripts/init.sql | 3 + packages/api/src/@core/sync/sync.service.ts | 129 ++++ .../api/src/accounting/accounting.module.ts | 2 + .../ecommerce/order/services/order.service.ts | 119 ++-- .../src/hris/bankinfo/bankinfo.controller.ts | 4 +- packages/api/src/hris/bankinfo/types/index.ts | 5 - .../src/hris/benefit/benefit.controller.ts | 4 +- .../api/src/hris/benefit/benefit.module.ts | 2 + .../api/src/hris/benefit/sync/sync.service.ts | 11 +- packages/api/src/hris/benefit/types/index.ts | 5 - .../src/hris/company/company.controller.ts | 3 + .../api/src/hris/company/company.module.ts | 3 +- .../hris/company/services/gusto/mappers.ts | 35 +- packages/api/src/hris/company/types/index.ts | 5 - .../hris/dependent/dependent.controller.ts | 4 +- .../src/hris/dependent/dependent.module.ts | 20 +- .../api/src/hris/dependent/types/index.ts | 5 - .../src/hris/employee/employee.controller.ts | 4 +- .../api/src/hris/employee/employee.module.ts | 3 +- .../employee/services/employee.service.ts | 160 ++++- .../src/hris/employee/services/gusto/index.ts | 4 +- .../hris/employee/services/gusto/mappers.ts | 2 +- .../src/hris/employee/sync/sync.service.ts | 11 +- .../employeepayrollrun.controller.ts | 4 +- .../employeepayrollrun.module.ts | 18 +- .../employeepayrollrun/sync/sync.service.ts | 11 +- .../hris/employeepayrollrun/types/index.ts | 5 - .../employerbenefit.controller.ts | 3 + .../employerbenefit/employerbenefit.module.ts | 3 +- .../hris/employerbenefit/sync/sync.service.ts | 13 +- .../hris/employment/employment.controller.ts | 3 + .../src/hris/employment/employment.module.ts | 5 +- .../employment/services/employment.service.ts | 5 +- .../api/src/hris/employment/types/index.ts | 5 - .../api/src/hris/group/group.controller.ts | 4 +- packages/api/src/hris/group/group.module.ts | 3 +- .../api/src/hris/group/sync/sync.service.ts | 11 +- packages/api/src/hris/hris.module.ts | 2 + .../src/hris/location/location.controller.ts | 4 +- .../api/src/hris/location/location.module.ts | 19 +- .../hris/location/services/gusto/mappers.ts | 5 +- packages/api/src/hris/location/types/index.ts | 5 - .../src/hris/paygroup/paygroup.controller.ts | 4 +- .../api/src/hris/paygroup/paygroup.module.ts | 20 +- packages/api/src/hris/paygroup/types/index.ts | 5 - .../hris/payrollrun/payrollrun.controller.ts | 4 +- .../src/hris/payrollrun/payrollrun.module.ts | 20 +- .../api/src/hris/payrollrun/types/index.ts | 5 - .../hris/timeoff/services/timeoff.service.ts | 161 ++++- .../api/src/hris/timeoff/sync/sync.service.ts | 4 +- .../src/hris/timeoff/timeoff.controller.ts | 3 + .../api/src/hris/timeoff/timeoff.module.ts | 23 +- .../hris/timeoffbalance/sync/sync.service.ts | 4 +- .../timeoffbalance.controller.ts | 4 +- .../timeoffbalance/timeoffbalance.module.ts | 23 +- .../src/hris/timeoffbalance/types/index.ts | 5 - .../services/timesheetentry.service.ts | 186 ++++-- .../timesheetentry.controller.ts | 3 + .../timesheetentry/timesheetentry.module.ts | 4 +- packages/api/swagger/swagger-spec.yaml | 602 ++++++++++++++---- packages/shared/src/connectors/metadata.ts | 2 +- 62 files changed, 1228 insertions(+), 528 deletions(-) diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index b3eff7b44..7d11243ec 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -1817,6 +1817,7 @@ model hris_companies { legal_name String? display_name String? eins String[] + locations String[] remote_id String? remote_created_at DateTime? @db.Timestamptz(6) created_at DateTime @db.Timestamptz(6) @@ -1926,6 +1927,8 @@ model hris_employees { modified_at DateTime @db.Timestamptz(6) remote_was_deleted Boolean id_connection String @db.Uuid + locations String[] + manager String? groups String[] employee_number String? id_hris_company String? @db.Uuid diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index fa91ac866..69db67e95 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -282,6 +282,7 @@ CREATE TABLE hris_companies legal_name text NULL, display_name text NULL, eins text[] NULL, + locations text[] NULL, remote_id text NULL, remote_created_at timestamp with time zone NULL, created_at timestamp with time zone NOT NULL, @@ -979,6 +980,8 @@ CREATE TABLE hris_employees modified_at timestamp with time zone NOT NULL, remote_was_deleted boolean NOT NULL, id_connection uuid NOT NULL, + locations text[] NULL, + manager text NULL, groups text[] NULL, employee_number text NULL, id_hris_company uuid NULL, diff --git a/packages/api/src/@core/sync/sync.service.ts b/packages/api/src/@core/sync/sync.service.ts index 36f78de5a..aa38429a3 100644 --- a/packages/api/src/@core/sync/sync.service.ts +++ b/packages/api/src/@core/sync/sync.service.ts @@ -30,6 +30,12 @@ export class CoreSyncService { case ConnectorCategory.Ats: await this.handleAtsSync(provider, linkedUserId); break; + case ConnectorCategory.Hris: + await this.handleHrisSync(provider, linkedUserId); + break; + case ConnectorCategory.Accounting: + await this.handleAccountingSync(provider, linkedUserId); + break; case ConnectorCategory.Ecommerce: await this.handleEcommerceSync(provider, linkedUserId); break; @@ -39,6 +45,129 @@ export class CoreSyncService { } } + // todo + async handleAccountingSync(provider: string, linkedUserId: string) { + return; + } + + async handleHrisSync(provider: string, linkedUserId: string) { + // add other objects when i have info on the order + //todo: define here the topological order PER provider + const tasks = [ + () => + this.registry.getService('hris', 'company').syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUserId, + }), + ]; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: provider.toLowerCase(), + }, + }); + + for (const task of tasks) { + try { + await task(); + } catch (error) { + this.logger.error(`Task failed: ${error.message}`, error); + } + } + const companies = await this.prisma.hris_companies.findMany({ + where: { + id_connection: connection.id_connection, + }, + }); + + const companiesEmployeeTasks = companies.map( + (company) => async () => + this.registry.getService('hris', 'employee').syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUserId, + id_company: company.id_hris_company, + }), + ); + + const companiesEmployerBenefitsTasks = companies.map( + (company) => async () => + this.registry.getService('hris', 'employerbenefit').syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUserId, + id_company: company.id_hris_company, + }), + ); + + const companiesGroupsTasks = companies.map( + (company) => async () => + this.registry.getService('hris', 'group').syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUserId, + id_company: company.id_hris_company, + }), + ); + + const employees = await this.prisma.hris_employees.findMany({ + where: { + id_connection: connection.id_connection, + }, + }); + + const employeesBenefitsTasks = employees.map( + (employee) => async () => + this.registry.getService('hris', 'benefit').syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUserId, + id_employee: employee.id_hris_employee, + }), + ); + + for (const task of companiesEmployeeTasks) { + try { + await task(); + } catch (error) { + this.logger.error( + `Companies Employee task failed: ${error.message}`, + error, + ); + } + } + + for (const task of companiesEmployerBenefitsTasks) { + try { + await task(); + } catch (error) { + this.logger.error( + `Companies Employer Benefits task failed: ${error.message}`, + error, + ); + } + } + + for (const task of companiesGroupsTasks) { + try { + await task(); + } catch (error) { + this.logger.error( + `Companies Groups task failed: ${error.message}`, + error, + ); + } + } + + for (const task of employeesBenefitsTasks) { + try { + await task(); + } catch (error) { + this.logger.error( + `Employees Benefits task failed: ${error.message}`, + error, + ); + } + } + } + async handleCrmSync(provider: string, linkedUserId: string) { const tasks = [ () => diff --git a/packages/api/src/accounting/accounting.module.ts b/packages/api/src/accounting/accounting.module.ts index c58d7cbaa..a6ea75d8f 100644 --- a/packages/api/src/accounting/accounting.module.ts +++ b/packages/api/src/accounting/accounting.module.ts @@ -19,6 +19,7 @@ import { TaxRateModule } from './taxrate/taxrate.module'; import { TrackingCategoryModule } from './trackingcategory/trackingcategory.module'; import { TransactionModule } from './transaction/transaction.module'; import { VendorCreditModule } from './vendorcredit/vendorcredit.module'; +import { AccountingUnificationService } from './@lib/@unification'; @Module({ exports: [ @@ -43,6 +44,7 @@ import { VendorCreditModule } from './vendorcredit/vendorcredit.module'; TransactionModule, VendorCreditModule, ], + providers: [AccountingUnificationService], imports: [ AccountModule, AddressModule, diff --git a/packages/api/src/ecommerce/order/services/order.service.ts b/packages/api/src/ecommerce/order/services/order.service.ts index 300b55a99..34161594e 100644 --- a/packages/api/src/ecommerce/order/services/order.service.ts +++ b/packages/api/src/ecommerce/order/services/order.service.ts @@ -2,7 +2,10 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; -import { UnifiedEcommerceOrderInput, UnifiedEcommerceOrderOutput } from '../types/model.unified'; +import { + UnifiedEcommerceOrderInput, + UnifiedEcommerceOrderOutput, +} from '../types/model.unified'; import { OriginalOrderOutput } from '@@core/utils/types/original/original.ecommerce'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; @@ -46,7 +49,7 @@ export class OrderService { } async addOrder( - UnifiedEcommerceOrderData: UnifiedEcommerceOrderInput, + unifiedEcommerceOrderData: UnifiedEcommerceOrderInput, connection_id: string, project_id: string, integrationId: string, @@ -55,11 +58,11 @@ export class OrderService { ): Promise { try { const linkedUser = await this.validateLinkedUser(linkedUserId); - await this.validateCustomerId(UnifiedEcommerceOrderData.customer_id); + await this.validateCustomerId(unifiedEcommerceOrderData.customer_id); const desunifiedObject = await this.coreUnification.desunify({ - sourceObject: UnifiedEcommerceOrderData, + sourceObject: unifiedEcommerceOrderData, targetType: EcommerceObject.order, providerName: integrationId, vertical: 'ecommerce', @@ -328,66 +331,68 @@ export class OrderService { prev_cursor = Buffer.from(cursor).toString('base64'); } - const UnifiedEcommerceOrders: UnifiedEcommerceOrderOutput[] = await Promise.all( - orders.map(async (order) => { - // Fetch field mappings for the order - const values = await this.prisma.value.findMany({ - where: { - entity: { - ressource_owner_id: order.id_ecom_order, + const UnifiedEcommerceOrders: UnifiedEcommerceOrderOutput[] = + await Promise.all( + orders.map(async (order) => { + // Fetch field mappings for the order + const values = await this.prisma.value.findMany({ + where: { + entity: { + ressource_owner_id: order.id_ecom_order, + }, }, - }, - include: { - attribute: true, - }, - }); - - // Create a map to store unique field mappings - const fieldMappingsMap = new Map(); - - values.forEach((value) => { - fieldMappingsMap.set(value.attribute.slug, value.data); - }); - - // Convert the map to an array of objects - const field_mappings = Object.fromEntries(fieldMappingsMap); - - // Transform to UnifiedEcommerceOrderOutput format - return { - id: order.id_ecom_order, - order_status: order.order_status, - order_number: order.order_number, - payment_status: order.payment_status, - currency: order.currency as CurrencyCode, - total_price: Number(order.total_price), - total_discount: Number(order.total_discount), - total_shipping: Number(order.total_shipping), - total_tax: Number(order.total_tax), - fulfillment_status: order.fulfillment_status, - customer_id: order.id_ecom_customer, - field_mappings: field_mappings, - remote_id: order.remote_id, - created_at: order.created_at.toISOString(), - modified_at: order.modified_at.toISOString(), - }; - }), - ); + include: { + attribute: true, + }, + }); - let res: UnifiedEcommerceOrderOutput[] = UnifiedEcommerceOrders; + // Create a map to store unique field mappings + const fieldMappingsMap = new Map(); - if (remote_data) { - const remote_array_data: UnifiedEcommerceOrderOutput[] = await Promise.all( - res.map(async (order) => { - const resp = await this.prisma.remote_data.findFirst({ - where: { - ressource_owner_id: order.id, - }, + values.forEach((value) => { + fieldMappingsMap.set(value.attribute.slug, value.data); }); - const remote_data = JSON.parse(resp.data); - return { ...order, remote_data }; + + // Convert the map to an array of objects + const field_mappings = Object.fromEntries(fieldMappingsMap); + + // Transform to UnifiedEcommerceOrderOutput format + return { + id: order.id_ecom_order, + order_status: order.order_status, + order_number: order.order_number, + payment_status: order.payment_status, + currency: order.currency as CurrencyCode, + total_price: Number(order.total_price), + total_discount: Number(order.total_discount), + total_shipping: Number(order.total_shipping), + total_tax: Number(order.total_tax), + fulfillment_status: order.fulfillment_status, + customer_id: order.id_ecom_customer, + field_mappings: field_mappings, + remote_id: order.remote_id, + created_at: order.created_at.toISOString(), + modified_at: order.modified_at.toISOString(), + }; }), ); + let res: UnifiedEcommerceOrderOutput[] = UnifiedEcommerceOrders; + + if (remote_data) { + const remote_array_data: UnifiedEcommerceOrderOutput[] = + await Promise.all( + res.map(async (order) => { + const resp = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: order.id, + }, + }); + const remote_data = JSON.parse(resp.data); + return { ...order, remote_data }; + }), + ); + res = remote_array_data; } diff --git a/packages/api/src/hris/bankinfo/bankinfo.controller.ts b/packages/api/src/hris/bankinfo/bankinfo.controller.ts index d97f772a6..b1c2a6db7 100644 --- a/packages/api/src/hris/bankinfo/bankinfo.controller.ts +++ b/packages/api/src/hris/bankinfo/bankinfo.controller.ts @@ -7,6 +7,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -32,7 +34,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/bankinfos') @Controller('hris/bankinfos') export class BankinfoController { @@ -107,6 +108,7 @@ export class BankinfoController { example: 'b008e199-eda9-4629-bd41-a01b6195864a', }) @ApiGetCustomResponse(UnifiedHrisBankinfoOutput) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @UseGuards(ApiKeyAuthGuard) @Get(':id') async retrieve( diff --git a/packages/api/src/hris/bankinfo/types/index.ts b/packages/api/src/hris/bankinfo/types/index.ts index bf5581db7..c1eb22cf9 100644 --- a/packages/api/src/hris/bankinfo/types/index.ts +++ b/packages/api/src/hris/bankinfo/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface IBankInfoService { - addBankinfo( - bankinfoData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/benefit/benefit.controller.ts b/packages/api/src/hris/benefit/benefit.controller.ts index cfdaf0aa5..b19e25efc 100644 --- a/packages/api/src/hris/benefit/benefit.controller.ts +++ b/packages/api/src/hris/benefit/benefit.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -33,7 +35,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/benefits') @Controller('hris/benefits') export class BenefitController { @@ -56,6 +57,7 @@ export class BenefitController { example: 'b008e199-eda9-4629-bd41-a01b6195864a', }) @ApiPaginatedResponse(UnifiedHrisBenefitOutput) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @UseGuards(ApiKeyAuthGuard) @Get() async getBenefits( diff --git a/packages/api/src/hris/benefit/benefit.module.ts b/packages/api/src/hris/benefit/benefit.module.ts index 6dd0b8eb4..30ef672e5 100644 --- a/packages/api/src/hris/benefit/benefit.module.ts +++ b/packages/api/src/hris/benefit/benefit.module.ts @@ -8,6 +8,7 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; import { GustoService } from './services/gusto'; import { GustoBenefitMapper } from './services/gusto/mappers'; +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [BenefitController], @@ -18,6 +19,7 @@ import { GustoBenefitMapper } from './services/gusto/mappers'; ServiceRegistry, IngestDataService, CoreUnification, + Utils, GustoBenefitMapper, /* PROVIDERS SERVICES */ GustoService, diff --git a/packages/api/src/hris/benefit/sync/sync.service.ts b/packages/api/src/hris/benefit/sync/sync.service.ts index 6ede672db..054265fd2 100644 --- a/packages/api/src/hris/benefit/sync/sync.service.ts +++ b/packages/api/src/hris/benefit/sync/sync.service.ts @@ -71,7 +71,7 @@ export class SyncService implements OnModuleInit, IBaseSync { async syncForLinkedUser(param: SyncLinkedUserType) { try { - const { integrationId, linkedUserId } = param; + const { integrationId, linkedUserId, id_employee } = param; const service: IBenefitService = this.serviceRegistry.getService(integrationId); if (!service) return; @@ -80,7 +80,14 @@ export class SyncService implements OnModuleInit, IBaseSync { UnifiedHrisBenefitOutput, OriginalBenefitOutput, IBenefitService - >(integrationId, linkedUserId, 'hris', 'benefit', service, []); + >(integrationId, linkedUserId, 'hris', 'benefit', service, [ + { + param: id_employee, + paramName: 'id_employee', + shouldPassToService: true, + shouldPassToIngest: true, + }, + ]); } catch (error) { throw error; } diff --git a/packages/api/src/hris/benefit/types/index.ts b/packages/api/src/hris/benefit/types/index.ts index d17fe41a9..ae78e2b9a 100644 --- a/packages/api/src/hris/benefit/types/index.ts +++ b/packages/api/src/hris/benefit/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface IBenefitService { - addBenefit?( - benefitData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/company/company.controller.ts b/packages/api/src/hris/company/company.controller.ts index 779ef9db6..842101332 100644 --- a/packages/api/src/hris/company/company.controller.ts +++ b/packages/api/src/hris/company/company.controller.ts @@ -6,6 +6,8 @@ import { Param, Query, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { ApiHeader, @@ -47,6 +49,7 @@ export class CompanyController { }) @ApiPaginatedResponse(UnifiedHrisCompanyOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getCompanies( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/company/company.module.ts b/packages/api/src/hris/company/company.module.ts index a5aaabb66..eae8227f4 100644 --- a/packages/api/src/hris/company/company.module.ts +++ b/packages/api/src/hris/company/company.module.ts @@ -8,7 +8,7 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; import { GustoCompanyMapper } from './services/gusto/mappers'; import { GustoService } from './services/gusto'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [CompanyController], providers: [ @@ -16,6 +16,7 @@ import { GustoService } from './services/gusto'; CoreUnification, SyncService, WebhookService, + Utils, ServiceRegistry, IngestDataService, GustoCompanyMapper, diff --git a/packages/api/src/hris/company/services/gusto/mappers.ts b/packages/api/src/hris/company/services/gusto/mappers.ts index 2c12f51d5..1aab6c009 100644 --- a/packages/api/src/hris/company/services/gusto/mappers.ts +++ b/packages/api/src/hris/company/services/gusto/mappers.ts @@ -60,7 +60,7 @@ export class GustoCompanyMapper implements ICompanyMapper { customFieldMappings?: { slug: string; remote_id: string }[], ): Promise { const opts: any = {}; - if (company.locations && company.locations.length > 0) { + /*if (company.locations && company.locations.length > 0) { const locations = await this.ingestService.ingestData< UnifiedHrisLocationOutput, GustoLocationOutput @@ -75,7 +75,7 @@ export class GustoCompanyMapper implements ICompanyMapper { if (locations) { opts.locations = locations; } - } + }*/ return { remote_id: company.uuid || null, legal_name: company.name || null, @@ -85,35 +85,4 @@ export class GustoCompanyMapper implements ICompanyMapper { ...opts, }; } - - private mapEntityType(entityType?: string): string | undefined { - switch (entityType) { - case 'C-Corporation': - return 'C-Corporation'; - case 'S-Corporation': - return 'S-Corporation'; - case 'Sole proprietor': - return 'Sole proprietor'; - case 'LLC': - return 'LLC'; - case 'LLP': - return 'LLP'; - case 'Limited partnership': - return 'Limited partnership'; - case 'Co-ownership': - return 'Co-ownership'; - case 'Association': - return 'Association'; - case 'Trusteeship': - return 'Trusteeship'; - case 'General partnership': - return 'General partnership'; - case 'Joint venture': - return 'Joint venture'; - case 'Non-Profit': - return 'Non-Profit'; - default: - return undefined; - } - } } diff --git a/packages/api/src/hris/company/types/index.ts b/packages/api/src/hris/company/types/index.ts index 2c5260695..d07fe0dc7 100644 --- a/packages/api/src/hris/company/types/index.ts +++ b/packages/api/src/hris/company/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface ICompanyService { - addCompany( - companyData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/dependent/dependent.controller.ts b/packages/api/src/hris/dependent/dependent.controller.ts index 391da2176..90fa2e949 100644 --- a/packages/api/src/hris/dependent/dependent.controller.ts +++ b/packages/api/src/hris/dependent/dependent.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -33,7 +35,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/dependents') @Controller('hris/dependents') export class DependentController { @@ -57,6 +58,7 @@ export class DependentController { }) @ApiPaginatedResponse(UnifiedHrisDependentOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getDependents( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/dependent/dependent.module.ts b/packages/api/src/hris/dependent/dependent.module.ts index 164fe3ad6..22423cbdc 100644 --- a/packages/api/src/hris/dependent/dependent.module.ts +++ b/packages/api/src/hris/dependent/dependent.module.ts @@ -1,33 +1,21 @@ import { Module } from '@nestjs/common'; import { DependentController } from './dependent.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { DependentService } from './services/dependent.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [DependentController], providers: [ DependentService, - + Utils, CoreUnification, - SyncService, WebhookService, - ServiceRegistry, - IngestDataService, /* PROVIDERS SERVICES */ ], diff --git a/packages/api/src/hris/dependent/types/index.ts b/packages/api/src/hris/dependent/types/index.ts index 5c268bd78..deea11064 100644 --- a/packages/api/src/hris/dependent/types/index.ts +++ b/packages/api/src/hris/dependent/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface IDependentService { - addDependent( - dependentData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/employee/employee.controller.ts b/packages/api/src/hris/employee/employee.controller.ts index 63d3ac95e..d0274e026 100644 --- a/packages/api/src/hris/employee/employee.controller.ts +++ b/packages/api/src/hris/employee/employee.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -34,7 +36,6 @@ import { ApiPostCustomResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/employees') @Controller('hris/employees') export class EmployeeController { @@ -58,6 +59,7 @@ export class EmployeeController { }) @ApiPaginatedResponse(UnifiedHrisEmployeeOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getEmployees( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/employee/employee.module.ts b/packages/api/src/hris/employee/employee.module.ts index 1ce66118c..ed03dd83f 100644 --- a/packages/api/src/hris/employee/employee.module.ts +++ b/packages/api/src/hris/employee/employee.module.ts @@ -8,13 +8,14 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; import { GustoEmployeeMapper } from './services/gusto/mappers'; import { GustoService } from './services/gusto'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [EmployeeController], providers: [ EmployeeService, CoreUnification, SyncService, + Utils, WebhookService, ServiceRegistry, IngestDataService, diff --git a/packages/api/src/hris/employee/services/employee.service.ts b/packages/api/src/hris/employee/services/employee.service.ts index 9c35cca70..8790c101c 100644 --- a/packages/api/src/hris/employee/services/employee.service.ts +++ b/packages/api/src/hris/employee/services/employee.service.ts @@ -9,6 +9,12 @@ import { UnifiedHrisEmployeeOutput, } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; +import { ApiResponse } from '@@core/utils/types'; +import { OriginalEmployeeOutput } from '@@core/utils/types/original/original.hris'; +import { HrisObject } from '@panora/shared'; +import { IEmployeeService } from '../types'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class EmployeeService { @@ -17,11 +23,21 @@ export class EmployeeService { private logger: LoggerService, private webhook: WebhookService, private fieldMappingService: FieldMappingService, + private coreUnification: CoreUnification, + private ingestService: IngestDataService, private serviceRegistry: ServiceRegistry, ) { this.logger.setContext(EmployeeService.name); } + async validateLinkedUser(linkedUserId: string) { + const linkedUser = await this.prisma.linked_users.findUnique({ + where: { id_linked_user: linkedUserId }, + }); + if (!linkedUser) throw new ReferenceError('Linked User Not Found'); + return linkedUser; + } + async addEmployee( unifiedEmployeeData: UnifiedHrisEmployeeInput, connection_id: string, @@ -31,36 +47,144 @@ export class EmployeeService { remote_data?: boolean, ): Promise { try { - const service = this.serviceRegistry.getService(integrationId); - const resp = await service.addEmployee(unifiedEmployeeData, linkedUserId); + const linkedUser = await this.validateLinkedUser(linkedUserId); + + const desunifiedObject = + await this.coreUnification.desunify({ + sourceObject: unifiedEmployeeData, + targetType: HrisObject.employee, + providerName: integrationId, + vertical: 'hris', + customFieldMappings: [], + }); + + const service: IEmployeeService = + this.serviceRegistry.getService(integrationId); + const resp: ApiResponse = + await service.addEmployee(desunifiedObject, linkedUserId); + + const unifiedObject = (await this.coreUnification.unify< + OriginalEmployeeOutput[] + >({ + sourceObject: [resp.data], + targetType: HrisObject.employee, + providerName: integrationId, + vertical: 'hris', + connectionId: connection_id, + customFieldMappings: [], + })) as UnifiedHrisEmployeeOutput[]; + + const source_employee = resp.data; + const target_employee = unifiedObject[0]; + + const unique_hris_employee_id = await this.saveOrUpdateEmployee( + target_employee, + connection_id, + ); - const savedEmployee = await this.prisma.hris_employees.create({ + await this.ingestService.processRemoteData( + unique_hris_employee_id, + source_employee, + ); + + const result_employee = await this.getEmployee( + unique_hris_employee_id, + undefined, + undefined, + connection_id, + project_id, + remote_data, + ); + + const status_resp = resp.statusCode === 201 ? 'success' : 'fail'; + const event = await this.prisma.events.create({ data: { - id_hris_employee: uuidv4(), - ...unifiedEmployeeData, - remote_id: resp.data.remote_id, id_connection: connection_id, - created_at: new Date(), - modified_at: new Date(), - remote_was_deleted: false, + id_project: project_id, + id_event: uuidv4(), + status: status_resp, + type: 'hris.employee.push', + method: 'POST', + url: '/hris/employees', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, }, }); - const result: UnifiedHrisEmployeeOutput = { - ...savedEmployee, - id: savedEmployee.id_hris_employee, - }; - - if (remote_data) { - result.remote_data = resp.data; - } + await this.webhook.dispatchWebhook( + result_employee, + 'hris.employee.created', + linkedUser.id_project, + event.id_event, + ); - return result; + return result_employee; } catch (error) { throw error; } } + async saveOrUpdateEmployee( + employee: UnifiedHrisEmployeeOutput, + connectionId: string, + ): Promise { + const existingEmployee = await this.prisma.hris_employees.findFirst({ + where: { remote_id: employee.remote_id, id_connection: connectionId }, + }); + + const data: any = { + locations: employee.locations || [], + groups: employee.groups || [], + employee_number: employee.employee_number, + id_hris_company: employee.company_id, + first_name: employee.first_name, + last_name: employee.last_name, + preferred_name: employee.preferred_name, + display_full_name: employee.display_full_name, + username: employee.username, + work_email: employee.work_email, + personal_email: employee.personal_email, + mobile_phone_number: employee.mobile_phone_number, + employments: employee.employments || [], + ssn: employee.ssn, + gender: employee.gender, + ethnicity: employee.ethnicity, + marital_status: employee.marital_status, + date_of_birth: employee.date_of_birth, + start_date: employee.start_date, + employment_status: employee.employment_status, + termination_date: employee.termination_date, + avatar_url: employee.avatar_url, + modified_at: new Date(), + }; + + if (existingEmployee) { + const res = await this.prisma.hris_employees.update({ + where: { id_hris_employee: existingEmployee.id_hris_employee }, + data: data, + }); + + return res.id_hris_employee; + } else { + data.created_at = new Date(); + data.remote_id = employee.remote_id; + data.id_connection = connectionId; + data.id_hris_employee = uuidv4(); + data.remote_was_deleted = employee.remote_was_deleted ?? false; + data.remote_created_at = employee.remote_created_at + ? new Date(employee.remote_created_at) + : null; + + const newEmployee = await this.prisma.hris_employees.create({ + data: data, + }); + + return newEmployee.id_hris_employee; + } + } + async getEmployee( id_hris_employee: string, linkedUserId: string, diff --git a/packages/api/src/hris/employee/services/gusto/index.ts b/packages/api/src/hris/employee/services/gusto/index.ts index f2a416eb3..942f1a548 100644 --- a/packages/api/src/hris/employee/services/gusto/index.ts +++ b/packages/api/src/hris/employee/services/gusto/index.ts @@ -28,7 +28,7 @@ export class GustoService implements IEmployeeService { async sync(data: SyncParam): Promise> { try { - const { linkedUserId, company_id } = data; + const { linkedUserId, id_company } = data; const connection = await this.prisma.connections.findFirst({ where: { @@ -40,7 +40,7 @@ export class GustoService implements IEmployeeService { const company = await this.prisma.hris_companies.findUnique({ where: { - id_hris_company: company_id as string, + id_hris_company: id_company as string, }, select: { remote_id: true, diff --git a/packages/api/src/hris/employee/services/gusto/mappers.ts b/packages/api/src/hris/employee/services/gusto/mappers.ts index 03114adc8..6ea34d7f6 100644 --- a/packages/api/src/hris/employee/services/gusto/mappers.ts +++ b/packages/api/src/hris/employee/services/gusto/mappers.ts @@ -108,7 +108,7 @@ export class GustoEmployeeMapper implements IEmployeeMapper { [], ); if (employments) { - opts.employments = employments; + opts.employments = employments.map((emp) => emp.id_hris_employment); } } diff --git a/packages/api/src/hris/employee/sync/sync.service.ts b/packages/api/src/hris/employee/sync/sync.service.ts index 8ff6cf57b..5a3e08894 100644 --- a/packages/api/src/hris/employee/sync/sync.service.ts +++ b/packages/api/src/hris/employee/sync/sync.service.ts @@ -72,7 +72,7 @@ export class SyncService implements OnModuleInit, IBaseSync { async syncForLinkedUser(param: SyncLinkedUserType) { try { - const { integrationId, linkedUserId } = param; + const { integrationId, linkedUserId, id_company } = param; const service: IEmployeeService = this.serviceRegistry.getService(integrationId); if (!service) return; @@ -81,7 +81,14 @@ export class SyncService implements OnModuleInit, IBaseSync { UnifiedHrisEmployeeOutput, OriginalEmployeeOutput, IEmployeeService - >(integrationId, linkedUserId, 'hris', 'employee', service, []); + >(integrationId, linkedUserId, 'hris', 'employee', service, [ + { + param: id_company, + paramName: 'id_company', + shouldPassToService: true, + shouldPassToIngest: true, + }, + ]); } catch (error) { throw error; } diff --git a/packages/api/src/hris/employeepayrollrun/employeepayrollrun.controller.ts b/packages/api/src/hris/employeepayrollrun/employeepayrollrun.controller.ts index 877e7dd27..ec3cddb5e 100644 --- a/packages/api/src/hris/employeepayrollrun/employeepayrollrun.controller.ts +++ b/packages/api/src/hris/employeepayrollrun/employeepayrollrun.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -33,7 +35,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/employeepayrollruns') @Controller('hris/employeepayrollruns') export class EmployeePayrollRunController { @@ -57,6 +58,7 @@ export class EmployeePayrollRunController { }) @ApiPaginatedResponse(UnifiedHrisEmployeepayrollrunOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getEmployeePayrollRuns( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/employeepayrollrun/employeepayrollrun.module.ts b/packages/api/src/hris/employeepayrollrun/employeepayrollrun.module.ts index 027bf0108..e918446e9 100644 --- a/packages/api/src/hris/employeepayrollrun/employeepayrollrun.module.ts +++ b/packages/api/src/hris/employeepayrollrun/employeepayrollrun.module.ts @@ -1,32 +1,22 @@ import { Module } from '@nestjs/common'; import { EmployeePayrollRunController } from './employeepayrollrun.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { EmployeePayrollRunService } from './services/employeepayrollrun.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [EmployeePayrollRunController], providers: [ EmployeePayrollRunService, CoreUnification, - + Utils, SyncService, WebhookService, - ServiceRegistry, - IngestDataService, /* PROVIDERS SERVICES */ ], diff --git a/packages/api/src/hris/employeepayrollrun/sync/sync.service.ts b/packages/api/src/hris/employeepayrollrun/sync/sync.service.ts index ffb0b043d..a10ce7a70 100644 --- a/packages/api/src/hris/employeepayrollrun/sync/sync.service.ts +++ b/packages/api/src/hris/employeepayrollrun/sync/sync.service.ts @@ -30,7 +30,7 @@ export class SyncService implements OnModuleInit, IBaseSync { private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); - this.registry.registerService('hris', 'employee_payroll_run', this); + this.registry.registerService('hris', 'employeepayrollrun', this); } async onModuleInit() { @@ -81,14 +81,7 @@ export class SyncService implements OnModuleInit, IBaseSync { UnifiedHrisEmployeepayrollrunOutput, OriginalEmployeePayrollRunOutput, IEmployeePayrollRunService - >( - integrationId, - linkedUserId, - 'hris', - 'employee_payroll_run', - service, - [], - ); + >(integrationId, linkedUserId, 'hris', 'employeepayrollrun', service, []); } catch (error) { throw error; } diff --git a/packages/api/src/hris/employeepayrollrun/types/index.ts b/packages/api/src/hris/employeepayrollrun/types/index.ts index 2949b6c28..71ef9f61e 100644 --- a/packages/api/src/hris/employeepayrollrun/types/index.ts +++ b/packages/api/src/hris/employeepayrollrun/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface IEmployeePayrollRunService { - addEmployeePayrollRun( - employeepayrollrunData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync( data: SyncParam, ): Promise>; diff --git a/packages/api/src/hris/employerbenefit/employerbenefit.controller.ts b/packages/api/src/hris/employerbenefit/employerbenefit.controller.ts index 90eb74233..d0067f6cc 100644 --- a/packages/api/src/hris/employerbenefit/employerbenefit.controller.ts +++ b/packages/api/src/hris/employerbenefit/employerbenefit.controller.ts @@ -6,6 +6,8 @@ import { Param, Query, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { ApiHeader, @@ -48,6 +50,7 @@ export class EmployerBenefitController { }) @ApiPaginatedResponse(UnifiedHrisEmployerbenefitOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getEmployerBenefits( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/employerbenefit/employerbenefit.module.ts b/packages/api/src/hris/employerbenefit/employerbenefit.module.ts index 984e0b462..29a62ed01 100644 --- a/packages/api/src/hris/employerbenefit/employerbenefit.module.ts +++ b/packages/api/src/hris/employerbenefit/employerbenefit.module.ts @@ -8,7 +8,7 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; import { GustoEmployerbenefitMapper } from './services/gusto/mappers'; import { GustoService } from './services/gusto'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [EmployerBenefitController], providers: [ @@ -17,6 +17,7 @@ import { GustoService } from './services/gusto'; SyncService, WebhookService, ServiceRegistry, + Utils, IngestDataService, GustoEmployerbenefitMapper, /* PROVIDERS SERVICES */ diff --git a/packages/api/src/hris/employerbenefit/sync/sync.service.ts b/packages/api/src/hris/employerbenefit/sync/sync.service.ts index e6702c113..ed021da8f 100644 --- a/packages/api/src/hris/employerbenefit/sync/sync.service.ts +++ b/packages/api/src/hris/employerbenefit/sync/sync.service.ts @@ -30,7 +30,7 @@ export class SyncService implements OnModuleInit, IBaseSync { private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); - this.registry.registerService('hris', 'employer_benefit', this); + this.registry.registerService('hris', 'employerbenefit', this); } async onModuleInit() { @@ -72,7 +72,7 @@ export class SyncService implements OnModuleInit, IBaseSync { async syncForLinkedUser(param: SyncLinkedUserType) { try { - const { integrationId, linkedUserId } = param; + const { integrationId, linkedUserId, id_company } = param; const service: IEmployerBenefitService = this.serviceRegistry.getService(integrationId); if (!service) return; @@ -81,7 +81,14 @@ export class SyncService implements OnModuleInit, IBaseSync { UnifiedHrisEmployerbenefitOutput, OriginalEmployerBenefitOutput, IEmployerBenefitService - >(integrationId, linkedUserId, 'hris', 'employer_benefit', service, []); + >(integrationId, linkedUserId, 'hris', 'employerbenefit', service, [ + { + param: id_company, + paramName: 'id_company', + shouldPassToService: true, + shouldPassToIngest: true, + }, + ]); } catch (error) { throw error; } diff --git a/packages/api/src/hris/employment/employment.controller.ts b/packages/api/src/hris/employment/employment.controller.ts index 3624d807f..3c8a010b7 100644 --- a/packages/api/src/hris/employment/employment.controller.ts +++ b/packages/api/src/hris/employment/employment.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -56,6 +58,7 @@ export class EmploymentController { }) @ApiPaginatedResponse(UnifiedHrisEmploymentOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getEmployments( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/employment/employment.module.ts b/packages/api/src/hris/employment/employment.module.ts index 780507515..e5d64d523 100644 --- a/packages/api/src/hris/employment/employment.module.ts +++ b/packages/api/src/hris/employment/employment.module.ts @@ -3,13 +3,11 @@ import { EmploymentController } from './employment.controller'; import { EmploymentService } from './services/employment.service'; import { ServiceRegistry } from './services/registry.service'; import { SyncService } from './sync/sync.service'; - import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; - import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; import { GustoEmploymentMapper } from './services/gusto/mappers'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [EmploymentController], providers: [ @@ -19,6 +17,7 @@ import { GustoEmploymentMapper } from './services/gusto/mappers'; WebhookService, ServiceRegistry, IngestDataService, + Utils, GustoEmploymentMapper, /* PROVIDERS SERVICES */ ], diff --git a/packages/api/src/hris/employment/services/employment.service.ts b/packages/api/src/hris/employment/services/employment.service.ts index 0b9adb891..ba0ab6d81 100644 --- a/packages/api/src/hris/employment/services/employment.service.ts +++ b/packages/api/src/hris/employment/services/employment.service.ts @@ -6,6 +6,7 @@ import { Injectable } from '@nestjs/common'; import { v4 as uuidv4 } from 'uuid'; import { UnifiedHrisEmploymentOutput } from '../types/model.unified'; import { ServiceRegistry } from './registry.service'; +import { CurrencyCode } from '@@core/utils/types'; @Injectable() export class EmploymentService { @@ -55,7 +56,7 @@ export class EmploymentService { pay_rate: Number(employment.pay_rate), pay_period: employment.pay_period, pay_frequency: employment.pay_frequency, - pay_currency: employment.pay_currency, + pay_currency: employment.pay_currency as CurrencyCode, flsa_status: employment.flsa_status, effective_date: employment.effective_date, employment_type: employment.employment_type, @@ -145,7 +146,7 @@ export class EmploymentService { pay_rate: Number(employment.pay_rate), pay_period: employment.pay_period, pay_frequency: employment.pay_frequency, - pay_currency: employment.pay_currency, + pay_currency: employment.pay_currency as CurrencyCode, flsa_status: employment.flsa_status, effective_date: employment.effective_date, employment_type: employment.employment_type, diff --git a/packages/api/src/hris/employment/types/index.ts b/packages/api/src/hris/employment/types/index.ts index 480e0bca8..3a3ec8bdc 100644 --- a/packages/api/src/hris/employment/types/index.ts +++ b/packages/api/src/hris/employment/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface IEmploymentService { - addEmployment( - employmentData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/group/group.controller.ts b/packages/api/src/hris/group/group.controller.ts index 03f83b8a9..73fe0fe09 100644 --- a/packages/api/src/hris/group/group.controller.ts +++ b/packages/api/src/hris/group/group.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -33,7 +35,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/groups') @Controller('hris/groups') export class GroupController { @@ -57,6 +58,7 @@ export class GroupController { }) @ApiPaginatedResponse(UnifiedHrisGroupOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getGroups( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/group/group.module.ts b/packages/api/src/hris/group/group.module.ts index e126e5188..9e1bdaa36 100644 --- a/packages/api/src/hris/group/group.module.ts +++ b/packages/api/src/hris/group/group.module.ts @@ -8,7 +8,7 @@ import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/w import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; import { GustoGroupMapper } from './services/gusto/mappers'; import { GustoService } from './services/gusto'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [GroupController], providers: [ @@ -19,6 +19,7 @@ import { GustoService } from './services/gusto'; ServiceRegistry, IngestDataService, GustoGroupMapper, + Utils, /* PROVIDERS SERVICES */ GustoService, ], diff --git a/packages/api/src/hris/group/sync/sync.service.ts b/packages/api/src/hris/group/sync/sync.service.ts index d4aab37a1..94888caf3 100644 --- a/packages/api/src/hris/group/sync/sync.service.ts +++ b/packages/api/src/hris/group/sync/sync.service.ts @@ -71,7 +71,7 @@ export class SyncService implements OnModuleInit, IBaseSync { async syncForLinkedUser(param: SyncLinkedUserType) { try { - const { integrationId, linkedUserId } = param; + const { integrationId, linkedUserId, id_company } = param; const service: IGroupService = this.serviceRegistry.getService(integrationId); if (!service) return; @@ -80,7 +80,14 @@ export class SyncService implements OnModuleInit, IBaseSync { UnifiedHrisGroupOutput, OriginalGroupOutput, IGroupService - >(integrationId, linkedUserId, 'hris', 'group', service, []); + >(integrationId, linkedUserId, 'hris', 'group', service, [ + { + param: id_company, + paramName: 'id_company', + shouldPassToService: true, + shouldPassToIngest: true, + }, + ]); } catch (error) { throw error; } diff --git a/packages/api/src/hris/hris.module.ts b/packages/api/src/hris/hris.module.ts index b7522a13c..87f23517b 100644 --- a/packages/api/src/hris/hris.module.ts +++ b/packages/api/src/hris/hris.module.ts @@ -14,6 +14,7 @@ import { PayrollRunModule } from './payrollrun/payrollrun.module'; import { TimeoffModule } from './timeoff/timeoff.module'; import { TimeoffBalanceModule } from './timeoffbalance/timeoffbalance.module'; import { TimesheetentryModule } from './timesheetentry/timesheetentry.module'; +import { HrisUnificationService } from './@lib/@unification'; @Module({ exports: [ @@ -33,6 +34,7 @@ import { TimesheetentryModule } from './timesheetentry/timesheetentry.module'; TimeoffBalanceModule, TimesheetentryModule, ], + providers: [HrisUnificationService], imports: [ BankInfoModule, BenefitModule, diff --git a/packages/api/src/hris/location/location.controller.ts b/packages/api/src/hris/location/location.controller.ts index 218f0a4a7..009a75e0e 100644 --- a/packages/api/src/hris/location/location.controller.ts +++ b/packages/api/src/hris/location/location.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -33,7 +35,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/locations') @Controller('hris/locations') export class LocationController { @@ -57,6 +58,7 @@ export class LocationController { }) @ApiPaginatedResponse(UnifiedHrisLocationOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getLocations( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/location/location.module.ts b/packages/api/src/hris/location/location.module.ts index 8f4e42f0c..37c5c4fd1 100644 --- a/packages/api/src/hris/location/location.module.ts +++ b/packages/api/src/hris/location/location.module.ts @@ -1,32 +1,21 @@ import { Module } from '@nestjs/common'; import { LocationController } from './location.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { LocationService } from './services/location.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [LocationController], providers: [ LocationService, CoreUnification, - + Utils, SyncService, WebhookService, - ServiceRegistry, - IngestDataService, /* PROVIDERS SERVICES */ ], diff --git a/packages/api/src/hris/location/services/gusto/mappers.ts b/packages/api/src/hris/location/services/gusto/mappers.ts index 511e7a4a4..151835854 100644 --- a/packages/api/src/hris/location/services/gusto/mappers.ts +++ b/packages/api/src/hris/location/services/gusto/mappers.ts @@ -58,7 +58,7 @@ export class GustoLocationMapper implements ILocationMapper { ): Promise { const opts: any = {}; - if (location.employee_uuid) { + /*if (location.employee_uuid) { const employee_id = await this.utils.getEmployeeUuidFromRemoteId( location.employee_uuid, connectionId, @@ -88,6 +88,7 @@ export class GustoLocationMapper implements ILocationMapper { ? parseFloat(location.company_contribution) : null, remote_was_deleted: null, - }; + };*/ + return; } } diff --git a/packages/api/src/hris/location/types/index.ts b/packages/api/src/hris/location/types/index.ts index 3cd608bda..d045e44b6 100644 --- a/packages/api/src/hris/location/types/index.ts +++ b/packages/api/src/hris/location/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface ILocationService { - addLocation?( - locationData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/paygroup/paygroup.controller.ts b/packages/api/src/hris/paygroup/paygroup.controller.ts index 8cf874362..81e7d69a5 100644 --- a/packages/api/src/hris/paygroup/paygroup.controller.ts +++ b/packages/api/src/hris/paygroup/paygroup.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -33,7 +35,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/paygroups') @Controller('hris/paygroups') export class PayGroupController { @@ -57,6 +58,7 @@ export class PayGroupController { }) @ApiPaginatedResponse(UnifiedHrisPaygroupOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getPayGroups( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/paygroup/paygroup.module.ts b/packages/api/src/hris/paygroup/paygroup.module.ts index c628cfd49..d13fe5594 100644 --- a/packages/api/src/hris/paygroup/paygroup.module.ts +++ b/packages/api/src/hris/paygroup/paygroup.module.ts @@ -1,33 +1,21 @@ import { Module } from '@nestjs/common'; import { PayGroupController } from './paygroup.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PayGroupService } from './services/paygroup.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [PayGroupController], providers: [ PayGroupService, - + Utils, CoreUnification, - SyncService, WebhookService, - ServiceRegistry, - IngestDataService, /* PROVIDERS SERVICES */ ], diff --git a/packages/api/src/hris/paygroup/types/index.ts b/packages/api/src/hris/paygroup/types/index.ts index d2cc3cf90..d302e1edc 100644 --- a/packages/api/src/hris/paygroup/types/index.ts +++ b/packages/api/src/hris/paygroup/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface IPayGroupService { - addPayGroup( - paygroupData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/payrollrun/payrollrun.controller.ts b/packages/api/src/hris/payrollrun/payrollrun.controller.ts index e9a84b27a..b0f31b6a7 100644 --- a/packages/api/src/hris/payrollrun/payrollrun.controller.ts +++ b/packages/api/src/hris/payrollrun/payrollrun.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -33,7 +35,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/payrollruns') @Controller('hris/payrollruns') export class PayrollRunController { @@ -57,6 +58,7 @@ export class PayrollRunController { }) @ApiPaginatedResponse(UnifiedHrisPayrollrunOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getPayrollRuns( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/payrollrun/payrollrun.module.ts b/packages/api/src/hris/payrollrun/payrollrun.module.ts index 2e94d346d..a7ffb3724 100644 --- a/packages/api/src/hris/payrollrun/payrollrun.module.ts +++ b/packages/api/src/hris/payrollrun/payrollrun.module.ts @@ -1,33 +1,21 @@ import { Module } from '@nestjs/common'; import { PayrollRunController } from './payrollrun.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PayrollRunService } from './services/payrollrun.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [PayrollRunController], providers: [ PayrollRunService, CoreUnification, - + Utils, SyncService, - WebhookService, - ServiceRegistry, - IngestDataService, /* PROVIDERS SERVICES */ ], diff --git a/packages/api/src/hris/payrollrun/types/index.ts b/packages/api/src/hris/payrollrun/types/index.ts index a7933d143..a8f390412 100644 --- a/packages/api/src/hris/payrollrun/types/index.ts +++ b/packages/api/src/hris/payrollrun/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface IPayrollRunService { - addPayrollRun( - payrollrunData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/timeoff/services/timeoff.service.ts b/packages/api/src/hris/timeoff/services/timeoff.service.ts index 947f1b834..780c1d37a 100644 --- a/packages/api/src/hris/timeoff/services/timeoff.service.ts +++ b/packages/api/src/hris/timeoff/services/timeoff.service.ts @@ -11,6 +11,11 @@ import { } from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; +import { OriginalTimeoffOutput } from '@@core/utils/types/original/original.hris'; +import { HrisObject } from '@panora/shared'; +import { ITimeoffService } from '../types'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Injectable() export class TimeoffService { @@ -19,11 +24,21 @@ export class TimeoffService { private logger: LoggerService, private webhook: WebhookService, private fieldMappingService: FieldMappingService, + private coreUnification: CoreUnification, + private ingestService: IngestDataService, private serviceRegistry: ServiceRegistry, ) { this.logger.setContext(TimeoffService.name); } + async validateLinkedUser(linkedUserId: string) { + const linkedUser = await this.prisma.linked_users.findUnique({ + where: { id_linked_user: linkedUserId }, + }); + if (!linkedUser) throw new ReferenceError('Linked User Not Found'); + return linkedUser; + } + async addTimeoff( unifiedTimeoffData: UnifiedHrisTimeoffInput, connectionId: string, @@ -33,49 +48,133 @@ export class TimeoffService { remote_data?: boolean, ): Promise { try { - const service = this.serviceRegistry.getService(integrationId); - const resp = await service.addTimeoff(unifiedTimeoffData, linkedUserId); + const linkedUser = await this.validateLinkedUser(linkedUserId); + // Add any necessary validations here, e.g., validateEmployeeId if needed + + const desunifiedObject = + await this.coreUnification.desunify({ + sourceObject: unifiedTimeoffData, + targetType: HrisObject.timeoff, + providerName: integrationId, + vertical: 'hris', + customFieldMappings: [], + }); + + const service: ITimeoffService = + this.serviceRegistry.getService(integrationId); + const resp: ApiResponse = await service.addTimeoff( + desunifiedObject, + linkedUserId, + ); + + const unifiedObject = (await this.coreUnification.unify< + OriginalTimeoffOutput[] + >({ + sourceObject: [resp.data], + targetType: HrisObject.timeoff, + providerName: integrationId, + vertical: 'hris', + connectionId: connectionId, + customFieldMappings: [], + })) as UnifiedHrisTimeoffOutput[]; + + const source_timeoff = resp.data; + const target_timeoff = unifiedObject[0]; + + const unique_hris_timeoff_id = await this.saveOrUpdateTimeoff( + target_timeoff, + connectionId, + ); + + await this.ingestService.processRemoteData( + unique_hris_timeoff_id, + source_timeoff, + ); + + const result_timeoff = await this.getTimeoff( + unique_hris_timeoff_id, + undefined, + undefined, + connectionId, + projectId, + remote_data, + ); - const savedTimeOff = await this.prisma.hris_time_off.create({ + const status_resp = resp.statusCode === 201 ? 'success' : 'fail'; + const event = await this.prisma.events.create({ data: { - id_hris_time_off: uuidv4(), - ...unifiedTimeoffData, - amount: unifiedTimeoffData.amount - ? BigInt(unifiedTimeoffData.amount) - : null, - start_time: unifiedTimeoffData.start_time - ? new Date(unifiedTimeoffData.start_time) - : null, - end_time: unifiedTimeoffData.end_time - ? new Date(unifiedTimeoffData.end_time) - : null, - remote_id: resp.data.remote_id, id_connection: connectionId, - created_at: new Date(), - modified_at: new Date(), - remote_created_at: resp.data.remote_created_at - ? new Date(resp.data.remote_created_at) - : null, - remote_was_deleted: false, + id_project: projectId, + id_event: uuidv4(), + status: status_resp, + type: 'hris.timeoff.push', + method: 'POST', + url: '/hris/timeoff', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, }, }); - const result: UnifiedHrisTimeoffOutput = { - ...savedTimeOff, - id: savedTimeOff.id_hris_time_off, - amount: Number(savedTimeOff.amount), - }; - - if (remote_data) { - result.remote_data = resp.data; - } + await this.webhook.dispatchWebhook( + result_timeoff, + 'hris.timeoff.created', + linkedUser.id_project, + event.id_event, + ); - return result; + return result_timeoff; } catch (error) { throw error; } } + async saveOrUpdateTimeoff( + timeoff: UnifiedHrisTimeoffOutput, + connectionId: string, + ): Promise { + const existingTimeoff = await this.prisma.hris_time_off.findFirst({ + where: { remote_id: timeoff.remote_id, id_connection: connectionId }, + }); + + const data: any = { + employee: timeoff.employee, + approver: timeoff.approver, + status: timeoff.status, + employee_note: timeoff.employee_note, + units: timeoff.units, + amount: timeoff.amount ? BigInt(timeoff.amount) : null, + request_type: timeoff.request_type, + start_time: timeoff.start_time ? new Date(timeoff.start_time) : null, + end_time: timeoff.end_time ? new Date(timeoff.end_time) : null, + field_mappings: timeoff.field_mappings, + modified_at: new Date(), + }; + + if (existingTimeoff) { + const res = await this.prisma.hris_time_off.update({ + where: { id_hris_time_off: existingTimeoff.id_hris_time_off }, + data: data, + }); + + return res.id_hris_time_off; + } else { + data.created_at = new Date(); + data.remote_id = timeoff.remote_id; + data.id_connection = connectionId; + data.id_hris_time_off = uuidv4(); + data.remote_was_deleted = timeoff.remote_was_deleted ?? false; + data.remote_created_at = timeoff.remote_created_at + ? new Date(timeoff.remote_created_at) + : null; + + const newTimeoff = await this.prisma.hris_time_off.create({ data: data }); + + return newTimeoff.id_hris_time_off; + } + } + async getTimeoff( id_hris_time_off: string, linkedUserId: string, diff --git a/packages/api/src/hris/timeoff/sync/sync.service.ts b/packages/api/src/hris/timeoff/sync/sync.service.ts index 710d8b6fb..be3841b5a 100644 --- a/packages/api/src/hris/timeoff/sync/sync.service.ts +++ b/packages/api/src/hris/timeoff/sync/sync.service.ts @@ -30,7 +30,7 @@ export class SyncService implements OnModuleInit, IBaseSync { private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); - this.registry.registerService('hris', 'time_off', this); + this.registry.registerService('hris', 'timeoff', this); } async onModuleInit() { @@ -81,7 +81,7 @@ export class SyncService implements OnModuleInit, IBaseSync { UnifiedHrisTimeoffOutput, OriginalTimeoffOutput, ITimeoffService - >(integrationId, linkedUserId, 'hris', 'time_off', service, []); + >(integrationId, linkedUserId, 'hris', 'timeoff', service, []); } catch (error) { throw error; } diff --git a/packages/api/src/hris/timeoff/timeoff.controller.ts b/packages/api/src/hris/timeoff/timeoff.controller.ts index 46099a778..d8895ecc1 100644 --- a/packages/api/src/hris/timeoff/timeoff.controller.ts +++ b/packages/api/src/hris/timeoff/timeoff.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -57,6 +59,7 @@ export class TimeoffController { }) @ApiPaginatedResponse(UnifiedHrisTimeoffOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getTimeoffs( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/timeoff/timeoff.module.ts b/packages/api/src/hris/timeoff/timeoff.module.ts index f1b718b92..86d017aa9 100644 --- a/packages/api/src/hris/timeoff/timeoff.module.ts +++ b/packages/api/src/hris/timeoff/timeoff.module.ts @@ -1,32 +1,21 @@ import { Module } from '@nestjs/common'; -import { TimeoffController } from './timeoff.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { TimeoffService } from './services/timeoff.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { TimeoffService } from './services/timeoff.service'; +import { SyncService } from './sync/sync.service'; +import { TimeoffController } from './timeoff.controller'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [TimeoffController], providers: [ TimeoffService, CoreUnification, - + Utils, SyncService, WebhookService, - ServiceRegistry, - IngestDataService, /* PROVIDERS SERVICES */ ], diff --git a/packages/api/src/hris/timeoffbalance/sync/sync.service.ts b/packages/api/src/hris/timeoffbalance/sync/sync.service.ts index eb2988a72..165f5fc2a 100644 --- a/packages/api/src/hris/timeoffbalance/sync/sync.service.ts +++ b/packages/api/src/hris/timeoffbalance/sync/sync.service.ts @@ -30,7 +30,7 @@ export class SyncService implements OnModuleInit, IBaseSync { private ingestService: IngestDataService, ) { this.logger.setContext(SyncService.name); - this.registry.registerService('hris', 'time_off_balance', this); + this.registry.registerService('hris', 'timeoffbalance', this); } async onModuleInit() { @@ -81,7 +81,7 @@ export class SyncService implements OnModuleInit, IBaseSync { UnifiedHrisTimeoffbalanceOutput, OriginalTimeoffBalanceOutput, ITimeoffBalanceService - >(integrationId, linkedUserId, 'hris', 'time_off_balance', service, []); + >(integrationId, linkedUserId, 'hris', 'timeoffbalance', service, []); } catch (error) { throw error; } diff --git a/packages/api/src/hris/timeoffbalance/timeoffbalance.controller.ts b/packages/api/src/hris/timeoffbalance/timeoffbalance.controller.ts index f01602fbd..3e99ab567 100644 --- a/packages/api/src/hris/timeoffbalance/timeoffbalance.controller.ts +++ b/packages/api/src/hris/timeoffbalance/timeoffbalance.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -33,7 +35,6 @@ import { ApiPaginatedResponse, } from '@@core/utils/dtos/openapi.respone.dto'; - @ApiTags('hris/timeoffbalances') @Controller('hris/timeoffbalances') export class TimeoffBalanceController { @@ -57,6 +58,7 @@ export class TimeoffBalanceController { }) @ApiPaginatedResponse(UnifiedHrisTimeoffbalanceOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getTimeoffBalances( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/timeoffbalance/timeoffbalance.module.ts b/packages/api/src/hris/timeoffbalance/timeoffbalance.module.ts index 8934473ee..106e1bdec 100644 --- a/packages/api/src/hris/timeoffbalance/timeoffbalance.module.ts +++ b/packages/api/src/hris/timeoffbalance/timeoffbalance.module.ts @@ -1,32 +1,21 @@ import { Module } from '@nestjs/common'; -import { TimeoffBalanceController } from './timeoffbalance.controller'; -import { SyncService } from './sync/sync.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { TimeoffBalanceService } from './services/timeoffbalance.service'; import { ServiceRegistry } from './services/registry.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; - -import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { BullModule } from '@nestjs/bull'; -import { ConnectionUtils } from '@@core/connections/@utils'; -import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { TimeoffBalanceService } from './services/timeoffbalance.service'; +import { SyncService } from './sync/sync.service'; +import { TimeoffBalanceController } from './timeoffbalance.controller'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; -import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; - +import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [TimeoffBalanceController], providers: [ TimeoffBalanceService, CoreUnification, - + Utils, SyncService, WebhookService, - ServiceRegistry, - IngestDataService, /* PROVIDERS SERVICES */ ], diff --git a/packages/api/src/hris/timeoffbalance/types/index.ts b/packages/api/src/hris/timeoffbalance/types/index.ts index 32fe6e43d..aef0aaf01 100644 --- a/packages/api/src/hris/timeoffbalance/types/index.ts +++ b/packages/api/src/hris/timeoffbalance/types/index.ts @@ -8,11 +8,6 @@ import { ApiResponse } from '@@core/utils/types'; import { SyncParam } from '@@core/utils/types/interface'; export interface ITimeoffBalanceService { - addTimeoffBalance( - timeoffbalanceData: DesunifyReturnType, - linkedUserId: string, - ): Promise>; - sync(data: SyncParam): Promise>; } diff --git a/packages/api/src/hris/timesheetentry/services/timesheetentry.service.ts b/packages/api/src/hris/timesheetentry/services/timesheetentry.service.ts index 03f36b9c8..aa7661e54 100644 --- a/packages/api/src/hris/timesheetentry/services/timesheetentry.service.ts +++ b/packages/api/src/hris/timesheetentry/services/timesheetentry.service.ts @@ -11,6 +11,11 @@ import { } from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { OriginalTimesheetentryOutput } from '@@core/utils/types/original/original.hris'; +import { HrisObject } from '@panora/shared'; +import { ITimesheetentryService } from '../types'; @Injectable() export class TimesheetentryService { @@ -19,67 +24,170 @@ export class TimesheetentryService { private logger: LoggerService, private webhook: WebhookService, private fieldMappingService: FieldMappingService, + private coreUnification: CoreUnification, + private ingestService: IngestDataService, private serviceRegistry: ServiceRegistry, ) { this.logger.setContext(TimesheetentryService.name); } + async validateLinkedUser(linkedUserId: string) { + const linkedUser = await this.prisma.linked_users.findUnique({ + where: { id_linked_user: linkedUserId }, + }); + if (!linkedUser) throw new ReferenceError('Linked User Not Found'); + return linkedUser; + } + async addTimesheetentry( unifiedTimesheetentryData: UnifiedHrisTimesheetEntryInput, - connection_id: string, - project_id: string, + connectionId: string, + projectId: string, integrationId: string, linkedUserId: string, remote_data?: boolean, ): Promise { try { - const service = this.serviceRegistry.getService(integrationId); - const resp = await service.addTimesheetentry( - unifiedTimesheetentryData, - linkedUserId, - ); + const linkedUser = await this.validateLinkedUser(linkedUserId); + // Add any necessary validations here, e.g., validateEmployeeId if needed - const savedTimesheetEntry = - await this.prisma.hris_timesheet_entries.create({ - data: { - id_hris_timesheet_entry: uuidv4(), - ...unifiedTimesheetentryData, - hours_worked: unifiedTimesheetentryData.hours_worked - ? BigInt(unifiedTimesheetentryData.hours_worked) - : null, - start_time: unifiedTimesheetentryData.start_time - ? new Date(unifiedTimesheetentryData.start_time) - : null, - end_time: unifiedTimesheetentryData.end_time - ? new Date(unifiedTimesheetentryData.end_time) - : null, - remote_id: resp.data.remote_id, - id_connection: connection_id, - created_at: new Date(), - modified_at: new Date(), - remote_created_at: resp.data.remote_created_at - ? new Date(resp.data.remote_created_at) - : null, - remote_was_deleted: false, - }, + const desunifiedObject = + await this.coreUnification.desunify({ + sourceObject: unifiedTimesheetentryData, + targetType: HrisObject.timesheetentry, + providerName: integrationId, + vertical: 'hris', + customFieldMappings: [], }); - const result: UnifiedHrisTimesheetEntryOutput = { - ...savedTimesheetEntry, - id: savedTimesheetEntry.id_hris_timesheet_entry, - hours_worked: Number(savedTimesheetEntry.hours_worked), - }; + const service: ITimesheetentryService = + this.serviceRegistry.getService(integrationId); + const resp: ApiResponse = + await service.addTimesheetentry(desunifiedObject, linkedUserId); - if (remote_data) { - result.remote_data = resp.data; - } + const unifiedObject = (await this.coreUnification.unify< + OriginalTimesheetentryOutput[] + >({ + sourceObject: [resp.data], + targetType: HrisObject.timesheetentry, + providerName: integrationId, + vertical: 'hris', + connectionId: connectionId, + customFieldMappings: [], + })) as UnifiedHrisTimesheetEntryOutput[]; + + const source_timesheetentry = resp.data; + const target_timesheetentry = unifiedObject[0]; + + const unique_hris_timesheetentry_id = + await this.saveOrUpdateTimesheetentry( + target_timesheetentry, + connectionId, + ); + + await this.ingestService.processRemoteData( + unique_hris_timesheetentry_id, + source_timesheetentry, + ); + + const result_timesheetentry = await this.getTimesheetentry( + unique_hris_timesheetentry_id, + undefined, + undefined, + connectionId, + projectId, + remote_data, + ); + + const status_resp = resp.statusCode === 201 ? 'success' : 'fail'; + const event = await this.prisma.events.create({ + data: { + id_connection: connectionId, + id_project: projectId, + id_event: uuidv4(), + status: status_resp, + type: 'hris.timesheetentry.push', + method: 'POST', + url: '/hris/timesheetentries', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + }, + }); - return result; + await this.webhook.dispatchWebhook( + result_timesheetentry, + 'hris.timesheetentry.created', + linkedUser.id_project, + event.id_event, + ); + + return result_timesheetentry; } catch (error) { throw error; } } + private async saveOrUpdateTimesheetentry( + timesheetentry: UnifiedHrisTimesheetEntryOutput, + connectionId: string, + ): Promise { + const existingTimesheetentry = + await this.prisma.hris_timesheet_entries.findFirst({ + where: { + remote_id: timesheetentry.remote_id, + id_connection: connectionId, + }, + }); + + const data: any = { + hours_worked: timesheetentry.hours_worked + ? BigInt(timesheetentry.hours_worked) + : null, + start_time: timesheetentry.start_time + ? new Date(timesheetentry.start_time) + : null, + end_time: timesheetentry.end_time + ? new Date(timesheetentry.end_time) + : null, + id_hris_employee: timesheetentry.employee_id, + remote_was_deleted: timesheetentry.remote_was_deleted ?? false, + modified_at: new Date(), + }; + + // Only include field_mappings if it exists in the input + if (timesheetentry.field_mappings) { + data.field_mappings = timesheetentry.field_mappings; + } + + if (existingTimesheetentry) { + const res = await this.prisma.hris_timesheet_entries.update({ + where: { + id_hris_timesheet_entry: + existingTimesheetentry.id_hris_timesheet_entry, + }, + data: data, + }); + + return res.id_hris_timesheet_entry; + } else { + data.created_at = new Date(); + data.remote_id = timesheetentry.remote_id; + data.id_connection = connectionId; + data.id_hris_timesheet_entry = uuidv4(); + data.remote_created_at = timesheetentry.remote_created_at + ? new Date(timesheetentry.remote_created_at) + : null; + + const newTimesheetentry = await this.prisma.hris_timesheet_entries.create( + { data: data }, + ); + + return newTimesheetentry.id_hris_timesheet_entry; + } + } + async getTimesheetentry( id_hris_timesheet_entry: string, linkedUserId: string, diff --git a/packages/api/src/hris/timesheetentry/timesheetentry.controller.ts b/packages/api/src/hris/timesheetentry/timesheetentry.controller.ts index 1551992ad..7732a3d73 100644 --- a/packages/api/src/hris/timesheetentry/timesheetentry.controller.ts +++ b/packages/api/src/hris/timesheetentry/timesheetentry.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { @@ -57,6 +59,7 @@ export class TimesheetentryController { }) @ApiPaginatedResponse(UnifiedHrisTimesheetEntryOutput) @UseGuards(ApiKeyAuthGuard) + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) @Get() async getTimesheetentrys( @Headers('x-connection-token') connection_token: string, diff --git a/packages/api/src/hris/timesheetentry/timesheetentry.module.ts b/packages/api/src/hris/timesheetentry/timesheetentry.module.ts index 120496854..5df86b0a2 100644 --- a/packages/api/src/hris/timesheetentry/timesheetentry.module.ts +++ b/packages/api/src/hris/timesheetentry/timesheetentry.module.ts @@ -5,14 +5,14 @@ import { TimesheetentryService } from './services/timesheetentry.service'; import { SyncService } from './sync/sync.service'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; - import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; - +import { Utils } from '@hris/@lib/@utils'; @Module({ controllers: [TimesheetentryController], providers: [ TimesheetentryService, CoreUnification, + Utils, SyncService, WebhookService, ServiceRegistry, diff --git a/packages/api/swagger/swagger-spec.yaml b/packages/api/swagger/swagger-spec.yaml index 4aa2386a2..b1a3c3c1d 100644 --- a/packages/api/swagger/swagger-spec.yaml +++ b/packages/api/swagger/swagger-spec.yaml @@ -12348,7 +12348,10 @@ components: properties: account_type: type: string - example: checking + example: CHECKING + enum: + - SAVINGS + - CHECKING nullable: true description: The type of the bank account bank_name: @@ -12522,6 +12525,14 @@ components: example: Acme Corporation nullable: true description: The legal name of the company + locations: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: UUIDs of the of the Location associated with the company + type: array + items: + type: string display_name: type: string example: Acme Corp @@ -12605,7 +12616,11 @@ components: description: The middle name of the dependent relationship: type: string - example: Child + example: CHILD + enum: + - CHILD + - SPOUSE + - DOMESTIC_PARTNER nullable: true description: The relationship of the dependent to the employee date_of_birth: @@ -12616,7 +12631,13 @@ components: description: The date of birth of the dependent gender: type: string - example: Male + example: MALE + enum: + - MALE + - FEMALE + - NON-BINARY + - OTHER + - PREFER_NOT_TO_DISCLOSE nullable: true description: The gender of the dependent phone_number: @@ -12870,6 +12891,14 @@ components: type: array items: type: string + locations: + example: &ref_125 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: UUIDs of the of the Location associated with the company + type: array + items: + type: string employee_number: type: string example: EMP001 @@ -12921,9 +12950,9 @@ components: nullable: true description: The mobile phone number of the employee employments: - example: &ref_125 - - Employment1 - - Employment2 + example: &ref_126 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The employments of the employee type: array @@ -12936,17 +12965,38 @@ components: description: The Social Security Number of the employee gender: type: string - example: Male + example: MALE + enum: &ref_127 + - MALE + - FEMALE + - NON-BINARY + - OTHER + - PREFER_NOT_TO_DISCLOSE nullable: true description: The gender of the employee ethnicity: type: string - example: Caucasian + example: AMERICAN_INDIAN_OR_ALASKA_NATIVE + enum: &ref_128 + - AMERICAN_INDIAN_OR_ALASKA_NATIVE + - ASIAN_OR_INDIAN_SUBCONTINENT + - BLACK_OR_AFRICAN_AMERICAN + - HISPANIC_OR_LATINO + - NATIVE_HAWAIIAN_OR_OTHER_PACIFIC_ISLANDER + - TWO_OR_MORE_RACES + - WHITE + - PREFER_NOT_TO_DISCLOSE nullable: true description: The ethnicity of the employee marital_status: type: string example: Married + enum: &ref_129 + - SINGLE + - MARRIED_FILING_JOINTLY + - MARRIED_FILING_SEPARATELY + - HEAD_OF_HOUSEHOLD + - QUALIFYING_WIDOW_OR_WIDOWER_WITH_DEPENDENT_CHILD nullable: true description: The marital status of the employee date_of_birth: @@ -12963,7 +13013,11 @@ components: description: The start date of the employee employment_status: type: string - example: Active + example: ACTIVE + enum: &ref_130 + - ACTIVE + - PENDING + - INACTIVE nullable: true description: The employment status of the employee termination_date: @@ -12977,9 +13031,14 @@ components: example: https://example.com/avatar.jpg nullable: true description: The URL of the employee's avatar + manager_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: UUID of the manager (employee) of the employee field_mappings: type: object - example: &ref_126 + example: &ref_131 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -13036,6 +13095,13 @@ components: type: array items: type: string + locations: + example: *ref_125 + nullable: true + description: UUIDs of the of the Location associated with the company + type: array + items: + type: string employee_number: type: string example: EMP001 @@ -13087,7 +13153,7 @@ components: nullable: true description: The mobile phone number of the employee employments: - example: *ref_125 + example: *ref_126 nullable: true description: The employments of the employee type: array @@ -13100,17 +13166,20 @@ components: description: The Social Security Number of the employee gender: type: string - example: Male + example: MALE + enum: *ref_127 nullable: true description: The gender of the employee ethnicity: type: string - example: Caucasian + example: AMERICAN_INDIAN_OR_ALASKA_NATIVE + enum: *ref_128 nullable: true description: The ethnicity of the employee marital_status: type: string example: Married + enum: *ref_129 nullable: true description: The marital status of the employee date_of_birth: @@ -13127,7 +13196,8 @@ components: description: The start date of the employee employment_status: type: string - example: Active + example: ACTIVE + enum: *ref_130 nullable: true description: The employment status of the employee termination_date: @@ -13141,9 +13211,14 @@ components: example: https://example.com/avatar.jpg nullable: true description: The URL of the employee's avatar + manager_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: UUID of the manager (employee) of the employee field_mappings: type: object - example: *ref_126 + example: *ref_131 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13154,6 +13229,12 @@ components: benefit_plan_type: type: string example: Health Insurance + enum: + - MEDICAL + - HEALTH_SAVINGS + - INSURANCE + - RETIREMENT + - OTHER nullable: true description: The type of the benefit plan name: @@ -13241,22 +13322,210 @@ components: description: The pay rate of the employment pay_period: type: string - example: Monthly + example: MONTHLY + enum: + - HOUR + - DAY + - WEEK + - EVERY_TWO_WEEKS + - SEMIMONTHLY + - MONTH + - QUARTER + - EVERY_SIX_MONTHS + - YEAR nullable: true description: The pay period of the employment pay_frequency: type: string - example: Bi-weekly + example: WEEKLY + enum: + - WEEKLY + - BIWEEKLY + - MONTHLY + - QUARTERLY + - SEMIANNUALLY + - ANNUALLY + - THIRTEEN-MONTHLY + - PRO_RATA + - SEMIMONTHLY nullable: true description: The pay frequency of the employment pay_currency: type: string example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL nullable: true description: The currency of the pay flsa_status: type: string - example: Exempt + example: EXEMPT + enum: + - EXEMPT + - SALARIED_NONEXEMPT + - NONEXEMPT + - OWNER nullable: true description: The FLSA status of the employment effective_date: @@ -13267,7 +13536,13 @@ components: description: The effective date of the employment employment_type: type: string - example: Full-time + example: FULL_TIME + enum: + - FULL_TIME + - PART_TIME + - INTERN + - CONTRACTOR + - FREELANCE nullable: true description: The type of employment pay_group_id: @@ -13344,7 +13619,13 @@ components: description: The name of the group type: type: string - example: Department + example: DEPARTMENT + enum: + - TEAM + - DEPARTMENT + - COST_CENTER + - BUSINESS_UNIT + - GROUP nullable: true description: The type of the group field_mappings: @@ -13441,7 +13722,10 @@ components: description: The country of the location location_type: type: string - example: Office + example: WORK + enum: + - WORK + - HOME nullable: true description: The type of the location field_mappings: @@ -13555,12 +13839,24 @@ components: properties: run_state: type: string - example: Completed + example: PAID + enum: + - PAID + - DRAFT + - APPROVED + - FAILED + - CLOSE nullable: true description: The state of the payroll run run_type: type: string - example: Regular + example: REGULAR + enum: + - REGULAR + - OFF_CYCLE + - CORRECTION + - TERMINATION + - SIGN_ON_BONUS nullable: true description: The type of the payroll run start_date: @@ -13655,7 +13951,13 @@ components: description: The UUID of the approver for the time off request status: type: string - example: Approved + example: REQUESTED + enum: &ref_132 + - REQUESTED + - APPROVED + - DECLINED + - CANCELLED + - DELETED nullable: true description: The status of the time off request employee_note: @@ -13665,7 +13967,10 @@ components: description: A note from the employee about the time off request units: type: string - example: Days + example: DAYS + enum: &ref_133 + - HOURS + - DAYS nullable: true description: The units used for the time off (e.g., Days, Hours) amount: @@ -13675,7 +13980,14 @@ components: description: The amount of time off requested request_type: type: string - example: Vacation + example: VACATION + enum: &ref_134 + - VACATION + - SICK + - PERSONAL + - JURY_DUTY + - VOLUNTEER + - BEREAVEMENT nullable: true description: The type of time off request start_time: @@ -13692,7 +14004,7 @@ components: description: The end time of the time off field_mappings: type: object - example: &ref_127 + example: &ref_135 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -13754,7 +14066,8 @@ components: description: The UUID of the approver for the time off request status: type: string - example: Approved + example: REQUESTED + enum: *ref_132 nullable: true description: The status of the time off request employee_note: @@ -13764,7 +14077,8 @@ components: description: A note from the employee about the time off request units: type: string - example: Days + example: DAYS + enum: *ref_133 nullable: true description: The units used for the time off (e.g., Days, Hours) amount: @@ -13774,7 +14088,8 @@ components: description: The amount of time off requested request_type: type: string - example: Vacation + example: VACATION + enum: *ref_134 nullable: true description: The type of time off request start_time: @@ -13791,7 +14106,7 @@ components: description: The end time of the time off field_mappings: type: object - example: *ref_127 + example: *ref_135 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -13816,7 +14131,14 @@ components: description: The amount of time off used policy_type: type: string - example: Vacation + example: VACATION + enum: + - VACATION + - SICK + - PERSONAL + - JURY_DUTY + - VOLUNTEER + - BEREAVEMENT nullable: true description: The type of time off policy field_mappings: @@ -13902,7 +14224,7 @@ components: description: Indicates if the timesheet entry was deleted in the remote system field_mappings: type: object - example: &ref_128 + example: &ref_136 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -13975,7 +14297,7 @@ components: description: Indicates if the timesheet entry was deleted in the remote system field_mappings: type: object - example: *ref_128 + example: *ref_136 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -14033,7 +14355,7 @@ components: properties: activity_type: type: string - enum: &ref_129 + enum: &ref_137 - NOTE - EMAIL - OTHER @@ -14052,7 +14374,7 @@ components: description: The body of the activity visibility: type: string - enum: &ref_130 + enum: &ref_138 - ADMIN_ONLY - PUBLIC - PRIVATE @@ -14072,7 +14394,7 @@ components: description: The remote creation date of the activity field_mappings: type: object - example: &ref_131 + example: &ref_139 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14115,7 +14437,7 @@ components: properties: activity_type: type: string - enum: *ref_129 + enum: *ref_137 example: NOTE nullable: true description: The type of activity @@ -14131,7 +14453,7 @@ components: description: The body of the activity visibility: type: string - enum: *ref_130 + enum: *ref_138 example: PUBLIC nullable: true description: The visibility of the activity @@ -14148,7 +14470,7 @@ components: description: The remote creation date of the activity field_mappings: type: object - example: *ref_131 + example: *ref_139 additionalProperties: true nullable: true description: >- @@ -14172,7 +14494,7 @@ components: offers: nullable: true description: The offers UUIDs for the application - example: &ref_132 + example: &ref_140 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f - 12345678-1234-1234-1234-123456789012 type: array @@ -14209,7 +14531,7 @@ components: example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f field_mappings: type: object - example: &ref_133 + example: &ref_141 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14275,7 +14597,7 @@ components: offers: nullable: true description: The offers UUIDs for the application - example: *ref_132 + example: *ref_140 type: array items: type: string @@ -14310,7 +14632,7 @@ components: example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f field_mappings: type: object - example: *ref_133 + example: *ref_141 additionalProperties: true nullable: true description: >- @@ -14332,7 +14654,7 @@ components: attachment_type: type: string example: RESUME - enum: &ref_134 + enum: &ref_142 - RESUME - COVER_LETTER - OFFER_LETTER @@ -14358,7 +14680,7 @@ components: description: The UUID of the candidate field_mappings: type: object - example: &ref_135 + example: &ref_143 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14412,7 +14734,7 @@ components: attachment_type: type: string example: RESUME - enum: *ref_134 + enum: *ref_142 nullable: true description: The type of the file remote_created_at: @@ -14434,7 +14756,7 @@ components: description: The UUID of the candidate field_mappings: type: object - example: *ref_135 + example: *ref_143 additionalProperties: true nullable: true description: >- @@ -14512,37 +14834,37 @@ components: description: The last interaction date with the candidate attachments: type: array - items: &ref_136 + items: &ref_144 oneOf: - type: string - $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' - example: &ref_137 + example: &ref_145 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The attachments UUIDs of the candidate applications: type: array - items: &ref_138 + items: &ref_146 oneOf: - type: string - $ref: '#/components/schemas/UnifiedAtsApplicationOutput' - example: &ref_139 + example: &ref_147 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The applications UUIDs of the candidate tags: type: array - items: &ref_140 + items: &ref_148 oneOf: - type: string - $ref: '#/components/schemas/UnifiedAtsTagOutput' - example: &ref_141 + example: &ref_149 - tag_1 - tag_2 nullable: true description: The tags of the candidate urls: - example: &ref_142 + example: &ref_150 - url: mywebsite.com url_type: WEBSITE nullable: true @@ -14553,7 +14875,7 @@ components: items: $ref: '#/components/schemas/Url' phone_numbers: - example: &ref_143 + example: &ref_151 - phone_number: '+33660688899' phone_type: WORK nullable: true @@ -14562,7 +14884,7 @@ components: items: $ref: '#/components/schemas/Phone' email_addresses: - example: &ref_144 + example: &ref_152 - email_address: joedoe@gmail.com email_address_type: WORK nullable: true @@ -14572,7 +14894,7 @@ components: $ref: '#/components/schemas/Email' field_mappings: type: object - example: &ref_145 + example: &ref_153 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14668,24 +14990,24 @@ components: description: The last interaction date with the candidate attachments: type: array - items: *ref_136 - example: *ref_137 + items: *ref_144 + example: *ref_145 nullable: true description: The attachments UUIDs of the candidate applications: type: array - items: *ref_138 - example: *ref_139 + items: *ref_146 + example: *ref_147 nullable: true description: The applications UUIDs of the candidate tags: type: array - items: *ref_140 - example: *ref_141 + items: *ref_148 + example: *ref_149 nullable: true description: The tags of the candidate urls: - example: *ref_142 + example: *ref_150 nullable: true description: >- The urls of the candidate, possible values for Url type are WEBSITE, @@ -14694,14 +15016,14 @@ components: items: $ref: '#/components/schemas/Url' phone_numbers: - example: *ref_143 + example: *ref_151 nullable: true description: The phone numbers of the candidate type: array items: $ref: '#/components/schemas/Phone' email_addresses: - example: *ref_144 + example: *ref_152 nullable: true description: The email addresses of the candidate type: array @@ -14709,7 +15031,7 @@ components: $ref: '#/components/schemas/Email' field_mappings: type: object - example: *ref_145 + example: *ref_153 additionalProperties: true nullable: true description: >- @@ -14769,7 +15091,7 @@ components: properties: status: type: string - enum: &ref_146 + enum: &ref_154 - SCHEDULED - AWAITING_FEEDBACK - COMPLETED @@ -14792,7 +15114,7 @@ components: nullable: true description: The UUID of the organizer interviewers: - example: &ref_147 + example: &ref_155 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUIDs of the interviewers @@ -14830,7 +15152,7 @@ components: description: The remote modification date of the interview field_mappings: type: object - example: &ref_148 + example: &ref_156 fav_dish: broccoli fav_color: red additionalProperties: true @@ -14873,7 +15195,7 @@ components: properties: status: type: string - enum: *ref_146 + enum: *ref_154 example: SCHEDULED nullable: true description: The status of the interview @@ -14893,7 +15215,7 @@ components: nullable: true description: The UUID of the organizer interviewers: - example: *ref_147 + example: *ref_155 nullable: true description: The UUIDs of the interviewers type: array @@ -14930,7 +15252,7 @@ components: description: The remote modification date of the interview field_mappings: type: object - example: *ref_148 + example: *ref_156 additionalProperties: true nullable: true description: >- @@ -15664,7 +15986,7 @@ components: currency: type: string example: USD - enum: &ref_149 + enum: &ref_157 - AED - AFN - ALL @@ -15846,7 +16168,7 @@ components: description: The UUID of the associated company info field_mappings: type: object - example: &ref_150 + example: &ref_158 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -15918,7 +16240,7 @@ components: currency: type: string example: USD - enum: *ref_149 + enum: *ref_157 nullable: true description: The currency of the account account_number: @@ -15938,7 +16260,7 @@ components: description: The UUID of the associated company info field_mappings: type: object - example: *ref_150 + example: *ref_158 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -16054,7 +16376,7 @@ components: description: The UUID of the associated account field_mappings: type: object - example: &ref_151 + example: &ref_159 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -16110,7 +16432,7 @@ components: description: The UUID of the associated account field_mappings: type: object - example: *ref_151 + example: *ref_159 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -16986,7 +17308,7 @@ components: type: string example: USD nullable: true - enum: &ref_152 + enum: &ref_160 - AED - AFN - ALL @@ -17162,7 +17484,7 @@ components: description: The UUID of the associated company info field_mappings: type: object - example: &ref_153 + example: &ref_161 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -17235,7 +17557,7 @@ components: type: string example: USD nullable: true - enum: *ref_152 + enum: *ref_160 description: The currency associated with the contact remote_updated_at: type: string @@ -17249,7 +17571,7 @@ components: description: The UUID of the associated company info field_mappings: type: object - example: *ref_153 + example: *ref_161 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -17574,7 +17896,7 @@ components: currency: type: string example: USD - enum: &ref_154 + enum: &ref_162 - AED - AFN - ALL @@ -17765,7 +18087,7 @@ components: nullable: true description: The UUID of the associated company info tracking_categories: - example: &ref_155 + example: &ref_163 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUIDs of the tracking categories associated with the expense @@ -17779,7 +18101,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_156 + example: &ref_164 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -17848,7 +18170,7 @@ components: currency: type: string example: USD - enum: *ref_154 + enum: *ref_162 nullable: true description: The currency of the expense exchange_rate: @@ -17877,7 +18199,7 @@ components: nullable: true description: The UUID of the associated company info tracking_categories: - example: *ref_155 + example: *ref_163 nullable: true description: The UUIDs of the tracking categories associated with the expense type: array @@ -17890,7 +18212,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_156 + example: *ref_164 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -18179,7 +18501,7 @@ components: currency: type: string example: USD - enum: &ref_157 + enum: &ref_165 - AED - AFN - ALL @@ -18390,7 +18712,7 @@ components: nullable: true description: The UUID of the associated accounting period tracking_categories: - example: &ref_158 + example: &ref_166 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true @@ -18405,7 +18727,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_159 + example: &ref_167 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -18486,7 +18808,7 @@ components: currency: type: string example: USD - enum: *ref_157 + enum: *ref_165 nullable: true description: The currency of the invoice exchange_rate: @@ -18535,7 +18857,7 @@ components: nullable: true description: The UUID of the associated accounting period tracking_categories: - example: *ref_158 + example: *ref_166 nullable: true description: The UUIDs of the tracking categories associated with the invoice type: array @@ -18548,7 +18870,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_159 + example: *ref_167 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -18645,7 +18967,7 @@ components: nullable: true description: The date of the transaction payments: - example: &ref_160 + example: &ref_168 - payment1 - payment2 nullable: true @@ -18654,7 +18976,7 @@ components: items: type: string applied_payments: - example: &ref_161 + example: &ref_169 - appliedPayment1 - appliedPayment2 nullable: true @@ -18670,7 +18992,7 @@ components: currency: type: string example: USD - enum: &ref_162 + enum: &ref_170 - AED - AFN - ALL @@ -18851,7 +19173,7 @@ components: nullable: true description: The journal number tracking_categories: - example: &ref_163 + example: &ref_171 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: >- @@ -18877,7 +19199,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_164 + example: &ref_172 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -18937,14 +19259,14 @@ components: nullable: true description: The date of the transaction payments: - example: *ref_160 + example: *ref_168 nullable: true description: The payments associated with the journal entry type: array items: type: string applied_payments: - example: *ref_161 + example: *ref_169 nullable: true description: The applied payments for the journal entry type: array @@ -18958,7 +19280,7 @@ components: currency: type: string example: USD - enum: *ref_162 + enum: *ref_170 nullable: true description: The currency of the journal entry exchange_rate: @@ -18977,7 +19299,7 @@ components: nullable: true description: The journal number tracking_categories: - example: *ref_163 + example: *ref_171 nullable: true description: >- The UUIDs of the tracking categories associated with the journal @@ -19002,7 +19324,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_164 + example: *ref_172 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -19034,7 +19356,7 @@ components: currency: type: string example: USD - enum: &ref_165 + enum: &ref_173 - AED - AFN - ALL @@ -19225,7 +19547,7 @@ components: nullable: true description: The UUID of the associated accounting period tracking_categories: - example: &ref_166 + example: &ref_174 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: The UUIDs of the tracking categories associated with the payment @@ -19239,7 +19561,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_167 + example: &ref_175 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -19308,7 +19630,7 @@ components: currency: type: string example: USD - enum: *ref_165 + enum: *ref_173 nullable: true description: The currency of the payment exchange_rate: @@ -19337,7 +19659,7 @@ components: nullable: true description: The UUID of the associated accounting period tracking_categories: - example: *ref_166 + example: *ref_174 nullable: true description: The UUIDs of the tracking categories associated with the payment type: array @@ -19350,7 +19672,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_167 + example: *ref_175 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -19474,7 +19796,7 @@ components: currency: type: string example: USD - enum: &ref_168 + enum: &ref_176 - AED - AFN - ALL @@ -19645,7 +19967,7 @@ components: nullable: true description: The exchange rate applied to the purchase order tracking_categories: - example: &ref_169 + example: &ref_177 - 801f9ede-c698-4e66-a7fc-48d19eebaa4f nullable: true description: >- @@ -19666,7 +19988,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: &ref_170 + example: &ref_178 custom_field_1: value1 custom_field_2: value2 nullable: true @@ -19776,7 +20098,7 @@ components: currency: type: string example: USD - enum: *ref_168 + enum: *ref_176 nullable: true description: The currency of the purchase order exchange_rate: @@ -19785,7 +20107,7 @@ components: nullable: true description: The exchange rate applied to the purchase order tracking_categories: - example: *ref_169 + example: *ref_177 nullable: true description: >- The UUIDs of the tracking categories associated with the purchase @@ -19805,7 +20127,7 @@ components: $ref: '#/components/schemas/LineItem' field_mappings: type: object - example: *ref_170 + example: *ref_178 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -20582,7 +20904,7 @@ components: nullable: true field_mappings: type: object - example: &ref_171 + example: &ref_179 fav_dish: broccoli fav_color: red description: >- @@ -20668,7 +20990,7 @@ components: nullable: true field_mappings: type: object - example: *ref_171 + example: *ref_179 description: >- The custom field mappings of the object between the remote 3rd party & Panora @@ -20726,7 +21048,7 @@ components: description: The UUID of the permission tied to the folder field_mappings: type: object - example: &ref_172 + example: &ref_180 fav_dish: broccoli fav_color: red additionalProperties: true @@ -20817,7 +21139,7 @@ components: description: The UUID of the permission tied to the folder field_mappings: type: object - example: *ref_172 + example: *ref_180 additionalProperties: true nullable: true description: >- @@ -20982,13 +21304,13 @@ components: type: string example: ACTIVE nullable: true - enum: &ref_173 + enum: &ref_181 - ARCHIVED - ACTIVE - DRAFT description: The status of the product. Either ACTIVE, DRAFT OR ARCHIVED. images_urls: - example: &ref_174 + example: &ref_182 - https://myproduct/image nullable: true description: The URLs of the product images @@ -21006,7 +21328,7 @@ components: nullable: true description: The vendor of the product variants: - example: &ref_175 + example: &ref_183 - title: teeshirt price: 20 sku: '3' @@ -21018,7 +21340,7 @@ components: items: $ref: '#/components/schemas/Variant' tags: - example: &ref_176 + example: &ref_184 - tag_1 nullable: true description: The tags associated with the product @@ -21027,7 +21349,7 @@ components: type: string field_mappings: type: object - example: &ref_177 + example: &ref_185 fav_dish: broccoli fav_color: red nullable: true @@ -21078,10 +21400,10 @@ components: type: string example: ACTIVE nullable: true - enum: *ref_173 + enum: *ref_181 description: The status of the product. Either ACTIVE, DRAFT OR ARCHIVED. images_urls: - example: *ref_174 + example: *ref_182 nullable: true description: The URLs of the product images type: array @@ -21098,13 +21420,13 @@ components: nullable: true description: The vendor of the product variants: - example: *ref_175 + example: *ref_183 description: The variants of the product type: array items: $ref: '#/components/schemas/Variant' tags: - example: *ref_176 + example: *ref_184 nullable: true description: The tags associated with the product type: array @@ -21112,7 +21434,7 @@ components: type: string field_mappings: type: object - example: *ref_177 + example: *ref_185 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -21139,7 +21461,7 @@ components: type: string nullable: true example: AUD - enum: &ref_178 + enum: &ref_186 - AED - AFN - ALL @@ -21338,11 +21660,11 @@ components: items: type: object nullable: true - example: &ref_179 {} + example: &ref_187 {} description: The items in the order field_mappings: type: object - example: &ref_180 + example: &ref_188 fav_dish: broccoli fav_color: red nullable: true @@ -21398,7 +21720,7 @@ components: type: string nullable: true example: AUD - enum: *ref_178 + enum: *ref_186 description: >- The currency of the order. Authorized value must be of type CurrencyCode (ISO 4217) @@ -21435,11 +21757,11 @@ components: items: type: object nullable: true - example: *ref_179 + example: *ref_187 description: The items in the order field_mappings: type: object - example: *ref_180 + example: *ref_188 nullable: true description: >- The custom field mappings of the object between the remote 3rd party @@ -21616,7 +21938,7 @@ components: field_mappings: type: object nullable: true - example: &ref_181 + example: &ref_189 fav_dish: broccoli fav_color: red description: >- @@ -21688,7 +22010,7 @@ components: field_mappings: type: object nullable: true - example: *ref_181 + example: *ref_189 description: >- The custom field mappings of the attachment between the remote 3rd party & Panora diff --git a/packages/shared/src/connectors/metadata.ts b/packages/shared/src/connectors/metadata.ts index d575b5a5b..dd568123b 100644 --- a/packages/shared/src/connectors/metadata.ts +++ b/packages/shared/src/connectors/metadata.ts @@ -2087,7 +2087,7 @@ export const CONNECTORS_METADATA: ProvidersConfig = { 'gusto': { urls: { docsUrl: 'https://docs.gusto.com/app-integrations/docs/introduction', - apiUrl: 'https://api.gusto.com', + apiUrl: 'https://api.gusto-demo.com', authBaseUrl: 'https://api.gusto-demo.com/oauth/authorize' }, logoPath: 'https://cdn.runalloy.com/landing/uploads-new/Gusto_Logo_67ca008403.png', From 153e6eacb0b0f12f23b135fdb3101a61ebd4acb9 Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 13 Aug 2024 22:31:08 +0200 Subject: [PATCH 7/8] :bug: Fix location --- packages/api/prisma/schema.prisma | 8 ++-- packages/api/scripts/init.sql | 8 ++-- .../request-retry/retry.handler.ts | 8 +++- .../hris/services/gusto/gusto.service.ts | 10 ++--- .../passthrough/passthrough.controller.ts | 2 + .../@core/passthrough/passthrough.service.ts | 15 +++++-- .../api/src/@core/passthrough/types/index.ts | 9 ++++- packages/api/src/@core/sync/sync.service.ts | 20 ++++++++++ .../hris/company/services/company.service.ts | 16 +++++++- .../hris/company/services/gusto/mappers.ts | 6 +-- .../api/src/hris/company/sync/sync.service.ts | 14 ++++++- .../employee/services/employee.service.ts | 17 ++++++-- .../src/hris/employee/sync/sync.service.ts | 1 - .../api/src/hris/location/location.module.ts | 4 ++ .../src/hris/location/services/gusto/index.ts | 26 ++++++------- .../hris/location/services/gusto/mappers.ts | 39 ++++--------------- .../src/hris/location/services/gusto/types.ts | 6 ++- .../location/services/location.service.ts | 4 ++ .../src/hris/location/sync/sync.service.ts | 14 ++++++- .../src/hris/location/types/model.unified.ts | 20 ++++++++++ packages/api/swagger/swagger-spec.yaml | 10 +++++ 21 files changed, 176 insertions(+), 81 deletions(-) diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index 7d11243ec..06473ab99 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -1817,7 +1817,6 @@ model hris_companies { legal_name String? display_name String? eins String[] - locations String[] remote_id String? remote_created_at DateTime? @db.Timestamptz(6) created_at DateTime @db.Timestamptz(6) @@ -1927,8 +1926,7 @@ model hris_employees { modified_at DateTime @db.Timestamptz(6) remote_was_deleted Boolean id_connection String @db.Uuid - locations String[] - manager String? + manager String? @db.Uuid groups String[] employee_number String? id_hris_company String? @db.Uuid @@ -2026,10 +2024,12 @@ model hris_locations { street_2 String? city String? state String? + id_hris_company String? @db.Uuid + id_hris_employee String? @db.Uuid zip_code String? country String? location_type String? - remote_id String + remote_id String? remote_created_at DateTime @db.Timestamptz(6) created_at DateTime @db.Timestamptz(6) modified_at DateTime @db.Timestamptz(6) diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index 69db67e95..f620e7bd1 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -228,10 +228,12 @@ CREATE TABLE hris_locations street_2 text NULL, city text NULL, "state" text NULL, + id_hris_company uuid NULL, + id_hris_employee uuid NULL, zip_code text NULL, country text NULL, location_type text NULL, - remote_id text NOT NULL, + remote_id text NULL, remote_created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL, modified_at timestamp with time zone NOT NULL, @@ -282,7 +284,6 @@ CREATE TABLE hris_companies legal_name text NULL, display_name text NULL, eins text[] NULL, - locations text[] NULL, remote_id text NULL, remote_created_at timestamp with time zone NULL, created_at timestamp with time zone NOT NULL, @@ -980,8 +981,7 @@ CREATE TABLE hris_employees modified_at timestamp with time zone NOT NULL, remote_was_deleted boolean NOT NULL, id_connection uuid NOT NULL, - locations text[] NULL, - manager text NULL, + manager uuid NULL, groups text[] NULL, employee_number text NULL, id_hris_company uuid NULL, diff --git a/packages/api/src/@core/@core-services/request-retry/retry.handler.ts b/packages/api/src/@core/@core-services/request-retry/retry.handler.ts index 3586e4b0b..0598f3115 100644 --- a/packages/api/src/@core/@core-services/request-retry/retry.handler.ts +++ b/packages/api/src/@core/@core-services/request-retry/retry.handler.ts @@ -17,7 +17,13 @@ export class RetryHandler { ): Promise { try { const response: AxiosResponse = await axios(config); - return response; + const responseInfo = { + status: response.status, + statusText: response.statusText, + headers: response.headers, + data: response.data, + }; + return responseInfo; } catch (error) { if (this.isRateLimitError(error)) { const retryId = uuidv4(); diff --git a/packages/api/src/@core/connections/hris/services/gusto/gusto.service.ts b/packages/api/src/@core/connections/hris/services/gusto/gusto.service.ts index 720052d8f..101a85a67 100644 --- a/packages/api/src/@core/connections/hris/services/gusto/gusto.service.ts +++ b/packages/api/src/@core/connections/hris/services/gusto/gusto.service.ts @@ -65,9 +65,9 @@ export class GustoConnectionService extends AbstractBaseConnectionService { }, }); - config.headers['Authorization'] = `Basic ${Buffer.from( - `${this.cryptoService.decrypt(connection.access_token)}:`, - ).toString('base64')}`; + config.headers['Authorization'] = `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`; config.headers = { ...config.headers, @@ -185,9 +185,7 @@ export class GustoConnectionService extends AbstractBaseConnectionService { async handleTokenRefresh(opts: RefreshParams) { try { const { connectionId, refreshToken, projectId } = opts; - const REDIRECT_URI = `${ - this.env.getPanoraBaseUrl() - }/connections/oauth/callback`; + const REDIRECT_URI = `${this.env.getPanoraBaseUrl()}/connections/oauth/callback`; const CREDENTIALS = (await this.cService.getCredentials( projectId, diff --git a/packages/api/src/@core/passthrough/passthrough.controller.ts b/packages/api/src/@core/passthrough/passthrough.controller.ts index ee9ed659b..9772a8a00 100644 --- a/packages/api/src/@core/passthrough/passthrough.controller.ts +++ b/packages/api/src/@core/passthrough/passthrough.controller.ts @@ -52,6 +52,7 @@ export class PassthroughController { remoteSource: integrationId, connectionId, vertical, + projectId, } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); @@ -61,6 +62,7 @@ export class PassthroughController { linkedUserId, vertical, connectionId, + projectId, ); } diff --git a/packages/api/src/@core/passthrough/passthrough.service.ts b/packages/api/src/@core/passthrough/passthrough.service.ts index 465d9d5bb..d43fdc7d4 100644 --- a/packages/api/src/@core/passthrough/passthrough.service.ts +++ b/packages/api/src/@core/passthrough/passthrough.service.ts @@ -23,15 +23,22 @@ export class PassthroughService { linkedUserId: string, vertical: string, connectionId: string, + projectId: string, ): Promise { try { - const { method, path, data, request_format, overrideBaseUrl, headers } = - requestParams; + const { + method, + path, + data, + request_format = 'JSON', + overrideBaseUrl, + headers, + } = requestParams; const job_resp_create = await this.prisma.events.create({ data: { id_connection: connectionId, - id_project: '', + id_project: projectId, id_event: uuidv4(), status: 'initialized', // Use whatever status is appropriate type: 'pull', @@ -68,7 +75,7 @@ export class PassthroughService { id_event: job_resp_create.id_event, }, data: { - status: status || (response as AxiosResponse).status, + status: String(status) || String((response as AxiosResponse).status), }, }); diff --git a/packages/api/src/@core/passthrough/types/index.ts b/packages/api/src/@core/passthrough/types/index.ts index f74c20b63..3ad9e9a4a 100644 --- a/packages/api/src/@core/passthrough/types/index.ts +++ b/packages/api/src/@core/passthrough/types/index.ts @@ -1,5 +1,10 @@ -import { AxiosResponse } from 'axios'; +type BaseResponse = { + status: number; + statusText: string; + headers: any; + data: any; +}; export type PassthroughResponse = - | AxiosResponse + | BaseResponse | { statusCode: number; retryId: string }; diff --git a/packages/api/src/@core/sync/sync.service.ts b/packages/api/src/@core/sync/sync.service.ts index aa38429a3..f3b870096 100644 --- a/packages/api/src/@core/sync/sync.service.ts +++ b/packages/api/src/@core/sync/sync.service.ts @@ -123,6 +123,15 @@ export class CoreSyncService { }), ); + const employeesLocationsTasks = employees.map( + (employee) => async () => + this.registry.getService('hris', 'location').syncForLinkedUser({ + integrationId: provider, + linkedUserId: linkedUserId, + id_employee: employee.id_hris_employee, + }), + ); + for (const task of companiesEmployeeTasks) { try { await task(); @@ -134,6 +143,17 @@ export class CoreSyncService { } } + for (const task of employeesLocationsTasks) { + try { + await task(); + } catch (error) { + this.logger.error( + `Companies Location task failed: ${error.message}`, + error, + ); + } + } + for (const task of companiesEmployerBenefitsTasks) { try { await task(); diff --git a/packages/api/src/hris/company/services/company.service.ts b/packages/api/src/hris/company/services/company.service.ts index d435c4fc0..9f9e0b4a7 100644 --- a/packages/api/src/hris/company/services/company.service.ts +++ b/packages/api/src/hris/company/services/company.service.ts @@ -47,13 +47,19 @@ export class CompanyService { values.map((value) => [value.attribute.slug, value.data]), ); + const locations = await this.prisma.hris_locations.findMany({ + where: { + id_hris_company: company.id_hris_company, + }, + }); + const unifiedCompany: UnifiedHrisCompanyOutput = { id: company.id_hris_company, legal_name: company.legal_name, display_name: company.display_name, - locations: company.locations, eins: company.eins, field_mappings: field_mappings, + locations: locations.map((loc) => loc.id_hris_location), remote_id: company.remote_id, remote_created_at: company.remote_created_at, created_at: company.created_at, @@ -125,6 +131,12 @@ export class CompanyService { include: { attribute: true }, }); + const locations = await this.prisma.hris_locations.findMany({ + where: { + id_hris_company: company.id_hris_company, + }, + }); + const field_mappings = Object.fromEntries( values.map((value) => [value.attribute.slug, value.data]), ); @@ -134,8 +146,8 @@ export class CompanyService { legal_name: company.legal_name, display_name: company.display_name, eins: company.eins, - locations: company.locations, field_mappings: field_mappings, + locations: locations.map((loc) => loc.id_hris_location), remote_id: company.remote_id, remote_created_at: company.remote_created_at, created_at: company.created_at, diff --git a/packages/api/src/hris/company/services/gusto/mappers.ts b/packages/api/src/hris/company/services/gusto/mappers.ts index 1aab6c009..91edecfbe 100644 --- a/packages/api/src/hris/company/services/gusto/mappers.ts +++ b/packages/api/src/hris/company/services/gusto/mappers.ts @@ -60,7 +60,7 @@ export class GustoCompanyMapper implements ICompanyMapper { customFieldMappings?: { slug: string; remote_id: string }[], ): Promise { const opts: any = {}; - /*if (company.locations && company.locations.length > 0) { + if (company.locations && company.locations.length > 0) { const locations = await this.ingestService.ingestData< UnifiedHrisLocationOutput, GustoLocationOutput @@ -73,9 +73,9 @@ export class GustoCompanyMapper implements ICompanyMapper { [], ); if (locations) { - opts.locations = locations; + opts.locations = locations.map((loc) => loc.id_hris_location); } - }*/ + } return { remote_id: company.uuid || null, legal_name: company.name || null, diff --git a/packages/api/src/hris/company/sync/sync.service.ts b/packages/api/src/hris/company/sync/sync.service.ts index a5026d099..e4233d9a8 100644 --- a/packages/api/src/hris/company/sync/sync.service.ts +++ b/packages/api/src/hris/company/sync/sync.service.ts @@ -112,7 +112,6 @@ export class SyncService implements OnModuleInit, IBaseSync { legal_name: company.legal_name, display_name: company.display_name, eins: company.eins || [], - locations: company.locations, remote_id: originId, remote_created_at: company.remote_created_at ? new Date(company.remote_created_at) @@ -137,6 +136,19 @@ export class SyncService implements OnModuleInit, IBaseSync { }); } + if (company.locations) { + for (const loc of company.locations) { + await this.prisma.hris_locations.update({ + where: { + id_hris_location: loc, + }, + data: { + id_hris_company: existingCompany.id_hris_company, + }, + }); + } + } + companyResults.push(existingCompany); // Process field mappings diff --git a/packages/api/src/hris/employee/services/employee.service.ts b/packages/api/src/hris/employee/services/employee.service.ts index 8790c101c..7fe253309 100644 --- a/packages/api/src/hris/employee/services/employee.service.ts +++ b/packages/api/src/hris/employee/services/employee.service.ts @@ -135,7 +135,6 @@ export class EmployeeService { }); const data: any = { - locations: employee.locations || [], groups: employee.groups || [], employee_number: employee.employee_number, id_hris_company: employee.company_id, @@ -213,6 +212,12 @@ export class EmployeeService { values.map((value) => [value.attribute.slug, value.data]), ); + const locations = await this.prisma.hris_locations.findMany({ + where: { + id_hris_employee: employee.id_hris_employee, + }, + }); + const unifiedEmployee: UnifiedHrisEmployeeOutput = { id: employee.id_hris_employee, groups: employee.groups, @@ -228,7 +233,6 @@ export class EmployeeService { mobile_phone_number: employee.mobile_phone_number, employments: employee.employments, ssn: employee.ssn, - locations: employee.locations, manager_id: employee.manager, gender: employee.gender, ethnicity: employee.ethnicity, @@ -238,6 +242,7 @@ export class EmployeeService { employment_status: employee.employment_status, termination_date: employee.termination_date, avatar_url: employee.avatar_url, + locations: locations.map((loc) => loc.id_hris_location), field_mappings: field_mappings, remote_id: employee.remote_id, remote_created_at: employee.remote_created_at, @@ -314,6 +319,12 @@ export class EmployeeService { values.map((value) => [value.attribute.slug, value.data]), ); + const locations = await this.prisma.hris_locations.findMany({ + where: { + id_hris_employee: employee.id_hris_employee, + }, + }); + const unifiedEmployee: UnifiedHrisEmployeeOutput = { id: employee.id_hris_employee, groups: employee.groups, @@ -324,7 +335,7 @@ export class EmployeeService { preferred_name: employee.preferred_name, display_full_name: employee.display_full_name, username: employee.username, - locations: employee.locations, + locations: locations.map((loc) => loc.id_hris_location), manager_id: employee.manager, work_email: employee.work_email, personal_email: employee.personal_email, diff --git a/packages/api/src/hris/employee/sync/sync.service.ts b/packages/api/src/hris/employee/sync/sync.service.ts index 5a3e08894..0f41f2fdc 100644 --- a/packages/api/src/hris/employee/sync/sync.service.ts +++ b/packages/api/src/hris/employee/sync/sync.service.ts @@ -123,7 +123,6 @@ export class SyncService implements OnModuleInit, IBaseSync { last_name: employee.last_name, preferred_name: employee.preferred_name, display_full_name: employee.display_full_name, - locations: employee.locations, username: employee.username, work_email: employee.work_email, personal_email: employee.personal_email, diff --git a/packages/api/src/hris/location/location.module.ts b/packages/api/src/hris/location/location.module.ts index 37c5c4fd1..443570585 100644 --- a/packages/api/src/hris/location/location.module.ts +++ b/packages/api/src/hris/location/location.module.ts @@ -7,6 +7,8 @@ import { IngestDataService } from '@@core/@core-services/unification/ingest-data import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; import { Utils } from '@hris/@lib/@utils'; +import { GustoLocationMapper } from './services/gusto/mappers'; +import { GustoService } from './services/gusto'; @Module({ controllers: [LocationController], providers: [ @@ -17,7 +19,9 @@ import { Utils } from '@hris/@lib/@utils'; WebhookService, ServiceRegistry, IngestDataService, + GustoLocationMapper, /* PROVIDERS SERVICES */ + GustoService, ], exports: [SyncService], }) diff --git a/packages/api/src/hris/location/services/gusto/index.ts b/packages/api/src/hris/location/services/gusto/index.ts index 6138824d3..4787af093 100644 --- a/packages/api/src/hris/location/services/gusto/index.ts +++ b/packages/api/src/hris/location/services/gusto/index.ts @@ -71,26 +71,22 @@ export class GustoService implements ILocationService { }, ); this.logger.log(`Synced gusto locations !`); + const resp_home = Array.isArray(resp.data) + ? resp.data.map((add) => ({ + ...add, + type: 'HOME', + })) + : []; - const resp_home = resp.data.map((add) => { - return { - ...add, - type: 'HOME', - }; - }); - - let resp_work; - if (resp_.data) { - resp_work = resp_.data.map((add) => { - return { + const resp_work = Array.isArray(resp_.data) + ? resp_.data.map((add) => ({ ...add, type: 'WORK', - }; - }); - } + })) + : []; return { - data: resp.data.map, + data: [...resp_home, ...resp_work], message: 'Gusto locations retrieved', statusCode: 200, }; diff --git a/packages/api/src/hris/location/services/gusto/mappers.ts b/packages/api/src/hris/location/services/gusto/mappers.ts index 151835854..b7486ba71 100644 --- a/packages/api/src/hris/location/services/gusto/mappers.ts +++ b/packages/api/src/hris/location/services/gusto/mappers.ts @@ -56,39 +56,16 @@ export class GustoLocationMapper implements ILocationMapper { connectionId: string, customFieldMappings?: { slug: string; remote_id: string }[], ): Promise { - const opts: any = {}; - - /*if (location.employee_uuid) { - const employee_id = await this.utils.getEmployeeUuidFromRemoteId( - location.employee_uuid, - connectionId, - ); - if (employee_id) { - opts.employee_id = employee_id; - } - } - if (location.company_location_uuid) { - const id = await this.utils.getEmployerLocationUuidFromRemoteId( - location.company_location_uuid, - connectionId, - ); - if (id) { - opts.employer_location_id = id; - } - } - return { remote_id: location.uuid || null, remote_data: location, - ...opts, - employee_contribution: location.employee_deduction - ? parseFloat(location.employee_deduction) - : null, - company_contribution: location.company_contribution - ? parseFloat(location.company_contribution) - : null, - remote_was_deleted: null, - };*/ - return; + street_1: location.street_1, + street_2: location.street_2, + city: location.city, + state: location.state, + zip_code: location.zip, + country: location.country, + location_type: location.type, + }; } } diff --git a/packages/api/src/hris/location/services/gusto/types.ts b/packages/api/src/hris/location/services/gusto/types.ts index 041cc4c07..f53987155 100644 --- a/packages/api/src/hris/location/services/gusto/types.ts +++ b/packages/api/src/hris/location/services/gusto/types.ts @@ -1,4 +1,5 @@ -export type GustoLocationOutput = { +export type GustoLocationOutput = Partial<{ + uuid: string; street_1: string; street_2: string; city: string; @@ -6,4 +7,5 @@ export type GustoLocationOutput = { zip: string; country: string; active: boolean; -}; + type: 'WORK' | 'HOME'; +}>; diff --git a/packages/api/src/hris/location/services/location.service.ts b/packages/api/src/hris/location/services/location.service.ts index 113e99b1a..111eab050 100644 --- a/packages/api/src/hris/location/services/location.service.ts +++ b/packages/api/src/hris/location/services/location.service.ts @@ -62,6 +62,8 @@ export class LocationService { state: location.state, zip_code: location.zip_code, country: location.country, + employee_id: location.id_hris_employee || null, + company_id: location.id_hris_company || null, location_type: location.location_type, field_mappings: field_mappings, remote_id: location.remote_id, @@ -150,6 +152,8 @@ export class LocationService { street_1: location.street_1, street_2: location.street_2, city: location.city, + employee_id: location.id_hris_employee || null, + company_id: location.id_hris_company || null, state: location.state, zip_code: location.zip_code, country: location.country, diff --git a/packages/api/src/hris/location/sync/sync.service.ts b/packages/api/src/hris/location/sync/sync.service.ts index 9fba5d7fd..f6ae377a1 100644 --- a/packages/api/src/hris/location/sync/sync.service.ts +++ b/packages/api/src/hris/location/sync/sync.service.ts @@ -71,7 +71,7 @@ export class SyncService implements OnModuleInit, IBaseSync { async syncForLinkedUser(param: SyncLinkedUserType) { try { - const { integrationId, linkedUserId } = param; + const { integrationId, linkedUserId, id_employee } = param; const service: ILocationService = this.serviceRegistry.getService(integrationId); if (!service) return; @@ -80,7 +80,14 @@ export class SyncService implements OnModuleInit, IBaseSync { UnifiedHrisLocationOutput, OriginalLocationOutput, ILocationService - >(integrationId, linkedUserId, 'hris', 'location', service, []); + >(integrationId, linkedUserId, 'hris', 'location', service, [ + { + param: id_employee, + paramName: 'id_employee', + shouldPassToService: true, + shouldPassToIngest: true, + }, + ]); } catch (error) { throw error; } @@ -92,6 +99,7 @@ export class SyncService implements OnModuleInit, IBaseSync { locations: UnifiedHrisLocationOutput[], originSource: string, remote_data: Record[], + id_employee?: string, ): Promise { try { const locationResults: HrisLocation[] = []; @@ -112,6 +120,8 @@ export class SyncService implements OnModuleInit, IBaseSync { phone_number: location.phone_number, street_1: location.street_1, street_2: location.street_2, + id_hris_employee: id_employee || null, + id_hris_company: location.company_id || null, city: location.city, state: location.state, zip_code: location.zip_code, diff --git a/packages/api/src/hris/location/types/model.unified.ts b/packages/api/src/hris/location/types/model.unified.ts index 416f15bda..7c2fa11da 100644 --- a/packages/api/src/hris/location/types/model.unified.ts +++ b/packages/api/src/hris/location/types/model.unified.ts @@ -102,6 +102,26 @@ export class UnifiedHrisLocationInput { @IsOptional() location_type?: LocationType | string; + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the company associated with the location', + }) + @IsUUID() + @IsOptional() + company_id?: string; + + @ApiPropertyOptional({ + type: String, + example: '801f9ede-c698-4e66-a7fc-48d19eebaa4f', + nullable: true, + description: 'The UUID of the employee associated with the location', + }) + @IsUUID() + @IsOptional() + employee_id?: string; + @ApiPropertyOptional({ type: Object, example: { diff --git a/packages/api/swagger/swagger-spec.yaml b/packages/api/swagger/swagger-spec.yaml index b1a3c3c1d..b13781b9a 100644 --- a/packages/api/swagger/swagger-spec.yaml +++ b/packages/api/swagger/swagger-spec.yaml @@ -13728,6 +13728,16 @@ components: - HOME nullable: true description: The type of the location + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company associated with the location + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee associated with the location field_mappings: type: object example: From 48290bbdc654dfa28bb844c0a1eb66fd50159d1b Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 14 Aug 2024 01:34:47 +0200 Subject: [PATCH 8/8] :rotating_light: Fix lint --- packages/api/swagger/swagger-spec.yaml | 22071 +++++++++++++++++++ packages/shared/src/authUrl.ts | 10 +- packages/shared/src/connectors/metadata.ts | 2 +- 3 files changed, 22077 insertions(+), 6 deletions(-) create mode 100644 packages/api/swagger/swagger-spec.yaml diff --git a/packages/api/swagger/swagger-spec.yaml b/packages/api/swagger/swagger-spec.yaml new file mode 100644 index 000000000..166080fe7 --- /dev/null +++ b/packages/api/swagger/swagger-spec.yaml @@ -0,0 +1,22071 @@ +openapi: 3.0.0 +paths: + /: + get: + operationId: hello + summary: '' + parameters: [] + responses: + '200': + description: Returns a greeting message + content: + text/plain: + schema: + type: string + /health: + get: + operationId: health + summary: '' + parameters: [] + responses: + '200': + description: Api is healthy + content: + application/json: + schema: + type: number + example: 200 + /auth/login: + post: + operationId: signIn + summary: Log In + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginDto' + responses: + '201': + description: '' + tags: + - auth + x-speakeasy-group: auth.login + /connections: + get: + operationId: getConnections + summary: List Connections + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Connection' + tags: + - connections + /webhooks: + get: + operationId: listWebhooks + summary: List webhooks + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/WebhookResponse' + tags: &ref_0 + - webhooks + x-speakeasy-group: webhooks + post: + operationId: createWebhookPublic + summary: Create webhook + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookDto' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponse' + tags: *ref_0 + x-speakeasy-group: webhooks + /webhooks/{id}: + delete: + operationId: delete + summary: Delete Webhook + parameters: + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the webhook to delete. + schema: + type: string + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponse' + tags: *ref_0 + x-speakeasy-group: webhooks + put: + operationId: updateStatus + summary: Update webhook status + parameters: + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the webhook to update. + schema: + type: string + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponse' + tags: *ref_0 + x-speakeasy-group: webhooks + /webhooks/verifyEvent: + post: + operationId: verifyEvent + summary: Verify payload signature of the webhook + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SignatureVerificationDto' + responses: + '200': + description: '' + content: + application/json: + schema: + properties: + data: + type: object + additionalProperties: true + description: Dynamic event payload + tags: *ref_0 + x-speakeasy-group: webhooks + /ticketing/tickets: + get: + operationId: listTicketingTicket + summary: List Tickets + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingTicketOutput' + tags: &ref_1 + - ticketing/tickets + x-speakeasy-group: ticketing.tickets + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createTicketingTicket + summary: Create Tickets + description: Create Tickets in any supported Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingTicketInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingTicketOutput' + tags: *ref_1 + x-speakeasy-group: ticketing.tickets + /ticketing/tickets/{id}: + get: + operationId: retrieveTicketingTicket + summary: Retrieve Tickets + description: Retrieve Tickets from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the `ticket` you want to retrive. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingTicketOutput' + tags: *ref_1 + x-speakeasy-group: ticketing.tickets + /ticketing/users: + get: + operationId: listTicketingUsers + summary: List Users + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingUserOutput' + tags: &ref_2 + - ticketing/users + x-speakeasy-group: ticketing.users + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ticketing/users/{id}: + get: + operationId: retrieveTicketingUser + summary: Retrieve User + description: Retrieve a User from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the user you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingUserOutput' + tags: *ref_2 + x-speakeasy-group: ticketing.users + /ticketing/accounts: + get: + operationId: listTicketingAccount + summary: List Accounts + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingAccountOutput' + tags: &ref_3 + - ticketing/accounts + x-speakeasy-group: ticketing.accounts + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ticketing/accounts/{id}: + get: + operationId: retrieveTicketingAccount + summary: Retrieve Accounts + description: Retrieve Accounts from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the account you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingAccountOutput' + tags: *ref_3 + x-speakeasy-group: ticketing.accounts + /ticketing/contacts: + get: + operationId: listTicketingContacts + summary: List Contacts + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingContactOutput' + tags: &ref_4 + - ticketing/contacts + x-speakeasy-group: ticketing.contacts + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ticketing/contacts/{id}: + get: + operationId: retrieveTicketingContact + summary: Retrieve Contact + description: Retrieve a Contact from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the contact you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingContactOutput' + tags: *ref_4 + x-speakeasy-group: ticketing.contacts + /sync/status/{vertical}: + get: + operationId: status + summary: Retrieve sync status of a certain vertical + parameters: + - name: vertical + required: true + in: path + example: ticketing + schema: + enum: + - ticketing + - marketingautomation + - crm + - filestorage + - ats + - hris + - accounting + - ecommerce + type: string + responses: + '200': + description: '' + tags: &ref_5 + - sync + x-speakeasy-group: sync + /sync/resync: + post: + operationId: resync + summary: Resync common objects across a vertical + parameters: [] + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ResyncStatusDto' + tags: *ref_5 + x-speakeasy-group: sync + /crm/companies: + get: + operationId: listCrmCompany + summary: List Companies + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedCrmCompanyOutput' + tags: &ref_6 + - crm/companies + x-speakeasy-group: crm.companies + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createCrmCompany + summary: Create Companies + description: Create Companies in any supported CRM software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original CRM software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmCompanyInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmCompanyOutput' + tags: *ref_6 + x-speakeasy-group: crm.companies + /crm/companies/{id}: + get: + operationId: retrieveCrmCompany + summary: Retrieve Companies + description: Retrieve Companies from any connected Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the company you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmCompanyOutput' + tags: *ref_6 + x-speakeasy-group: crm.companies + /crm/contacts: + get: + operationId: listCrmContacts + summary: List CRM Contacts + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedCrmContactOutput' + tags: &ref_7 + - crm/contacts + x-speakeasy-group: crm.contacts + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createCrmContact + summary: Create Contacts + description: Create Contacts in any supported CRM + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original CRM software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmContactInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmContactOutput' + tags: *ref_7 + x-speakeasy-group: crm.contacts + /crm/contacts/{id}: + get: + operationId: retrieveCrmContact + summary: Retrieve Contacts + description: Retrieve Contacts from any connected CRM + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the `contact` you want to retrive. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original CRM software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmContactOutput' + tags: *ref_7 + x-speakeasy-group: crm.contacts + /crm/deals: + get: + operationId: listCrmDeals + summary: List Deals + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedCrmDealOutput' + tags: &ref_8 + - crm/deals + x-speakeasy-group: crm.deals + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createCrmDeal + summary: Create Deals + description: Create Deals in any supported Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmDealInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmDealOutput' + tags: *ref_8 + x-speakeasy-group: crm.deals + /crm/deals/{id}: + get: + operationId: retrieveCrmDeal + summary: Retrieve Deals + description: Retrieve Deals from any connected Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the deal you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmDealOutput' + tags: *ref_8 + x-speakeasy-group: crm.deals + /crm/engagements: + get: + operationId: listCrmEngagements + summary: List Engagements + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedCrmEngagementOutput' + tags: &ref_9 + - crm/engagements + x-speakeasy-group: crm.engagements + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createCrmEngagement + summary: Create Engagements + description: Create Engagements in any supported Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmEngagementInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmEngagementOutput' + tags: *ref_9 + x-speakeasy-group: crm.engagements + /crm/engagements/{id}: + get: + operationId: retrieveCrmEngagement + summary: Retrieve Engagements + description: Retrieve Engagements from any connected Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the engagement you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmEngagementOutput' + tags: *ref_9 + x-speakeasy-group: crm.engagements + /crm/notes: + get: + operationId: listCrmNote + summary: List Notes + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedCrmNoteOutput' + tags: &ref_10 + - crm/notes + x-speakeasy-group: crm.notes + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createCrmNote + summary: Create Notes + description: Create Notes in any supported Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmNoteInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmNoteOutput' + tags: *ref_10 + x-speakeasy-group: crm.notes + /crm/notes/{id}: + get: + operationId: retrieveCrmNote + summary: Retrieve Notes + description: Retrieve Notes from any connected Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the note you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmNoteOutput' + tags: *ref_10 + x-speakeasy-group: crm.notes + /crm/stages: + get: + operationId: listCrmStages + summary: List Stages + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedCrmStageOutput' + tags: &ref_11 + - crm/stages + x-speakeasy-group: crm.stages + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /crm/stages/{id}: + get: + operationId: retrieveCrmStage + summary: Retrieve Stages + description: Retrieve Stages from any connected Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the stage you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmStageOutput' + tags: *ref_11 + x-speakeasy-group: crm.stages + /crm/tasks: + get: + operationId: listCrmTask + summary: List Tasks + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedCrmTaskOutput' + tags: &ref_12 + - crm/tasks + x-speakeasy-group: crm.tasks + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createCrmTask + summary: Create Tasks + description: Create Tasks in any supported Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmTaskInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmTaskOutput' + tags: *ref_12 + x-speakeasy-group: crm.tasks + /crm/tasks/{id}: + get: + operationId: retrieveCrmTask + summary: Retrieve Tasks + description: Retrieve Tasks from any connected Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the task you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Crm software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmTaskOutput' + tags: *ref_12 + x-speakeasy-group: crm.tasks + /crm/users: + get: + operationId: listCrmUsers + summary: List Users + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedCrmUserOutput' + tags: &ref_13 + - crm/users + x-speakeasy-group: crm.users + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /crm/users/{id}: + get: + operationId: retrieveCrmUser + summary: Retrieve Users + description: Retrieve Users from any connected Crm software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: b008e199-eda9-4629-bd41-a01b6195864a + description: id of the user you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original Crm software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedCrmUserOutput' + tags: *ref_13 + x-speakeasy-group: crm.users + /ticketing/collections: + get: + operationId: listTicketingCollections + summary: List Collections + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedTicketingCollectionOutput + tags: &ref_14 + - ticketing/collections + x-speakeasy-group: ticketing.collections + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ticketing/collections/{id}: + get: + operationId: retrieveCollection + summary: Retrieve Collections + description: Retrieve Collections from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the collection you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingCollectionOutput' + tags: *ref_14 + x-speakeasy-group: ticketing.collections + /ticketing/comments: + get: + operationId: listTicketingComments + summary: List Comments + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingCommentOutput' + tags: &ref_15 + - ticketing/comments + x-speakeasy-group: ticketing.comments + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createTicketingComment + summary: Create Comments + description: Create Comments in any supported Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingCommentInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingCommentOutput' + tags: *ref_15 + x-speakeasy-group: ticketing.comments + /ticketing/comments/{id}: + get: + operationId: retrieveTicketingComment + summary: Retrieve Comment + description: Retrieve a Comment from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the `comment` you want to retrive. + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingCommentOutput' + tags: *ref_15 + x-speakeasy-group: ticketing.comments + /ticketing/tags: + get: + operationId: listTicketingTags + summary: List Tags + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingTagOutput' + tags: &ref_16 + - ticketing/tags + x-speakeasy-group: ticketing.tags + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ticketing/tags/{id}: + get: + operationId: retrieveTicketingTag + summary: Retrieve Tag + description: Retrieve a Tag from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the tag you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingTagOutput' + tags: *ref_16 + x-speakeasy-group: ticketing.tags + /ticketing/teams: + get: + operationId: listTicketingTeams + summary: List Teams + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedTicketingTeamOutput' + tags: &ref_17 + - ticketing/teams + x-speakeasy-group: ticketing.teams + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ticketing/teams/{id}: + get: + operationId: retrieveTicketingTeam + summary: Retrieve Teams + description: Retrieve Teams from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the team you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingTeamOutput' + tags: *ref_17 + x-speakeasy-group: ticketing.teams + /linked_users: + post: + operationId: createLinkedUser + summary: Create Linked Users + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateLinkedUserDto' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/LinkedUserResponse' + tags: &ref_18 + - linkedUsers + x-speakeasy-group: linkedUsers + get: + operationId: listLinkedUsers + summary: List Linked Users + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LinkedUserResponse' + tags: *ref_18 + x-speakeasy-group: linkedUsers + /linked_users/batch: + post: + operationId: importBatch + summary: Add Batch Linked Users + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateBatchLinkedUserDto' + responses: + '201': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LinkedUserResponse' + tags: *ref_18 + x-speakeasy-group: linkedUsers + /linked_users/{id}: + get: + operationId: retrieveLinkedUser + summary: Retrieve Linked Users + parameters: + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/LinkedUserResponse' + tags: *ref_18 + x-speakeasy-group: linkedUsers + /linked_users/fromRemoteId: + get: + operationId: remoteId + summary: Retrieve a Linked User From A Remote Id + parameters: + - name: remoteId + required: true + in: query + example: id_1 + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/LinkedUserResponse' + tags: *ref_18 + x-speakeasy-group: linkedUsers + /projects: + get: + operationId: getProjects + summary: Retrieve projects + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProjectResponse' + tags: &ref_19 + - projects + post: + operationId: createProject + summary: Create a project + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectDto' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectResponse' + tags: *ref_19 + /field_mappings/values: + get: + operationId: getFieldMappingValues + summary: Retrieve field mappings values + parameters: [] + responses: + '200': + description: '' + tags: &ref_20 + - fieldMappings + x-speakeasy-group: fieldMappings + /field_mappings/entities: + get: + operationId: getFieldMappingsEntities + summary: Retrieve field mapping entities + parameters: [] + responses: + '200': + description: '' + tags: *ref_20 + x-speakeasy-group: fieldMappings + /field_mappings/attributes: + get: + operationId: getFieldMappings + summary: Retrieve field mappings + parameters: [] + responses: + '200': + description: '' + tags: *ref_20 + x-speakeasy-group: fieldMappings + /field_mappings/define: + post: + operationId: definitions + summary: Define target Field + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DefineTargetFieldDto' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldResponse' + tags: *ref_20 + x-speakeasy-group: fieldMappings + /field_mappings: + post: + operationId: defineCustomField + summary: Create Custom Field + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldCreateDto' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldResponse' + tags: *ref_20 + x-speakeasy-group: fieldMappings + /field_mappings/map: + post: + operationId: map + summary: Map Custom Field + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MapFieldToProviderDto' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldResponse' + tags: *ref_20 + x-speakeasy-group: fieldMappings + /events: + get: + operationId: getPanoraCoreEvents + summary: List Events + parameters: + - name: page + required: false + in: query + schema: + minimum: 1 + default: 1 + type: number + - name: limit + required: false + in: query + schema: + minimum: 1 + default: 10 + type: number + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EventResponse' + tags: + - events + /passthrough: + post: + operationId: request + summary: Make a passthrough request + parameters: + - name: x-connection-token + required: true + in: header + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PassThroughRequestDto' + responses: + '200': + description: '' + content: + application/json: + schema: + type: object + tags: &ref_21 + - passthrough + x-speakeasy-group: passthrough + /passthrough/{retryId}: + get: + operationId: getRetriedRequestResponse + summary: Retrieve response of a failed passthrough request due to rate limits + parameters: + - name: retryId + required: true + in: path + description: >- + id of the retryJob returned when you initiated a passthrough + request. + schema: + type: string + responses: + '200': + description: '' + tags: *ref_21 + x-speakeasy-group: passthrough.{retryid} + /hris/bankinfos: + get: + operationId: listHrisBankInfo + summary: List Bank Info + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisBankinfoOutput' + tags: &ref_22 + - hris/bankinfos + x-speakeasy-group: hris.bankinfos + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/bankinfos/{id}: + get: + operationId: retrieveHrisBankInfo + summary: Retrieve Bank Info + description: Retrieve Bank Info from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the bank info you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisBankinfoOutput' + tags: *ref_22 + x-speakeasy-group: hris.bankinfos + /hris/benefits: + get: + operationId: listHrisBenefits + summary: List Benefits + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisBenefitOutput' + tags: &ref_23 + - hris/benefits + x-speakeasy-group: hris.benefits + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/benefits/{id}: + get: + operationId: retrieveHrisBenefit + summary: Retrieve Benefit + description: Retrieve a Benefit from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the benefit you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisBenefitOutput' + tags: *ref_23 + x-speakeasy-group: hris.benefits + /hris/companies: + get: + operationId: listHrisCompanies + summary: List Companies + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisCompanyOutput' + tags: &ref_24 + - hris/companies + x-speakeasy-group: hris.companies + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/companies/{id}: + get: + operationId: retrieveHrisCompany + summary: Retrieve Company + description: Retrieve a Company from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the company you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisCompanyOutput' + tags: *ref_24 + x-speakeasy-group: hris.companies + /hris/dependents: + get: + operationId: listHrisDependents + summary: List Dependents + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisDependentOutput' + tags: &ref_25 + - hris/dependents + x-speakeasy-group: hris.dependents + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/dependents/{id}: + get: + operationId: retrieveHrisDependent + summary: Retrieve Dependent + description: Retrieve a Dependent from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the dependent you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisDependentOutput' + tags: *ref_25 + x-speakeasy-group: hris.dependents + /hris/employeepayrollruns: + get: + operationId: listHrisEmployeePayrollRun + summary: List Employee Payroll Runs + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedHrisEmployeepayrollrunOutput + tags: &ref_26 + - hris/employeepayrollruns + x-speakeasy-group: hris.employeepayrollruns + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/employeepayrollruns/{id}: + get: + operationId: retrieveHrisEmployeePayrollRun + summary: Retrieve Employee Payroll Run + description: Retrieve Employee Payroll Run from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the employeepayrollrun you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisEmployeepayrollrunOutput' + tags: *ref_26 + x-speakeasy-group: hris.employeepayrollruns + /hris/employees: + get: + operationId: listHrisEmployees + summary: List Employees + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisEmployeeOutput' + tags: &ref_27 + - hris/employees + x-speakeasy-group: hris.employees + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createHrisEmployee + summary: Create Employees + description: Create Employees in any supported Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisEmployeeInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisEmployeeOutput' + tags: *ref_27 + x-speakeasy-group: hris.employees + /hris/employees/{id}: + get: + operationId: retrieveHrisEmployee + summary: Retrieve Employee + description: Retrieve an Employee from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the employee you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisEmployeeOutput' + tags: *ref_27 + x-speakeasy-group: hris.employees + /hris/employerbenefits: + get: + operationId: listHrisEmployerBenefits + summary: List Employer Benefits + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedHrisEmployerbenefitOutput + tags: &ref_28 + - hris/employerbenefits + x-speakeasy-group: hris.employerbenefits + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/employerbenefits/{id}: + get: + operationId: retrieveHrisEmployerBenefit + summary: Retrieve Employer Benefit + description: Retrieve an Employer Benefit from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the employer benefit you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisEmployerbenefitOutput' + tags: *ref_28 + x-speakeasy-group: hris.employerbenefits + /hris/employments: + get: + operationId: listHrisEmployments + summary: List Employments + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisEmploymentOutput' + tags: &ref_29 + - hris/employments + x-speakeasy-group: hris.employments + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/employments/{id}: + get: + operationId: retrieveHrisEmployment + summary: Retrieve Employment + description: Retrieve an Employment from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the employment you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisEmploymentOutput' + tags: *ref_29 + x-speakeasy-group: hris.employments + /hris/groups: + get: + operationId: listHrisGroups + summary: List Groups + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisGroupOutput' + tags: &ref_30 + - hris/groups + x-speakeasy-group: hris.groups + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/groups/{id}: + get: + operationId: retrieveHrisGroup + summary: Retrieve Group + description: Retrieve a Group from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the group you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisGroupOutput' + tags: *ref_30 + x-speakeasy-group: hris.groups + /hris/locations: + get: + operationId: listHrisLocations + summary: List Locations + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisLocationOutput' + tags: &ref_31 + - hris/locations + x-speakeasy-group: hris.locations + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/locations/{id}: + get: + operationId: retrieveHrisLocation + summary: Retrieve Location + description: Retrieve a Location from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the location you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisLocationOutput' + tags: *ref_31 + x-speakeasy-group: hris.locations + /hris/paygroups: + get: + operationId: listHrisPaygroups + summary: List Pay Groups + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisPaygroupOutput' + tags: &ref_32 + - hris/paygroups + x-speakeasy-group: hris.paygroups + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/paygroups/{id}: + get: + operationId: retrieveHrisPaygroup + summary: Retrieve Pay Group + description: Retrieve a Pay Group from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the paygroup you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisPaygroupOutput' + tags: *ref_32 + x-speakeasy-group: hris.paygroups + /hris/payrollruns: + get: + operationId: listHrisPayrollRuns + summary: List Payroll Runs + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisPayrollrunOutput' + tags: &ref_33 + - hris/payrollruns + x-speakeasy-group: hris.payrollruns + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/payrollruns/{id}: + get: + operationId: retrieveHrisPayrollRun + summary: Retrieve Payroll Run + description: Retrieve a Payroll Run from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the payroll run you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisPayrollrunOutput' + tags: *ref_33 + x-speakeasy-group: hris.payrollruns + /hris/timeoffs: + get: + operationId: listHrisTimeoffs + summary: List Time Offs + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisTimeoffOutput' + tags: &ref_34 + - hris/timeoffs + x-speakeasy-group: hris.timeoffs + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createHrisTimeoff + summary: Create Timeoffs + description: Create Timeoffs in any supported Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimeoffInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimeoffOutput' + tags: *ref_34 + x-speakeasy-group: hris.timeoffs + /hris/timeoffs/{id}: + get: + operationId: retrieveHrisTimeoff + summary: Retrieve Time Off + description: Retrieve a Time Off from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the time off you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimeoffOutput' + tags: *ref_34 + x-speakeasy-group: hris.timeoffs + /hris/timeoffbalances: + get: + operationId: listHrisTimeoffbalances + summary: List TimeoffBalances + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisTimeoffbalanceOutput' + tags: &ref_35 + - hris/timeoffbalances + x-speakeasy-group: hris.timeoffbalances + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /hris/timeoffbalances/{id}: + get: + operationId: retrieveHrisTimeoffbalance + summary: Retrieve Time off Balances + description: Retrieve Time off Balances from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the timeoffbalance you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimeoffbalanceOutput' + tags: *ref_35 + x-speakeasy-group: hris.timeoffbalances + /hris/timesheetentries: + get: + operationId: listHrisTimesheetentries + summary: List Timesheetentries + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedHrisTimesheetEntryOutput' + tags: &ref_36 + - hris/timesheetentries + x-speakeasy-group: hris.timesheetentries + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createHrisTimesheetentry + summary: Create Timesheetentrys + description: Create Timesheetentrys in any supported Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimesheetEntryInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimesheetEntryOutput' + tags: *ref_36 + x-speakeasy-group: hris.timesheetentries + /hris/timesheetentries/{id}: + get: + operationId: retrieveHrisTimesheetentry + summary: Retrieve Timesheetentry + description: Retrieve an Timesheetentry from any connected Hris software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the timesheetentry you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Hris software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedHrisTimesheetEntryOutput' + tags: *ref_36 + x-speakeasy-group: hris.timesheetentries + /marketingautomation/actions: + get: + operationId: listMarketingautomationAction + summary: List Actions + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationActionOutput + tags: &ref_37 + - marketingautomation/actions + x-speakeasy-group: marketingautomation.actions + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createMarketingautomationAction + summary: Create Action + description: Create a action in any supported Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationActionInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationActionOutput' + tags: *ref_37 + x-speakeasy-group: marketingautomation.actions + /marketingautomation/actions/{id}: + get: + operationId: retrieveMarketingautomationAction + summary: Retrieve Actions + description: Retrieve Actions from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the action you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationActionOutput' + tags: *ref_37 + x-speakeasy-group: marketingautomation.actions + /marketingautomation/automations: + get: + operationId: listMarketingautomationAutomations + summary: List Automations + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationAutomationOutput + tags: &ref_38 + - marketingautomation/automations + x-speakeasy-group: marketingautomation.automations + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createMarketingautomationAutomation + summary: Create Automation + description: Create a automation in any supported Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationAutomationInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: >- + #/components/schemas/UnifiedMarketingautomationAutomationOutput + tags: *ref_38 + x-speakeasy-group: marketingautomation.automations + /marketingautomation/automations/{id}: + get: + operationId: retrieveMarketingautomationAutomation + summary: Retrieve Automation + description: Retrieve an Automation from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the automation you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: >- + #/components/schemas/UnifiedMarketingautomationAutomationOutput + tags: *ref_38 + x-speakeasy-group: marketingautomation.automations + /marketingautomation/campaigns: + get: + operationId: listMarketingautomationCampaigns + summary: List Campaigns + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationCampaignOutput + tags: &ref_39 + - marketingautomation/campaigns + x-speakeasy-group: marketingautomation.campaigns + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createMarketingautomationCampaign + summary: Create Campaign + description: Create a campaign in any supported Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationCampaignInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationCampaignOutput' + tags: *ref_39 + x-speakeasy-group: marketingautomation.campaigns + /marketingautomation/campaigns/{id}: + get: + operationId: retrieveMarketingautomationCampaign + summary: Retrieve Campaign + description: Retrieve a Campaign from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the campaign you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationCampaignOutput' + tags: *ref_39 + x-speakeasy-group: marketingautomation.campaigns + /marketingautomation/contacts: + get: + operationId: listMarketingAutomationContacts + summary: List Contacts + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationContactOutput + tags: &ref_40 + - marketingautomation/contacts + x-speakeasy-group: marketingautomation.contacts + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createMarketingAutomationContact + summary: Create Contact + description: Create a contact in any supported Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationContactInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationContactOutput' + tags: *ref_40 + x-speakeasy-group: marketingautomation.contacts + /marketingautomation/contacts/{id}: + get: + operationId: retrieveMarketingAutomationContact + summary: Retrieve Contacts + description: Retrieve Contacts from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the contact you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationContactOutput' + tags: *ref_40 + x-speakeasy-group: marketingautomation.contacts + /marketingautomation/emails: + get: + operationId: listMarketingautomationEmails + summary: List Emails + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationEmailOutput + tags: &ref_41 + - marketingautomation/emails + x-speakeasy-group: marketingautomation.emails + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /marketingautomation/emails/{id}: + get: + operationId: retrieveMarketingautomationEmail + summary: Retrieve Email + description: Retrieve an Email from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the email you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationEmailOutput' + tags: *ref_41 + x-speakeasy-group: marketingautomation.emails + /marketingautomation/events: + get: + operationId: listMarketingAutomationEvents + summary: List Events + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationEventOutput + tags: &ref_42 + - marketingautomation/events + x-speakeasy-group: marketingautomation.events + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /marketingautomation/events/{id}: + get: + operationId: retrieveMarketingautomationEvent + summary: Retrieve Event + description: Retrieve an Event from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the event you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationEventOutput' + tags: *ref_42 + x-speakeasy-group: marketingautomation.events + /marketingautomation/lists: + get: + operationId: listMarketingautomationLists + summary: List Lists + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationListOutput + tags: &ref_43 + - marketingautomation/lists + x-speakeasy-group: marketingautomation.lists + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createMarketingautomationList + summary: Create Lists + description: Create Lists in any supported Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationListInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationListOutput' + tags: *ref_43 + x-speakeasy-group: marketingautomation.lists + /marketingautomation/lists/{id}: + get: + operationId: retrieveMarketingautomationList + summary: Retrieve List + description: Retrieve a List from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the list you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationListOutput' + tags: *ref_43 + x-speakeasy-group: marketingautomation.lists + /marketingautomation/messages: + get: + operationId: listMarketingautomationMessages + summary: List Messages + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationMessageOutput + tags: &ref_44 + - marketingautomation/messages + x-speakeasy-group: marketingautomation.messages + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /marketingautomation/messages/{id}: + get: + operationId: retrieveMarketingautomationMessage + summary: Retrieve Messages + description: Retrieve Messages from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the message you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationMessageOutput' + tags: *ref_44 + x-speakeasy-group: marketingautomation.messages + /marketingautomation/templates: + get: + operationId: listMarketingautomationTemplates + summary: List Templates + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationTemplateOutput + tags: &ref_45 + - marketingautomation/templates + x-speakeasy-group: marketingautomation.templates + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createMarketingautomationTemplate + summary: Create Template + description: Create a template in any supported Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationTemplateInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationTemplateOutput' + tags: *ref_45 + x-speakeasy-group: marketingautomation.templates + /marketingautomation/templates/{id}: + get: + operationId: retrieveMarketingautomationTemplate + summary: Retrieve Template + description: Retrieve a Template from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the template you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationTemplateOutput' + tags: *ref_45 + x-speakeasy-group: marketingautomation.templates + /marketingautomation/users: + get: + operationId: listMarketingAutomationUsers + summary: List Users + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedMarketingautomationUserOutput + tags: &ref_46 + - marketingautomation/users + x-speakeasy-group: marketingautomation.users + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /marketingautomation/users/{id}: + get: + operationId: retrieveMarketingAutomationUser + summary: Retrieve Users + description: Retrieve Users from any connected Marketingautomation software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the user you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: >- + Set to true to include data from the original Marketingautomation + software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedMarketingautomationUserOutput' + tags: *ref_46 + x-speakeasy-group: marketingautomation.users + /ats/activities: + get: + operationId: listAtsActivity + summary: List Activities + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsActivityOutput' + tags: &ref_47 + - ats/activities + x-speakeasy-group: ats.activities + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAtsActivity + summary: Create Activities + description: Create Activities in any supported Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsActivityInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsActivityOutput' + tags: *ref_47 + x-speakeasy-group: ats.activities + /ats/activities/{id}: + get: + operationId: retrieveAtsActivity + summary: Retrieve Activities + description: Retrieve Activities from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the activity you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsActivityOutput' + tags: *ref_47 + x-speakeasy-group: ats.activities + /ats/applications: + get: + operationId: listAtsApplication + summary: List Applications + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsApplicationOutput' + tags: &ref_48 + - ats/applications + x-speakeasy-group: ats.applications + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAtsApplication + summary: Create Applications + description: Create Applications in any supported Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsApplicationInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsApplicationOutput' + tags: *ref_48 + x-speakeasy-group: ats.applications + /ats/applications/{id}: + get: + operationId: retrieveAtsApplication + summary: Retrieve Applications + description: Retrieve Applications from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the application you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsApplicationOutput' + tags: *ref_48 + x-speakeasy-group: ats.applications + /ats/attachments: + get: + operationId: listAtsAttachment + summary: List Attachments + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' + tags: &ref_49 + - ats/attachments + x-speakeasy-group: ats.attachments + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAtsAttachment + summary: Create Attachments + description: Create Attachments in any supported ATS software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsAttachmentInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' + tags: *ref_49 + x-speakeasy-group: ats.attachments + /ats/attachments/{id}: + get: + operationId: retrieveAtsAttachment + summary: Retrieve Attachments + description: Retrieve Attachments from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the attachment you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' + tags: *ref_49 + x-speakeasy-group: ats.attachments + /ats/candidates: + get: + operationId: listAtsCandidate + summary: List Candidates + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsCandidateOutput' + tags: &ref_50 + - ats/candidates + x-speakeasy-group: ats.candidates + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAtsCandidate + summary: Create Candidates + description: Create Candidates in any supported Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsCandidateInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsCandidateOutput' + tags: *ref_50 + x-speakeasy-group: ats.candidates + /ats/candidates/{id}: + get: + operationId: retrieveAtsCandidate + summary: Retrieve Candidates + description: Retrieve Candidates from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the candidate you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsCandidateOutput' + tags: *ref_50 + x-speakeasy-group: ats.candidates + /ats/departments: + get: + operationId: listAtsDepartments + summary: List Departments + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsDepartmentOutput' + tags: &ref_51 + - ats/departments + x-speakeasy-group: ats.departments + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/departments/{id}: + get: + operationId: retrieveAtsDepartment + summary: Retrieve Departments + description: Retrieve Departments from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the department you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsDepartmentOutput' + tags: *ref_51 + x-speakeasy-group: ats.departments + /ats/interviews: + get: + operationId: listAtsInterview + summary: List Interviews + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsInterviewOutput' + tags: &ref_52 + - ats/interviews + x-speakeasy-group: ats.interviews + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAtsInterview + summary: Create Interviews + description: Create Interviews in any supported Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsInterviewInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsInterviewOutput' + tags: *ref_52 + x-speakeasy-group: ats.interviews + /ats/interviews/{id}: + get: + operationId: retrieveAtsInterview + summary: Retrieve Interviews + description: Retrieve Interviews from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the interview you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsInterviewOutput' + tags: *ref_52 + x-speakeasy-group: ats.interviews + /ats/jobinterviewstages: + get: + operationId: listAtsJobInterviewStage + summary: List JobInterviewStages + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAtsJobinterviewstageOutput + tags: &ref_53 + - ats/jobinterviewstages + x-speakeasy-group: ats.jobinterviewstages + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/jobinterviewstages/{id}: + get: + operationId: retrieveAtsJobInterviewStage + summary: Retrieve Job Interview Stages + description: Retrieve Job Interview Stages from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the jobinterviewstage you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsJobinterviewstageOutput' + tags: *ref_53 + x-speakeasy-group: ats.jobinterviewstages + /ats/jobs: + get: + operationId: listAtsJob + summary: List Jobs + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsJobOutput' + tags: &ref_54 + - ats/jobs + x-speakeasy-group: ats.jobs + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/jobs/{id}: + get: + operationId: retrieveAtsJob + summary: Retrieve Jobs + description: Retrieve Jobs from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the job you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsJobOutput' + tags: *ref_54 + x-speakeasy-group: ats.jobs + /ats/offers: + get: + operationId: listAtsOffer + summary: List Offers + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsOfferOutput' + tags: &ref_55 + - ats/offers + x-speakeasy-group: ats.offers + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/offers/{id}: + get: + operationId: retrieveAtsOffer + summary: Retrieve Offers + description: Retrieve Offers from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the offer you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsOfferOutput' + tags: *ref_55 + x-speakeasy-group: ats.offers + /ats/offices: + get: + operationId: listAtsOffice + summary: List Offices + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsOfficeOutput' + tags: &ref_56 + - ats/offices + x-speakeasy-group: ats.offices + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/offices/{id}: + get: + operationId: retrieveAtsOffice + summary: Retrieve Offices + description: Retrieve Offices from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the office you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsOfficeOutput' + tags: *ref_56 + x-speakeasy-group: ats.offices + /ats/rejectreasons: + get: + operationId: listAtsRejectReasons + summary: List RejectReasons + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsRejectreasonOutput' + tags: &ref_57 + - ats/rejectreasons + x-speakeasy-group: ats.rejectreasons + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/rejectreasons/{id}: + get: + operationId: retrieveAtsRejectReason + summary: Retrieve Reject Reasons + description: Retrieve Reject Reasons from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the rejectreason you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsRejectreasonOutput' + tags: *ref_57 + x-speakeasy-group: ats.rejectreasons + /ats/scorecards: + get: + operationId: listAtsScorecard + summary: List ScoreCards + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsScorecardOutput' + tags: &ref_58 + - ats/scorecards + x-speakeasy-group: ats.scorecards + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/scorecards/{id}: + get: + operationId: retrieveAtsScorecard + summary: Retrieve Score Cards + description: Retrieve Score Cards from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the scorecard you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsScorecardOutput' + tags: *ref_58 + x-speakeasy-group: ats.scorecards + /ats/tags: + get: + operationId: listAtsTags + summary: List Tags + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsTagOutput' + tags: &ref_59 + - ats/tags + x-speakeasy-group: ats.tags + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/tags/{id}: + get: + operationId: retrieveAtsTag + summary: Retrieve Tags + description: Retrieve Tags from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the tag you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsTagOutput' + tags: *ref_59 + x-speakeasy-group: ats.tags + /ats/users: + get: + operationId: listAtsUsers + summary: List Users + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsUserOutput' + tags: &ref_60 + - ats/users + x-speakeasy-group: ats.users + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/users/{id}: + get: + operationId: retrieveAtsUser + summary: Retrieve Users + description: Retrieve Users from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the user you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsUserOutput' + tags: *ref_60 + x-speakeasy-group: ats.users + /ats/eeocs: + get: + operationId: listAtsEeocs + summary: List Eeocss + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAtsEeocsOutput' + tags: &ref_61 + - ats/eeocs + x-speakeasy-group: ats.eeocs + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ats/eeocs/{id}: + get: + operationId: retrieveAtsEeocs + summary: Retrieve Eeocs + description: Retrieve a eeocs from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the eeocs you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAtsEeocsOutput' + tags: *ref_61 + x-speakeasy-group: ats.eeocs + /accounting/accounts: + get: + operationId: listAccountingAccounts + summary: List Accounts + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAccountingAccountOutput' + tags: &ref_62 + - accounting/accounts + x-speakeasy-group: accounting.accounts + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAccountingAccount + summary: Create Accounts + description: Create accounts in any supported Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingAccountInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingAccountOutput' + tags: *ref_62 + x-speakeasy-group: accounting.accounts + /accounting/accounts/{id}: + get: + operationId: retrieveAccountingAccount + summary: Retrieve Accounts + description: Retrieve Accounts from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the account you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingAccountOutput' + tags: *ref_62 + x-speakeasy-group: accounting.accounts + /accounting/addresses: + get: + operationId: listAccountingAddress + summary: List Addresss + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAccountingAddressOutput' + tags: &ref_63 + - accounting/addresses + x-speakeasy-group: accounting.addresses + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/addresses/{id}: + get: + operationId: retrieveAccountingAddress + summary: Retrieve Addresses + description: Retrieve Addresses from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the address you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingAddressOutput' + tags: *ref_63 + x-speakeasy-group: accounting.addresses + /accounting/attachments: + get: + operationId: listAccountingAttachments + summary: List Attachments + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingAttachmentOutput + tags: &ref_64 + - accounting/attachments + x-speakeasy-group: accounting.attachments + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAccountingAttachment + summary: Create Attachments + description: Create attachments in any supported Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingAttachmentInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingAttachmentOutput' + tags: *ref_64 + x-speakeasy-group: accounting.attachments + /accounting/attachments/{id}: + get: + operationId: retrieveAccountingAttachment + summary: Retrieve Attachments + description: Retrieve attachments from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the attachment you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingAttachmentOutput' + tags: *ref_64 + x-speakeasy-group: accounting.attachments + /accounting/balancesheets: + get: + operationId: listAccountingBalanceSheets + summary: List BalanceSheets + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingBalancesheetOutput + tags: &ref_65 + - accounting/balancesheets + x-speakeasy-group: accounting.balancesheets + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/balancesheets/{id}: + get: + operationId: retrieveAccountingBalanceSheet + summary: Retrieve BalanceSheets + description: Retrieve BalanceSheets from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the balancesheet you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingBalancesheetOutput' + tags: *ref_65 + x-speakeasy-group: accounting.balancesheets + /accounting/cashflowstatements: + get: + operationId: listAccountingCashflowStatement + summary: List CashflowStatements + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingCashflowstatementOutput + tags: &ref_66 + - accounting/cashflowstatements + x-speakeasy-group: accounting.cashflowstatements + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/cashflowstatements/{id}: + get: + operationId: retrieveAccountingCashflowStatement + summary: Retrieve Cashflow Statements + description: Retrieve Cashflow Statements from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the cashflowstatement you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingCashflowstatementOutput' + tags: *ref_66 + x-speakeasy-group: accounting.cashflowstatements + /accounting/companyinfos: + get: + operationId: listAccountingCompanyInfos + summary: List CompanyInfos + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingCompanyinfoOutput + tags: &ref_67 + - accounting/companyinfos + x-speakeasy-group: accounting.companyinfos + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/companyinfos/{id}: + get: + operationId: retrieveAccountingCompanyInfo + summary: Retrieve Company Infos + description: Retrieve Company Infos from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the companyinfo you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingCompanyinfoOutput' + tags: *ref_67 + x-speakeasy-group: accounting.companyinfos + /accounting/contacts: + get: + operationId: listAccountingContacts + summary: List Contacts + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAccountingContactOutput' + tags: &ref_68 + - accounting/contacts + x-speakeasy-group: accounting.contacts + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAccountingContact + summary: Create Contacts + description: Create contacts in any supported Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingContactInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingContactOutput' + tags: *ref_68 + x-speakeasy-group: accounting.contacts + /accounting/contacts/{id}: + get: + operationId: retrieveAccountingContact + summary: Retrieve Contacts + description: Retrieve Contacts from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the contact you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingContactOutput' + tags: *ref_68 + x-speakeasy-group: accounting.contacts + /accounting/creditnotes: + get: + operationId: listAccountingCreditNote + summary: List CreditNotes + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingCreditnoteOutput + tags: &ref_69 + - accounting/creditnotes + x-speakeasy-group: accounting.creditnotes + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/creditnotes/{id}: + get: + operationId: retrieveAccountingCreditNote + summary: Retrieve Credit Notes + description: Retrieve Credit Notes from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the creditnote you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingCreditnoteOutput' + tags: *ref_69 + x-speakeasy-group: accounting.creditnotes + /accounting/expenses: + get: + operationId: listAccountingExpense + summary: List Expenses + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAccountingExpenseOutput' + tags: &ref_70 + - accounting/expenses + x-speakeasy-group: accounting.expenses + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAccountingExpense + summary: Create Expenses + description: Create Expenses in any supported Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingExpenseInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingExpenseOutput' + tags: *ref_70 + x-speakeasy-group: accounting.expenses + /accounting/expenses/{id}: + get: + operationId: retrieveAccountingExpense + summary: Retrieve Expenses + description: Retrieve Expenses from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the expense you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingExpenseOutput' + tags: *ref_70 + x-speakeasy-group: accounting.expenses + /accounting/incomestatements: + get: + operationId: listAccountingIncomeStatement + summary: List IncomeStatements + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingIncomestatementOutput + tags: &ref_71 + - accounting/incomestatements + x-speakeasy-group: accounting.incomestatements + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/incomestatements/{id}: + get: + operationId: retrieveAccountingIncomeStatement + summary: Retrieve Income Statements + description: Retrieve Income Statements from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the incomestatement you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingIncomestatementOutput' + tags: *ref_71 + x-speakeasy-group: accounting.incomestatements + /accounting/invoices: + get: + operationId: listAccountingInvoice + summary: List Invoices + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAccountingInvoiceOutput' + tags: &ref_72 + - accounting/invoices + x-speakeasy-group: accounting.invoices + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAccountingInvoice + summary: Create Invoices + description: Create invoices in any supported Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingInvoiceInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingInvoiceOutput' + tags: *ref_72 + x-speakeasy-group: accounting.invoices + /accounting/invoices/{id}: + get: + operationId: retrieveAccountingInvoice + summary: Retrieve Invoices + description: Retrieve Invoices from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the invoice you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingInvoiceOutput' + tags: *ref_72 + x-speakeasy-group: accounting.invoices + /accounting/items: + get: + operationId: listAccountingItem + summary: List Items + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAccountingItemOutput' + tags: &ref_73 + - accounting/items + x-speakeasy-group: accounting.items + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/items/{id}: + get: + operationId: retrieveAccountingItem + summary: Retrieve Items + description: Retrieve Items from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the item you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingItemOutput' + tags: *ref_73 + x-speakeasy-group: accounting.items + /accounting/journalentries: + get: + operationId: listAccountingJournalEntry + summary: List JournalEntrys + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingJournalentryOutput + tags: &ref_74 + - accounting/journalentries + x-speakeasy-group: accounting.journalentries + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAccountingJournalEntry + summary: Create Journal Entries + description: Create Journal Entries in any supported Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingJournalentryInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingJournalentryOutput' + tags: *ref_74 + x-speakeasy-group: accounting.journalentries + /accounting/journalentries/{id}: + get: + operationId: retrieveAccountingJournalEntry + summary: Retrieve Journal Entries + description: Retrieve Journal Entries from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the journalentry you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingJournalentryOutput' + tags: *ref_74 + x-speakeasy-group: accounting.journalentries + /accounting/payments: + get: + operationId: listAccountingPayment + summary: List Payments + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAccountingPaymentOutput' + tags: &ref_75 + - accounting/payments + x-speakeasy-group: accounting.payments + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAccountingPayment + summary: Create Payments + description: Create Payments in any supported Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingPaymentInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingPaymentOutput' + tags: *ref_75 + x-speakeasy-group: accounting.payments + /accounting/payments/{id}: + get: + operationId: retrieveAccountingPayment + summary: Retrieve Payments + description: Retrieve Payments from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the payment you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingPaymentOutput' + tags: *ref_75 + x-speakeasy-group: accounting.payments + /accounting/phonenumbers: + get: + operationId: listAccountingPhonenumber + summary: List PhoneNumbers + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingPhonenumberOutput + tags: &ref_76 + - accounting/phonenumbers + x-speakeasy-group: accounting.phonenumbers + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/phonenumbers/{id}: + get: + operationId: retrieveAccountingPhonenumber + summary: Retrieve Phone Numbers + description: Retrieve Phone Numbers from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the phonenumber you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingPhonenumberOutput' + tags: *ref_76 + x-speakeasy-group: accounting.phonenumbers + /accounting/purchaseorders: + get: + operationId: listAccountingPurchaseOrder + summary: List PurchaseOrders + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingPurchaseorderOutput + tags: &ref_77 + - accounting/purchaseorders + x-speakeasy-group: accounting.purchaseorders + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createAccountingPurchaseOrder + summary: Create Purchase Orders + description: Create Purchase Orders in any supported Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingPurchaseorderInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingPurchaseorderOutput' + tags: *ref_77 + x-speakeasy-group: accounting.purchaseorders + /accounting/purchaseorders/{id}: + get: + operationId: retrieveAccountingPurchaseOrder + summary: Retrieve Purchase Orders + description: Retrieve Purchase Orders from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the purchaseorder you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingPurchaseorderOutput' + tags: *ref_77 + x-speakeasy-group: accounting.purchaseorders + /accounting/taxrates: + get: + operationId: listAccountingTaxRate + summary: List TaxRates + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedAccountingTaxrateOutput' + tags: &ref_78 + - accounting/taxrates + x-speakeasy-group: accounting.taxrates + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/taxrates/{id}: + get: + operationId: retrieveAccountingTaxRate + summary: Retrieve Tax Rates + description: Retrieve Tax Rates from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the taxrate you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingTaxrateOutput' + tags: *ref_78 + x-speakeasy-group: accounting.taxrates + /accounting/trackingcategories: + get: + operationId: listAccountingTrackingCategorys + summary: List TrackingCategorys + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingTrackingcategoryOutput + tags: &ref_79 + - accounting/trackingcategories + x-speakeasy-group: accounting.trackingcategories + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/trackingcategories/{id}: + get: + operationId: retrieveAccountingTrackingCategory + summary: Retrieve Tracking Categories + description: Retrieve Tracking Categories from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the trackingcategory you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingTrackingcategoryOutput' + tags: *ref_79 + x-speakeasy-group: accounting.trackingcategories + /accounting/transactions: + get: + operationId: listAccountingTransaction + summary: List Transactions + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingTransactionOutput + tags: &ref_80 + - accounting/transactions + x-speakeasy-group: accounting.transactions + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/transactions/{id}: + get: + operationId: retrieveAccountingTransaction + summary: Retrieve Transactions + description: Retrieve Transactions from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the transaction you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingTransactionOutput' + tags: *ref_80 + x-speakeasy-group: accounting.transactions + /accounting/vendorcredits: + get: + operationId: listAccountingVendorCredit + summary: List VendorCredits + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedAccountingVendorcreditOutput + tags: &ref_81 + - accounting/vendorcredits + x-speakeasy-group: accounting.vendorcredits + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /accounting/vendorcredits/{id}: + get: + operationId: retrieveAccountingVendorCredit + summary: Retrieve Vendor Credits + description: Retrieve Vendor Credits from any connected Accounting software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: id of the vendorcredit you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedAccountingVendorcreditOutput' + tags: *ref_81 + x-speakeasy-group: accounting.vendorcredits + /filestorage/drives: + get: + operationId: listFilestorageDrives + summary: List Drives + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedFilestorageDriveOutput' + tags: &ref_82 + - filestorage/drives + x-speakeasy-group: filestorage.drives + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /filestorage/drives/{id}: + get: + operationId: retrieveFilestorageDrive + summary: Retrieve Drive + description: Retrieve a Drive from any connected file storage service + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the drive you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original file storage service. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageDriveOutput' + tags: *ref_82 + x-speakeasy-group: filestorage.drives + /filestorage/files: + get: + operationId: listFilestorageFile + summary: List Files + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedFilestorageFileOutput' + tags: &ref_83 + - filestorage/files + x-speakeasy-group: filestorage.files + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createFilestorageFile + summary: Create Files + description: Create Files in any supported Filestorage software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageFileInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageFileOutput' + tags: *ref_83 + x-speakeasy-group: filestorage.files + /filestorage/files/{id}: + get: + operationId: retrieveFilestorageFile + summary: Retrieve Files + description: Retrieve Files from any connected Filestorage software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the file you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original File Storage software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageFileOutput' + tags: *ref_83 + x-speakeasy-group: filestorage.files + /filestorage/folders: + get: + operationId: listFilestorageFolder + summary: List Folders + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedFilestorageFolderOutput' + tags: &ref_84 + - filestorage/folders + x-speakeasy-group: filestorage.folders + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createFilestorageFolder + summary: Create Folders + description: Create Folders in any supported Filestorage software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageFolderInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageFolderOutput' + tags: *ref_84 + x-speakeasy-group: filestorage.folders + /filestorage/folders/{id}: + get: + operationId: retrieveFilestorageFolder + summary: Retrieve Folders + description: Retrieve Folders from any connected Filestorage software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the folder you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original File Storage software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageFolderOutput' + tags: *ref_84 + x-speakeasy-group: filestorage.folders + /filestorage/groups: + get: + operationId: listFilestorageGroup + summary: List Groups + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedFilestorageGroupOutput' + tags: &ref_85 + - filestorage/groups + x-speakeasy-group: filestorage.groups + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /filestorage/groups/{id}: + get: + operationId: retrieveFilestorageGroup + summary: Retrieve Groups + description: Retrieve Groups from any connected Filestorage software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the permission you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original File Storage software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageGroupOutput' + tags: *ref_85 + x-speakeasy-group: filestorage.groups + /filestorage/users: + get: + operationId: listFilestorageUsers + summary: List Users + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedFilestorageUserOutput' + tags: &ref_86 + - filestorage/users + x-speakeasy-group: filestorage.users + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /filestorage/users/{id}: + get: + operationId: retrieveFilestorageUser + summary: Retrieve Users + description: Retrieve Users from any connected Filestorage software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the permission you want to retrieve. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original File Storage software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedFilestorageUserOutput' + tags: *ref_86 + x-speakeasy-group: filestorage.users + /ecommerce/products: + get: + operationId: listEcommerceProducts + summary: List Products + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedEcommerceProductOutput' + tags: &ref_87 + - ecommerce/products + x-speakeasy-group: ecommerce.products + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createEcommerceProduct + summary: Create Products + description: Create Products in any supported Ecommerce software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedEcommerceProductInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedEcommerceProductOutput' + tags: *ref_87 + x-speakeasy-group: ecommerce.products + /ecommerce/products/{id}: + get: + operationId: retrieveEcommerceProduct + summary: Retrieve Products + description: Retrieve products from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the product you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedEcommerceProductOutput' + tags: *ref_87 + x-speakeasy-group: ecommerce.products + /ecommerce/orders: + get: + operationId: listEcommerceOrders + summary: List Orders + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedEcommerceOrderOutput' + tags: &ref_88 + - ecommerce/orders + x-speakeasy-group: ecommerce.orders + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createEcommerceOrder + summary: Create Orders + description: Create Orders in any supported Ecommerce software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: false + description: Set to true to include data from the original Accounting software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedEcommerceOrderInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedEcommerceOrderOutput' + tags: *ref_88 + x-speakeasy-group: ecommerce.orders + /ecommerce/orders/{id}: + get: + operationId: retrieveEcommerceOrder + summary: Retrieve Orders + description: Retrieve orders from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the order you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedEcommerceOrderOutput' + tags: *ref_88 + x-speakeasy-group: ecommerce.orders + /ecommerce/customers: + get: + operationId: listEcommerceCustomers + summary: List Customers + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/UnifiedEcommerceCustomerOutput' + tags: &ref_89 + - ecommerce/customers + x-speakeasy-group: ecommerce.customers + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ecommerce/customers/{id}: + get: + operationId: retrieveEcommerceCustomer + summary: Retrieve Customers + description: Retrieve customers from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the customer you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedEcommerceCustomerOutput' + tags: *ref_89 + x-speakeasy-group: ecommerce.customers + /ecommerce/fulfillments: + get: + operationId: listEcommerceFulfillments + summary: List Fulfillments + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedEcommerceFulfillmentOutput + tags: &ref_90 + - ecommerce/fulfillments + x-speakeasy-group: ecommerce.fulfillments + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + /ecommerce/fulfillments/{id}: + get: + operationId: retrieveEcommerceFulfillment + summary: Retrieve Fulfillments + description: Retrieve fulfillments from any connected Ats software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the fulfillment you want to retrieve. + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ats software. + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedEcommerceFulfillmentOutput' + tags: *ref_90 + x-speakeasy-group: ecommerce.fulfillments + /ticketing/attachments: + get: + operationId: listTicketingAttachments + summary: List Attachments + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + example: true + description: Set to true to include data from the original software. + schema: + type: boolean + - name: limit + required: false + in: query + example: 10 + description: Set to get the number of records. + schema: + default: 50 + type: number + - name: cursor + required: false + in: query + example: 1b8b05bb-5273-4012-b520-8657b0b90874 + description: Set to get the number of records after this cursor. + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedDto' + - properties: + data: + type: array + items: + $ref: >- + #/components/schemas/UnifiedTicketingAttachmentOutput + tags: &ref_91 + - ticketing/attachments + x-speakeasy-group: ticketing.attachments + x-speakeasy-pagination: + type: cursor + inputs: + - name: cursor + in: parameters + type: cursor + outputs: + nextCursor: $.next_cursor + post: + operationId: createTicketingAttachment + summary: Create Attachments + description: Create Attachments in any supported Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + schema: + type: boolean + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingAttachmentInput' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingAttachmentOutput' + tags: *ref_91 + x-speakeasy-group: ticketing.attachments + /ticketing/attachments/{id}: + get: + operationId: retrieveTicketingAttachment + summary: Retrieve Attachments + description: Retrieve Attachments from any connected Ticketing software + parameters: + - name: x-connection-token + required: true + in: header + description: The connection token + schema: + type: string + - name: id + required: true + in: path + description: id of the attachment you want to retrive. + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + schema: + type: string + - name: remote_data + required: false + in: query + description: Set to true to include data from the original Ticketing software. + example: false + schema: + type: boolean + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedTicketingAttachmentOutput' + tags: *ref_91 + x-speakeasy-group: ticketing.attachments +info: + title: Panora API + description: A unified API to ship integrations + version: '1.0' + contact: {} +tags: [] +servers: + - url: https://api.panora.dev + description: Production server + - url: https://api-sandbox.panora.dev + description: Sandbox server + - url: https://api-dev.panora.dev + description: Development server +components: + securitySchemes: + api_key: + type: apiKey + in: header + name: x-api-key + schemas: + LoginDto: + type: object + properties: + id_user: + type: string + email: + type: string + password_hash: + type: string + required: + - email + - password_hash + Connection: + type: object + properties: + id_connection: + type: string + example: 123e4567-e89b-12d3-a456-426614174000 + description: Unique identifier for the connection + status: + type: string + example: active + description: Status of the connection + provider_slug: + type: string + example: hubspot + description: Slug for the provider + vertical: + type: string + example: crm + description: Vertical category of the connection + account_url: + type: string + example: https://example.com/account + description: URL of the account + token_type: + type: string + example: oauth2 + enum: + - oauth2 + - apikey + - basic + description: Strategy type + access_token: + type: string + example: access_token_example + description: Access token for the connection + refresh_token: + type: string + example: refresh_token_example + description: Refresh token for the connection + expiration_timestamp: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: Expiration timestamp of the access token + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: Timestamp when the connection was created + connection_token: + type: string + example: 123e4567-e89b-12d3-a456-426614174000 + description: UUID Token for the connection + id_project: + type: string + example: 123e4567-e89b-12d3-a456-426614174001 + description: Project ID associated with the connection + id_linked_user: + type: string + example: 123e4567-e89b-12d3-a456-426614174002 + description: Linked user ID associated with the connection + required: + - id_connection + - status + - provider_slug + - vertical + - account_url + - token_type + - access_token + - refresh_token + - expiration_timestamp + - created_at + - connection_token + - id_project + - id_linked_user + WebhookResponse: + type: object + properties: + id_webhook_endpoint: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The unique UUID of the webhook. + endpoint_description: + type: string + nullable: true + example: Webhook to receive connection events + description: The description of the webhook. + url: + type: string + example: https://acme.com/webhook_receiver + nullable: true + description: The endpoint url of the webhook. + secret: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The secret of the webhook. + active: + type: boolean + example: true + nullable: true + description: The status of the webhook. + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the webhook. + nullable: true + scope: + example: + - connection.created + nullable: true + description: The events that the webhook listen to. + type: array + items: + type: string + id_project: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The project id tied to the webhook. + last_update: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The last update date of the webhook. + required: + - id_webhook_endpoint + - endpoint_description + - url + - secret + - active + - created_at + - scope + - id_project + - last_update + WebhookDto: + type: object + properties: + url: + type: string + example: https://acme.com/webhook_receiver + nullable: true + description: The endpoint url of the webhook. + description: + type: string + example: Webhook to receive connection events + nullable: true + description: The description of the webhook. + scope: + example: + - connection.created + nullable: true + description: The events that the webhook listen to. + type: array + items: + type: string + required: + - url + - scope + SignatureVerificationDto: + type: object + properties: + payload: + type: object + additionalProperties: true + nullable: true + description: The payload event of the webhook. + signature: + type: string + nullable: true + description: The signature of the webhook. + secret: + type: string + nullable: true + description: The secret of the webhook. + required: + - payload + - signature + - secret + PaginatedDto: + type: object + properties: + prev_cursor: + type: string + nullable: true + next_cursor: + type: string + nullable: true + data: + type: array + items: + type: object + required: + - prev_cursor + - next_cursor + - data + UnifiedTicketingCommentInput: + type: object + properties: + body: + type: string + nullable: true + example: Assigned to Eric ! + description: The body of the comment + html_body: + type: string + nullable: true + example:

Assigned to Eric !

+ description: The html body of the comment + is_private: + type: boolean + nullable: true + example: false + description: The public status of the comment + creator_type: + type: string + nullable: true + example: USER + enum: &ref_121 + - USER + - CONTACT + description: >- + The creator type of the comment. Authorized values are either USER + or CONTACT + ticket_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the ticket the comment is tied to + contact_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: >- + The UUID of the contact which the comment belongs to (if no user_id + specified) + user_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: >- + The UUID of the user which the comment belongs to (if no contact_id + specified) + attachments: + type: array + items: &ref_122 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedTicketingAttachmentOutput' + nullable: true + example: &ref_123 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The attachements UUIDs tied to the comment + required: + - body + UnifiedTicketingTicketOutput: + type: object + properties: + name: + type: string + example: Customer Service Inquiry + nullable: true + description: The name of the ticket + status: + type: string + example: OPEN + enum: &ref_92 + - OPEN + - CLOSED + nullable: true + description: The status of the ticket. Authorized values are OPEN or CLOSED. + description: + type: string + example: Help customer + nullable: true + description: The description of the ticket + due_date: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date the ticket is due + type: + type: string + example: BUG + enum: &ref_93 + - BUG + - SUBTASK + - TASK + - TO-DO + nullable: true + description: >- + The type of the ticket. Authorized values are PROBLEM, QUESTION, or + TASK + parent_ticket: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent ticket + collections: + type: array + items: &ref_94 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedTicketingCollectionOutput' + example: &ref_95 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The collection UUIDs the ticket belongs to + tags: + type: array + items: &ref_96 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedTicketingTagOutput' + example: &ref_97 + - my_tag + - urgent_tag + nullable: true + description: The tags names of the ticket + completed_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date the ticket has been completed + priority: + type: string + example: HIGH + enum: &ref_98 + - HIGH + - MEDIUM + - LOW + nullable: true + description: >- + The priority of the ticket. Authorized values are HIGH, MEDIUM or + LOW. + assigned_to: + example: &ref_99 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The users UUIDs the ticket is assigned to + type: array + items: + type: string + comment: + example: &ref_100 + content: Assigned the issue ! + nullable: true + description: The comment of the ticket + allOf: + - $ref: '#/components/schemas/UnifiedTicketingCommentInput' + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the account which the ticket belongs to + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the contact which the ticket belongs to + attachments: + type: array + items: &ref_101 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedTicketingAttachmentInput' + example: &ref_102 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The attachements UUIDs tied to the ticket + nullable: true + field_mappings: + type: object + example: &ref_103 + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the ticket between the remote 3rd party + & Panora + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the ticket + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the ticket in the context of the 3rd Party + remote_data: + type: object + example: + key1: value1 + key2: 42 + key3: true + nullable: true + additionalProperties: true + description: The remote data of the ticket in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - name + - description + UnifiedTicketingTicketInput: + type: object + properties: + name: + type: string + example: Customer Service Inquiry + nullable: true + description: The name of the ticket + status: + type: string + example: OPEN + enum: *ref_92 + nullable: true + description: The status of the ticket. Authorized values are OPEN or CLOSED. + description: + type: string + example: Help customer + nullable: true + description: The description of the ticket + due_date: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date the ticket is due + type: + type: string + example: BUG + enum: *ref_93 + nullable: true + description: >- + The type of the ticket. Authorized values are PROBLEM, QUESTION, or + TASK + parent_ticket: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent ticket + collections: + type: array + items: *ref_94 + example: *ref_95 + nullable: true + description: The collection UUIDs the ticket belongs to + tags: + type: array + items: *ref_96 + example: *ref_97 + nullable: true + description: The tags names of the ticket + completed_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date the ticket has been completed + priority: + type: string + example: HIGH + enum: *ref_98 + nullable: true + description: >- + The priority of the ticket. Authorized values are HIGH, MEDIUM or + LOW. + assigned_to: + example: *ref_99 + nullable: true + description: The users UUIDs the ticket is assigned to + type: array + items: + type: string + comment: + example: *ref_100 + nullable: true + description: The comment of the ticket + allOf: + - $ref: '#/components/schemas/UnifiedTicketingCommentInput' + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the account which the ticket belongs to + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the contact which the ticket belongs to + attachments: + type: array + items: *ref_101 + example: *ref_102 + description: The attachements UUIDs tied to the ticket + nullable: true + field_mappings: + type: object + example: *ref_103 + nullable: true + description: >- + The custom field mappings of the ticket between the remote 3rd party + & Panora + additionalProperties: true + required: + - name + - description + UnifiedTicketingUserOutput: + type: object + properties: + name: + type: string + nullable: true + description: The name of the user + example: John Doe + email_address: + type: string + nullable: true + description: The email address of the user + example: john.doe@example.com + teams: + nullable: true + description: The teams whose the user is part of + example: + - team1 + - team2 + type: array + items: + type: string + account_id: + type: string + nullable: true + description: The account or organization the user is part of + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the user between the remote 3rd party & + Panora + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the user + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the user in the context of the 3rd Party + remote_data: + type: object + example: + key1: value1 + key2: 42 + key3: true + nullable: true + additionalProperties: true + description: The remote data of the user in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2023-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - name + - email_address + UnifiedTicketingAccountOutput: + type: object + properties: + name: + type: string + example: My Personal Account + nullable: true + description: The name of the account + domains: + example: + - acme.com + - acme-test.com + nullable: true + description: The domains of the account + type: array + items: + type: string + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the account between the remote 3rd + party & Panora + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the account + remote_id: + type: string + example: id_1 + description: The remote ID of the account in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the account in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the account + nullable: true + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The modified date of the account + nullable: true + required: + - name + UnifiedTicketingContactOutput: + type: object + properties: + name: + type: string + example: Joe + nullable: true + description: The name of the contact + email_address: + type: string + example: joedoe@acme.org + nullable: true + description: The email address of the contact + phone_number: + type: string + example: +33 6 50 11 11 10 + nullable: true + description: The phone number of the contact + details: + type: string + example: Contact Details + nullable: true + description: The details of the contact + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the contact between the remote 3rd + party & Panora + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the contact + remote_id: + type: string + example: id_1 + description: The remote ID of the contact in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the contact in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - name + - email_address + ResyncStatusDto: + type: object + properties: + timestamp: + format: date-time + type: string + example: '' + nullable: true + vertical: + type: string + example: ticketing + enum: + - ticketing + - ats + - accounting + - hris + - crm + - filestorage + - ecommerce + - marketingautomation + nullable: true + provider: + type: string + example: gitlab + nullable: true + status: + type: string + example: success + enum: + - success + - fail + nullable: true + required: + - timestamp + - vertical + - provider + - status + Email: + type: object + properties: + email_address: + type: string + nullable: true + description: The email address + email_address_type: + type: string + enum: + - PERSONAL + - WORK + nullable: true + description: >- + The email address type. Authorized values are either PERSONAL or + WORK. + owner_type: + type: string + enum: + - COMPANY + - CONTACT + nullable: true + description: The owner type of an email + required: + - email_address + - email_address_type + Address: + type: object + properties: + street_1: + type: string + nullable: true + example: 5th Avenue + description: The street + street_2: + type: string + nullable: true + example: Street 2 + description: 'More information about the street ' + city: + type: string + nullable: true + example: New York + description: The city + state: + type: string + example: New York + nullable: true + description: The state + postal_code: + type: string + example: '10001' + nullable: true + description: The postal code + country: + type: string + example: United States of America + nullable: true + description: The country + address_type: + type: string + enum: + - PERSONAL + - WORK + nullable: true + example: PERSONAL + description: The address type. Authorized values are either PERSONAL or WORK. + owner_type: + type: string + nullable: true + description: The owner type of the address + required: + - street_1 + - street_2 + - city + - state + - postal_code + - country + - address_type + - owner_type + Phone: + type: object + properties: + phone_number: + type: string + nullable: true + description: >- + The phone number starting with a plus (+) followed by the country + code (e.g +336676778890 for France) + phone_type: + type: string + enum: + - MOBILE + - WORK + nullable: true + description: The phone type. Authorized values are either MOBILE or WORK + owner_type: + type: string + nullable: true + description: The owner type of a phone number + required: + - phone_number + - phone_type + UnifiedCrmCompanyOutput: + type: object + properties: + name: + type: string + example: Acme + description: The name of the company + nullable: true + industry: + type: string + example: ACCOUNTING + enum: &ref_104 + - ACCOUNTING + - AIRLINES_AVIATION + - ALTERNATIVE_DISPUTE_RESOLUTION + - ALTERNATIVE_MEDICINE + - ANIMATION + - APPAREL_FASHION + - ARCHITECTURE_PLANNING + - ARTS_AND_CRAFTS + - AUTOMOTIVE + - AVIATION_AEROSPACE + - BANKING + - BIOTECHNOLOGY + - BROADCAST_MEDIA + - BUILDING_MATERIALS + - BUSINESS_SUPPLIES_AND_EQUIPMENT + - CAPITAL_MARKETS + - CHEMICALS + - CIVIC_SOCIAL_ORGANIZATION + - CIVIL_ENGINEERING + - COMMERCIAL_REAL_ESTATE + - COMPUTER_NETWORK_SECURITY + - COMPUTER_GAMES + - COMPUTER_HARDWARE + - COMPUTER_NETWORKING + - COMPUTER_SOFTWARE + - INTERNET + - CONSTRUCTION + - CONSUMER_ELECTRONICS + - CONSUMER_GOODS + - CONSUMER_SERVICES + - COSMETICS + - DAIRY + - DEFENSE_SPACE + - DESIGN + - EDUCATION_MANAGEMENT + - E_LEARNING + - ELECTRICAL_ELECTRONIC_MANUFACTURING + - ENTERTAINMENT + - ENVIRONMENTAL_SERVICES + - EVENTS_SERVICES + - EXECUTIVE_OFFICE + - FACILITIES_SERVICES + - FARMING + - FINANCIAL_SERVICES + - FINE_ART + - FISHERY + - FOOD_BEVERAGES + - FOOD_PRODUCTION + - FUND_RAISING + - FURNITURE + - GAMBLING_CASINOS + - GLASS_CERAMICS_CONCRETE + - GOVERNMENT_ADMINISTRATION + - GOVERNMENT_RELATIONS + - GRAPHIC_DESIGN + - HEALTH_WELLNESS_AND_FITNESS + - HIGHER_EDUCATION + - HOSPITAL_HEALTH_CARE + - HOSPITALITY + - HUMAN_RESOURCES + - IMPORT_AND_EXPORT + - INDIVIDUAL_FAMILY_SERVICES + - INDUSTRIAL_AUTOMATION + - INFORMATION_SERVICES + - INFORMATION_TECHNOLOGY_AND_SERVICES + - INSURANCE + - INTERNATIONAL_AFFAIRS + - INTERNATIONAL_TRADE_AND_DEVELOPMENT + - INVESTMENT_BANKING + - INVESTMENT_MANAGEMENT + - JUDICIARY + - LAW_ENFORCEMENT + - LAW_PRACTICE + - LEGAL_SERVICES + - LEGISLATIVE_OFFICE + - LEISURE_TRAVEL_TOURISM + - LIBRARIES + - LOGISTICS_AND_SUPPLY_CHAIN + - LUXURY_GOODS_JEWELRY + - MACHINERY + - MANAGEMENT_CONSULTING + - MARITIME + - MARKET_RESEARCH + - MARKETING_AND_ADVERTISING + - MECHANICAL_OR_INDUSTRIAL_ENGINEERING + - MEDIA_PRODUCTION + - MEDICAL_DEVICES + - MEDICAL_PRACTICE + - MENTAL_HEALTH_CARE + - MILITARY + - MINING_METALS + - MOTION_PICTURES_AND_FILM + - MUSEUMS_AND_INSTITUTIONS + - MUSIC + - NANOTECHNOLOGY + - NEWSPAPERS + - NON_PROFIT_ORGANIZATION_MANAGEMENT + - OIL_ENERGY + - ONLINE_MEDIA + - OUTSOURCING_OFFSHORING + - PACKAGE_FREIGHT_DELIVERY + - PACKAGING_AND_CONTAINERS + - PAPER_FOREST_PRODUCTS + - PERFORMING_ARTS + - PHARMACEUTICALS + - PHILANTHROPY + - PHOTOGRAPHY + - PLASTICS + - POLITICAL_ORGANIZATION + - PRIMARY_SECONDARY_EDUCATION + - PRINTING + - PROFESSIONAL_TRAINING_COACHING + - PROGRAM_DEVELOPMENT + - PUBLIC_POLICY + - PUBLIC_RELATIONS_AND_COMMUNICATIONS + - PUBLIC_SAFETY + - PUBLISHING + - RAILROAD_MANUFACTURE + - RANCHING + - REAL_ESTATE + - RECREATIONAL_FACILITIES_AND_SERVICES + - RELIGIOUS_INSTITUTIONS + - RENEWABLES_ENVIRONMENT + - RESEARCH + - RESTAURANTS + - RETAIL + - SECURITY_AND_INVESTIGATIONS + - SEMICONDUCTORS + - SHIPBUILDING + - SPORTING_GOODS + - SPORTS + - STAFFING_AND_RECRUITING + - SUPERMARKETS + - TELECOMMUNICATIONS + - TEXTILES + - THINK_TANKS + - TOBACCO + - TRANSLATION_AND_LOCALIZATION + - TRANSPORTATION_TRUCKING_RAILROAD + - UTILITIES + - VENTURE_CAPITAL_PRIVATE_EQUITY + - VETERINARY + - WAREHOUSING + - WHOLESALE + - WINE_AND_SPIRITS + - WIRELESS + - WRITING_AND_EDITING + description: >- + The industry of the company. Authorized values can be found in the + Industry enum. + nullable: true + number_of_employees: + type: number + example: 10 + description: The number of employees of the company + nullable: true + user_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user who owns the company + nullable: true + email_addresses: + description: The email addresses of the company + example: &ref_105 + - email_address: acme@gmail.com + email_address_type: WORK + nullable: true + type: array + items: + $ref: '#/components/schemas/Email' + addresses: + description: The addresses of the company + example: &ref_106 + - street_1: 5th Avenue + city: New York + state: NY + country: USA + address_type: WORK + nullable: true + type: array + items: + $ref: '#/components/schemas/Address' + phone_numbers: + description: The phone numbers of the company + example: &ref_107 + - phone_number: '+33660606067' + phone_type: WORK + nullable: true + type: array + items: + $ref: '#/components/schemas/Phone' + field_mappings: + type: object + example: &ref_108 + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the company between the remote 3rd + party & Panora + nullable: true + additionalProperties: true + id: + type: string + description: The UUID of the company + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + remote_id: + type: string + example: id_1 + description: The id of the company in the context of the Crm 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + description: The remote data of the company in the context of the Crm 3rd Party + nullable: true + additionalProperties: true + created_at: + type: object + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + type: object + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + required: + - name + UnifiedCrmCompanyInput: + type: object + properties: + name: + type: string + example: Acme + description: The name of the company + nullable: true + industry: + type: string + example: ACCOUNTING + enum: *ref_104 + description: >- + The industry of the company. Authorized values can be found in the + Industry enum. + nullable: true + number_of_employees: + type: number + example: 10 + description: The number of employees of the company + nullable: true + user_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user who owns the company + nullable: true + email_addresses: + description: The email addresses of the company + example: *ref_105 + nullable: true + type: array + items: + $ref: '#/components/schemas/Email' + addresses: + description: The addresses of the company + example: *ref_106 + nullable: true + type: array + items: + $ref: '#/components/schemas/Address' + phone_numbers: + description: The phone numbers of the company + example: *ref_107 + nullable: true + type: array + items: + $ref: '#/components/schemas/Phone' + field_mappings: + type: object + example: *ref_108 + description: >- + The custom field mappings of the company between the remote 3rd + party & Panora + nullable: true + additionalProperties: true + required: + - name + UnifiedCrmContactOutput: + type: object + properties: + first_name: + type: string + description: The first name of the contact + example: John + nullable: true + last_name: + type: string + description: The last name of the contact + example: Doe + nullable: true + email_addresses: + nullable: true + description: The email addresses of the contact + example: &ref_109 + - email: john.doe@example.com + type: WORK + type: array + items: + $ref: '#/components/schemas/Email' + phone_numbers: + nullable: true + description: The phone numbers of the contact + example: &ref_110 + - phone: '1234567890' + type: WORK + type: array + items: + $ref: '#/components/schemas/Phone' + addresses: + nullable: true + description: The addresses of the contact + example: &ref_111 + - street: 123 Main St + city: Anytown + state: CA + zip: '12345' + country: USA + type: WORK + type: array + items: + $ref: '#/components/schemas/Address' + user_id: + type: string + nullable: true + description: The UUID of the user who owns the contact + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + field_mappings: + type: object + example: &ref_112 + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the contact between the remote 3rd + party & Panora + additionalProperties: true + id: + type: string + description: The UUID of the contact + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the contact in the context of the Crm 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the contact in the context of the Crm 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - first_name + - last_name + UnifiedCrmContactInput: + type: object + properties: + first_name: + type: string + description: The first name of the contact + example: John + nullable: true + last_name: + type: string + description: The last name of the contact + example: Doe + nullable: true + email_addresses: + nullable: true + description: The email addresses of the contact + example: *ref_109 + type: array + items: + $ref: '#/components/schemas/Email' + phone_numbers: + nullable: true + description: The phone numbers of the contact + example: *ref_110 + type: array + items: + $ref: '#/components/schemas/Phone' + addresses: + nullable: true + description: The addresses of the contact + example: *ref_111 + type: array + items: + $ref: '#/components/schemas/Address' + user_id: + type: string + nullable: true + description: The UUID of the user who owns the contact + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + field_mappings: + type: object + example: *ref_112 + nullable: true + description: >- + The custom field mappings of the contact between the remote 3rd + party & Panora + additionalProperties: true + required: + - first_name + - last_name + UnifiedCrmDealOutput: + type: object + properties: + name: + type: string + example: Huge Contract with Acme + description: The name of the deal + nullable: true + description: + type: string + example: Contract with Sales Operations Team + description: The description of the deal + nullable: true + amount: + type: number + example: 1000 + description: The amount of the deal + nullable: true + user_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user who is on the deal + stage_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the stage of the deal + company_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the company tied to the deal + field_mappings: + type: object + nullable: true + example: &ref_113 + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the company between the remote 3rd + party & Panora + additionalProperties: true + id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the deal + remote_id: + type: string + nullable: true + example: id_1 + description: The id of the deal in the context of the Crm 3rd Party + remote_data: + type: object + nullable: true + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + description: The remote data of the deal in the context of the Crm 3rd Party + created_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The created date of the object + modified_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + required: + - name + - description + - amount + UnifiedCrmDealInput: + type: object + properties: + name: + type: string + example: Huge Contract with Acme + description: The name of the deal + nullable: true + description: + type: string + example: Contract with Sales Operations Team + description: The description of the deal + nullable: true + amount: + type: number + example: 1000 + description: The amount of the deal + nullable: true + user_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user who is on the deal + stage_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the stage of the deal + company_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the company tied to the deal + field_mappings: + type: object + nullable: true + example: *ref_113 + description: >- + The custom field mappings of the company between the remote 3rd + party & Panora + additionalProperties: true + required: + - name + - description + - amount + UnifiedCrmEngagementOutput: + type: object + properties: + content: + type: string + nullable: true + example: Meeting call with CTO + description: The content of the engagement + direction: + type: string + nullable: true + example: INBOUND + enum: &ref_114 + - INBOUND + - OUTBOUND + description: >- + The direction of the engagement. Authorized values are INBOUND or + OUTBOUND + subject: + type: string + example: Technical features planning + nullable: true + description: The subject of the engagement + start_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The start time of the engagement + end_time: + format: date-time + type: string + nullable: true + example: '2024-10-01T22:00:00Z' + description: The end time of the engagement + type: + type: string + nullable: true + example: MEETING + enum: &ref_115 + - EMAIL + - CALL + - MEETING + description: >- + The type of the engagement. Authorized values are EMAIL, CALL or + MEETING + user_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user tied to the engagement + company_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the company tied to the engagement + contacts: + nullable: true + example: &ref_116 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUIDs of contacts tied to the engagement object + type: array + items: + type: string + field_mappings: + type: object + nullable: true + example: &ref_117 + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the engagement between the remote 3rd + party & Panora + additionalProperties: true + id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the engagement + remote_id: + type: string + nullable: true + example: id_1 + description: The id of the engagement in the context of the Crm 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The remote data of the engagement in the context of the Crm 3rd + Party + created_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The created date of the object + modified_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + required: + - type + UnifiedCrmEngagementInput: + type: object + properties: + content: + type: string + nullable: true + example: Meeting call with CTO + description: The content of the engagement + direction: + type: string + nullable: true + example: INBOUND + enum: *ref_114 + description: >- + The direction of the engagement. Authorized values are INBOUND or + OUTBOUND + subject: + type: string + example: Technical features planning + nullable: true + description: The subject of the engagement + start_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The start time of the engagement + end_time: + format: date-time + type: string + nullable: true + example: '2024-10-01T22:00:00Z' + description: The end time of the engagement + type: + type: string + nullable: true + example: MEETING + enum: *ref_115 + description: >- + The type of the engagement. Authorized values are EMAIL, CALL or + MEETING + user_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user tied to the engagement + company_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the company tied to the engagement + contacts: + nullable: true + example: *ref_116 + description: The UUIDs of contacts tied to the engagement object + type: array + items: + type: string + field_mappings: + type: object + nullable: true + example: *ref_117 + description: >- + The custom field mappings of the engagement between the remote 3rd + party & Panora + additionalProperties: true + required: + - type + UnifiedCrmNoteOutput: + type: object + properties: + content: + type: string + example: My notes taken during the meeting + description: The content of the note + nullable: true + user_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user tied to the note + nullable: true + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company tied to the note + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the contact tied to the note + nullable: true + deal_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the deal tied to the note + field_mappings: + type: object + example: &ref_118 + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the note between the remote 3rd party & + Panora + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the note + remote_id: + type: string + example: id_1 + description: The ID of the note in the context of the Crm 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the note in the context of the Crm 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - content + UnifiedCrmNoteInput: + type: object + properties: + content: + type: string + example: My notes taken during the meeting + description: The content of the note + nullable: true + user_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user tied to the note + nullable: true + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company tied to the note + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the contact tied to the note + nullable: true + deal_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the deal tied to the note + field_mappings: + type: object + example: *ref_118 + nullable: true + description: >- + The custom field mappings of the note between the remote 3rd party & + Panora + additionalProperties: true + required: + - content + UnifiedCrmStageOutput: + type: object + properties: + stage_name: + type: string + example: Qualified + description: The name of the stage + nullable: true + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the stage between the remote 3rd party + & Panora + nullable: true + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the stage + nullable: true + remote_id: + type: string + example: id_1 + description: The ID of the stage in the context of the Crm 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + description: The remote data of the stage in the context of the Crm 3rd Party + nullable: true + additionalProperties: true + created_at: + type: object + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + type: object + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + required: + - stage_name + UnifiedCrmTaskOutput: + type: object + properties: + subject: + type: string + example: Answer customers + description: The subject of the task + nullable: true + content: + type: string + example: Prepare email campaign + description: The content of the task + nullable: true + status: + type: string + example: PENDING + enum: &ref_119 + - PENDING + - COMPLETED + description: The status of the task. Authorized values are PENDING, COMPLETED. + nullable: true + due_date: + type: string + example: '2024-10-01T12:00:00Z' + description: The due date of the task + nullable: true + finished_date: + type: string + example: '2024-10-01T12:00:00Z' + description: The finished date of the task + nullable: true + user_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user tied to the task + nullable: true + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the company tied to the task + nullable: true + deal_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the deal tied to the task + nullable: true + field_mappings: + type: object + example: &ref_120 + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the task between the remote 3rd party & + Panora + nullable: true + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the task + nullable: true + remote_id: + type: string + example: id_1 + description: The ID of the task in the context of the Crm 3rd Party + nullable: true + remote_data: + type: object + example: + key1: value1 + key2: 42 + key3: true + description: The remote data of the task in the context of the Crm 3rd Party + nullable: true + additionalProperties: true + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + required: + - subject + - content + - status + UnifiedCrmTaskInput: + type: object + properties: + subject: + type: string + example: Answer customers + description: The subject of the task + nullable: true + content: + type: string + example: Prepare email campaign + description: The content of the task + nullable: true + status: + type: string + example: PENDING + enum: *ref_119 + description: The status of the task. Authorized values are PENDING, COMPLETED. + nullable: true + due_date: + type: string + example: '2024-10-01T12:00:00Z' + description: The due date of the task + nullable: true + finished_date: + type: string + example: '2024-10-01T12:00:00Z' + description: The finished date of the task + nullable: true + user_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user tied to the task + nullable: true + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the company tied to the task + nullable: true + deal_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the deal tied to the task + nullable: true + field_mappings: + type: object + example: *ref_120 + description: >- + The custom field mappings of the task between the remote 3rd party & + Panora + nullable: true + additionalProperties: true + required: + - subject + - content + - status + UnifiedCrmUserOutput: + type: object + properties: + name: + type: string + example: Jane Doe + description: The name of the user + nullable: true + email: + type: string + example: jane.doe@example.com + description: The email of the user + nullable: true + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the user between the remote 3rd party & + Panora + nullable: true + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user + nullable: true + remote_id: + type: string + example: id_1 + description: The id of the user in the context of the Crm 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + description: The remote data of the user in the context of the Crm 3rd Party + nullable: true + additionalProperties: true + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + required: + - name + - email + UnifiedTicketingCollectionOutput: + type: object + properties: + name: + type: string + example: My Personal Collection + nullable: true + description: The name of the collection + description: + type: string + example: Collect issues + nullable: true + description: The description of the collection + collection_type: + type: string + example: PROJECT + enum: + - PROJECT + - LIST + nullable: true + description: >- + The type of the collection. Authorized values are either PROJECT or + LIST + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the collection + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the collection in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the collection in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - name + UnifiedTicketingCommentOutput: + type: object + properties: + body: + type: string + nullable: true + example: Assigned to Eric ! + description: The body of the comment + html_body: + type: string + nullable: true + example:

Assigned to Eric !

+ description: The html body of the comment + is_private: + type: boolean + nullable: true + example: false + description: The public status of the comment + creator_type: + type: string + nullable: true + example: USER + enum: *ref_121 + description: >- + The creator type of the comment. Authorized values are either USER + or CONTACT + ticket_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the ticket the comment is tied to + contact_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: >- + The UUID of the contact which the comment belongs to (if no user_id + specified) + user_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: >- + The UUID of the user which the comment belongs to (if no contact_id + specified) + attachments: + type: array + items: *ref_122 + nullable: true + example: *ref_123 + description: The attachements UUIDs tied to the comment + id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the comment + remote_id: + type: string + nullable: true + example: id_1 + description: The id of the comment in the context of the 3rd Party + remote_data: + type: object + nullable: true + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + description: The remote data of the comment in the context of the 3rd Party + created_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The created date of the object + modified_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + required: + - body + UnifiedTicketingTagOutput: + type: object + properties: + name: + type: string + example: urgent_tag + nullable: true + description: The name of the tag + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the tag between the remote 3rd party & + Panora + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the tag + remote_id: + type: string + example: id_1 + description: The remote ID of the tag in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the tag in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the tag + nullable: true + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The modified date of the tag + nullable: true + required: + - name + UnifiedTicketingTeamOutput: + type: object + properties: + name: + type: string + example: My team + nullable: true + description: The name of the team + description: + type: string + example: Internal members + nullable: true + description: The description of the team + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the team between the remote 3rd party & + Panora + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the team + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the team in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the team in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - name + LinkedUserResponse: + type: object + properties: + id_linked_user: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + linked_user_origin_id: + type: string + example: id_1 + nullable: true + alias: + type: string + example: acme + nullable: true + id_project: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + required: + - id_linked_user + - linked_user_origin_id + - alias + - id_project + CreateLinkedUserDto: + type: object + properties: + linked_user_origin_id: + type: string + description: The id of the user in the context of your own software + example: id_1 + alias: + type: string + nullable: true + description: Your company alias + example: acme + required: + - linked_user_origin_id + - alias + CreateBatchLinkedUserDto: + type: object + properties: + linked_user_origin_ids: + nullable: true + description: The ids of the users in the context of your own software + example: + - id_1 + type: array + items: + type: string + alias: + type: string + nullable: true + description: Your company alias + example: acme + required: + - linked_user_origin_ids + - alias + ProjectResponse: + type: object + properties: + id_project: + type: string + example: 123e4567-e89b-12d3-a456-426614174000 + description: Unique identifier for the project + name: + type: string + example: My Project + description: Name of the project + sync_mode: + type: string + example: automatic + description: Synchronization mode of the project + pull_frequency: + type: number + example: 3600 + description: Frequency of pulling data in seconds + redirect_url: + type: string + example: https://example.com/redirect + description: Redirect URL for the project + id_user: + type: string + example: 123e4567-e89b-12d3-a456-426614174001 + description: User ID associated with the project + id_connector_set: + type: string + example: 123e4567-e89b-12d3-a456-426614174002 + description: Connector set ID associated with the project + required: + - id_project + - name + - sync_mode + - id_user + - id_connector_set + CreateProjectDto: + type: object + properties: + name: + type: string + example: Project Name + description: The name of the project + id_organization: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The organization ID + id_user: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The user ID + required: + - name + - id_user + CustomFieldResponse: + type: object + properties: + id_attribute: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: Attribute Id + status: + type: string + nullable: true + example: '' + description: Attribute Status + ressource_owner_type: + type: string + example: '' + nullable: true + description: Attribute Ressource Owner Type + slug: + type: string + nullable: true + example: fav_dish + description: Attribute Slug + description: + type: string + nullable: true + example: My favorite dish + description: Attribute Description + data_type: + type: string + nullable: true + example: string + enum: + - string + - number + description: Attribute Data Type + remote_id: + type: string + nullable: true + example: id_1 + description: Attribute Remote Id + source: + type: string + nullable: true + example: hubspot + description: Attribute Source + id_entity: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: Attribute Entity Id + id_project: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: Attribute Project Id + scope: + type: string + nullable: true + example: '' + description: Attribute Scope + id_consumer: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: Attribute Consumer Id + created_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: Attribute Created Date + modified_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: Attribute Modified Date + required: + - id_attribute + - status + - ressource_owner_type + - slug + - description + - data_type + - remote_id + - source + - id_entity + - id_project + - scope + - id_consumer + - created_at + - modified_at + DefineTargetFieldDto: + type: object + properties: + object_type_owner: + type: string + example: company + enum: + - company + - contact + - deal + - lead + - note + - task + - engagement + - stage + - user + nullable: true + name: + type: string + nullable: true + example: fav_dish + description: The name of the target field + description: + type: string + nullable: true + example: My favorite dish + description: The description of the target field + data_type: + type: string + nullable: true + example: string + enum: + - string + - number + description: The data type of the target field + required: + - object_type_owner + - name + - description + - data_type + CustomFieldCreateDto: + type: object + properties: + object_type_owner: + type: string + example: company + enum: + - company + - contact + - deal + - lead + - note + - task + - engagement + - stage + - user + nullable: true + name: + type: string + nullable: true + example: my_favorite_dish + description: The name of the custom field + description: + type: string + nullable: true + example: Favorite Dish + description: The description of the custom field + data_type: + type: string + example: string + nullable: true + enum: + - string + - number + description: The data type of the custom field + source_custom_field_id: + type: string + nullable: true + example: id_1 + description: The source custom field ID + source_provider: + type: string + nullable: true + example: hubspot + description: The name of the source software/provider + linked_user_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The linked user ID + required: + - object_type_owner + - name + - description + - data_type + - source_custom_field_id + - source_provider + - linked_user_id + MapFieldToProviderDto: + type: object + properties: + attributeId: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The attribute ID + source_custom_field_id: + type: string + nullable: true + example: id_1 + description: The source custom field ID + source_provider: + type: string + nullable: true + example: hubspot + description: The source provider + linked_user_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The linked user ID + required: + - attributeId + - source_custom_field_id + - source_provider + - linked_user_id + EventResponse: + type: object + properties: + id_event: + type: string + example: 123e4567-e89b-12d3-a456-426614174000 + description: Unique identifier for the event + id_connection: + type: string + example: 123e4567-e89b-12d3-a456-426614174001 + description: Connection ID associated with the event + id_project: + type: string + example: 123e4567-e89b-12d3-a456-426614174002 + description: Project ID associated with the event + type: + type: string + example: connection.created + enum: + - crm.contact.created + - crm.contact.pulled + - crm.company.created + - crm.company.pulled + - crm.deal.created + - crm.deal.pulled + - crm.engagement.created + - crm.engagement.pulled + - crm.note.created + - crm.note.pulled + - crm.stage.pulled + - crm.task.pulled + - crm.task.created + - crm.user.pulled + - ticketing.ticket.created + - ticketing.ticket.pulled + - ticketing.comment.created + - ticketing.comment.pulled + - ticketing.attachment.created + - ticketing.attachment.pulled + - ticketing.collection.pulled + - ticketing.account.pulled + - ticketing.contact.pulled + - ticketing.tag.pulled + - ticketing.team.pulled + - ticketing.user.pulled + - ats.activity.created + - ats.activity.pulled + - ats.application.created + - ats.application.pulled + - ats.attachment.created + - ats.attachment.pulled + - ats.candidate.created + - ats.candidate.pulled + - ats.department.pulled + - ats.eecos.pulled + - ats.interview.created + - ats.interview.pulled + - ats.job.pulled + - ats.jobinterviewstage.pulled + - ats.offer.created + - ats.office.pulled + - ats.rejectreason.pulled + - ats.scorecard.pulled + - ats.tag.pulled + - ats.user.pulled + - filestorage.file.created + - filestorage.file.pulled + - filestorage.folder.created + - filestorage.folder.pulled + - filestorage.group.pulled + - filestorage.user.pulled + - filestorage.drive.pulled + - filestorage.permission.pulled + - filestorage.sharedlink.pulled + - connection.created + description: Scope of the event + status: + type: string + example: success + enum: + - success + - fail + description: Status of the event + direction: + type: string + example: '0' + description: Direction of the event + method: + type: string + example: POST + enum: + - GET + - POST + - PUT + - DELETE + description: HTTP method used for the event + url: + type: string + example: /crm/companies + description: URL associated with the event + provider: + type: string + example: hubspot + description: Provider associated with the event + timestamp: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: Timestamp of the event + id_linked_user: + type: string + example: 123e4567-e89b-12d3-a456-426614174003 + description: Linked user ID associated with the event + required: + - id_event + - id_connection + - id_project + - type + - status + - direction + - method + - url + - provider + - timestamp + - id_linked_user + PassThroughRequestDto: + type: object + properties: + method: + enum: + - GET + - POST + type: string + path: + type: string + nullable: true + data: + type: object + request_format: + oneOf: + - type: object + additionalProperties: true + - type: array + items: + type: object + additionalProperties: true + nullable: true + overrideBaseUrl: + type: object + additionalProperties: true + nullable: true + headers: + type: object + required: + - method + - path + UnifiedHrisBankinfoOutput: + type: object + properties: + account_type: + type: string + example: CHECKING + enum: + - SAVINGS + - CHECKING + nullable: true + description: The type of the bank account + bank_name: + type: string + example: Bank of America + nullable: true + description: The name of the bank + account_number: + type: string + example: '1234567890' + nullable: true + description: The account number + routing_number: + type: string + example: '021000021' + nullable: true + description: The routing number of the bank + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the bank info record + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the bank info in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the bank info in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the bank info was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the bank info record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the bank info record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the bank info was deleted in the remote system + required: + - id + - created_at + - modified_at + - remote_was_deleted + UnifiedHrisBenefitOutput: + type: object + properties: + provider_name: + type: string + example: Health Insurance Provider + nullable: true + description: The name of the benefit provider + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + employee_contribution: + type: number + example: 100 + nullable: true + description: The employee contribution amount + company_contribution: + type: number + example: 200 + nullable: true + description: The company contribution amount + start_date: + format: date-time + type: string + example: '2024-01-01T00:00:00Z' + nullable: true + description: The start date of the benefit + end_date: + format: date-time + type: string + example: '2024-12-31T23:59:59Z' + nullable: true + description: The end date of the benefit + employer_benefit_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employer benefit + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the benefit record + remote_id: + type: string + example: benefit_1234 + nullable: true + description: The remote ID of the benefit in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the benefit in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the benefit was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the benefit record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the benefit record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the benefit was deleted in the remote system + UnifiedHrisCompanyOutput: + type: object + properties: + legal_name: + type: string + example: Acme Corporation + nullable: true + description: The legal name of the company + locations: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: UUIDs of the of the Location associated with the company + type: array + items: + type: string + display_name: + type: string + example: Acme Corp + nullable: true + description: The display name of the company + eins: + example: + - 12-3456789 + - 98-7654321 + nullable: true + description: The Employer Identification Numbers (EINs) of the company + type: array + items: + type: string + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company record + remote_id: + type: string + example: company_1234 + nullable: true + description: The remote ID of the company in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the company in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the company was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the company record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the company record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the company was deleted in the remote system + UnifiedHrisDependentOutput: + type: object + properties: + first_name: + type: string + example: John + nullable: true + description: The first name of the dependent + last_name: + type: string + example: Doe + nullable: true + description: The last name of the dependent + middle_name: + type: string + example: Michael + nullable: true + description: The middle name of the dependent + relationship: + type: string + example: CHILD + enum: + - CHILD + - SPOUSE + - DOMESTIC_PARTNER + nullable: true + description: The relationship of the dependent to the employee + date_of_birth: + format: date-time + type: string + example: '2020-01-01' + nullable: true + description: The date of birth of the dependent + gender: + type: string + example: MALE + enum: + - MALE + - FEMALE + - NON-BINARY + - OTHER + - PREFER_NOT_TO_DISCLOSE + nullable: true + description: The gender of the dependent + phone_number: + type: string + example: '+1234567890' + nullable: true + description: The phone number of the dependent + home_location: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the home location + is_student: + type: boolean + example: true + nullable: true + description: Indicates if the dependent is a student + ssn: + type: string + example: 123-45-6789 + nullable: true + description: The Social Security Number of the dependent + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the dependent record + remote_id: + type: string + example: dependent_1234 + nullable: true + description: The remote ID of the dependent in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the dependent in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the dependent was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the dependent record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the dependent record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the dependent was deleted in the remote system + DeductionItem: + type: object + properties: + name: + type: string + example: Health Insurance + nullable: true + description: The name of the deduction + employee_deduction: + type: number + example: 100 + nullable: true + description: The amount of employee deduction + company_deduction: + type: number + example: 200 + nullable: true + description: The amount of company deduction + EarningItem: + type: object + properties: + amount: + type: number + example: 1000 + nullable: true + description: The amount of the earning + type: + type: string + example: Salary + nullable: true + description: The type of the earning + TaxItem: + type: object + properties: + name: + type: string + example: Federal Income Tax + nullable: true + description: The name of the tax + amount: + type: number + example: 250 + nullable: true + description: The amount of the tax + employer_tax: + type: boolean + example: true + nullable: true + description: Indicates if this is an employer tax + UnifiedHrisEmployeepayrollrunOutput: + type: object + properties: + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + payroll_run_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated payroll run + gross_pay: + type: number + example: 5000 + nullable: true + description: The gross pay amount + net_pay: + type: number + example: 4000 + nullable: true + description: The net pay amount + start_date: + format: date-time + type: string + example: '2023-01-01T00:00:00Z' + nullable: true + description: The start date of the pay period + end_date: + format: date-time + type: string + example: '2023-01-15T23:59:59Z' + nullable: true + description: The end date of the pay period + check_date: + format: date-time + type: string + example: '2023-01-20T00:00:00Z' + nullable: true + description: The date the check was issued + deductions: + nullable: true + description: The list of deductions for this payroll run + type: array + items: + $ref: '#/components/schemas/DeductionItem' + earnings: + nullable: true + description: The list of earnings for this payroll run + type: array + items: + $ref: '#/components/schemas/EarningItem' + taxes: + nullable: true + description: The list of taxes for this payroll run + type: array + items: + $ref: '#/components/schemas/TaxItem' + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee payroll run record + remote_id: + type: string + example: payroll_run_1234 + nullable: true + description: >- + The remote ID of the employee payroll run in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the employee payroll run in the context of the + 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: >- + The date when the employee payroll run was created in the 3rd party + system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the employee payroll run record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the employee payroll run record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: >- + Indicates if the employee payroll run was deleted in the remote + system + UnifiedHrisEmployeeOutput: + type: object + properties: + groups: + example: &ref_124 + - Group1 + - Group2 + nullable: true + description: The groups the employee belongs to + type: array + items: + type: string + locations: + example: &ref_125 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: UUIDs of the of the Location associated with the company + type: array + items: + type: string + employee_number: + type: string + example: EMP001 + nullable: true + description: The employee number + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + first_name: + type: string + example: John + nullable: true + description: The first name of the employee + last_name: + type: string + example: Doe + nullable: true + description: The last name of the employee + preferred_name: + type: string + example: Johnny + nullable: true + description: The preferred name of the employee + display_full_name: + type: string + example: John Doe + nullable: true + description: The full display name of the employee + username: + type: string + example: johndoe + nullable: true + description: The username of the employee + work_email: + type: string + example: john.doe@company.com + nullable: true + description: The work email of the employee + personal_email: + type: string + example: john.doe@personal.com + nullable: true + description: The personal email of the employee + mobile_phone_number: + type: string + example: '+1234567890' + nullable: true + description: The mobile phone number of the employee + employments: + example: &ref_126 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The employments of the employee + type: array + items: + type: string + ssn: + type: string + example: 123-45-6789 + nullable: true + description: The Social Security Number of the employee + gender: + type: string + example: MALE + enum: &ref_127 + - MALE + - FEMALE + - NON-BINARY + - OTHER + - PREFER_NOT_TO_DISCLOSE + nullable: true + description: The gender of the employee + ethnicity: + type: string + example: AMERICAN_INDIAN_OR_ALASKA_NATIVE + enum: &ref_128 + - AMERICAN_INDIAN_OR_ALASKA_NATIVE + - ASIAN_OR_INDIAN_SUBCONTINENT + - BLACK_OR_AFRICAN_AMERICAN + - HISPANIC_OR_LATINO + - NATIVE_HAWAIIAN_OR_OTHER_PACIFIC_ISLANDER + - TWO_OR_MORE_RACES + - WHITE + - PREFER_NOT_TO_DISCLOSE + nullable: true + description: The ethnicity of the employee + marital_status: + type: string + example: Married + enum: &ref_129 + - SINGLE + - MARRIED_FILING_JOINTLY + - MARRIED_FILING_SEPARATELY + - HEAD_OF_HOUSEHOLD + - QUALIFYING_WIDOW_OR_WIDOWER_WITH_DEPENDENT_CHILD + nullable: true + description: The marital status of the employee + date_of_birth: + format: date-time + type: string + example: '1990-01-01' + nullable: true + description: The date of birth of the employee + start_date: + format: date-time + type: string + example: '2020-01-01' + nullable: true + description: The start date of the employee + employment_status: + type: string + example: ACTIVE + enum: &ref_130 + - ACTIVE + - PENDING + - INACTIVE + nullable: true + description: The employment status of the employee + termination_date: + format: date-time + type: string + example: '2025-01-01' + nullable: true + description: The termination date of the employee + avatar_url: + type: string + example: https://example.com/avatar.jpg + nullable: true + description: The URL of the employee's avatar + manager_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: UUID of the manager (employee) of the employee + field_mappings: + type: object + example: &ref_131 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee record + remote_id: + type: string + example: employee_1234 + nullable: true + description: The remote ID of the employee in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the employee in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the employee was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the employee record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the employee record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the employee was deleted in the remote system + UnifiedHrisEmployeeInput: + type: object + properties: + groups: + example: *ref_124 + nullable: true + description: The groups the employee belongs to + type: array + items: + type: string + locations: + example: *ref_125 + nullable: true + description: UUIDs of the of the Location associated with the company + type: array + items: + type: string + employee_number: + type: string + example: EMP001 + nullable: true + description: The employee number + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + first_name: + type: string + example: John + nullable: true + description: The first name of the employee + last_name: + type: string + example: Doe + nullable: true + description: The last name of the employee + preferred_name: + type: string + example: Johnny + nullable: true + description: The preferred name of the employee + display_full_name: + type: string + example: John Doe + nullable: true + description: The full display name of the employee + username: + type: string + example: johndoe + nullable: true + description: The username of the employee + work_email: + type: string + example: john.doe@company.com + nullable: true + description: The work email of the employee + personal_email: + type: string + example: john.doe@personal.com + nullable: true + description: The personal email of the employee + mobile_phone_number: + type: string + example: '+1234567890' + nullable: true + description: The mobile phone number of the employee + employments: + example: *ref_126 + nullable: true + description: The employments of the employee + type: array + items: + type: string + ssn: + type: string + example: 123-45-6789 + nullable: true + description: The Social Security Number of the employee + gender: + type: string + example: MALE + enum: *ref_127 + nullable: true + description: The gender of the employee + ethnicity: + type: string + example: AMERICAN_INDIAN_OR_ALASKA_NATIVE + enum: *ref_128 + nullable: true + description: The ethnicity of the employee + marital_status: + type: string + example: Married + enum: *ref_129 + nullable: true + description: The marital status of the employee + date_of_birth: + format: date-time + type: string + example: '1990-01-01' + nullable: true + description: The date of birth of the employee + start_date: + format: date-time + type: string + example: '2020-01-01' + nullable: true + description: The start date of the employee + employment_status: + type: string + example: ACTIVE + enum: *ref_130 + nullable: true + description: The employment status of the employee + termination_date: + format: date-time + type: string + example: '2025-01-01' + nullable: true + description: The termination date of the employee + avatar_url: + type: string + example: https://example.com/avatar.jpg + nullable: true + description: The URL of the employee's avatar + manager_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: UUID of the manager (employee) of the employee + field_mappings: + type: object + example: *ref_131 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedHrisEmployerbenefitOutput: + type: object + properties: + benefit_plan_type: + type: string + example: Health Insurance + enum: + - MEDICAL + - HEALTH_SAVINGS + - INSURANCE + - RETIREMENT + - OTHER + nullable: true + description: The type of the benefit plan + name: + type: string + example: Company Health Plan + nullable: true + description: The name of the employer benefit + description: + type: string + example: Comprehensive health insurance coverage for employees + nullable: true + description: The description of the employer benefit + deduction_code: + type: string + example: HEALTH-001 + nullable: true + description: The deduction code for the employer benefit + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employer benefit record + remote_id: + type: string + example: benefit_1234 + nullable: true + description: >- + The remote ID of the employer benefit in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the employer benefit in the context of the 3rd + Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: >- + The date when the employer benefit was created in the 3rd party + system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the employer benefit record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the employer benefit record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the employer benefit was deleted in the remote system + UnifiedHrisEmploymentOutput: + type: object + properties: + job_title: + type: string + example: Software Engineer + nullable: true + description: The job title of the employment + pay_rate: + type: number + example: 100000 + nullable: true + description: The pay rate of the employment + pay_period: + type: string + example: MONTHLY + enum: + - HOUR + - DAY + - WEEK + - EVERY_TWO_WEEKS + - SEMIMONTHLY + - MONTH + - QUARTER + - EVERY_SIX_MONTHS + - YEAR + nullable: true + description: The pay period of the employment + pay_frequency: + type: string + example: WEEKLY + enum: + - WEEKLY + - BIWEEKLY + - MONTHLY + - QUARTERLY + - SEMIANNUALLY + - ANNUALLY + - THIRTEEN-MONTHLY + - PRO_RATA + - SEMIMONTHLY + nullable: true + description: The pay frequency of the employment + pay_currency: + type: string + example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the pay + flsa_status: + type: string + example: EXEMPT + enum: + - EXEMPT + - SALARIED_NONEXEMPT + - NONEXEMPT + - OWNER + nullable: true + description: The FLSA status of the employment + effective_date: + format: date-time + type: string + example: '2023-01-01' + nullable: true + description: The effective date of the employment + employment_type: + type: string + example: FULL_TIME + enum: + - FULL_TIME + - PART_TIME + - INTERN + - CONTRACTOR + - FREELANCE + nullable: true + description: The type of employment + pay_group_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated pay group + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employment record + remote_id: + type: string + example: employment_1234 + nullable: true + description: The remote ID of the employment in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the employment in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the employment was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the employment record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the employment record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the employment was deleted in the remote system + UnifiedHrisGroupOutput: + type: object + properties: + parent_group: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent group + name: + type: string + example: Engineering Team + nullable: true + description: The name of the group + type: + type: string + example: DEPARTMENT + enum: + - TEAM + - DEPARTMENT + - COST_CENTER + - BUSINESS_UNIT + - GROUP + nullable: true + description: The type of the group + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the group record + remote_id: + type: string + example: group_1234 + nullable: true + description: The remote ID of the group in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the group in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the group was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the group record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the group record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the group was deleted in the remote system + UnifiedHrisLocationOutput: + type: object + properties: + name: + type: string + example: Headquarters + nullable: true + description: The name of the location + phone_number: + type: string + example: '+1234567890' + nullable: true + description: The phone number of the location + street_1: + type: string + example: 123 Main St + nullable: true + description: The first line of the street address + street_2: + type: string + example: Suite 456 + nullable: true + description: The second line of the street address + city: + type: string + example: San Francisco + nullable: true + description: The city of the location + state: + type: string + example: CA + nullable: true + description: The state or region of the location + zip_code: + type: string + example: '94105' + nullable: true + description: The zip or postal code of the location + country: + type: string + example: USA + nullable: true + description: The country of the location + location_type: + type: string + example: WORK + enum: + - WORK + - HOME + nullable: true + description: The type of the location + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company associated with the location + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee associated with the location + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the location record + remote_id: + type: string + example: location_1234 + nullable: true + description: The remote ID of the location in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the location in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the location was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the location record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the location record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the location was deleted in the remote system + UnifiedHrisPaygroupOutput: + type: object + properties: + pay_group_name: + type: string + example: Monthly Salaried + nullable: true + description: The name of the pay group + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the pay group record + remote_id: + type: string + example: paygroup_1234 + nullable: true + description: The remote ID of the pay group in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the pay group in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the pay group was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the pay group record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the pay group record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the pay group was deleted in the remote system + UnifiedHrisPayrollrunOutput: + type: object + properties: + run_state: + type: string + example: PAID + enum: + - PAID + - DRAFT + - APPROVED + - FAILED + - CLOSE + nullable: true + description: The state of the payroll run + run_type: + type: string + example: REGULAR + enum: + - REGULAR + - OFF_CYCLE + - CORRECTION + - TERMINATION + - SIGN_ON_BONUS + nullable: true + description: The type of the payroll run + start_date: + format: date-time + type: string + example: '2024-01-01T00:00:00Z' + nullable: true + description: The start date of the payroll run + end_date: + format: date-time + type: string + example: '2024-01-15T23:59:59Z' + nullable: true + description: The end date of the payroll run + check_date: + format: date-time + type: string + example: '2024-01-20T00:00:00Z' + nullable: true + description: The check date of the payroll run + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the payroll run record + remote_id: + type: string + example: payroll_run_1234 + nullable: true + description: The remote ID of the payroll run in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the payroll run in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the payroll run was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the payroll run record + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The last modified date of the payroll run record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the payroll run was deleted in the remote system + employee_payroll_runs: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: >- + The UUIDs of the employee payroll runs associated with this payroll + run + type: array + items: + type: string + UnifiedHrisTimeoffOutput: + type: object + properties: + employee: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee taking time off + approver: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the approver for the time off request + status: + type: string + example: REQUESTED + enum: &ref_132 + - REQUESTED + - APPROVED + - DECLINED + - CANCELLED + - DELETED + nullable: true + description: The status of the time off request + employee_note: + type: string + example: Annual vacation + nullable: true + description: A note from the employee about the time off request + units: + type: string + example: DAYS + enum: &ref_133 + - HOURS + - DAYS + nullable: true + description: The units used for the time off (e.g., Days, Hours) + amount: + type: number + example: 5 + nullable: true + description: The amount of time off requested + request_type: + type: string + example: VACATION + enum: &ref_134 + - VACATION + - SICK + - PERSONAL + - JURY_DUTY + - VOLUNTEER + - BEREAVEMENT + nullable: true + description: The type of time off request + start_time: + format: date-time + type: string + example: '2024-07-01T09:00:00Z' + nullable: true + description: The start time of the time off + end_time: + format: date-time + type: string + example: '2024-07-05T17:00:00Z' + nullable: true + description: The end time of the time off + field_mappings: + type: object + example: &ref_135 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the time off record + remote_id: + type: string + example: timeoff_1234 + nullable: true + description: The remote ID of the time off in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the time off in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the time off was created in the 3rd party system + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the time off record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the time off record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the time off was deleted in the remote system + UnifiedHrisTimeoffInput: + type: object + properties: + employee: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the employee taking time off + approver: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the approver for the time off request + status: + type: string + example: REQUESTED + enum: *ref_132 + nullable: true + description: The status of the time off request + employee_note: + type: string + example: Annual vacation + nullable: true + description: A note from the employee about the time off request + units: + type: string + example: DAYS + enum: *ref_133 + nullable: true + description: The units used for the time off (e.g., Days, Hours) + amount: + type: number + example: 5 + nullable: true + description: The amount of time off requested + request_type: + type: string + example: VACATION + enum: *ref_134 + nullable: true + description: The type of time off request + start_time: + format: date-time + type: string + example: '2024-07-01T09:00:00Z' + nullable: true + description: The start time of the time off + end_time: + format: date-time + type: string + example: '2024-07-05T17:00:00Z' + nullable: true + description: The end time of the time off + field_mappings: + type: object + example: *ref_135 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedHrisTimeoffbalanceOutput: + type: object + properties: + balance: + type: number + example: 80 + nullable: true + description: The current balance of time off + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + used: + type: number + example: 40 + nullable: true + description: The amount of time off used + policy_type: + type: string + example: VACATION + enum: + - VACATION + - SICK + - PERSONAL + - JURY_DUTY + - VOLUNTEER + - BEREAVEMENT + nullable: true + description: The type of time off policy + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the time off balance record + remote_id: + type: string + example: timeoff_balance_1234 + nullable: true + description: >- + The remote ID of the time off balance in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the time off balance in the context of the 3rd + Party + remote_created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: >- + The date when the time off balance was created in the 3rd party + system + created_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the time off balance record + modified_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the time off balance record + remote_was_deleted: + type: boolean + example: false + nullable: true + description: Indicates if the time off balance was deleted in the remote system + UnifiedHrisTimesheetEntryOutput: + type: object + properties: + hours_worked: + type: number + example: 40 + nullable: true + description: The number of hours worked + start_time: + format: date-time + type: string + example: '2024-10-01T08:00:00Z' + nullable: true + description: The start time of the timesheet entry + end_time: + format: date-time + type: string + example: '2024-10-01T16:00:00Z' + nullable: true + description: The end time of the timesheet entry + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + remote_was_deleted: + type: boolean + example: false + description: Indicates if the timesheet entry was deleted in the remote system + field_mappings: + type: object + example: &ref_136 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the timesheet entry record + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the timesheet entry + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The date when the timesheet entry was created in the remote system + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the timesheet entry + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The last modified date of the timesheet entry + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the timesheet entry in the context of the 3rd + Party + UnifiedHrisTimesheetEntryInput: + type: object + properties: + hours_worked: + type: number + example: 40 + nullable: true + description: The number of hours worked + start_time: + format: date-time + type: string + example: '2024-10-01T08:00:00Z' + nullable: true + description: The start time of the timesheet entry + end_time: + format: date-time + type: string + example: '2024-10-01T16:00:00Z' + nullable: true + description: The end time of the timesheet entry + employee_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated employee + remote_was_deleted: + type: boolean + example: false + description: Indicates if the timesheet entry was deleted in the remote system + field_mappings: + type: object + example: *ref_136 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedMarketingautomationActionOutput: + type: object + properties: {} + UnifiedMarketingautomationActionInput: + type: object + properties: {} + UnifiedMarketingautomationAutomationOutput: + type: object + properties: {} + UnifiedMarketingautomationAutomationInput: + type: object + properties: {} + UnifiedMarketingautomationCampaignOutput: + type: object + properties: {} + UnifiedMarketingautomationCampaignInput: + type: object + properties: {} + UnifiedMarketingautomationContactOutput: + type: object + properties: {} + UnifiedMarketingautomationContactInput: + type: object + properties: {} + UnifiedMarketingautomationEmailOutput: + type: object + properties: {} + UnifiedMarketingautomationEventOutput: + type: object + properties: {} + UnifiedMarketingautomationListOutput: + type: object + properties: {} + UnifiedMarketingautomationListInput: + type: object + properties: {} + UnifiedMarketingautomationMessageOutput: + type: object + properties: {} + UnifiedMarketingautomationTemplateOutput: + type: object + properties: {} + UnifiedMarketingautomationTemplateInput: + type: object + properties: {} + UnifiedMarketingautomationUserOutput: + type: object + properties: {} + UnifiedAtsActivityOutput: + type: object + properties: + activity_type: + type: string + enum: &ref_137 + - NOTE + - EMAIL + - OTHER + example: NOTE + nullable: true + description: The type of activity + subject: + type: string + example: Email subject + nullable: true + description: The subject of the activity + body: + type: string + example: Dear Diana, I love you + nullable: true + description: The body of the activity + visibility: + type: string + enum: &ref_138 + - ADMIN_ONLY + - PUBLIC + - PRIVATE + example: PUBLIC + nullable: true + description: The visibility of the activity + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + remote_created_at: + type: string + format: date-time + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote creation date of the activity + field_mappings: + type: object + example: &ref_139 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the activity + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the activity in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the activity in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsActivityInput: + type: object + properties: + activity_type: + type: string + enum: *ref_137 + example: NOTE + nullable: true + description: The type of activity + subject: + type: string + example: Email subject + nullable: true + description: The subject of the activity + body: + type: string + example: Dear Diana, I love you + nullable: true + description: The body of the activity + visibility: + type: string + enum: *ref_138 + example: PUBLIC + nullable: true + description: The visibility of the activity + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + remote_created_at: + type: string + format: date-time + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote creation date of the activity + field_mappings: + type: object + example: *ref_139 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAtsApplicationOutput: + type: object + properties: + applied_at: + format: date-time + type: string + nullable: true + description: The application date + example: '2024-10-01T12:00:00Z' + rejected_at: + format: date-time + type: string + nullable: true + description: The rejection date + example: '2024-10-01T12:00:00Z' + offers: + nullable: true + description: The offers UUIDs for the application + example: &ref_140 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + - 12345678-1234-1234-1234-123456789012 + type: array + items: + type: string + source: + type: string + nullable: true + description: The source of the application + example: Source Name + credited_to: + type: string + nullable: true + description: The UUID of the person credited for the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + current_stage: + type: string + nullable: true + description: The UUID of the current stage of the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + reject_reason: + type: string + nullable: true + description: The rejection reason for the application + example: Candidate not experienced enough + candidate_id: + type: string + nullable: true + description: The UUID of the candidate + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + job_id: + type: string + description: The UUID of the job + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + field_mappings: + type: object + example: &ref_141 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + nullable: true + description: The UUID of the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + remote_id: + type: string + nullable: true + description: The remote ID of the application in the context of the 3rd Party + example: id_1 + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the application in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + remote_created_at: + format: date-time + type: string + nullable: true + description: The remote created date of the object + remote_modified_at: + format: date-time + type: string + nullable: true + description: The remote modified date of the object + UnifiedAtsApplicationInput: + type: object + properties: + applied_at: + format: date-time + type: string + nullable: true + description: The application date + example: '2024-10-01T12:00:00Z' + rejected_at: + format: date-time + type: string + nullable: true + description: The rejection date + example: '2024-10-01T12:00:00Z' + offers: + nullable: true + description: The offers UUIDs for the application + example: *ref_140 + type: array + items: + type: string + source: + type: string + nullable: true + description: The source of the application + example: Source Name + credited_to: + type: string + nullable: true + description: The UUID of the person credited for the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + current_stage: + type: string + nullable: true + description: The UUID of the current stage of the application + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + reject_reason: + type: string + nullable: true + description: The rejection reason for the application + example: Candidate not experienced enough + candidate_id: + type: string + nullable: true + description: The UUID of the candidate + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + job_id: + type: string + description: The UUID of the job + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + field_mappings: + type: object + example: *ref_141 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAtsAttachmentOutput: + type: object + properties: + file_url: + type: string + example: https://example.com/file.pdf + nullable: true + description: The URL of the file + file_name: + type: string + example: file.pdf + nullable: true + description: The name of the file + attachment_type: + type: string + example: RESUME + enum: &ref_142 + - RESUME + - COVER_LETTER + - OFFER_LETTER + - OTHER + nullable: true + description: The type of the file + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the attachment + remote_modified_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the attachment + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + field_mappings: + type: object + example: &ref_143 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the attachment + remote_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The remote ID of the attachment + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the attachment in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsAttachmentInput: + type: object + properties: + file_url: + type: string + example: https://example.com/file.pdf + nullable: true + description: The URL of the file + file_name: + type: string + example: file.pdf + nullable: true + description: The name of the file + attachment_type: + type: string + example: RESUME + enum: *ref_142 + nullable: true + description: The type of the file + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the attachment + remote_modified_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the attachment + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + field_mappings: + type: object + example: *ref_143 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + Url: + type: object + properties: + url: + type: string + nullable: true + description: The url. + url_type: + type: string + nullable: true + description: The url type. It takes [WEBSITE | BLOG | LINKEDIN | GITHUB | OTHER] + required: + - url + - url_type + UnifiedAtsCandidateOutput: + type: object + properties: + first_name: + type: string + example: Joe + nullable: true + description: The first name of the candidate + last_name: + type: string + example: Doe + nullable: true + description: The last name of the candidate + company: + type: string + example: Acme + nullable: true + description: The company of the candidate + title: + type: string + example: Analyst + nullable: true + description: The title of the candidate + locations: + type: string + example: New York + nullable: true + description: The locations of the candidate + is_private: + type: boolean + example: false + nullable: true + description: Whether the candidate is private + email_reachable: + type: boolean + example: true + nullable: true + description: Whether the candidate is reachable by email + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the candidate + remote_modified_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the candidate + last_interaction_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The last interaction date with the candidate + attachments: + type: array + items: &ref_144 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedAtsAttachmentOutput' + example: &ref_145 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The attachments UUIDs of the candidate + applications: + type: array + items: &ref_146 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedAtsApplicationOutput' + example: &ref_147 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The applications UUIDs of the candidate + tags: + type: array + items: &ref_148 + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedAtsTagOutput' + example: &ref_149 + - tag_1 + - tag_2 + nullable: true + description: The tags of the candidate + urls: + example: &ref_150 + - url: mywebsite.com + url_type: WEBSITE + nullable: true + description: >- + The urls of the candidate, possible values for Url type are WEBSITE, + BLOG, LINKEDIN, GITHUB, or OTHER + type: array + items: + $ref: '#/components/schemas/Url' + phone_numbers: + example: &ref_151 + - phone_number: '+33660688899' + phone_type: WORK + nullable: true + description: The phone numbers of the candidate + type: array + items: + $ref: '#/components/schemas/Phone' + email_addresses: + example: &ref_152 + - email_address: joedoe@gmail.com + email_address_type: WORK + nullable: true + description: The email addresses of the candidate + type: array + items: + $ref: '#/components/schemas/Email' + field_mappings: + type: object + example: &ref_153 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the candidate in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the candidate in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsCandidateInput: + type: object + properties: + first_name: + type: string + example: Joe + nullable: true + description: The first name of the candidate + last_name: + type: string + example: Doe + nullable: true + description: The last name of the candidate + company: + type: string + example: Acme + nullable: true + description: The company of the candidate + title: + type: string + example: Analyst + nullable: true + description: The title of the candidate + locations: + type: string + example: New York + nullable: true + description: The locations of the candidate + is_private: + type: boolean + example: false + nullable: true + description: Whether the candidate is private + email_reachable: + type: boolean + example: true + nullable: true + description: Whether the candidate is reachable by email + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the candidate + remote_modified_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the candidate + last_interaction_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The last interaction date with the candidate + attachments: + type: array + items: *ref_144 + example: *ref_145 + nullable: true + description: The attachments UUIDs of the candidate + applications: + type: array + items: *ref_146 + example: *ref_147 + nullable: true + description: The applications UUIDs of the candidate + tags: + type: array + items: *ref_148 + example: *ref_149 + nullable: true + description: The tags of the candidate + urls: + example: *ref_150 + nullable: true + description: >- + The urls of the candidate, possible values for Url type are WEBSITE, + BLOG, LINKEDIN, GITHUB, or OTHER + type: array + items: + $ref: '#/components/schemas/Url' + phone_numbers: + example: *ref_151 + nullable: true + description: The phone numbers of the candidate + type: array + items: + $ref: '#/components/schemas/Phone' + email_addresses: + example: *ref_152 + nullable: true + description: The email addresses of the candidate + type: array + items: + $ref: '#/components/schemas/Email' + field_mappings: + type: object + example: *ref_153 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAtsDepartmentOutput: + type: object + properties: + name: + type: string + example: Sales + nullable: true + description: The name of the department + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the department + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the department in the context of the 3rd Party + remote_data: + type: object + example: + key1: value1 + key2: 42 + key3: true + nullable: true + additionalProperties: true + description: The remote data of the department in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2023-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsInterviewOutput: + type: object + properties: + status: + type: string + enum: &ref_154 + - SCHEDULED + - AWAITING_FEEDBACK + - COMPLETED + example: SCHEDULED + nullable: true + description: The status of the interview + application_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the application + job_interview_stage_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job interview stage + organized_by: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the organizer + interviewers: + example: &ref_155 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUIDs of the interviewers + type: array + items: + type: string + location: + type: string + example: San Francisco + nullable: true + description: The location of the interview + start_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The start date and time of the interview + end_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The end date and time of the interview + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote creation date of the interview + remote_updated_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote modification date of the interview + field_mappings: + type: object + example: &ref_156 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the interview + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the interview in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the interview in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsInterviewInput: + type: object + properties: + status: + type: string + enum: *ref_154 + example: SCHEDULED + nullable: true + description: The status of the interview + application_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the application + job_interview_stage_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job interview stage + organized_by: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the organizer + interviewers: + example: *ref_155 + nullable: true + description: The UUIDs of the interviewers + type: array + items: + type: string + location: + type: string + example: San Francisco + nullable: true + description: The location of the interview + start_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The start date and time of the interview + end_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The end date and time of the interview + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote creation date of the interview + remote_updated_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The remote modification date of the interview + field_mappings: + type: object + example: *ref_156 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAtsJobinterviewstageOutput: + type: object + properties: + name: + type: string + example: Second Call + nullable: true + description: The name of the job interview stage + stage_order: + type: number + example: 1 + nullable: true + description: The order of the stage + job_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job interview stage + remote_id: + type: string + example: id_1 + nullable: true + description: >- + The remote ID of the job interview stage in the context of the 3rd + Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: >- + The remote data of the job interview stage in the context of the 3rd + Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsJobOutput: + type: object + properties: + name: + type: string + example: Financial Analyst + nullable: true + description: The name of the job + description: + type: string + example: Extract financial data and write detailed investment thesis + nullable: true + description: The description of the job + code: + type: string + example: JOB123 + nullable: true + description: The code of the job + status: + type: string + enum: + - OPEN + - CLOSED + - DRAFT + - ARCHIVED + - PENDING + example: OPEN + nullable: true + description: The status of the job + type: + type: string + example: POSTING + enum: + - POSTING + - REQUISITION + - PROFILE + nullable: true + description: The type of the job + confidential: + type: boolean + example: true + nullable: true + description: Whether the job is confidential + departments: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The departments UUIDs associated with the job + type: array + items: + type: string + offices: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The offices UUIDs associated with the job + type: array + items: + type: string + managers: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The managers UUIDs associated with the job + type: array + items: + type: string + recruiters: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The recruiters UUIDs associated with the job + type: array + items: + type: string + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the job + remote_updated_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote modification date of the job + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the job + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the job in the context of the 3rd Party + remote_data: + type: object + example: + key1: value1 + key2: 42 + key3: true + nullable: true + additionalProperties: true + description: The remote data of the job in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2023-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsOfferOutput: + type: object + properties: + created_by: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the creator + nullable: true + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The remote creation date of the offer + nullable: true + closed_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The closing date of the offer + nullable: true + sent_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The sending date of the offer + nullable: true + start_date: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The start date of the offer + nullable: true + status: + type: string + example: DRAFT + enum: + - DRAFT + - APPROVAL_SENT + - APPROVED + - SENT + - SENT_MANUALLY + - OPENED + - DENIED + - SIGNED + - DEPRECATED + description: The status of the offer + nullable: true + application_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the application + nullable: true + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + nullable: true + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the offer + nullable: true + remote_id: + type: string + example: id_1 + description: The remote ID of the offer in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + description: The remote data of the offer in the context of the 3rd Party + nullable: true + additionalProperties: true + created_at: + type: object + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + type: object + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + UnifiedAtsOfficeOutput: + type: object + properties: + name: + type: string + example: Condo Office 5th + nullable: true + description: The name of the office + location: + type: string + example: New York + nullable: true + description: The location of the office + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the office + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the office in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the office in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsRejectreasonOutput: + type: object + properties: + name: + type: string + example: Candidate inexperienced + nullable: true + description: The name of the reject reason + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + nullable: true + description: The UUID of the reject reason + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + remote_id: + type: string + nullable: true + description: The remote ID of the reject reason in the context of the 3rd Party + example: id_1 + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the reject reason in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsScorecardOutput: + type: object + properties: + overall_recommendation: + type: string + enum: + - DEFINITELY_NO + - 'NO' + - 'YES' + - STRONG_YES + - NO_DECISION + example: 'YES' + nullable: true + description: The overall recommendation + application_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the application + interview_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the interview + remote_created_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The remote creation date of the scorecard + submitted_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The submission date of the scorecard + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the scorecard + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the scorecard in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the scorecard in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAtsTagOutput: + type: object + properties: + name: + type: string + example: Important + nullable: true + description: The name of the tag + id_ats_candidate: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the tag + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the tag in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the tag in the context of the 3rd Party + created_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The creation date of the tag + modified_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The modification date of the tag + UnifiedAtsUserOutput: + type: object + properties: + first_name: + type: string + example: John + description: The first name of the user + nullable: true + last_name: + type: string + example: Doe + description: The last name of the user + nullable: true + email: + type: string + example: john.doe@example.com + description: The email of the user + nullable: true + disabled: + type: boolean + example: false + description: Whether the user is disabled + nullable: true + access_role: + type: string + example: ADMIN + enum: + - SUPER_ADMIN + - ADMIN + - TEAM_MEMBER + - LIMITED_TEAM_MEMBER + - INTERVIEWER + description: The access role of the user + nullable: true + remote_created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The remote creation date of the user + nullable: true + remote_modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The remote modification date of the user + nullable: true + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + nullable: true + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user + nullable: true + remote_id: + type: string + example: id_1 + description: The remote ID of the user in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + description: The remote data of the user in the context of the 3rd Party + nullable: true + additionalProperties: true + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + UnifiedAtsEeocsOutput: + type: object + properties: + candidate_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the candidate + submitted_at: + type: string + example: '2024-10-01T12:00:00Z' + format: date-time + nullable: true + description: The submission date of the EEOC + race: + type: string + enum: + - AMERICAN_INDIAN_OR_ALASKAN_NATIVE + - ASIAN + - BLACK_OR_AFRICAN_AMERICAN + - HISPANIC_OR_LATINO + - WHITE + - NATIVE_HAWAIIAN_OR_OTHER_PACIFIC_ISLANDER + - TWO_OR_MORE_RACES + - DECLINE_TO_SELF_IDENTIFY + example: AMERICAN_INDIAN_OR_ALASKAN_NATIVE + nullable: true + description: The race of the candidate + gender: + type: string + example: MALE + enum: + - MALE + - FEMALE + - NON_BINARY + - OTHER + - DECLINE_TO_SELF_IDENTIFY + nullable: true + description: The gender of the candidate + veteran_status: + type: string + example: I_AM_NOT_A_PROTECTED_VETERAN + enum: + - I_AM_NOT_A_PROTECTED_VETERAN + - >- + I_IDENTIFY_AS_ONE_OR_MORE_OF_THE_CLASSIFICATIONS_OF_A_PROTECTED_VETERAN + - I_DONT_WISH_TO_ANSWER + nullable: true + description: The veteran status of the candidate + disability_status: + type: string + enum: + - YES_I_HAVE_A_DISABILITY_OR_PREVIOUSLY_HAD_A_DISABILITY + - NO_I_DONT_HAVE_A_DISABILITY + - I_DONT_WISH_TO_ANSWER + example: YES_I_HAVE_A_DISABILITY_OR_PREVIOUSLY_HAD_A_DISABILITY + nullable: true + description: The disability status of the candidate + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the EEOC + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the EEOC in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the EEOC in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedAccountingAccountOutput: + type: object + properties: + name: + type: string + example: Cash + nullable: true + description: The name of the account + description: + type: string + example: Main cash account for daily operations + nullable: true + description: A description of the account + classification: + type: string + example: Asset + nullable: true + description: The classification of the account + type: + type: string + example: Current Asset + nullable: true + description: The type of the account + status: + type: string + example: Active + nullable: true + description: The status of the account + current_balance: + type: number + example: 10000 + nullable: true + description: The current balance of the account + currency: + type: string + example: USD + enum: &ref_157 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the account + account_number: + type: string + example: '1000' + nullable: true + description: The account number + parent_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent account + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: &ref_158 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the account record + remote_id: + type: string + example: account_1234 + nullable: true + description: The remote ID of the account in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the account in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the account record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the account record + UnifiedAccountingAccountInput: + type: object + properties: + name: + type: string + example: Cash + nullable: true + description: The name of the account + description: + type: string + example: Main cash account for daily operations + nullable: true + description: A description of the account + classification: + type: string + example: Asset + nullable: true + description: The classification of the account + type: + type: string + example: Current Asset + nullable: true + description: The type of the account + status: + type: string + example: Active + nullable: true + description: The status of the account + current_balance: + type: number + example: 10000 + nullable: true + description: The current balance of the account + currency: + type: string + example: USD + enum: *ref_157 + nullable: true + description: The currency of the account + account_number: + type: string + example: '1000' + nullable: true + description: The account number + parent_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent account + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: *ref_158 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingAddressOutput: + type: object + properties: + type: + type: string + example: Billing + nullable: true + description: The type of the address + street_1: + type: string + example: 123 Main St + nullable: true + description: The first line of the street address + street_2: + type: string + example: Apt 4B + nullable: true + description: The second line of the street address + city: + type: string + example: New York + nullable: true + description: The city of the address + state: + type: string + example: NY + nullable: true + description: The state of the address + country_subdivision: + type: string + example: New York + nullable: true + description: The country subdivision (e.g., province or state) of the address + country: + type: string + example: USA + nullable: true + description: The country of the address + zip: + type: string + example: '10001' + nullable: true + description: The zip or postal code of the address + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the address record + remote_id: + type: string + example: address_1234 + nullable: true + description: The remote ID of the address in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the address in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the address record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the address record + UnifiedAccountingAttachmentOutput: + type: object + properties: + file_name: + type: string + example: invoice.pdf + nullable: true + description: The name of the attached file + file_url: + type: string + example: https://example.com/files/invoice.pdf + nullable: true + description: The URL where the file can be accessed + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + field_mappings: + type: object + example: &ref_159 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the attachment record + remote_id: + type: string + example: attachment_1234 + nullable: true + description: The remote ID of the attachment in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the attachment in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the attachment record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the attachment record + UnifiedAccountingAttachmentInput: + type: object + properties: + file_name: + type: string + example: invoice.pdf + nullable: true + description: The name of the attached file + file_url: + type: string + example: https://example.com/files/invoice.pdf + nullable: true + description: The URL where the file can be accessed + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + field_mappings: + type: object + example: *ref_159 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + LineItem: + type: object + properties: + name: + type: string + example: Net Income + nullable: true + description: The name of the report item + value: + type: number + example: 100000 + nullable: true + description: The value of the report item + type: + type: string + example: Operating Activities + nullable: true + description: The type of the report item + parent_item: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent item + remote_id: + type: string + example: report_item_1234 + nullable: true + description: The remote ID of the report item + remote_generated_at: + format: date-time + type: string + example: '2024-07-01T12:00:00Z' + nullable: true + description: The date when the report item was generated in the remote system + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info object + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + description: The created date of the report item + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + description: The last modified date of the report item + UnifiedAccountingBalancesheetOutput: + type: object + properties: + name: + type: string + example: Q2 2024 Balance Sheet + nullable: true + description: The name of the balance sheet + currency: + type: string + example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency used in the balance sheet + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + date: + format: date-time + type: string + example: '2024-06-30T23:59:59Z' + nullable: true + description: The date of the balance sheet + net_assets: + type: number + example: 1000000 + nullable: true + description: The net assets value + assets: + example: + - Cash + - Accounts Receivable + - Inventory + nullable: true + description: The list of assets + type: array + items: + type: string + liabilities: + example: + - Accounts Payable + - Long-term Debt + nullable: true + description: The list of liabilities + type: array + items: + type: string + equity: + example: + - Common Stock + - Retained Earnings + nullable: true + description: The list of equity items + type: array + items: + type: string + remote_generated_at: + format: date-time + type: string + example: '2024-07-01T12:00:00Z' + nullable: true + description: The date when the balance sheet was generated in the remote system + line_items: + description: The report items associated with this balance sheet + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the balance sheet record + remote_id: + type: string + example: balancesheet_1234 + nullable: true + description: The remote ID of the balance sheet in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the balance sheet in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the balance sheet record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the balance sheet record + UnifiedAccountingCashflowstatementOutput: + type: object + properties: + name: + type: string + example: Q2 2024 Cash Flow Statement + nullable: true + description: The name of the cash flow statement + currency: + type: string + example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency used in the cash flow statement + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + start_period: + format: date-time + type: string + example: '2024-04-01T00:00:00Z' + nullable: true + description: The start date of the period covered by the cash flow statement + end_period: + format: date-time + type: string + example: '2024-06-30T23:59:59Z' + nullable: true + description: The end date of the period covered by the cash flow statement + cash_at_beginning_of_period: + type: number + example: 1000000 + nullable: true + description: The cash balance at the beginning of the period + cash_at_end_of_period: + type: number + example: 1200000 + nullable: true + description: The cash balance at the end of the period + remote_generated_at: + format: date-time + type: string + example: '2024-07-01T12:00:00Z' + nullable: true + description: >- + The date when the cash flow statement was generated in the remote + system + line_items: + description: The report items associated with this cash flow statement + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the cash flow statement record + remote_id: + type: string + example: cashflowstatement_1234 + nullable: true + description: >- + The remote ID of the cash flow statement in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the cash flow statement in the context of the 3rd + Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the cash flow statement record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the cash flow statement record + UnifiedAccountingCompanyinfoOutput: + type: object + properties: + name: + type: string + example: Acme Corporation + nullable: true + description: The name of the company + legal_name: + type: string + example: Acme Corporation LLC + nullable: true + description: The legal name of the company + tax_number: + type: string + example: '123456789' + nullable: true + description: The tax number of the company + fiscal_year_end_month: + type: number + example: 12 + nullable: true + description: The month of the fiscal year end (1-12) + fiscal_year_end_day: + type: number + example: 31 + nullable: true + description: The day of the fiscal year end (1-31) + currency: + type: string + example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency used by the company + urls: + example: + - https://www.acmecorp.com + - https://store.acmecorp.com + nullable: true + description: The URLs associated with the company + type: array + items: + type: string + tracking_categories: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUIDs of the tracking categories used by the company + type: array + items: + type: string + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company info record + remote_id: + type: string + example: company_1234 + nullable: true + description: The remote ID of the company info in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the company info in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the company info was created in the remote system + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the company info record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the company info record + UnifiedAccountingContactOutput: + type: object + properties: + name: + type: string + example: John Doe + nullable: true + description: The name of the contact + is_supplier: + type: boolean + example: true + nullable: true + description: Indicates if the contact is a supplier + is_customer: + type: boolean + example: false + nullable: true + description: Indicates if the contact is a customer + email_address: + type: string + example: john.doe@example.com + nullable: true + description: The email address of the contact + tax_number: + type: string + example: '123456789' + nullable: true + description: The tax number of the contact + status: + type: string + example: Active + nullable: true + description: The status of the contact + currency: + type: string + example: USD + nullable: true + enum: &ref_160 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + description: The currency associated with the contact + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the contact was last updated in the remote system + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: &ref_161 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the contact record + remote_id: + type: string + example: contact_1234 + nullable: true + description: The remote ID of the contact in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the contact in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the contact record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the contact record + UnifiedAccountingContactInput: + type: object + properties: + name: + type: string + example: John Doe + nullable: true + description: The name of the contact + is_supplier: + type: boolean + example: true + nullable: true + description: Indicates if the contact is a supplier + is_customer: + type: boolean + example: false + nullable: true + description: Indicates if the contact is a customer + email_address: + type: string + example: john.doe@example.com + nullable: true + description: The email address of the contact + tax_number: + type: string + example: '123456789' + nullable: true + description: The tax number of the contact + status: + type: string + example: Active + nullable: true + description: The status of the contact + currency: + type: string + example: USD + nullable: true + enum: *ref_160 + description: The currency associated with the contact + remote_updated_at: + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the contact was last updated in the remote system + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: *ref_161 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingCreditnoteOutput: + type: object + properties: + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the credit note transaction + status: + type: string + example: Issued + nullable: true + description: The status of the credit note + number: + type: string + example: CN-001 + nullable: true + description: The number of the credit note + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the credit note + total_amount: + type: number + example: 10000 + nullable: true + description: The total amount of the credit note + remaining_credit: + type: number + example: 5000 + nullable: true + description: The remaining credit on the credit note + tracking_categories: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUIDs of the tracking categories associated with the credit note + type: array + items: + type: string + currency: + type: string + example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the credit note + payments: + example: + - PAYMENT-001 + - PAYMENT-002 + nullable: true + description: The payments associated with the credit note + type: array + items: + type: string + applied_payments: + example: + - APPLIED-001 + - APPLIED-002 + nullable: true + description: The applied payments associated with the credit note + type: array + items: + type: string + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the credit note record + remote_id: + type: string + example: creditnote_1234 + nullable: true + description: The remote ID of the credit note in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the credit note in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the credit note was created in the remote system + remote_updated_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the credit note was last updated in the remote system + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the credit note record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the credit note record + UnifiedAccountingExpenseOutput: + type: object + properties: + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the expense transaction + total_amount: + type: number + example: 10000 + nullable: true + description: The total amount of the expense + sub_total: + type: number + example: 9000 + nullable: true + description: The sub-total amount of the expense (before tax) + total_tax_amount: + type: number + example: 1000 + nullable: true + description: The total tax amount of the expense + currency: + type: string + example: USD + enum: &ref_162 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the expense + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the expense + memo: + type: string + example: Business lunch with client + nullable: true + description: A memo or description for the expense + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + tracking_categories: + example: &ref_163 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUIDs of the tracking categories associated with the expense + type: array + items: + type: string + line_items: + description: The line items associated with this expense + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: &ref_164 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the expense record + remote_id: + type: string + example: expense_1234 + nullable: true + description: The remote ID of the expense in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the expense in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the expense was created in the remote system + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the expense record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the expense record + UnifiedAccountingExpenseInput: + type: object + properties: + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the expense transaction + total_amount: + type: number + example: 10000 + nullable: true + description: The total amount of the expense + sub_total: + type: number + example: 9000 + nullable: true + description: The sub-total amount of the expense (before tax) + total_tax_amount: + type: number + example: 1000 + nullable: true + description: The total tax amount of the expense + currency: + type: string + example: USD + enum: *ref_162 + nullable: true + description: The currency of the expense + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the expense + memo: + type: string + example: Business lunch with client + nullable: true + description: A memo or description for the expense + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + tracking_categories: + example: *ref_163 + nullable: true + description: The UUIDs of the tracking categories associated with the expense + type: array + items: + type: string + line_items: + description: The line items associated with this expense + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: *ref_164 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingIncomestatementOutput: + type: object + properties: + name: + type: string + example: Q2 2024 Income Statement + nullable: true + description: The name of the income statement + currency: + type: string + example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency used in the income statement + start_period: + format: date-time + type: string + example: '2024-04-01T00:00:00Z' + nullable: true + description: The start date of the period covered by the income statement + end_period: + format: date-time + type: string + example: '2024-06-30T23:59:59Z' + nullable: true + description: The end date of the period covered by the income statement + gross_profit: + type: number + example: 1000000 + nullable: true + description: The gross profit for the period + net_operating_income: + type: number + example: 800000 + nullable: true + description: The net operating income for the period + net_income: + type: number + example: 750000 + nullable: true + description: The net income for the period + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the income statement record + remote_id: + type: string + example: incomestatement_1234 + nullable: true + description: >- + The remote ID of the income statement in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the income statement in the context of the 3rd + Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the income statement record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the income statement record + UnifiedAccountingInvoiceOutput: + type: object + properties: + type: + type: string + example: Sales + nullable: true + description: The type of the invoice + number: + type: string + example: INV-001 + nullable: true + description: The invoice number + issue_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date the invoice was issued + due_date: + format: date-time + type: string + example: '2024-07-15T12:00:00Z' + nullable: true + description: The due date of the invoice + paid_on_date: + format: date-time + type: string + example: '2024-07-10T12:00:00Z' + nullable: true + description: The date the invoice was paid + memo: + type: string + example: Payment for services rendered + nullable: true + description: A memo or note on the invoice + currency: + type: string + example: USD + enum: &ref_165 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the invoice + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the invoice + total_discount: + type: number + example: 1000 + nullable: true + description: The total discount applied to the invoice + sub_total: + type: number + example: 10000 + nullable: true + description: The subtotal of the invoice + status: + type: string + example: Paid + nullable: true + description: The status of the invoice + total_tax_amount: + type: number + example: 1000 + nullable: true + description: The total tax amount on the invoice + total_amount: + type: number + example: 11000 + nullable: true + description: The total amount of the invoice + balance: + type: number + example: 0 + nullable: true + description: The remaining balance on the invoice + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + tracking_categories: + example: &ref_166 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUIDs of the tracking categories associated with the invoice + type: array + items: + type: string + line_items: + description: The line items associated with this invoice + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: &ref_167 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the invoice record + remote_id: + type: string + example: invoice_1234 + nullable: true + description: The remote ID of the invoice in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the invoice in the context of the 3rd Party + remote_updated_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the invoice was last updated in the remote system + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the invoice record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the invoice record + UnifiedAccountingInvoiceInput: + type: object + properties: + type: + type: string + example: Sales + nullable: true + description: The type of the invoice + number: + type: string + example: INV-001 + nullable: true + description: The invoice number + issue_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date the invoice was issued + due_date: + format: date-time + type: string + example: '2024-07-15T12:00:00Z' + nullable: true + description: The due date of the invoice + paid_on_date: + format: date-time + type: string + example: '2024-07-10T12:00:00Z' + nullable: true + description: The date the invoice was paid + memo: + type: string + example: Payment for services rendered + nullable: true + description: A memo or note on the invoice + currency: + type: string + example: USD + enum: *ref_165 + nullable: true + description: The currency of the invoice + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the invoice + total_discount: + type: number + example: 1000 + nullable: true + description: The total discount applied to the invoice + sub_total: + type: number + example: 10000 + nullable: true + description: The subtotal of the invoice + status: + type: string + example: Paid + nullable: true + description: The status of the invoice + total_tax_amount: + type: number + example: 1000 + nullable: true + description: The total tax amount on the invoice + total_amount: + type: number + example: 11000 + nullable: true + description: The total amount of the invoice + balance: + type: number + example: 0 + nullable: true + description: The remaining balance on the invoice + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + tracking_categories: + example: *ref_166 + nullable: true + description: The UUIDs of the tracking categories associated with the invoice + type: array + items: + type: string + line_items: + description: The line items associated with this invoice + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: *ref_167 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingItemOutput: + type: object + properties: + name: + type: string + example: Product A + nullable: true + description: The name of the accounting item + status: + type: string + example: Active + nullable: true + description: The status of the accounting item + unit_price: + type: number + example: 1000 + nullable: true + description: The unit price of the item in cents + purchase_price: + type: number + example: 800 + nullable: true + description: The purchase price of the item in cents + sales_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated sales account + purchase_account: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated purchase account + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the accounting item record + remote_id: + type: string + example: item_1234 + nullable: true + description: The remote ID of the item in the context of the 3rd Party + remote_updated_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the item was last updated in the remote system + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the item in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the accounting item record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the accounting item record + UnifiedAccountingJournalentryOutput: + type: object + properties: + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the transaction + payments: + example: &ref_168 + - payment1 + - payment2 + nullable: true + description: The payments associated with the journal entry + type: array + items: + type: string + applied_payments: + example: &ref_169 + - appliedPayment1 + - appliedPayment2 + nullable: true + description: The applied payments for the journal entry + type: array + items: + type: string + memo: + type: string + example: Monthly expense journal entry + nullable: true + description: A memo or note for the journal entry + currency: + type: string + example: USD + enum: &ref_170 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the journal entry + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the journal entry + id_acc_company_info: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: false + description: The UUID of the associated company info + journal_number: + type: string + example: JE-001 + nullable: true + description: The journal number + tracking_categories: + example: &ref_171 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: >- + The UUIDs of the tracking categories associated with the journal + entry + type: array + items: + type: string + id_acc_accounting_period: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + posting_status: + type: string + example: Posted + nullable: true + description: The posting status of the journal entry + line_items: + description: The line items associated with this journal entry + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: &ref_172 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the journal entry record + remote_id: + type: string + example: journal_entry_1234 + nullable: false + description: The remote ID of the journal entry in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the journal entry was created in the remote system + remote_modiified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: >- + The date when the journal entry was last modified in the remote + system + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the journal entry in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the journal entry record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the journal entry record + UnifiedAccountingJournalentryInput: + type: object + properties: + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the transaction + payments: + example: *ref_168 + nullable: true + description: The payments associated with the journal entry + type: array + items: + type: string + applied_payments: + example: *ref_169 + nullable: true + description: The applied payments for the journal entry + type: array + items: + type: string + memo: + type: string + example: Monthly expense journal entry + nullable: true + description: A memo or note for the journal entry + currency: + type: string + example: USD + enum: *ref_170 + nullable: true + description: The currency of the journal entry + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the journal entry + id_acc_company_info: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: false + description: The UUID of the associated company info + journal_number: + type: string + example: JE-001 + nullable: true + description: The journal number + tracking_categories: + example: *ref_171 + nullable: true + description: >- + The UUIDs of the tracking categories associated with the journal + entry + type: array + items: + type: string + id_acc_accounting_period: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + posting_status: + type: string + example: Posted + nullable: true + description: The posting status of the journal entry + line_items: + description: The line items associated with this journal entry + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: *ref_172 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingPaymentOutput: + type: object + properties: + invoice_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated invoice + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the transaction + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + currency: + type: string + example: USD + enum: &ref_173 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the payment + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the payment + total_amount: + type: number + example: 10000 + nullable: true + description: The total amount of the payment in cents + type: + type: string + example: Credit Card + nullable: true + description: The type of payment + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + tracking_categories: + example: &ref_174 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUIDs of the tracking categories associated with the payment + type: array + items: + type: string + line_items: + description: The line items associated with this payment + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: &ref_175 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the payment record + remote_id: + type: string + example: payment_1234 + nullable: true + description: The remote ID of the payment in the context of the 3rd Party + remote_updated_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the payment was last updated in the remote system + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the payment in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the payment record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the payment record + UnifiedAccountingPaymentInput: + type: object + properties: + invoice_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated invoice + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the transaction + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + currency: + type: string + example: USD + enum: *ref_173 + nullable: true + description: The currency of the payment + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the payment + total_amount: + type: number + example: 10000 + nullable: true + description: The total amount of the payment in cents + type: + type: string + example: Credit Card + nullable: true + description: The type of payment + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + tracking_categories: + example: *ref_174 + nullable: true + description: The UUIDs of the tracking categories associated with the payment + type: array + items: + type: string + line_items: + description: The line items associated with this payment + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: *ref_175 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingPhonenumberOutput: + type: object + properties: + number: + type: string + example: '+1234567890' + nullable: true + description: The phone number + type: + type: string + example: Mobile + nullable: true + description: The type of phone number + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: false + description: The UUID of the associated contact + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the phone number record + remote_id: + type: string + example: phone_1234 + nullable: true + description: The remote ID of the phone number in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the phone number in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the phone number record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the phone number record + UnifiedAccountingPurchaseorderOutput: + type: object + properties: + status: + type: string + example: Pending + nullable: true + description: The status of the purchase order + issue_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The issue date of the purchase order + purchase_order_number: + type: string + example: PO-001 + nullable: true + description: The purchase order number + delivery_date: + format: date-time + type: string + example: '2024-07-15T12:00:00Z' + nullable: true + description: The delivery date for the purchase order + delivery_address: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the delivery address + customer: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the customer + vendor: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the vendor + memo: + type: string + example: Purchase order for Q3 inventory + nullable: true + description: A memo or note for the purchase order + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company + total_amount: + type: number + example: 100000 + nullable: true + description: The total amount of the purchase order in cents + currency: + type: string + example: USD + enum: &ref_176 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the purchase order + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the purchase order + tracking_categories: + example: &ref_177 + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: >- + The UUIDs of the tracking categories associated with the purchase + order + type: array + items: + type: string + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + line_items: + description: The line items associated with this purchase order + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: &ref_178 + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the purchase order record + remote_id: + type: string + example: po_1234 + nullable: true + description: The remote ID of the purchase order in the context of the 3rd Party + remote_created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the purchase order was created in the remote system + remote_updated_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: >- + The date when the purchase order was last updated in the remote + system + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the purchase order in the context of the 3rd + Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the purchase order record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the purchase order record + UnifiedAccountingPurchaseorderInput: + type: object + properties: + status: + type: string + example: Pending + nullable: true + description: The status of the purchase order + issue_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The issue date of the purchase order + purchase_order_number: + type: string + example: PO-001 + nullable: true + description: The purchase order number + delivery_date: + format: date-time + type: string + example: '2024-07-15T12:00:00Z' + nullable: true + description: The delivery date for the purchase order + delivery_address: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the delivery address + customer: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the customer + vendor: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the vendor + memo: + type: string + example: Purchase order for Q3 inventory + nullable: true + description: A memo or note for the purchase order + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the company + total_amount: + type: number + example: 100000 + nullable: true + description: The total amount of the purchase order in cents + currency: + type: string + example: USD + enum: *ref_176 + nullable: true + description: The currency of the purchase order + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the purchase order + tracking_categories: + example: *ref_177 + nullable: true + description: >- + The UUIDs of the tracking categories associated with the purchase + order + type: array + items: + type: string + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + line_items: + description: The line items associated with this purchase order + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: *ref_178 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedAccountingTaxrateOutput: + type: object + properties: + description: + type: string + example: VAT 20% + nullable: true + description: The description of the tax rate + total_tax_ratge: + type: number + example: 2000 + nullable: true + description: The total tax rate in basis points (e.g., 2000 for 20%) + effective_tax_rate: + type: number + example: 1900 + nullable: true + description: The effective tax rate in basis points (e.g., 1900 for 19%) + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the tax rate record + remote_id: + type: string + example: tax_rate_1234 + nullable: true + description: The remote ID of the tax rate in the context of the 3rd Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the tax rate in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the tax rate record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the tax rate record + UnifiedAccountingTrackingcategoryOutput: + type: object + properties: + name: + type: string + example: Department + nullable: true + description: The name of the tracking category + status: + type: string + example: Active + nullable: true + description: The status of the tracking category + category_type: + type: string + example: Expense + nullable: true + description: The type of the tracking category + parent_category: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent category, if applicable + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the tracking category record + remote_id: + type: string + example: tracking_category_1234 + nullable: true + description: >- + The remote ID of the tracking category in the context of the 3rd + Party + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the tracking category in the context of the 3rd + Party + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The created date of the tracking category record + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The last modified date of the tracking category record + UnifiedAccountingTransactionOutput: + type: object + properties: + transaction_type: + type: string + example: Sale + nullable: true + description: The type of the transaction + number: + type: string + example: '1001' + nullable: true + description: The transaction number + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the transaction + total_amount: + type: string + example: '1000' + nullable: true + description: The total amount of the transaction + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the transaction + currency: + type: string + example: USD + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + nullable: true + description: The currency of the transaction + tracking_categories: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUIDs of the tracking categories associated with the transaction + type: array + items: + type: string + account_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated account + contact_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated contact + company_info_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company info + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + line_items: + description: The line items associated with this transaction + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the transaction record + remote_id: + type: string + example: remote_id_1234 + nullable: false + description: The remote ID of the transaction + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: false + description: The created date of the transaction + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: >- + The remote data of the tracking category in the context of the 3rd + Party + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: false + description: The last modified date of the transaction + remote_updated_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date when the transaction was last updated in the remote system + UnifiedAccountingVendorcreditOutput: + type: object + properties: + number: + type: string + example: VC-001 + nullable: true + description: The number of the vendor credit + transaction_date: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: The date of the transaction + vendor: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the vendor associated with the credit + total_amount: + type: string + example: '1000' + nullable: true + description: The total amount of the vendor credit + currency: + type: string + example: USD + nullable: true + enum: + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + description: The currency of the vendor credit + exchange_rate: + type: string + example: '1.2' + nullable: true + description: The exchange rate applied to the vendor credit + company_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated company + tracking_categories: + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: >- + The UUID of the tracking categories associated with the vendor + credit + type: array + items: + type: string + accounting_period_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the associated accounting period + line_items: + description: The line items associated with this vendor credit + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: + custom_field_1: value1 + custom_field_2: value2 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the vendor credit record + remote_id: + type: string + example: remote_id_1234 + nullable: true + description: The remote ID of the vendor credit + created_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: false + description: The created date of the vendor credit + modified_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: false + description: The last modified date of the vendor credit + remote_updated_at: + format: date-time + type: string + example: '2024-06-15T12:00:00Z' + nullable: true + description: >- + The date when the vendor credit was last updated in the remote + system + remote_data: + type: object + example: + raw_data: + additional_field: some value + nullable: true + description: The remote data of the vendor credit in the context of the 3rd Party + UnifiedFilestorageDriveOutput: + type: object + properties: + name: + type: string + nullable: true + example: school + description: The name of the drive + remote_created_at: + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: When the third party s drive was created. + drive_url: + type: string + nullable: true + example: https://example.com/school + description: The url of the drive + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the drive + remote_id: + type: string + nullable: true + example: id_1 + description: The id of the drive in the context of the 3rd Party + remote_data: + type: object + nullable: true + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + description: The remote data of the drive in the context of the 3rd Party + created_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The created date of the object + modified_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + required: + - name + - remote_created_at + - drive_url + UnifiedFilestorageFileOutput: + type: object + properties: + name: + type: string + example: my_paris_photo.png + description: The name of the file + nullable: true + file_url: + type: string + example: https://example.com/my_paris_photo.png + description: The url of the file + nullable: true + mime_type: + type: string + example: application/pdf + description: The mime type of the file + nullable: true + size: + type: string + example: '1024' + description: The size of the file + nullable: true + folder_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the folder tied to the file + nullable: true + permission: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the permission tied to the file + nullable: true + shared_link: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the shared link tied to the file + nullable: true + field_mappings: + type: object + example: &ref_179 + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + nullable: true + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the file + nullable: true + remote_id: + type: string + example: id_1 + description: The id of the file in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + description: The remote data of the file in the context of the 3rd Party + nullable: true + additionalProperties: true + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the object + nullable: true + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + nullable: true + required: + - name + - file_url + - mime_type + - size + - folder_id + - permission + - shared_link + UnifiedFilestorageFileInput: + type: object + properties: + name: + type: string + example: my_paris_photo.png + description: The name of the file + nullable: true + file_url: + type: string + example: https://example.com/my_paris_photo.png + description: The url of the file + nullable: true + mime_type: + type: string + example: application/pdf + description: The mime type of the file + nullable: true + size: + type: string + example: '1024' + description: The size of the file + nullable: true + folder_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the folder tied to the file + nullable: true + permission: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the permission tied to the file + nullable: true + shared_link: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the shared link tied to the file + nullable: true + field_mappings: + type: object + example: *ref_179 + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + nullable: true + additionalProperties: true + required: + - name + - file_url + - mime_type + - size + - folder_id + - permission + - shared_link + UnifiedFilestorageFolderOutput: + type: object + properties: + name: + type: string + example: school + nullable: true + description: The name of the folder + size: + type: string + example: '2048' + nullable: true + description: The size of the folder + folder_url: + type: string + example: https://example.com/school + nullable: true + description: The url of the folder + description: + type: string + example: All things school related + description: The description of the folder + drive_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the drive tied to the folder + parent_folder_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent folder + shared_link: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the shared link tied to the folder + permission: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the permission tied to the folder + field_mappings: + type: object + example: &ref_180 + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the folder + remote_id: + type: string + example: id_1 + description: The remote ID of the folder in the context of the 3rd Party + nullable: true + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: The remote data of the folder in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The created date of the folder + nullable: true + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + description: The modified date of the folder + nullable: true + required: + - name + - size + - folder_url + - description + - drive_id + - parent_folder_id + - shared_link + - permission + UnifiedFilestorageFolderInput: + type: object + properties: + name: + type: string + example: school + nullable: true + description: The name of the folder + size: + type: string + example: '2048' + nullable: true + description: The size of the folder + folder_url: + type: string + example: https://example.com/school + nullable: true + description: The url of the folder + description: + type: string + example: All things school related + description: The description of the folder + drive_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the drive tied to the folder + parent_folder_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the parent folder + shared_link: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the shared link tied to the folder + permission: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the permission tied to the folder + field_mappings: + type: object + example: *ref_180 + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + required: + - name + - size + - folder_url + - description + - drive_id + - parent_folder_id + - shared_link + - permission + UnifiedFilestorageGroupOutput: + type: object + properties: + name: + type: string + example: My group + nullable: true + description: The name of the group + users: + type: array + items: + oneOf: + - type: string + - $ref: '#/components/schemas/UnifiedFilestorageUserOutput' + example: + - 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: Uuids of users of the group + remote_was_deleted: + type: boolean + example: false + nullable: true + description: >- + Indicates whether or not this object has been deleted in the third + party platform. + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the group + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the group in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + additionalProperties: true + description: The remote data of the group in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - name + - users + - remote_was_deleted + UnifiedFilestorageUserOutput: + type: object + properties: + name: + type: string + nullable: true + example: Joe Doe + description: The name of the user + email: + type: string + nullable: true + example: joe.doe@gmail.com + description: The email of the user + is_me: + type: boolean + nullable: true + example: true + description: Whether the user is the one who linked this account. + field_mappings: + type: object + nullable: true + example: + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + additionalProperties: true + id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the user + remote_id: + type: string + nullable: true + example: id_1 + description: The id of the user in the context of the 3rd Party + remote_data: + type: object + nullable: true + example: + fav_dish: broccoli + fav_color: red + additionalProperties: true + description: The remote data of the user in the context of the 3rd Party + created_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The created date of the object + modified_at: + format: date-time + type: string + nullable: true + example: '2024-10-01T12:00:00Z' + description: The modified date of the object + required: + - name + - email + - is_me + Variant: + type: object + properties: {} + UnifiedEcommerceProductOutput: + type: object + properties: + product_url: + type: string + example: https://product_url/tee + nullable: true + description: The URL of the product + product_type: + type: string + example: teeshirt + nullable: true + description: The type of the product + product_status: + type: string + example: ACTIVE + nullable: true + enum: &ref_181 + - ARCHIVED + - ACTIVE + - DRAFT + description: The status of the product. Either ACTIVE, DRAFT OR ARCHIVED. + images_urls: + example: &ref_182 + - https://myproduct/image + nullable: true + description: The URLs of the product images + type: array + items: + type: string + description: + type: string + example: best tee ever + nullable: true + description: The description of the product + vendor: + type: string + example: vendor_extern + nullable: true + description: The vendor of the product + variants: + example: &ref_183 + - title: teeshirt + price: 20 + sku: '3' + options: null + weight: 10 + inventory_quantity: 100 + description: The variants of the product + type: array + items: + $ref: '#/components/schemas/Variant' + tags: + example: &ref_184 + - tag_1 + nullable: true + description: The tags associated with the product + type: array + items: + type: string + field_mappings: + type: object + example: &ref_185 + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the product + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the product in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: The remote data of the customer in the context of the 3rd Party + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedEcommerceProductInput: + type: object + properties: + product_url: + type: string + example: https://product_url/tee + nullable: true + description: The URL of the product + product_type: + type: string + example: teeshirt + nullable: true + description: The type of the product + product_status: + type: string + example: ACTIVE + nullable: true + enum: *ref_181 + description: The status of the product. Either ACTIVE, DRAFT OR ARCHIVED. + images_urls: + example: *ref_182 + nullable: true + description: The URLs of the product images + type: array + items: + type: string + description: + type: string + example: best tee ever + nullable: true + description: The description of the product + vendor: + type: string + example: vendor_extern + nullable: true + description: The vendor of the product + variants: + example: *ref_183 + description: The variants of the product + type: array + items: + $ref: '#/components/schemas/Variant' + tags: + example: *ref_184 + nullable: true + description: The tags associated with the product + type: array + items: + type: string + field_mappings: + type: object + example: *ref_185 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedEcommerceOrderOutput: + type: object + properties: + order_status: + type: string + example: UNSHIPPED + enum: &ref_186 + - PENDING + - UNSHIPPED + - SHIPPED + - CANCELED + nullable: true + description: The status of the order + order_number: + type: string + example: 19823838833 + nullable: true + description: The number of the order + payment_status: + type: string + example: SUCCESS + enum: &ref_187 + - SUCCESS + - FAIL + nullable: true + description: The payment status of the order + currency: + type: string + nullable: true + example: AUD + enum: &ref_188 + - AED + - AFN + - ALL + - AMD + - ANG + - AOA + - ARS + - AUD + - AWG + - AZN + - BAM + - BBD + - BDT + - BGN + - BHD + - BIF + - BMD + - BND + - BOB + - BRL + - BSD + - BTN + - BWP + - BYN + - BZD + - CAD + - CDF + - CHF + - CLP + - CNY + - COP + - CRC + - CUP + - CVE + - CZK + - DJF + - DKK + - DOP + - DZD + - EGP + - ERN + - ETB + - EUR + - FJD + - FKP + - FOK + - GBP + - GEL + - GGP + - GHS + - GIP + - GMD + - GNF + - GTQ + - GYD + - HKD + - HNL + - HRK + - HTG + - HUF + - IDR + - ILS + - IMP + - INR + - IQD + - IRR + - ISK + - JEP + - JMD + - JOD + - JPY + - KES + - KGS + - KHR + - KID + - KMF + - KRW + - KWD + - KYD + - KZT + - LAK + - LBP + - LKR + - LRD + - LSL + - LYD + - MAD + - MDL + - MGA + - MKD + - MMK + - MNT + - MOP + - MRU + - MUR + - MVR + - MWK + - MXN + - MYR + - MZN + - NAD + - NGN + - NIO + - NOK + - NPR + - NZD + - OMR + - PAB + - PEN + - PGK + - PHP + - PKR + - PLN + - PYG + - QAR + - RON + - RSD + - RUB + - RWF + - SAR + - SBD + - SCR + - SDG + - SEK + - SGD + - SHP + - SLE + - SLL + - SOS + - SRD + - SSP + - STN + - SYP + - SZL + - THB + - TJS + - TMT + - TND + - TOP + - TRY + - TTD + - TVD + - TWD + - TZS + - UAH + - UGX + - USD + - UYU + - UZS + - VES + - VND + - VUV + - WST + - XAF + - XCD + - XDR + - XOF + - XPF + - YER + - ZAR + - ZMW + - ZWL + description: >- + The currency of the order. Authorized value must be of type + CurrencyCode (ISO 4217) + total_price: + type: number + example: 300 + nullable: true + description: The total price of the order + total_discount: + type: number + example: 10 + nullable: true + description: The total discount on the order + total_shipping: + type: number + example: 120 + nullable: true + description: The total shipping cost of the order + total_tax: + type: number + example: 120 + nullable: true + description: The total tax on the order + fulfillment_status: + type: string + nullable: true + example: PENDING + enum: &ref_189 + - PENDING + - FULFILLED + - CANCELED + description: The fulfillment status of the order + customer_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the customer associated with the order + items: + nullable: true + example: &ref_190 + - remote_id: '12345' + product_id: prod_001 + variant_id: var_001 + sku: SKU123 + title: Sample Product + quantity: 2 + price: '19.99' + total: '39.98' + fulfillment_status: PENDING + requires_shipping: true + taxable: true + weight: 1.5 + variant_title: Size M + vendor: Sample Vendor + properties: + - name: Color + value: Red + tax_lines: + - title: Sales Tax + price: '3.00' + rate: 0.075 + discount_allocations: + - amount: '5.00' + discount_application_index: 0 + description: The items in the order + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: &ref_191 + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the order + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the order in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: The remote data of the customer in the context of the 3rd Party + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedEcommerceOrderInput: + type: object + properties: + order_status: + type: string + example: UNSHIPPED + enum: *ref_186 + nullable: true + description: The status of the order + order_number: + type: string + example: 19823838833 + nullable: true + description: The number of the order + payment_status: + type: string + example: SUCCESS + enum: *ref_187 + nullable: true + description: The payment status of the order + currency: + type: string + nullable: true + example: AUD + enum: *ref_188 + description: >- + The currency of the order. Authorized value must be of type + CurrencyCode (ISO 4217) + total_price: + type: number + example: 300 + nullable: true + description: The total price of the order + total_discount: + type: number + example: 10 + nullable: true + description: The total discount on the order + total_shipping: + type: number + example: 120 + nullable: true + description: The total shipping cost of the order + total_tax: + type: number + example: 120 + nullable: true + description: The total tax on the order + fulfillment_status: + type: string + nullable: true + example: PENDING + enum: *ref_189 + description: The fulfillment status of the order + customer_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the customer associated with the order + items: + nullable: true + example: *ref_190 + description: The items in the order + type: array + items: + $ref: '#/components/schemas/LineItem' + field_mappings: + type: object + example: *ref_191 + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + UnifiedEcommerceCustomerOutput: + type: object + properties: + email: + type: string + example: joedoe@gmail.com + nullable: true + description: The email of the customer + first_name: + type: string + example: Joe + nullable: true + description: The first name of the customer + last_name: + type: string + example: Doe + nullable: true + description: The last name of the customer + phone_number: + type: string + example: '+336666666' + nullable: true + description: The phone number of the customer + addresses: + example: + - address_type: PERSONAL + street_1: 5th Avenue + state: New York + city: New York + country: United States of America + nullable: true + description: The addresses of the customer + type: array + items: + $ref: '#/components/schemas/Address' + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the customer + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the customer in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: The remote data of the customer in the context of the 3rd Party + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedEcommerceFulfillmentOutput: + type: object + properties: + carrier: + type: string + nullable: true + example: DHL + description: The carrier of the fulfilment + tracking_urls: + nullable: true + example: + - https://tracing-url.sf.com + description: The tracking URLs of the fulfilment + type: array + items: + type: string + tracking_numbers: + nullable: true + example: + - track_1029_191919 + description: The tracking numbers of the fulfilment + type: array + items: + type: string + items: + type: object + nullable: true + example: {} + description: The items in the fulfilment + order_id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the order associated with the fulfilment + field_mappings: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: >- + The custom field mappings of the object between the remote 3rd party + & Panora + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the fulfilment + remote_id: + type: string + example: id_1 + nullable: true + description: The remote ID of the fulfilment in the context of the 3rd Party + remote_data: + type: object + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: The remote data of the customer in the context of the 3rd Party + created_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + UnifiedTicketingAttachmentOutput: + type: object + properties: + file_name: + type: string + example: features_planning.pdf + nullable: true + description: The file name of the attachment + file_url: + type: string + example: https://example.com/features_planning.pdf + nullable: true + description: The file url of the attachment + uploader: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The uploader's UUID of the attachment + ticket_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the ticket the attachment is tied to + comment_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the comment the attachment is tied to + field_mappings: + type: object + nullable: true + example: &ref_192 + fav_dish: broccoli + fav_color: red + description: >- + The custom field mappings of the attachment between the remote 3rd + party & Panora + additionalProperties: true + id: + type: string + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + nullable: true + description: The UUID of the attachment + remote_id: + type: string + example: id_1 + nullable: true + description: The id of the attachment in the context of the 3rd Party + remote_data: + type: object + additionalProperties: true + example: + fav_dish: broccoli + fav_color: red + nullable: true + description: The remote data of the attachment in the context of the 3rd Party + created_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The created date of the object + modified_at: + format: date-time + type: string + example: '2024-10-01T12:00:00Z' + nullable: true + description: The modified date of the object + required: + - file_name + - file_url + - uploader + UnifiedTicketingAttachmentInput: + type: object + properties: + file_name: + type: string + example: features_planning.pdf + nullable: true + description: The file name of the attachment + file_url: + type: string + example: https://example.com/features_planning.pdf + nullable: true + description: The file url of the attachment + uploader: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The uploader's UUID of the attachment + ticket_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the ticket the attachment is tied to + comment_id: + type: string + nullable: true + example: 801f9ede-c698-4e66-a7fc-48d19eebaa4f + description: The UUID of the comment the attachment is tied to + field_mappings: + type: object + nullable: true + example: *ref_192 + description: >- + The custom field mappings of the attachment between the remote 3rd + party & Panora + additionalProperties: true + required: + - file_name + - file_url + - uploader +security: + - api_key: [] +x-speakeasy-name-override: + - operationId: ^retrieve.* + methodNameOverride: retrieve + - operationId: ^list.* + methodNameOverride: list + - operationId: ^create.* + methodNameOverride: create diff --git a/packages/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index 1a05ee201..cf809851d 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -54,13 +54,13 @@ export const constructAuthUrl = async ({ projectId, linkedUserId, providerName, if (config.options && config.options.local_redirect_uri_in_https === true && redirectUriIngress && redirectUriIngress.status === true) { baseRedirectURL = redirectUriIngress.value!; } - let encodedRedirectUrl = encodeURIComponent(`${baseRedirectURL}/connections/oauth/callback`); + const encodedRedirectUrl = encodeURIComponent(`${baseRedirectURL}/connections/oauth/callback`); let state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl })); if (providerName === 'microsoftdynamicssales') { state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl, resource: additionalParams!.end_user_domain })); } if (providerName === 'deel') { - const randomState = randomString(); + const randomState = randomString(); state = encodeURIComponent(randomState + 'deel_delimiter' + Buffer.from(JSON.stringify({ projectId, linkedUserId, @@ -68,9 +68,9 @@ export const constructAuthUrl = async ({ projectId, linkedUserId, providerName, vertical, returnUrl, resource: additionalParams!.end_user_domain! - })).toString('base64')); + })).toString('base64')); } - if(providerName === 'squarespace'){ + if (providerName === 'squarespace') { const randomState = randomString(); state = encodeURIComponent(randomState + 'squarespace_delimiter' + Buffer.from(JSON.stringify({ projectId, @@ -79,7 +79,7 @@ export const constructAuthUrl = async ({ projectId, linkedUserId, providerName, vertical, returnUrl, resource: additionalParams!.end_user_domain! - })).toString('base64')); + })).toString('base64')); } // console.log('State : ', JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl })); // console.log('encodedRedirect URL : ', encodedRedirectUrl); diff --git a/packages/shared/src/connectors/metadata.ts b/packages/shared/src/connectors/metadata.ts index 0bd8dd76c..74656d7fb 100644 --- a/packages/shared/src/connectors/metadata.ts +++ b/packages/shared/src/connectors/metadata.ts @@ -2087,7 +2087,7 @@ export const CONNECTORS_METADATA: ProvidersConfig = { 'gusto': { urls: { docsUrl: 'https://docs.gusto.com/app-integrations/docs/introduction', - apiUrl: 'https://api.gusto.com', //api.gusto-demo.com + apiUrl: 'https://api.gusto.com', // api.gusto-demo.com authBaseUrl: 'https://api.gusto-demo.com/oauth/authorize' }, logoPath: 'https://cdn.runalloy.com/landing/uploads-new/Gusto_Logo_67ca008403.png',