Skip to content

Commit fa72884

Browse files
authored
Updated README to include CSV template download instructions
- Added a step in the README instructing users to download the CSV template from OSPOS before preparing their data. - Clarified that the CSV file should be edited in a spreadsheet or text editor and moved to a dedicated folder before running the script. - Ensures users follow the correct workflow to prevent formatting issues during upload.
1 parent dab47e0 commit fa72884

File tree

3 files changed

+264
-0
lines changed

3 files changed

+264
-0
lines changed

.gitiginore

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Ignore Python compiled files
2+
__pycache__/
3+
*.pyc
4+
*.pyo
5+
6+
# Ignore virtual environments
7+
venv/
8+
.env/
9+
.venv/
10+
11+
# Ignore logs and temp files
12+
*.log
13+
*.tmp
14+
15+
# Ignore OS-specific files
16+
.DS_Store
17+
Thumbs.db
18+
19+
# Ignore keyring storage (if custom file-based storage is ever used)
20+
keyring_pass.cfg
21+
22+
# Ignore any backup or temp files
23+
backup/
24+
*.bak
25+
*.swp
26+

Readme.MD

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# OSPOS CSV Upload Automation Script
2+
3+
This script automates the process of uploading CSV files to the Open Source Point of Sale (OSPOS) system. It supports importing data into both the **Items** and **Customers** sections, using Selenium for web automation.
4+
5+
## Features
6+
✅ Supports both **Items** and **Customers** CSV uploads
7+
✅ Securely stores credentials and settings using **keyring**
8+
✅ Automatically navigates and imports CSV files one by one
9+
✅ Allows users to set and store OSPOS URL, ChromeDriver path, and CSV directory
10+
✅ Handles failed uploads and retries them
11+
✅ Default credentials and paths can be used or overridden
12+
13+
## Requirements
14+
- **Python 3.8 or later**
15+
- **Google Chrome** (latest version recommended)
16+
- **ChromeDriver** (matching your Chrome version)
17+
- Required Python modules:
18+
```sh
19+
pip install selenium keyring
20+
```
21+
- OSPOS running on any of the following server environments:
22+
- **XAMPP** (Windows, Linux, Mac)
23+
- **WAMP** (Windows-based stack)
24+
- **LAMP** (Linux-based stack)
25+
- **MAMP** (Mac-based stack)
26+
- **Docker-based OSPOS setup**
27+
28+
## How to Use
29+
1. **Download the CSV template from the CSV Import option in OSPOS** before preparing your CSV files.
30+
2. **Edit the downloaded CSV file** using a **spreadsheet editor (Excel, Google Sheets, LibreOffice Calc)** or **a text editor (Notepad, VS Code, etc.)** to input your data.
31+
3. **Move the edited CSV file to a dedicated folder** where it will be uploaded from.
32+
4. **Clone the repository**
33+
```sh
34+
git clone https://github.com/ShadowXByte/ospos-csv-automation.git
35+
cd ospos-csv-automation
36+
```
37+
5. **Run the script**
38+
```sh
39+
python upload_ospos.py
40+
```
41+
6. **Follow the prompts** to enter your OSPOS credentials, URL, and CSV directory.
42+
7. Select whether you want to upload **Items** or **Customers** CSV files.
43+
8. The script will automatically process and upload all CSV files.
44+
45+
## Configuration
46+
- The script will **store credentials** (username, password, OSPOS URL) securely using `keyring`.
47+
- Default values can be modified in the script.
48+
- Users can re-enter credentials at any time.
49+
50+
## Important Update: Selenium 4 Compatibility
51+
- **Starting from Selenium 4, `executable_path` is no longer used.** Instead, ChromeDriver should be initialized using:
52+
```python
53+
from selenium.webdriver.chrome.service import Service
54+
55+
service = Service(CHROMEDRIVER_PATH)
56+
driver = webdriver.Chrome(service=service, options=options)
57+
```
58+
This script is updated to be fully compatible with **Selenium 4**.
59+
60+
## Troubleshooting
61+
- **OSPOS URL Not Working?** Ensure OSPOS is running and the correct URL is provided.
62+
- **ChromeDriver Error?** Check if your ChromeDriver version matches your installed Chrome version.
63+
- **Server Not Running?** Ensure your server environment (XAMPP, WAMP, LAMP, MAMP, Docker) is properly configured.
64+
65+
## License
66+
This project is licensed under the **MIT License**. Feel free to modify and contribute!
67+
68+
## Contributions
69+
Pull requests are welcome! If you improve the script or add features, consider sharing them with the community.
70+
71+
---
72+
73+
### Maintained by [ShadowXByte](https://github.com/ShadowXByte)
74+

