Shop Sales Automation

Shop Sales Automation – AutoHotkey script in action

Project Overview

The Shop Sales Automation is a custom automation tool designed to streamline a repetitive task at work: filling the job column of every shop sale in MYOB with the number '6'. The script uses recorded mouse positions, clipboard checks, and row counting to automate the process, looping through each transaction until completion. It dramatically reduces manual effort and error, making a tedious process fast and reliable.

Tech Stack

  • AutoHotkey (automation scripting)
  • Windows API integration
  • Clipboard manipulation
  • Mouse & keyboard automation

How It Works

  • Records mouse positions for key UI elements in MYOB.
  • Counts rows in each transaction by copying cell data to the clipboard.
  • Automatically enters '6' in the job column for each row.
  • Loops through all transactions, pausing or stopping as needed.

Automation Script (AutoHotkey)

The complete script is provided below for reference and learning purposes.

```autohotkey #Persistent #SingleInstance Force SetBatchLines, -1 ; Run at maximum CPU utilization SetWorkingDir, %A_ScriptDir% ; Ensure the script runs in its own directory CoordMode, Mouse, Screen ; Use screen coordinates for mouse actions SetTitleMatchMode, 2 ; Allows partial matching of window titles DetectHiddenWindows, On ; Ensure hidden windows are detected ; ------------------------------------ ; Configuration Section ; ------------------------------------ ; Define standard cursor IDs IDC_ARROW := 32512 IDC_WAIT := 32514 IDC_APPSTARTING := 32650 ; Load the standard arrow cursor handle once hArrow := DllCall("LoadCursor", "Ptr", 0, "Int", IDC_ARROW, "Ptr") ; Define the path to the INI file IniFilePath := A_ScriptDir . "\ClickPositions.ini" ; Initialize ClickCoords array ClickCoords := [] ; ------------------------------------ ; Initialization Section ; ------------------------------------ ; Attempt to read existing click coordinates from the INI file if (FileExist(IniFilePath)) { Loop, 4 { ; Changed from 3 to 4 index := A_Index IniRead, X, %IniFilePath%, Click%index%, X, 0 IniRead, Y, %IniFilePath%, Click%index%, Y, 0 if (X != 0 and Y != 0) { ClickCoords.Push([X, Y]) } else { ClickCoords := [] break } } if (ClickCoords.Length() = 4) { ; Changed from 3 to 4 ToolTip, Click positions loaded successfully from the file. Sleep, 2000 ToolTip } else { ClickCoords := [] ToolTip, Click positions file is incomplete or corrupted. Please set new click positions using F1. Sleep, 3000 ToolTip } } else { ToolTip, No saved click positions found. Please set them using F1. Sleep, 3000 ToolTip } ; ------------------------------------ ; Function Definitions ; ------------------------------------ IsCursorNormal() { global IDC_ARROW, hArrow ; Define the CURSORINFO structure using VarSetCapacity VarSetCapacity(ci, 4 + 4 + A_PtrSize + 8, 0) ; DWORD cbSize + DWORD flags + HCURSOR hCursor + POINT ptScreenPos ; Set cbSize to the size of the structure NumPut(VarSetCapacity(ci), ci, 0, "UInt") ; Set flags to 1 (CURSOR_SHOWING) NumPut(1, ci, 4, "UInt") ; Call GetCursorInfo DllCall("GetCursorInfo", "Ptr", &ci, "UInt") ; Get hCursor from the structure hCursor := NumGet(ci, 8, "Ptr") return (hCursor = hArrow) } WaitForNormalCursor(Timeout := 30000) { ; Default timeout: 30 seconds StartTime := A_TickCount while !IsCursorNormal() { Sleep, 100 if (A_TickCount - StartTime > Timeout) { MsgBox, 48, Timeout, The cursor did not return to normal state within %Timeout% milliseconds.`nExiting script. ExitApp } } } WaitForWindowActive(WindowTitle, Timeout := 30000) { StartTime := A_TickCount while !WinActive(WindowTitle) { Sleep, 100 if (A_TickCount - StartTime > Timeout) { MsgBox, 48, Timeout, The window "%WindowTitle%" did not become active within %Timeout% milliseconds.`nExiting script. ExitApp } } } CountRows() { rowCount := 0 Loop { Clipboard := "" SendInput, ^c ClipWait, 1 ; Wait up to 1 second for clipboard data cellData := Trim(Clipboard) Clipboard := "" if (cellData = "" || InStr(cellData, "Description")) { break } else { rowCount++ SendInput, {Down} Sleep, 50 ; Minimal delay to allow UI to update } } return rowCount } ; ------------------------------------ ; Hotkey Definitions ; ------------------------------------ ; Hotkey: F1 - Set Click Locations F1:: ClickCount := 4 ; Changed from 3 to 4 ClickCoords := [] ; Reset the array Loop, %ClickCount% { index := A_Index MsgBox, 64, Set Click Location, Click on location %index% and press Enter. KeyWait, Enter, D MouseGetPos, X, Y ClickCoords.Push([X, Y]) MsgBox, 64, Click Location Set, Click location %index% set at (%X%, %Y%). } ; Save the coordinates to the INI file using temporary variables Loop, %ClickCount% { index := A_Index tempX := ClickCoords[index][1] tempY := ClickCoords[index][2] IniWrite, %tempX%, %IniFilePath%, Click%index%, X IniWrite, %tempY%, %IniFilePath%, Click%index%, Y } MsgBox, 64, Success, Click positions have been saved successfully. return ; Hotkey: F2 - Execute Automation Loop (start/resume automation) F2:: if (ClickCoords.Length() < 4) { ; Changed from 3 to 4 MsgBox, 48, Missing Click Coordinates, Click coordinates are not set. Please press F1 to set them before running the automation. return } Loop { if GetKeyState("Esc", "P") { ; Break the loop if Esc is pressed MsgBox, 64, Script Terminated, The script has been terminated by the user. break } ; Ensure MYOB window is active WinActivate, MYOB AccountRight WaitForWindowActive("MYOB AccountRight", 30000) WaitForNormalCursor() ; Move to the starting cell SendInput, {Home} Sleep, 100 ; Move to the correct column if necessary SendInput, {Right} Sleep, 100 ; Count the number of rows rowCount := CountRows() if (rowCount = 0) { MsgBox, 48, No Rows Detected, No rows detected. Pausing automation. break } ; Navigate back to the starting position SendInput, {Left 2} Sleep, 100 Sleep, 100 ; Perform the typing sequence for each row Loop, %rowCount% { SendInput, 6 Sleep, 5 SendInput, {Enter} Sleep, 5 SendInput, {Up} Sleep, 5 SendInput, {Left} Sleep, 500 } WaitForNormalCursor() ; Perform the clicks using temporary variables for coordinates x1 := ClickCoords[1][1] y1 := ClickCoords[1][2] x2 := ClickCoords[2][1] y2 := ClickCoords[2][2] x3 := ClickCoords[3][1] y3 := ClickCoords[3][2] x4 := ClickCoords[4][1] y4 := ClickCoords[4][2] MouseClick, left, %x1%, %y1%, 1, 0 Sleep, 500 WaitForNormalCursor() Sleep, 1750 ; Ensure cursor is normal before double-clicking MouseClick, left, %x2%, %y2%, 2, 0 WaitForNormalCursor() Sleep, 1000 MouseClick, left, %x3%, %y3%, 1, 0 Sleep, 10 MouseClick, left, %x4%, %y4%, 1, 0 Sleep, 10 ; === Clipboard Check === Clipboard := "" SendInput, ^c ClipWait, 1 if (Clipboard = "6") { MsgBox, 64, Completion, Clipboard contains "6". Pausing automation. Press F2 to resume. break ; Exit the loop, pausing the automation } else if (Clipboard = "") { continue } } return ; Hotkey: Esc - Exit the Script Esc::ExitApp ```
← Back to all projects