Staff Profile

Test

Test GET

import requests

url = "http://localhost:9090/api/v1/staff/profile/WHAT"


def get_primary_keys(url):
	response = requests.get(url)
	data = response.json()
	keys = list(data.keys())
	return keys, data

def get_secondary_keys(url):
	secondary = []
	primary_keys, data = get_primary_keys(url)
	for i in range(len(data)):
		secondary_data.append(data[i])
	secondary_keys_1 = data[primary_keys[0]].keys()
	secondary_keys_2 = data[primary_keys[1]].keys()
	return secondary_keys_1, secondary_keys_2

def main():
	keys, data = get_primary_keys(url)
	print("Primary Keys")
	for key in keys:
		print("      ",key)
	secondary_keys_1, secondary_keys_2 = get_secondary_keys(url)
	print("Secondary Keys 1:")
	for key in secondary_keys_1:
		print ("      ", key)



if __name__ == "__main__":
    main()

V 1 N+1 Queries

func (r *staffRepository) CheckIfExists(staffID string) bool {
	var staff model.Staff
	err := r.db.Where("staff_id = ?", staffID).First(&staff).Error
	return !errors.Is(err, gorm.ErrRecordNotFound)
}

@from

https://stackoverflow.com/questions/66392372/select-exists-with-gorm

check if this works.

var exists bool
err = db.Model(model).
         Select("count(*) > 0").
         Where("id = ?", id).
         Find(&exists).
         Error

DTOs

General Details

Staff Profile General Details.png

type GeneralStaffDetails struct {
	Title         string    `json:"title"`
	FullName      string    `json:"full_name"`
	DateOfJoin    time.Time `json:"join_date"`
	DateOfBirth   time.Time `json:"dob"`
	Religion      int       `json:"religion"` // table staff details
	FacultyCode   string    `json:"faculty_code"`
	StaffID       string    `json:"staff"`
	Gender        string    `json:"gender"`
	EmployeeID    string    `json:"employee_id"`
	BloodGroup    string    `json:"blood_group"`
	CasteID       int       `json:"caste_id"` // might be int
	DepartmentID  int       `json:"dept_id"`
	DesignationID int       `json:"desg_id"`
	ContractType  int       `json:"contract_type"`
	ResearchArea  string    `json:"research_area"`
	Category      int       `json:"category"`
	KtuID         string    `json:"ktu_id"`
	PenNo         string    `json:"pen_no"`
	Expertise     string    `json:"expertise"`
}
staff, err := h.staffRepo.FindByID(staffID)
if staff == nil {
	c.JSON(http.StatusNoContent, gin.H{"error": "No Records with the reference id " + staffID + " found"})
	return
}
if err != nil {
	c.JSON(http.StatusInternalServerError, gin.H{"error": err})
	return
}

staffDetail, err := h.detailRepo.FindByID(staff.ID)
if err != nil {
	c.JSON(http.StatusInternalServerError, gin.H{"error": "Something Bad Happend"})
	return
}

staffPayScale, err := h.payScaleRepo.FindByID(staff.ID)
if err != nil {
	c.JSON(http.StatusInternalServerError, gin.H{"error": "Something Bad Happend"})
	return
}
staff.AssignDefaultVals(staff)
staffDetail.AssignDefaultVals(staffDetail)
generalDetails := dto.GeneralStaffDetails{
	Title:         *staff.Title,
	FullName:      staff.FullName,
	DateOfJoin:    staff.JoinDate,
	DateOfBirth:   staffDetail.DOB,
	Religion:      *staffDetail.ReligionID,
	FacultyCode:   *staff.FacultyCode,
	StaffID:       staff.StaffID, // TODO: Think if i want to send this
	Gender:        staff.Gender,
	EmployeeID:    *staff.EmployeeID,
	BloodGroup:    *staffDetail.BloodGroup,
	CasteID:       *staffDetail.CasteID,
	DepartmentID:  *staff.DeptID,
	DesignationID: *staff.DesgID,
	ContractType:  *staff.ContractType,
	ResearchArea:  *staffDetail.ResearchArea,
	Category:      *staff.CatID,
	KtuID:         *staff.KtuID,
	PenNo:         *staffDetail.PenNo,
	Expertise:     *staffDetail.Expertise,
}