upload_ospos.py

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import keyring
2+
import getpass
3+
import os
4+
import re
5+
import time
6+
from selenium import webdriver
7+
from selenium.webdriver.chrome.service import Service
8+
from selenium.webdriver.common.by import By
9+
from selenium.webdriver.support.ui import WebDriverWait
10+
from selenium.webdriver.support import expected_conditions as EC
11+
12+
# Service name for storing credentials in keyring
13+
SERVICE_NAME = "OSPOS_AutoUploader"
14+
15+
# Default values (can be overridden by user input)
16+
DEFAULT_OSPOS_URL = "http://localhost/ospos/public/"
17+
DEFAULT_USERNAME = "admin"
18+
DEFAULT_PASSWORD = "pointofsale"
19+
DEFAULT_CHROMEDRIVER_PATH = "C:\\path\\to\\chromedriver.exe"
20+
21+
def get_credentials():
22+
"""Retrieve stored credentials or prompt for new ones."""
23+
username = keyring.get_password(SERVICE_NAME, "username") or DEFAULT_USERNAME
24+
password = keyring.get_password(SERVICE_NAME, "password") or DEFAULT_PASSWORD
25+
26+
use_default = input(f"Use default credentials ({username})? (y/n): ").strip().lower()
27+
if use_default != 'y':
28+
username = input("Enter OSPOS Username: ")
29+
password = getpass.getpass("Enter OSPOS Password: ") # Hides password input
30+
keyring.set_password(SERVICE_NAME, "username", username)
31+
keyring.set_password(SERVICE_NAME, "password", password)
32+
print("Credentials saved securely!")
33+
return username, password
34+
35+
def get_ospos_url():
36+
"""Retrieve stored OSPOS URL or prompt for new one."""
37+
ospos_url = keyring.get_password(SERVICE_NAME, "ospos_url") or DEFAULT_OSPOS_URL
38+
39+
use_default = input(f"Use default OSPOS URL ({ospos_url})? (y/n): ").strip().lower()
40+
if use_default != 'y':
41+
ospos_url = input("Enter OSPOS URL: ")
42+
keyring.set_password(SERVICE_NAME, "ospos_url", ospos_url)
43+
print("OSPOS URL saved!")
44+
return ospos_url
45+
46+
def get_csv_directory(section):
47+
"""Retrieve stored CSV directory for section or prompt for new one."""
48+
csv_directory = keyring.get_password(SERVICE_NAME, f"csv_directory_{section}")
49+
if not csv_directory:
50+
csv_directory = input(f"Enter CSV Directory Path for {section}: ")
51+
keyring.set_password(SERVICE_NAME, f"csv_directory_{section}", csv_directory)
52+
print(f"CSV directory for {section} saved!")
53+
return csv_directory
54+
55+
def get_chromedriver_path():
56+
"""Retrieve stored ChromeDriver path or prompt for new one."""
57+
chromedriver_path = keyring.get_password(SERVICE_NAME, "chromedriver_path") or DEFAULT_CHROMEDRIVER_PATH
58+
59+
use_default = input(f"Use default ChromeDriver path ({chromedriver_path})? (y/n): ").strip().lower()
60+
if use_default != 'y':
61+
chromedriver_path = input("Enter ChromeDriver Path: ")
62+
keyring.set_password(SERVICE_NAME, "chromedriver_path", chromedriver_path)
63+
print("ChromeDriver path saved!")
64+
return chromedriver_path
65+
66+
def natural_sort_key(file_name):
67+
"""Sort files in natural order (numbers in file names sorted correctly)."""
68+
return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', file_name)]
69+
70+
# Prompt user to choose which section to upload files to
71+
section = input("Enter the section to upload CSV files to (Items/Customers): ").strip().capitalize()
72+
73+
# Get user credentials, OSPOS URL, CSV directory, and ChromeDriver path
74+
USERNAME, PASSWORD = get_credentials()
75+
OSPOS_URL = get_ospos_url()
76+
CSV_DIRECTORY = get_csv_directory(section)
77+
CHROMEDRIVER_PATH = get_chromedriver_path()
78+
79+
# Initialize WebDriver
80+
def setup_driver():
81+
options = webdriver.ChromeOptions()
82+
service = Service(CHROMEDRIVER_PATH) # Correct way to set ChromeDriver path in Selenium 4
83+
driver = webdriver.Chrome(service=service, options=options)
84+
driver.get(OSPOS_URL)
85+
return driver
86+
87+
# Login to OSPOS
88+
def login(driver):
89+
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "input-username"))).send_keys(USERNAME)
90+
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "input-password"))).send_keys(PASSWORD)
91+
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//button[@type='submit']"))).click()
92+
time.sleep(3) # Allow time for login
93+
94+
# Ensure modal is closed before proceeding
95+
def close_modal_if_open(driver):
96+
try:
97+
close_button = WebDriverWait(driver, 2).until(EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Close')]")))
98+
close_button.click()
99+
WebDriverWait(driver, 5).until_not(EC.presence_of_element_located((By.CLASS_NAME, "modal-dialog")))
100+
time.sleep(2)
101+
print("Closed stuck modal.")
102+
except:
103+
pass # If no modal is open, continue
104+
105+
# Navigate to CSV Import Page
106+
def navigate_to_csv_import(driver, section):
107+
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, section))).click()
108+
time.sleep(3)
109+
close_modal_if_open(driver)
110+
csv_import_button = WebDriverWait(driver, 10).until(
111+
EC.element_to_be_clickable((By.XPATH, f"//button[contains(@data-href, '{section.lower()}/csvImport')]"))
112+
)
113+
csv_import_button.click()
114+
time.sleep(3) # Allow modal to open
115+
116+
# Upload a single CSV file
117+
def upload_csv(driver, file_path, section):
118+
navigate_to_csv_import(driver, section)
119+
120+
try:
121+
file_input = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.NAME, "file_path")))
122+
file_input.send_keys(file_path)
123+
124+
submit_button = WebDriverWait(driver, 10).until(
125+
EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Submit')]"))
126+
)
127+
submit_button.click()
128+
129+
time.sleep(5) # Wait for the upload to complete
130+
131+
# Wait for modal to close before proceeding
132+
WebDriverWait(driver, 10).until_not(EC.presence_of_element_located((By.CLASS_NAME, "modal-dialog")))
133+
time.sleep(2)
134+
135+
print(f"Uploaded: {file_path}")
136+
return True
137+
except Exception as e:
138+
print(f"Failed to upload {file_path}: {e}")
139+
close_modal_if_open(driver) # Force close modal if stuck
140+
return False
141+
142+
# Process all CSV files
143+
def upload_csv_files():
144+
driver = setup_driver()
145+
login(driver)
146+
147+
# Get all CSV files
148+
files = [f for f in os.listdir(CSV_DIRECTORY) if f.endswith(".csv")]
149+
150+
# Sort files naturally if they have numbers, else keep original order
151+
if any(re.search(r'\d+', f) for f in files):
152+
sorted_files = sorted(files, key=natural_sort_key)
153+
else:
154+
sorted_files = files
155+
156+
for filename in sorted_files:
157+
file_path = os.path.join(CSV_DIRECTORY, filename)
158+
upload_csv(driver, file_path, section)
159+
160+
print("Process Completed.")
161+
driver.quit()
162+
163+
# Run the script
164+
upload_csv_files()

0 commit comments

Comments
 (0)