Personal Details

Staff Profile Personal Details.png

dto
type PersonalStaffDetails struct {
	MaritalStatus string `json:"marital_status"`
	MotherName    string `json:"mother_name"`
	BloodGroup    string `json:"blood_group"`
	PanNo         string `json:"pan_no"`
	FatherName    string `json:"father_name"`
	SpouseName    string `json:"spouse_name"`
	Nationality   string `json:"nationality"`
}
personalDetails := dto.PersonalStaffDetails{
	MaritalStatus: *staffDetail.MaritalStatus,
	MotherName:    *staffDetail.MotherName,
	BloodGroup:    *staffDetail.BloodGroup,
	PanNo:         *staffDetail.PanNo,
	FatherName:    *staffDetail.FatherName,
	SpouseName:    *staffDetail.SpouseName,
	Nationality:   *staffDetail.Nationality,
}

Service Break

@rule r001

type SeriveBreakDetails struct {
	SLNo      string    `json:"sl_no"`
	StartDate time.Time `json:"start_date"`
	EndDate   time.Time `json:"end_date"`
}
Repository
type StaffServiceBreakRepository interface {
	Create(staff *model.StaffServiceBreak) error
	FindByID(id int) (*[]model.StaffServiceBreak, error)
	Update(staff *model.StaffServiceBreak) error
	Delete(id int) error
}

Check if Exists: can be realized using FindByID using len()

Contact Details

Staff Profile Contact Details.png

'address1'=>'House Name',
'address2'=>'Street'=>'Post / Street 2',
'district'=>'District',
'state'=>'State',
'pres_address_pin'=>'PIN',
'pres_address_1'=>'House Name',
'pres_address_2'=>'Street',
'pres_address_3'=>'Post / Street 2',
'pres_address_district'=>'District',
'pres_address_state' => 'State',

Responses

{
  "bank_details": {
    "bank_name": null,
    "bank_account_number": null,
    "bank_branch": null,
    "ifsc_code": null
  },
  "contact_details": {
    "phone": "+919747350199",
    "phone_res": null,
    "json": "aruncs31ss@gmail.com",
    "phone_office": null,
    "ABC Office": null,
    "Present": {
      "house_name": null,
      "street": null,
      "post": null,
      "district": null,
      "pin": null,
      "state": null
    },
    "Permenent": {
      "house_name": null,
      "street": null,
      "post": null,
      "district": null,
      "pin": null,
      "state": null
    }
  },
  "general_details": {
    "title": "Staff",
    "full_name": "Arun CS",
    "join_date": "2023-07-01T00:00:00+05:30",
    "dob": "2025-10-10T00:00:00+05:30",
    "religion": null,
    "faculty_code": "",
    "staff": "STAFF_K",
    "gender": "Male",
    "employee_id": "",
    "blood_group": null,
    "caste_id": null,
    "dept_id": 5,
    "desg_id": 1,
    "contract_type": 1,
    "research_area": null,
    "category": 1,
    "ktu_id": "",
    "pen_no": null,
    "expertise": null
  },
  "login_details": { "username": "STAFF_K", "default_passwd": "61eee5" },
  "pay_scale_details": {
    "designation_id": 1,
    "pay_scheme": null,
    "basic_pay": null,
    "aicte_id": null,
    "agp": null,
    "pay_band_level": null,
    "period_from": null,
    "Period_to": null
  },
  "personal_details": {
    "marital_status": null,
    "mother_name": null,
    "blood_group": null,
    "pan_no": null,
    "father_name": null,
    "spouse_name": null,
    "nationality": null
  },
  "service_break": [
    {
      "id": 1,
      "staff_id": 1,
      "start_date": "2025-10-10T00:00:00+05:30",
      "end_date": "2025-10-10T00:00:00+05:30"
    },
    {
      "id": 2,
      "staff_id": 1,
      "start_date": "2025-10-10T00:00:00+05:30",
      "end_date": "2025-10-10T00:00:00+05:30"
    }
  ]
}

Old Service

// Package service provides an interface for staff profile services.
/*
TODO: Document
*/
package service

import (
	"staff/dto"
	"staff/model"
)

type StaffProfileService interface {
	// ProvideAllDetails(staff *model.Staff, staffDetail *model.StaffDetail, staffPayScale *model.StaffPayScale, staffAdditionalDetail *model.StaffAdditionalDetail)
	GetGeneralDetails() *dto.GeneralStaffDetails
	GetPersonalDetails() *dto.PersonalStaffDetails
	GetPayScaleDetails() *dto.PayScaleDetails
	GetContactDetails() *dto.ContactDetails
	GetBankDetails() *dto.BankDetails
	// GetLoginDetails() *dto.LoginDetails // its now statically provided , or hardbinded
}
type staffProfileService struct {
	staff                 *model.Staff
	staffDetail           *model.StaffDetail
	staffPayScale         *model.StaffPayScale
	staffAdditionalDetail *model.StaffAdditionalDetail
}

func NewStaffProfileService(staff *model.Staff, staffDetail *model.StaffDetail, staffPayScale *model.StaffPayScale, staffAdditionalDetail *model.StaffAdditionalDetail) StaffProfileService {
	return &staffProfileService{
		staff:                 staff,
		staffDetail:           staffDetail,
		staffPayScale:         staffPayScale,
		staffAdditionalDetail: staffAdditionalDetail,
	}
}

// func (s *staffProfileService) ProvideAllDetails(staff *model.Staff, staffDetail *model.StaffDetail, staffPayScale *model.StaffPayScale, staffAdditionalDetail *model.StaffAdditionalDetail) {
// 	s.staff = staff
// 	s.staffDetail = staffDetail
// 	s.staffPayScale = staffPayScale
// 	s.staffAdditionalDetail = staffAdditionalDetail
// }

func (s *staffProfileService) GetGeneralDetails() *dto.GeneralStaffDetails {
	var generalDetails dto.GeneralStaffDetails

	generalDetails.Title = s.staff.Title
	generalDetails.FullName = s.staff.FullName
	generalDetails.DateOfJoin = s.staff.JoinDate
	generalDetails.FacultyCode = s.staff.FacultyCode
	generalDetails.StaffID = s.staff.StaffID // TODO: Think if i want to send this
	generalDetails.Gender = s.staff.Gender
	generalDetails.EmployeeID = s.staff.EmployeeID
	generalDetails.DepartmentID = s.staff.DeptID
	generalDetails.DesignationID = s.staff.DesgID
	generalDetails.ContractType = s.staff.ContractType
	generalDetails.Category = s.staff.CatID
	generalDetails.KtuID = s.staff.KtuID
	generalDetails.DateOfBirth = s.staffDetail.DOB
	generalDetails.Religion = s.staffDetail.ReligionID
	generalDetails.ResearchArea = s.staffDetail.ResearchArea
	generalDetails.BloodGroup = s.staffDetail.BloodGroup
	generalDetails.CasteID = s.staffDetail.CasteID
	generalDetails.PenNo = s.staffDetail.PenNo
	generalDetails.Expertise = s.staffDetail.Expertise
	generalDetails.VidwanID = s.staffDetail.VidwanID
	generalDetails.AICTEID = s.staffDetail.AICTEID
	generalDetails.HighestDegreeQualification = s.staffDetail.HighQualifications
	generalDetails.DesignatedAsProffesorDate = s.staffDetail.ServiceStartDate // TODO= Check if this correct
	generalDetails.LastWorkedInstitution = s.staffDetail.LastInstitution
	generalDetails.AadhaarNo = s.staffDetail.AadharNo
	generalDetails.PanNo = s.staffDetail.PanNo
	generalDetails.AreaOfSpecialization = s.staffDetail.SpecialisationArea
	generalDetails.CurrentlyPursuingPHD = s.staffDetail.PursingPhD
	generalDetails.PHDGuidance = s.staffDetail.PhDGuidance
	generalDetails.YearOfEnrollment = s.staffDetail.PhDYearOfEnrollment
	generalDetails.ResearchProgress = s.staffDetail.PhDResearchProgress
	generalDetails.Institution = s.staffDetail.LastInstitution
	return &generalDetails
}

func (s *staffProfileService) GetPersonalDetails() *dto.PersonalStaffDetails {
	var personalDetails dto.PersonalStaffDetails
	personalDetails.MaritalStatus = s.staffDetail.MaritalStatus
	personalDetails.MotherName = s.staffDetail.MotherName
	personalDetails.FatherName = s.staffDetail.FatherName
	personalDetails.SpouseName = s.staffDetail.SpouseName
	personalDetails.Nationality = s.staffDetail.Nationality
	return &personalDetails
}

func (s *staffProfileService) GetPayScaleDetails() *dto.PayScaleDetails {
	var payScaleDetails dto.PayScaleDetails
	payScaleDetails.PayScheme = s.staffPayScale.PayScheme
	payScaleDetails.BasicPay = s.staffPayScale.BasicPay
	payScaleDetails.AICTEScheme = s.staffPayScale.AISTE
	payScaleDetails.AGP = s.staffPayScale.AGP
	payScaleDetails.PayBandLevel = s.staffPayScale.PayBand // TODO: Figure out is this PayBand or Payband Level
	payScaleDetails.PeriodFrom = s.staffPayScale.From
	payScaleDetails.PeriodTo = s.staffPayScale.To
	return &payScaleDetails
}

func (s *staffProfileService) GetBankDetails() *dto.BankDetails {
	var bankDetails dto.BankDetails
	bankDetails.BankName = s.staffDetail.BankName
	bankDetails.BankAccountNumber = s.staffDetail.BankAccNo
	bankDetails.BankBranch = s.staffDetail.BankBranch
	bankDetails.IFSCCode = s.staffDetail.BankIFSCCode
	return &bankDetails
}

func (s *staffProfileService) GetContactDetails() *dto.ContactDetails {
	var contactDetails dto.ContactDetails
	contactDetails.Phone = s.staff.Phone

	contactDetails.PhoneRES = s.staffDetail.PhoneHome
	contactDetails.PhoneOffice = s.staffDetail.PhoneOffice
	contactDetails.OfficeAddress = s.staffDetail.OfficeAddress
	// contactDetails.Permenent = dto.PermenentAddress{
	// 	HouseName: s.staffDetail.Address1,
	// 	Street:    s.staffDetail.Address2,
	// 	Post:      s.staffDetail.Address3,
	// 	District:  s.staffDetail.District,
	// 	Pin:       s.staffDetail.Pin,
	// 	State:     s.staffDetail.State,
	// }
	// contactDetails.Present = dto.PresentAddress{
	// 	HouseName: s.staffDetail.PresAddress1,
	// 	Street:    s.staffDetail.PresAddress2,
	// 	Post:      s.staffDetail.PresAddress3,
	// 	District:  s.staffDetail.PresAddressDistrict,
	// 	Pin:       s.staffDetail.PresAddressPin,
	// 	State:     s.staffDetail.PresAddressState,
	// }
	contactDetails.Email = s.staffAdditionalDetail.Email
	return &contactDetails
}

V 2 Using Joins

Getting Profile Data

Response Style 1

{
  "all_details": {
    "title": null,
    "full_name": "Arun",
    "join_date": "0001-01-01T00:00:00Z",
    "dob": "0001-01-01T00:00:00Z",
    "religion_id": null,
    "faculty_code": null,
    "staff_id": "WHAT",
    "gender": "Male",
    "employee_id": null,
    "blood_group": null,
    "caste_id": null,
    "dept_id": null,
    "desg_id": null,
    "contract_type": 1,
    "research_area": null,
    "category": null,
    "ktu_id": null,
    "pen_no": null,
    "expertise": null,
    "Vidwan_id": null,
    "aicte_id": null,
    "highest_degree_qualification": null,
    "designated_as_professor_date": null,
    "last_worked_institution": null,
    "aadhaar_number": null,
    "pan_no": null,
    "area_of_specialization": null,
    "is_doing_phd": null,
    "institution": null,
    "research_progress": null,
    "year_of_enrollment": null,
    "phd_guidance": null,
    "marital_status": null,
    "mother_name": null,
    "father_name": null,
    "spouse_name": null,
    "nationality": null,
    "pay_scheme": null,
    "basic_pay": null,
    "aiste_id_2": null,
    "agp": null,
    "pay_band_level": null,
    "period_from": null,
    "period_to": null,
    "phone": null,
    "phone_res": null,
    "email": "testmail@gmail.com",
    "phone_office": null,
    "office_address": null,
    "house_name": null,
    "street": null,
    "post": null,
    "district": null,
    "pin": null,
    "state": null,
    "perm_house_name": null,
    "perm_street": null,
    "perm_post": null,
    "perm_district": null,
    "perm_pin": null,
    "perm_state": null,
    "bank_name": null,
    "bank_account_number": null,
    "bank_branch": null,
    "ifsc_code": null
  },
  "login_details": {
    "username": "WHAT",
    "default_passwd": "61eee5"
  }
}
all_details
login_details

All Details Table rep

Key Use
title
full_name
join_date
dob
religion_id
faculty_code
staff_id
gender
employee_id
blood_group
caste_id
dept_id
desg_id
contract_type
research_area
category
ktu_id
pen_no
expertise
Vidwan_id
aicte_id
highest_degree_qualification
designated_as_professor_date
last_worked_institution
aadhaar_number
pan_no
area_of_specialization
is_doing_phd
institution
research_progress
year_of_enrollment
phd_guidance
marital_status
mother_name
father_name
spouse_name
nationality
pay_scheme
basic_pay
aiste_id_2
agp
pay_band_level
period_from
period_to
phone
phone_res
email
phone_office
office_address
house_name
street
post
district
pin
state
perm_house_name
perm_street
perm_post
perm_district
perm_pin
perm_state
bank_name
bank_account_number
bank_branch
ifsc_code

Response Style 1 Grouping of Concerns

{
  "bank_details": {
    "bank_name": null,
    "bank_account_number": null,
    "bank_branch": null,
    "ifsc_code": null
  },
  "contact_details": {
    "phone": null,
    "phone_res": null,
    "email": "testmail@gmail.com",
    "phone_office": null,
    "office_address": null,
    "house_name": null,
    "street": null,
    "post": null,
    "district": null,
    "pin": null,
    "state": null,
    "perm_house_name": null,
    "perm_street": null,
    "perm_post": null,
    "perm_district": null,
    "perm_pin": null,
    "perm_state": null
  },
  "general_details": {
    "title": null,
    "full_name": "Arun",
    "join_date": "0001-01-01T00:00:00Z",
    "dob": "0001-01-01T00:00:00Z",
    "religion_id": null,
    "faculty_code": null,
    "staff_id": "",
    "gender": "Male",
    "employee_id": null,
    "blood_group": null,
    "caste_id": null,
    "dept_id": null,
    "desg_id": null,
    "contract_type": 1,
    "research_area": null,
    "category": null,
    "ktu_id": null,
    "pen_no": null,
    "expertise": null,
    "Vidwan_id": null,
    "aicte_id": null,
    "highest_degree_qualification": null,
    "designated_as_professor_date": null,
    "last_worked_institution": null,
    "aadhaar_number": null,
    "pan_no": null,
    "area_of_specialization": null,
    "is_doing_phd": null,
    "institution": null,
    "research_progress": null,
    "year_of_enrollment": null,
    "phd_guidance": null
  },
  "login_details": {
    "username": "",
    "default_passwd": "61eee5"
  },
  "pay_scale_details": {
    "pay_scheme": null,
    "basic_pay": null,
    "aiste_id_2": null,
    "agp": null,
    "pay_band_level": null,
    "period_from": null,
    "period_to": null
  },
  "personal_details": {
    "marital_status": null,
    "mother_name": null,
    "father_name": null,
    "spouse_name": null,
    "nationality": null
  },
  "service_break": [
    {
      "id": 11,
      "staff_id": 10,
      "start_date": "2025-10-10T00:00:00+05:30",
      "end_date": "2025-10-10T00:00:00+05:30"
    }
  ]
}z

Primary Keys

bank_details
contact_details
general_details
login_details
pay_scale_details
personal_details
service_break
bank_details
contact_details
general_details
login_details
pay_scale_details
personal_details
service_break

Some err

// get http://localhost:9090/api/v1/staff/profile/WHAT
{"details":"near \"from\": syntax error","error":"Failed to fetch staff profile details"}

Updating Profile Data

In this there are 4 tables

Json Format

When updating profile data the json data will have the following format

"bank_details": {},
"contact_details": {},
"general_details": {},
"login_details": {},
"pay_scale_details": {},
"personal_details": {},
"service_break": []
type UpdateProfileDetailsData struct {
	ID int `json:"id"` // staff id

	GeneralDetails  GeneralStaffDetails  `json:"general_details"`
	PersonalDetails PersonalStaffDetails `json:"personal_details"`
	PayScale        PayScaleDetails      `json:"pay_scale_details"`
	Contact         ContactDetails       `json:"contact_details"`
	Bank            BankDetails          `json:"bank_details"`
	Login           LoginDetails         `json:"login_details"`
}

Updating Process

id, err := s.staffRepo.GetStaffID(staff.StaffID)
if err != nil || id == 0 {
	return err
}

this function

GetStaffID() (string)

func (r *staffRepository) GetStaffID(staffID string) (int, error) {
	var staffPrimaryKey int

	err := r.db.Table(model.Staff{}.TableName()).Where("staff_id = ?", staffID).Select("id").Scan(&staffPrimaryKey).Error
	if err != nil {
		return 0, err
	}
	return staffPrimaryKey, nil
}
if err != nil || id == 0 {
		return err
	}
tx := initializers.DB.Begin()
defer func() {
	if r := recover(); r != nil {
		tx.Rollback()
	}
}()
if err := tx.Error;err  != nil {
	return tx.Error
}

staff.ID = id
if err := s.staffRepo.Update(tx, staff); err != nil {
	return err
}
staffDetailID, _ := s.staffDetailRepo.GetIDByStaffID(tx, staff.ID)
staffDetail.ID = staffDetailID
staffDetail.StaffID = staff.ID
if err := s.staffDetailRepo.Update(tx, staffDetail); err != nil {
	tx.Rollback()
	return err
}
staffAdditionalDetailID, _ := s.staffAdditionalDetailRepo.GetIDByStaffID(tx, staff.ID)
	staffAdditionalDetail.ID = staffAdditionalDetailID
	staffAdditionalDetail.StaffID = staff.ID
	if err := s.staffAdditionalDetailRepo.Update(tx, staffAdditionalDetail); err != nil {
		tx.Rollback()
		return err
	}
staffAdditionalDetailID, _ := s.staffAdditionalDetailRepo.GetIDByStaffID(tx, staff.ID)
	staffAdditionalDetail.ID = staffAdditionalDetailID
	staffAdditionalDetail.StaffID = staff.ID
	if err := s.staffAdditionalDetailRepo.Update(tx, staffAdditionalDetail); err != nil {
		tx.Rollback()
		return err
	}
staffPayScale.ID = staffPayScaleID
staffPayScale.StaffID = staff.ID
if err := s.staffPayScaleRepo.Update(tx, staffPayScale); err != nil {
	tx.Rollback()
	return err
}
if err := tx.Commit().Error; err != nil {
		return err
	}

OLD

func (s *staffProfileWriter) UpdateProfile(
	staff *model.Staff,
	staffDetail *model.StaffDetail,
	staffAdditionalDetail *model.StaffAdditionalDetail,
	staffPayScale *model.StaffPayScale,
	data *dto.UpdateProfileDetailsData,
) error {
	fmt.Println("StartUpdating")
	fmt.Println("staff id is", staff.ID)
	staffID, err := s.staffRepo.GetStaffID(staff.StaffID)
	fmt.Println("id", staffID)
	if err != nil || staffID == 0 {
		return err
	}

	tx := initializers.DB.Begin()

	if tx.Error != nil {
		return tx.Error
	}
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	staff.ID = staffID

	// Find the primary Key of the staff_
	fmt.Println("BEFORE: staff id is", staff.ID)
	fmt.Println("BEFORE: staff staff_id is", staff.StaffID)
	// Update the staff object
	if err := s.staffRepo.Update(tx, staff); err != nil {
		tx.Rollback()
		return err
	}
	fmt.Println("staff id is", staff.ID)
	fmt.Println("staff staff_id is", staff.StaffID)
	// Update the Staff Details
	staffDetailID, _ := s.staffDetailRepo.GetIDByStaffID(tx, staff.ID)
	staffDetail.ID = staffDetailID
	staffDetail.StaffID = staff.ID
	if err := s.staffDetailRepo.Update(tx, staffDetail); err != nil {
		tx.Rollback()
		return err
	}
	fmt.Println("staffDetail id is", staffDetail.ID)

	// Update StaffAdditionalDetail
	staffAdditionalDetailID, _ := s.staffAdditionalDetailRepo.GetIDByStaffID(tx, staff.ID)
	staffAdditionalDetail.ID = staffAdditionalDetailID
	staffAdditionalDetail.StaffID = staff.ID
	if err := s.staffAdditionalDetailRepo.Update(tx, staffAdditionalDetail); err != nil {
		tx.Rollback()
		return err
	}

	fmt.Println("staffAdditionalDetail id is", staffAdditionalDetail.ID)
	// Update staff payscale
	staffPayScaleID, _ := s.staffPayScaleRepo.GetIDByStaffID(tx, staff.ID)

	staffPayScale.ID = staffPayScaleID
	staffPayScale.StaffID = staff.ID
	if err := s.staffPayScaleRepo.Update(tx, staffPayScale); err != nil {
		tx.Rollback()
		return err
	}

	fmt.Println("staffPayScale id is", staffPayScale.ID)
	// Commit the transaction
	if err := tx.Commit().Error; err != nil {
		return err
	}

	return nil
}

NEW

func (s *staffProfileWriter) UpdateProfile(
	staff *model.Staff,
	staffDetail *model.StaffDetail,
	staffAdditionalDetail *model.StaffAdditionalDetail,
	staffPayScale *model.StaffPayScale,
	data *dto.UpdateProfileDetailsData,
) error {
	// Wrap everything in a transaction
	return initializers.DB.Transaction(func(tx *gorm.DB) error {
		// Lookup staff ID
		staffID, err := s.staffRepo.GetStaffID(staff.StaffID)
		if err != nil {
			return fmt.Errorf("failed to get staff ID: %w", err)
		}
		if staffID == 0 {
			return fmt.Errorf("staff not found with staff_id=%s", staff.StaffID)
		}
		staff.ID = staffID

		// Update staff
		if err := s.staffRepo.Update(tx, staff); err != nil {
			return fmt.Errorf("failed to update staff: %w", err)
		}

		// Update staff details
		if err := s.updateStaffDetail(tx, staff.ID, staffDetail); err != nil {
			return err
		}

		// Update additional details
		if err := s.updateStaffAdditionalDetail(tx, staff.ID, staffAdditionalDetail); err != nil {
			return err
		}

		// Update pay scale
		if err := s.updateStaffPayScale(tx, staff.ID, staffPayScale); err != nil {
			return err
		}

		return nil // commit happens automatically if no error
	})
}

// --- helper methods --- //

func (s *staffProfileWriter) updateStaffDetail(tx *gorm.DB, staffID int, detail *model.StaffDetail) error {
	id, err := s.staffDetailRepo.GetIDByStaffID(tx, staffID)
	if err != nil {
		return fmt.Errorf("failed to get staffDetail ID: %w", err)
	}
	detail.ID = id
	detail.StaffID = staffID
	if err := s.staffDetailRepo.Update(tx, detail); err != nil {
		return fmt.Errorf("failed to update staffDetail: %w", err)
	}
	return nil
}

func (s *staffProfileWriter) updateStaffAdditionalDetail(tx *gorm.DB, staffID int, detail *model.StaffAdditionalDetail) error {
	id, err := s.staffAdditionalDetailRepo.GetIDByStaffID(tx, staffID)
	if err != nil {
		return fmt.Errorf("failed to get staffAdditionalDetail ID: %w", err)
	}
	detail.ID = id
	detail.StaffID = staffID
	if err := s.staffAdditionalDetailRepo.Update(tx, detail); err != nil {
		return fmt.Errorf("failed to update staffAdditionalDetail: %w", err)
	}
	return nil
}

func (s *staffProfileWriter) updateStaffPayScale(tx *gorm.DB, staffID int, scale *model.StaffPayScale) error {
	id, err := s.staffPayScaleRepo.GetIDByStaffID(tx, staffID)
	if err != nil {
		return fmt.Errorf("failed to get staffPayScale ID: %w", err)
	}
	scale.ID = id
	scale.StaffID = staffID
	if err := s.staffPayScaleRepo.Update(tx, scale); err != nil {
		return fmt.Errorf("failed to update staffPayScale: %w", err)
	}
	return nil
}