kolay-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kolay_cli-0.1.0/LICENSE +21 -0
- kolay_cli-0.1.0/PKG-INFO +159 -0
- kolay_cli-0.1.0/README.md +130 -0
- kolay_cli-0.1.0/pyproject.toml +47 -0
- kolay_cli-0.1.0/setup.cfg +4 -0
- kolay_cli-0.1.0/src/kolay_cli/__init__.py +4 -0
- kolay_cli-0.1.0/src/kolay_cli/api.py +56 -0
- kolay_cli-0.1.0/src/kolay_cli/cli.py +47 -0
- kolay_cli-0.1.0/src/kolay_cli/commands/__init__.py +3 -0
- kolay_cli-0.1.0/src/kolay_cli/commands/auth.py +26 -0
- kolay_cli-0.1.0/src/kolay_cli/commands/leave.py +204 -0
- kolay_cli-0.1.0/src/kolay_cli/commands/person.py +340 -0
- kolay_cli-0.1.0/src/kolay_cli/config.py +54 -0
- kolay_cli-0.1.0/src/kolay_cli.egg-info/PKG-INFO +159 -0
- kolay_cli-0.1.0/src/kolay_cli.egg-info/SOURCES.txt +17 -0
- kolay_cli-0.1.0/src/kolay_cli.egg-info/dependency_links.txt +1 -0
- kolay_cli-0.1.0/src/kolay_cli.egg-info/entry_points.txt +2 -0
- kolay_cli-0.1.0/src/kolay_cli.egg-info/requires.txt +3 -0
- kolay_cli-0.1.0/src/kolay_cli.egg-info/top_level.txt +1 -0
kolay_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tunc Aucer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
kolay_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kolay-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line interface for Kolay IK (https://apidocs.kolayik.com)
|
|
5
|
+
Author: Tunc Aucer
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ezapmar/kolay-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/ezapmar/kolay-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/ezapmar/kolay-cli/issues
|
|
10
|
+
Keywords: kolay,hr,cli,kolayik,human-resources
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Office/Business
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: typer>=0.9.0
|
|
26
|
+
Requires-Dist: requests>=2.31.0
|
|
27
|
+
Requires-Dist: rich>=13.4.2
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Kolay CLI
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
+************
|
|
34
|
+
+*+ **+
|
|
35
|
+
+*+ +*+ %% #%%
|
|
36
|
+
+*+ +*+ %% *#* #%%# #%% #%%#*#*+** **+
|
|
37
|
+
+**+ +*+ %% #%%* %%%##%%% #%% %%%#*%%%% %%# #%%
|
|
38
|
+
**+ +**+ %%%%# %%# %%##%%#%% %%% %%##%%#
|
|
39
|
+
+*+ +*++*+ %%#%%# %%% %%##%%*%% #%% %%%%#
|
|
40
|
+
+*+ ** +*+ %% #%% *%%%%%%# #%% #%%%%%%%% *%%%
|
|
41
|
+
+*+** **+ #%%
|
|
42
|
+
+************ #%%%
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A powerful, user-friendly command-line interface for the [Kolay IK API](https://apidocs.kolayik.com). Manage your HR operations directly from the terminal.
|
|
46
|
+
|
|
47
|
+
## ✨ Features
|
|
48
|
+
|
|
49
|
+
| Module | Commands | Description |
|
|
50
|
+
|--------|----------|-------------|
|
|
51
|
+
| **Auth** | `login`, `status` | Securely store and manage your API token |
|
|
52
|
+
| **Person** | `list`, `view`, `leave-status`, `terminate` | List employees, view structured profiles, check leave balances, terminate with SGK codes |
|
|
53
|
+
| **Leave** | `list`, `view`, `create` | List leave records, view details, create leaves with interactive type picker |
|
|
54
|
+
|
|
55
|
+
### Highlights
|
|
56
|
+
|
|
57
|
+
- 🎨 **Rich terminal output** — Structured tables, panels, and color-coded statuses
|
|
58
|
+
- 🔐 **Secure token storage** — Config file with restricted permissions (`0600`)
|
|
59
|
+
- 🇹🇷 **Turkish HR aware** — SGK termination codes, DD.MM.YYYY date format support
|
|
60
|
+
- ⚡ **Smart prompts** — Leave type picker, today's date defaults, active employee pre-checks
|
|
61
|
+
|
|
62
|
+
## 📦 Installation
|
|
63
|
+
|
|
64
|
+
### Using pipx (Recommended)
|
|
65
|
+
```bash
|
|
66
|
+
pipx install kolay-cli
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### From source
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/ezapmar/kolay-cli.git
|
|
72
|
+
cd kolay-cli
|
|
73
|
+
pip install -e .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 🚀 Quick Start
|
|
77
|
+
|
|
78
|
+
### 1. Authenticate
|
|
79
|
+
Generate an API token from [Kolay Developer Settings](https://app.kolayik.com/settings/developer-settings), then:
|
|
80
|
+
```bash
|
|
81
|
+
kolay auth login
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 2. List your employees
|
|
85
|
+
```bash
|
|
86
|
+
kolay person list
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. View someone's profile
|
|
90
|
+
```bash
|
|
91
|
+
kolay person view <person-id>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. Check leave balances
|
|
95
|
+
```bash
|
|
96
|
+
kolay person leave-status <person-id>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 5. Create a leave request
|
|
100
|
+
```bash
|
|
101
|
+
kolay leave create
|
|
102
|
+
```
|
|
103
|
+
The CLI will interactively show available leave types and let you pick by number.
|
|
104
|
+
|
|
105
|
+
## 📖 Command Reference
|
|
106
|
+
|
|
107
|
+
### Auth
|
|
108
|
+
```bash
|
|
109
|
+
kolay auth login # Save your API token (prompted securely)
|
|
110
|
+
kolay auth status # Check if you're logged in
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Person
|
|
114
|
+
```bash
|
|
115
|
+
kolay person list # List active employees
|
|
116
|
+
kolay person list --status inactive # List inactive employees
|
|
117
|
+
kolay person list --page 2 # Paginate results
|
|
118
|
+
kolay person view <id> # Structured profile view
|
|
119
|
+
kolay person leave-status <id> # Leave balances table
|
|
120
|
+
kolay person terminate <id> # Terminate with SGK reason codes
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Leave
|
|
124
|
+
```bash
|
|
125
|
+
kolay leave list # List approved leaves (current year)
|
|
126
|
+
kolay leave list --status waiting # List pending leaves
|
|
127
|
+
kolay leave list --person-id <id> # Filter by person
|
|
128
|
+
kolay leave list --start-date "2025-01-01 00:00:00" --end-date "2025-12-31 23:59:59"
|
|
129
|
+
kolay leave view <leave-id> # View leave record details
|
|
130
|
+
kolay leave create # Interactive leave creation
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## ⚙️ Configuration
|
|
134
|
+
|
|
135
|
+
The CLI stores its configuration at `~/.config/kolay/config.json` with restricted file permissions.
|
|
136
|
+
|
|
137
|
+
You can also use environment variables (they take precedence):
|
|
138
|
+
```bash
|
|
139
|
+
export KOLAY_API_TOKEN="your-token-here"
|
|
140
|
+
export KOLAY_BASE_URL="https://api.kolayik.com" # optional
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 🛠 Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Clone and setup
|
|
147
|
+
git clone https://github.com/ezapmar/kolay-cli.git
|
|
148
|
+
cd kolay-cli
|
|
149
|
+
python -m venv .venv
|
|
150
|
+
source .venv/bin/activate # or .venv\Scripts\activate on Windows
|
|
151
|
+
pip install -e .
|
|
152
|
+
|
|
153
|
+
# Run
|
|
154
|
+
kolay --help
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 📄 License
|
|
158
|
+
|
|
159
|
+
This project is licensed under the [MIT License](LICENSE) — use it freely, modify it, share it. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Kolay CLI
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
+************
|
|
5
|
+
+*+ **+
|
|
6
|
+
+*+ +*+ %% #%%
|
|
7
|
+
+*+ +*+ %% *#* #%%# #%% #%%#*#*+** **+
|
|
8
|
+
+**+ +*+ %% #%%* %%%##%%% #%% %%%#*%%%% %%# #%%
|
|
9
|
+
**+ +**+ %%%%# %%# %%##%%#%% %%% %%##%%#
|
|
10
|
+
+*+ +*++*+ %%#%%# %%% %%##%%*%% #%% %%%%#
|
|
11
|
+
+*+ ** +*+ %% #%% *%%%%%%# #%% #%%%%%%%% *%%%
|
|
12
|
+
+*+** **+ #%%
|
|
13
|
+
+************ #%%%
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
A powerful, user-friendly command-line interface for the [Kolay IK API](https://apidocs.kolayik.com). Manage your HR operations directly from the terminal.
|
|
17
|
+
|
|
18
|
+
## ✨ Features
|
|
19
|
+
|
|
20
|
+
| Module | Commands | Description |
|
|
21
|
+
|--------|----------|-------------|
|
|
22
|
+
| **Auth** | `login`, `status` | Securely store and manage your API token |
|
|
23
|
+
| **Person** | `list`, `view`, `leave-status`, `terminate` | List employees, view structured profiles, check leave balances, terminate with SGK codes |
|
|
24
|
+
| **Leave** | `list`, `view`, `create` | List leave records, view details, create leaves with interactive type picker |
|
|
25
|
+
|
|
26
|
+
### Highlights
|
|
27
|
+
|
|
28
|
+
- 🎨 **Rich terminal output** — Structured tables, panels, and color-coded statuses
|
|
29
|
+
- 🔐 **Secure token storage** — Config file with restricted permissions (`0600`)
|
|
30
|
+
- 🇹🇷 **Turkish HR aware** — SGK termination codes, DD.MM.YYYY date format support
|
|
31
|
+
- ⚡ **Smart prompts** — Leave type picker, today's date defaults, active employee pre-checks
|
|
32
|
+
|
|
33
|
+
## 📦 Installation
|
|
34
|
+
|
|
35
|
+
### Using pipx (Recommended)
|
|
36
|
+
```bash
|
|
37
|
+
pipx install kolay-cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### From source
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/ezapmar/kolay-cli.git
|
|
43
|
+
cd kolay-cli
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🚀 Quick Start
|
|
48
|
+
|
|
49
|
+
### 1. Authenticate
|
|
50
|
+
Generate an API token from [Kolay Developer Settings](https://app.kolayik.com/settings/developer-settings), then:
|
|
51
|
+
```bash
|
|
52
|
+
kolay auth login
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. List your employees
|
|
56
|
+
```bash
|
|
57
|
+
kolay person list
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. View someone's profile
|
|
61
|
+
```bash
|
|
62
|
+
kolay person view <person-id>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 4. Check leave balances
|
|
66
|
+
```bash
|
|
67
|
+
kolay person leave-status <person-id>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 5. Create a leave request
|
|
71
|
+
```bash
|
|
72
|
+
kolay leave create
|
|
73
|
+
```
|
|
74
|
+
The CLI will interactively show available leave types and let you pick by number.
|
|
75
|
+
|
|
76
|
+
## 📖 Command Reference
|
|
77
|
+
|
|
78
|
+
### Auth
|
|
79
|
+
```bash
|
|
80
|
+
kolay auth login # Save your API token (prompted securely)
|
|
81
|
+
kolay auth status # Check if you're logged in
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Person
|
|
85
|
+
```bash
|
|
86
|
+
kolay person list # List active employees
|
|
87
|
+
kolay person list --status inactive # List inactive employees
|
|
88
|
+
kolay person list --page 2 # Paginate results
|
|
89
|
+
kolay person view <id> # Structured profile view
|
|
90
|
+
kolay person leave-status <id> # Leave balances table
|
|
91
|
+
kolay person terminate <id> # Terminate with SGK reason codes
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Leave
|
|
95
|
+
```bash
|
|
96
|
+
kolay leave list # List approved leaves (current year)
|
|
97
|
+
kolay leave list --status waiting # List pending leaves
|
|
98
|
+
kolay leave list --person-id <id> # Filter by person
|
|
99
|
+
kolay leave list --start-date "2025-01-01 00:00:00" --end-date "2025-12-31 23:59:59"
|
|
100
|
+
kolay leave view <leave-id> # View leave record details
|
|
101
|
+
kolay leave create # Interactive leave creation
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## ⚙️ Configuration
|
|
105
|
+
|
|
106
|
+
The CLI stores its configuration at `~/.config/kolay/config.json` with restricted file permissions.
|
|
107
|
+
|
|
108
|
+
You can also use environment variables (they take precedence):
|
|
109
|
+
```bash
|
|
110
|
+
export KOLAY_API_TOKEN="your-token-here"
|
|
111
|
+
export KOLAY_BASE_URL="https://api.kolayik.com" # optional
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 🛠 Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Clone and setup
|
|
118
|
+
git clone https://github.com/ezapmar/kolay-cli.git
|
|
119
|
+
cd kolay-cli
|
|
120
|
+
python -m venv .venv
|
|
121
|
+
source .venv/bin/activate # or .venv\Scripts\activate on Windows
|
|
122
|
+
pip install -e .
|
|
123
|
+
|
|
124
|
+
# Run
|
|
125
|
+
kolay --help
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 📄 License
|
|
129
|
+
|
|
130
|
+
This project is licensed under the [MIT License](LICENSE) — use it freely, modify it, share it. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kolay-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Command-line interface for Kolay IK (https://apidocs.kolayik.com)"
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "Tunc Aucer"}
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"typer>=0.9.0",
|
|
16
|
+
"requests>=2.31.0",
|
|
17
|
+
"rich>=13.4.2"
|
|
18
|
+
]
|
|
19
|
+
license = {text = "MIT"}
|
|
20
|
+
keywords = ["kolay", "hr", "cli", "kolayik", "human-resources"]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 4 - Beta",
|
|
23
|
+
"Environment :: Console",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Intended Audience :: System Administrators",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.9",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Topic :: Office/Business",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/ezapmar/kolay-cli"
|
|
37
|
+
Repository = "https://github.com/ezapmar/kolay-cli"
|
|
38
|
+
Issues = "https://github.com/ezapmar/kolay-cli/issues"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
kolay = "kolay_cli.cli:app"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools]
|
|
44
|
+
package-dir = {"" = "src"}
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["src"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
|
|
4
|
+
from . import config
|
|
5
|
+
|
|
6
|
+
class APIError(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class KolayClient:
|
|
10
|
+
def __init__(self, token: Optional[str] = None, base_url: Optional[str] = None):
|
|
11
|
+
self.token = token or config.get_api_token()
|
|
12
|
+
self.base_url = (base_url or config.get_base_url()).rstrip("/")
|
|
13
|
+
|
|
14
|
+
if not self.token:
|
|
15
|
+
raise APIError("API token is required. Please set it using 'kolay auth login' or the KOLAY_API_TOKEN environment variable.")
|
|
16
|
+
|
|
17
|
+
self.session = requests.Session()
|
|
18
|
+
self.session.headers.update({
|
|
19
|
+
"Authorization": f"Bearer {self.token}",
|
|
20
|
+
"Accept": "application/json",
|
|
21
|
+
"Content-Type": "application/json"
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
25
|
+
return self._request("GET", endpoint, params=params)
|
|
26
|
+
|
|
27
|
+
def post(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
28
|
+
return self._request("POST", endpoint, json=data)
|
|
29
|
+
|
|
30
|
+
def put(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
31
|
+
return self._request("PUT", endpoint, json=data)
|
|
32
|
+
|
|
33
|
+
def delete(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
34
|
+
return self._request("DELETE", endpoint, params=params)
|
|
35
|
+
|
|
36
|
+
def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
|
|
37
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
response = self.session.request(method, url, **kwargs)
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
# Most Kolay API responses return a json payload
|
|
43
|
+
if response.content:
|
|
44
|
+
return response.json()
|
|
45
|
+
return {}
|
|
46
|
+
except requests.exceptions.HTTPError as e:
|
|
47
|
+
msg = f"API Error: {e.response.status_code}"
|
|
48
|
+
try:
|
|
49
|
+
error_data = e.response.json()
|
|
50
|
+
if "message" in error_data:
|
|
51
|
+
msg += f" - {error_data['message']}"
|
|
52
|
+
except Exception:
|
|
53
|
+
msg += f" - {e.response.text}"
|
|
54
|
+
raise APIError(msg)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from . import __version__
|
|
4
|
+
from .commands import auth, person, leave
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
LOGO = """\b
|
|
8
|
+
[bold cyan] +************ [/bold cyan]
|
|
9
|
+
[bold cyan] +*+ **+ [/bold cyan]
|
|
10
|
+
[bold cyan] +*+ +*+ %% #%% [/bold cyan]
|
|
11
|
+
[bold cyan] +*+ +*+ %% *#* #%%# #%% #%%#*#*+** **+ [/bold cyan]
|
|
12
|
+
[bold cyan] +**+ +*+ %% #%%* %%%##%%% #%% %%%#*%%%% %%# #%% [/bold cyan]
|
|
13
|
+
[bold cyan] **+ +**+ %%%%# %%# %%##%%#%% %%% %%##%%# [/bold cyan]
|
|
14
|
+
[bold cyan] +*+ +*++*+ %%#%%# %%% %%##%%*%% #%% %%%%# [/bold cyan]
|
|
15
|
+
[bold cyan] +*+ ** +*+ %% #%% *%%%%%%# #%% #%%%%%%%% *%%% [/bold cyan]
|
|
16
|
+
[bold cyan] +*+** **+ #%% [/bold cyan]
|
|
17
|
+
[bold cyan] +************ #%%% [/bold cyan]
|
|
18
|
+
|
|
19
|
+
Kolay CLI - A command-line interface for the Kolay IK API.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
app = typer.Typer(
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
rich_markup_mode="rich"
|
|
25
|
+
)
|
|
26
|
+
console = Console()
|
|
27
|
+
|
|
28
|
+
app.add_typer(auth.app, name="auth")
|
|
29
|
+
app.add_typer(person.app, name="person")
|
|
30
|
+
app.add_typer(leave.app, name="leave")
|
|
31
|
+
|
|
32
|
+
@app.callback(invoke_without_command=True, help=LOGO)
|
|
33
|
+
def main(
|
|
34
|
+
version: bool = typer.Option(
|
|
35
|
+
False,
|
|
36
|
+
"--version",
|
|
37
|
+
"-v",
|
|
38
|
+
help="Print the CLI version.",
|
|
39
|
+
is_eager=True,
|
|
40
|
+
)
|
|
41
|
+
):
|
|
42
|
+
if version:
|
|
43
|
+
console.print(f"Kolay CLI version: [bold cyan]{__version__}[/bold cyan]")
|
|
44
|
+
raise typer.Exit()
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
app()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from ..config import set_config_value, get_api_token
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
app = typer.Typer(help="Auth commands for the Kolay CLI.")
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
@app.command()
|
|
9
|
+
def login(token: str = typer.Option(..., prompt="Please enter your Kolay API token", hide_input=True)):
|
|
10
|
+
"""
|
|
11
|
+
Save your Kolay API token for future CLI use.
|
|
12
|
+
"""
|
|
13
|
+
# Simple check if token works (maybe we can hit a profile endpoint later, for now we just save it)
|
|
14
|
+
set_config_value("api_token", token)
|
|
15
|
+
console.print(f"[green]Successfully saved API token to your configuration.[/green]")
|
|
16
|
+
|
|
17
|
+
@app.command()
|
|
18
|
+
def status():
|
|
19
|
+
"""
|
|
20
|
+
Check if you are currently logged in.
|
|
21
|
+
"""
|
|
22
|
+
token = get_api_token()
|
|
23
|
+
if token:
|
|
24
|
+
console.print(f"[green]You are logged in.[/green] (Token: ...{token[-4:] if len(token) > 4 else ''})")
|
|
25
|
+
else:
|
|
26
|
+
console.print("[red]You are NOT logged in.[/red] Please run [bold cyan]kolay auth login[/bold cyan].")
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from ..api import KolayClient, APIError
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Manage leave records in Kolay.")
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command(name="list")
|
|
14
|
+
def list_leaves(
|
|
15
|
+
status: str = typer.Option("approved", help="Filter by status: approved, waiting, rejected, cancelled"),
|
|
16
|
+
start_date: Optional[str] = typer.Option(None, help="Start date filter (YYYY-MM-DD HH:MM:SS). Defaults to Jan 1 of current year."),
|
|
17
|
+
end_date: Optional[str] = typer.Option(None, help="End date filter (YYYY-MM-DD HH:MM:SS). Defaults to Dec 31 of current year."),
|
|
18
|
+
person_id: Optional[str] = typer.Option(None, help="Filter by person ID"),
|
|
19
|
+
limit: int = typer.Option(100, help="Max number of records to return"),
|
|
20
|
+
include_inactive: bool = typer.Option(False, help="Include inactive employees"),
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
List leave records. Filter by status, date range, or person.
|
|
24
|
+
"""
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
client = KolayClient()
|
|
29
|
+
|
|
30
|
+
# API requires startDate and endDate — default to current year
|
|
31
|
+
now = datetime.now()
|
|
32
|
+
if not start_date:
|
|
33
|
+
start_date = f"{now.year}-01-01 00:00:00"
|
|
34
|
+
if not end_date:
|
|
35
|
+
end_date = f"{now.year}-12-31 23:59:59"
|
|
36
|
+
|
|
37
|
+
params: dict = {
|
|
38
|
+
"status": status,
|
|
39
|
+
"startDate": start_date,
|
|
40
|
+
"endDate": end_date,
|
|
41
|
+
"limit": limit,
|
|
42
|
+
}
|
|
43
|
+
if include_inactive:
|
|
44
|
+
params["include_inactive_employees"] = "true"
|
|
45
|
+
if person_id:
|
|
46
|
+
params["personId"] = person_id
|
|
47
|
+
|
|
48
|
+
console.print(f"Fetching leave records ({status}, {start_date[:10]} → {end_date[:10]})...")
|
|
49
|
+
response = client.get("v2/leave/list", params=params)
|
|
50
|
+
|
|
51
|
+
data = response.get("data", [])
|
|
52
|
+
if not data:
|
|
53
|
+
console.print("[yellow]No leave records found.[/yellow]")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
table = Table(title="Leave Records")
|
|
57
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
58
|
+
table.add_column("Person", style="magenta")
|
|
59
|
+
table.add_column("Type", style="yellow")
|
|
60
|
+
table.add_column("Start", style="green")
|
|
61
|
+
table.add_column("End", style="green")
|
|
62
|
+
table.add_column("Status", style="bold")
|
|
63
|
+
|
|
64
|
+
for leave in data:
|
|
65
|
+
lid = str(leave.get("id", "N/A"))
|
|
66
|
+
|
|
67
|
+
person = leave.get("person", {})
|
|
68
|
+
if isinstance(person, dict):
|
|
69
|
+
person_name = person.get("name", "N/A")
|
|
70
|
+
else:
|
|
71
|
+
person_name = str(leave.get("personId", "N/A"))
|
|
72
|
+
|
|
73
|
+
leave_type = leave.get("type", leave.get("leaveType", {}))
|
|
74
|
+
type_name = leave_type.get("name", "N/A") if isinstance(leave_type, dict) else str(leave_type)
|
|
75
|
+
|
|
76
|
+
start = leave.get("startDate", "N/A")
|
|
77
|
+
end = leave.get("endDate", "N/A")
|
|
78
|
+
lstatus = leave.get("status", "N/A")
|
|
79
|
+
|
|
80
|
+
status_color = {
|
|
81
|
+
"approved": "[green]approved[/green]",
|
|
82
|
+
"waiting": "[yellow]waiting[/yellow]",
|
|
83
|
+
"rejected": "[red]rejected[/red]",
|
|
84
|
+
"cancelled": "[dim]cancelled[/dim]",
|
|
85
|
+
}.get(str(lstatus).lower(), lstatus)
|
|
86
|
+
|
|
87
|
+
table.add_row(lid, person_name, type_name, start, end, status_color)
|
|
88
|
+
|
|
89
|
+
console.print(table)
|
|
90
|
+
except APIError as e:
|
|
91
|
+
console.print(f"[bold red]API Error:[/bold red] {e}")
|
|
92
|
+
raise typer.Exit(1)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command(name="view")
|
|
96
|
+
def view_leave(
|
|
97
|
+
leave_id: str = typer.Argument(..., help="ID of the leave record to view"),
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
View the details of a specific leave record.
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
client = KolayClient()
|
|
104
|
+
response = client.get(f"v2/leave/view/{leave_id}")
|
|
105
|
+
|
|
106
|
+
data = response.get("data", {})
|
|
107
|
+
if not data:
|
|
108
|
+
console.print(f"[yellow]Leave record '{leave_id}' not found or permission denied.[/yellow]")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
console.print(f"\n[bold cyan]Leave Record ({leave_id})[/bold cyan]")
|
|
112
|
+
console.print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
113
|
+
except APIError as e:
|
|
114
|
+
console.print(f"[bold red]API Error:[/bold red] {e}")
|
|
115
|
+
raise typer.Exit(1)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@app.command(name="create")
|
|
119
|
+
def create_leave(
|
|
120
|
+
person_id: Optional[str] = typer.Option(None, help="Person ID to create leave for"),
|
|
121
|
+
leave_type_id: Optional[str] = typer.Option(None, help="Leave type ID (auto-listed if omitted)"),
|
|
122
|
+
start_date: Optional[str] = typer.Option(None, help="Start datetime (YYYY-MM-DD HH:MM:SS)"),
|
|
123
|
+
end_date: Optional[str] = typer.Option(None, help="End datetime (YYYY-MM-DD HH:MM:SS)"),
|
|
124
|
+
comment: Optional[str] = typer.Option(None, help="Optional comment"),
|
|
125
|
+
replacement_person_id: Optional[str] = typer.Option(None, help="Optional replacement person ID"),
|
|
126
|
+
):
|
|
127
|
+
"""
|
|
128
|
+
Create a new leave record. Shows available leave types for the person.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
client = KolayClient()
|
|
133
|
+
|
|
134
|
+
# 1. Get person ID
|
|
135
|
+
if not person_id:
|
|
136
|
+
person_id = typer.prompt("Person ID")
|
|
137
|
+
|
|
138
|
+
# 2. Fetch available leave types for this person
|
|
139
|
+
if not leave_type_id:
|
|
140
|
+
console.print(f"\nFetching available leave types for person {person_id}...")
|
|
141
|
+
try:
|
|
142
|
+
status_resp = client.get(f"v2/person/leave-status/{person_id}")
|
|
143
|
+
leave_types = status_resp.get("data", [])
|
|
144
|
+
except APIError:
|
|
145
|
+
leave_types = []
|
|
146
|
+
|
|
147
|
+
if leave_types:
|
|
148
|
+
table = Table(title="Available Leave Types", header_style="bold cyan")
|
|
149
|
+
table.add_column("#", style="bold white", width=3)
|
|
150
|
+
table.add_column("Name")
|
|
151
|
+
table.add_column("Remaining", justify="right", style="green")
|
|
152
|
+
table.add_column("ID", style="dim")
|
|
153
|
+
|
|
154
|
+
for i, lt in enumerate(leave_types, 1):
|
|
155
|
+
name = lt.get("name", "N/A")
|
|
156
|
+
if lt.get("primary"):
|
|
157
|
+
name = f"⭐ {name}"
|
|
158
|
+
unused = lt.get("unused")
|
|
159
|
+
remaining = str(unused) if unused is not None else "∞"
|
|
160
|
+
table.add_row(str(i), name, remaining, lt.get("id", "N/A"))
|
|
161
|
+
|
|
162
|
+
console.print(table)
|
|
163
|
+
|
|
164
|
+
choice = typer.prompt("Pick a leave type (enter # or ID)")
|
|
165
|
+
# If user entered a number, map it
|
|
166
|
+
try:
|
|
167
|
+
idx = int(choice) - 1
|
|
168
|
+
if 0 <= idx < len(leave_types):
|
|
169
|
+
leave_type_id = leave_types[idx].get("id")
|
|
170
|
+
else:
|
|
171
|
+
leave_type_id = choice
|
|
172
|
+
except ValueError:
|
|
173
|
+
leave_type_id = choice
|
|
174
|
+
else:
|
|
175
|
+
leave_type_id = typer.prompt("Leave type ID")
|
|
176
|
+
|
|
177
|
+
# 3. Get dates
|
|
178
|
+
if not start_date:
|
|
179
|
+
start_date = typer.prompt("Start datetime (YYYY-MM-DD HH:MM:SS)")
|
|
180
|
+
if not end_date:
|
|
181
|
+
end_date = typer.prompt("End datetime (YYYY-MM-DD HH:MM:SS)")
|
|
182
|
+
|
|
183
|
+
# 4. Build payload and create
|
|
184
|
+
params: dict = {
|
|
185
|
+
"personId": person_id,
|
|
186
|
+
"leaveTypeId": leave_type_id,
|
|
187
|
+
"startDate": start_date,
|
|
188
|
+
"endDate": end_date,
|
|
189
|
+
}
|
|
190
|
+
if comment:
|
|
191
|
+
params["comment"] = comment
|
|
192
|
+
if replacement_person_id:
|
|
193
|
+
params["replacementPersonId"] = replacement_person_id
|
|
194
|
+
|
|
195
|
+
console.print("Creating leave record...")
|
|
196
|
+
response = client.post("v2/leave/create", data=params)
|
|
197
|
+
|
|
198
|
+
data = response.get("data", {})
|
|
199
|
+
leave_id = data.get("id", "N/A") if isinstance(data, dict) else "N/A"
|
|
200
|
+
console.print(f"[bold green]Leave record created![/bold green] ID: [cyan]{leave_id}[/cyan]")
|
|
201
|
+
except APIError as e:
|
|
202
|
+
console.print(f"[bold red]API Error:[/bold red] {e}")
|
|
203
|
+
raise typer.Exit(1)
|
|
204
|
+
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from ..api import KolayClient, APIError
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Manage person/employee records in Kolay.")
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
@app.command(name="list")
|
|
13
|
+
def list_people(page: int = 1, status: str = "active"):
|
|
14
|
+
"""
|
|
15
|
+
List people from the Kolay API.
|
|
16
|
+
By default, it lists active employees.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
client = KolayClient()
|
|
20
|
+
console.print(f"Fetching people... (Page {page}, Status: {status})")
|
|
21
|
+
# Kolay uses POST for v2/person/list
|
|
22
|
+
payload = {"page": page, "status": status}
|
|
23
|
+
response = client.post("v2/person/list", data=payload)
|
|
24
|
+
|
|
25
|
+
data_resp = response.get("data", {})
|
|
26
|
+
if isinstance(data_resp, dict):
|
|
27
|
+
items = data_resp.get("items", [])
|
|
28
|
+
else:
|
|
29
|
+
items = data_resp
|
|
30
|
+
|
|
31
|
+
if not items:
|
|
32
|
+
console.print("[yellow]No people found.[/yellow]")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
table = Table(title=f"Kolay Personnel ({status})")
|
|
36
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
37
|
+
table.add_column("Name", style="magenta")
|
|
38
|
+
table.add_column("Email", style="green")
|
|
39
|
+
table.add_column("Title", style="yellow")
|
|
40
|
+
|
|
41
|
+
for person in items:
|
|
42
|
+
# Person object depends on Kolay API response structure
|
|
43
|
+
# Usually has id, firstName, lastName, workEmail etc
|
|
44
|
+
# Fallback to get them gracefully
|
|
45
|
+
pid = str(person.get("id", "N/A"))
|
|
46
|
+
fname = person.get("firstName", person.get("first_name", ""))
|
|
47
|
+
lname = person.get("lastName", person.get("last_name", ""))
|
|
48
|
+
name = f"{fname} {lname}".strip() or person.get("name", "N/A")
|
|
49
|
+
|
|
50
|
+
email = person.get("workEmail", person.get("email", "N/A"))
|
|
51
|
+
|
|
52
|
+
# Extract title if present in unitList or another field
|
|
53
|
+
title = person.get("title", "N/A")
|
|
54
|
+
if title == "N/A" and "unitList" in person:
|
|
55
|
+
for unit in person.get("unitList", []):
|
|
56
|
+
if unit.get("default") or unit.get("active"):
|
|
57
|
+
for item in unit.get("items", []):
|
|
58
|
+
if item.get("unitName") == "Unvan":
|
|
59
|
+
title = item.get("unitItemName", title)
|
|
60
|
+
|
|
61
|
+
table.add_row(pid, name, email, title)
|
|
62
|
+
|
|
63
|
+
console.print(table)
|
|
64
|
+
except APIError as e:
|
|
65
|
+
console.print(f"[bold red]API Error:[/bold red] {e}")
|
|
66
|
+
raise typer.Exit(1)
|
|
67
|
+
|
|
68
|
+
@app.command(name="view")
|
|
69
|
+
def view_person(person_id: str = typer.Argument(..., help="ID of the person to view")):
|
|
70
|
+
"""
|
|
71
|
+
View structured details of a specific person.
|
|
72
|
+
"""
|
|
73
|
+
from rich.panel import Panel
|
|
74
|
+
from rich.columns import Columns
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
client = KolayClient()
|
|
78
|
+
response = client.get(f"v2/person/view/{person_id}")
|
|
79
|
+
|
|
80
|
+
data = response.get("data", {})
|
|
81
|
+
person = data.get("person", data) if isinstance(data, dict) else data
|
|
82
|
+
|
|
83
|
+
if not person:
|
|
84
|
+
console.print(f"[yellow]Person {person_id} not found or permission denied.[/yellow]")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# Basic Info
|
|
88
|
+
full_name = f"{person.get('firstName', '')} {person.get('lastName', '')}".strip()
|
|
89
|
+
status_val = person.get('status', 'N/A')
|
|
90
|
+
status_color = "green" if status_val == "active" else "red"
|
|
91
|
+
|
|
92
|
+
console.print(f"\n[bold cyan]Person Profile:[/bold cyan] [bold white]{full_name}[/bold white] [[{status_color}]{status_val}[/{status_color}]]")
|
|
93
|
+
|
|
94
|
+
# 1. Personal Info Table
|
|
95
|
+
personal_table = Table(show_header=False, box=None)
|
|
96
|
+
personal_table.add_row("[bold]ID:[/bold]", person.get("id"))
|
|
97
|
+
personal_table.add_row("[bold]Email:[/bold]", person.get("workEmail") or person.get("email") or "N/A")
|
|
98
|
+
personal_table.add_row("[bold]Phone:[/bold]", person.get("mobilePhone") or "N/A")
|
|
99
|
+
personal_table.add_row("[bold]Birthday:[/bold]", person.get("birthday") or "N/A")
|
|
100
|
+
personal_table.add_row("[bold]Gender:[/bold]", person.get("gender") or "N/A")
|
|
101
|
+
personal_table.add_row("[bold]ID Number:[/bold]", person.get("idNumber") or "N/A")
|
|
102
|
+
|
|
103
|
+
# 2. Employment Table
|
|
104
|
+
employment_table = Table(show_header=False, box=None)
|
|
105
|
+
employment_table.add_row("[bold]Start Date:[/bold]", person.get("employmentStartDate") or "N/A")
|
|
106
|
+
|
|
107
|
+
# Extract unit info
|
|
108
|
+
units = person.get("unitList", [])
|
|
109
|
+
active_unit = next((u for u in units if u.get("active") or u.get("default")), {})
|
|
110
|
+
if active_unit:
|
|
111
|
+
employment_table.add_row("[bold]Type:[/bold]", active_unit.get("employmentType", "N/A"))
|
|
112
|
+
for item in active_unit.get("items", []):
|
|
113
|
+
employment_table.add_row(f"[bold]{item.get('unitName')}:[/bold]", item.get("unitItemName"))
|
|
114
|
+
|
|
115
|
+
console.print(Columns([
|
|
116
|
+
Panel(personal_table, title="Personal Information", border_style="blue", expand=True),
|
|
117
|
+
Panel(employment_table, title="Employment", border_style="magenta", expand=True)
|
|
118
|
+
]))
|
|
119
|
+
|
|
120
|
+
# 3. Custom Fields (Data List)
|
|
121
|
+
custom_data = person.get("dataList", [])
|
|
122
|
+
# Filter only non-empty values
|
|
123
|
+
non_empty_custom = [f for f in custom_data if f.get("value")]
|
|
124
|
+
|
|
125
|
+
if non_empty_custom:
|
|
126
|
+
custom_table = Table(title="Custom Fields", box=None, show_header=True, header_style="bold cyan")
|
|
127
|
+
custom_table.add_column("Field")
|
|
128
|
+
custom_table.add_column("Value")
|
|
129
|
+
for field in non_empty_custom:
|
|
130
|
+
custom_table.add_row(field.get("fieldToken", "N/A"), str(field.get("value")))
|
|
131
|
+
|
|
132
|
+
console.print(Panel(custom_table, border_style="dim"))
|
|
133
|
+
|
|
134
|
+
except APIError as e:
|
|
135
|
+
console.print(f"[bold red]API Error:[/bold red] {e}")
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
|
|
138
|
+
@app.command(name="leave-status")
|
|
139
|
+
def view_leave_status(person_id: str = typer.Argument(..., help="ID of the person to view leave status for")):
|
|
140
|
+
"""
|
|
141
|
+
Get structured leave status and balances for a specific person.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
client = KolayClient()
|
|
145
|
+
response = client.get(f"v2/person/leave-status/{person_id}")
|
|
146
|
+
|
|
147
|
+
data = response.get("data", [])
|
|
148
|
+
if not data:
|
|
149
|
+
console.print(f"[yellow]No leave status data for person {person_id}.[/yellow]")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
console.print(f"\n[bold cyan]Leave Status & Balances:[/bold cyan] (ID: {person_id})")
|
|
153
|
+
|
|
154
|
+
table = Table(title=None, box=None, header_style="bold cyan")
|
|
155
|
+
table.add_column("Leave Type")
|
|
156
|
+
table.add_column("Paid?", justify="center")
|
|
157
|
+
table.add_column("Total", justify="right")
|
|
158
|
+
table.add_column("Used", justify="right")
|
|
159
|
+
table.add_column("Upcoming", justify="right")
|
|
160
|
+
table.add_column("Remaining", justify="right", style="bold green")
|
|
161
|
+
table.add_column("Next Accrual", justify="center")
|
|
162
|
+
|
|
163
|
+
def fmt(val):
|
|
164
|
+
if val is None: return "-"
|
|
165
|
+
try:
|
|
166
|
+
return str(int(val)) if float(val) == int(val) else str(val)
|
|
167
|
+
except (ValueError, TypeError):
|
|
168
|
+
return str(val)
|
|
169
|
+
|
|
170
|
+
for leave in data:
|
|
171
|
+
name = leave.get("name", "N/A")
|
|
172
|
+
if leave.get("primary"):
|
|
173
|
+
name = f"[bold white]⭐ {name}[/bold white]"
|
|
174
|
+
|
|
175
|
+
is_paid = "[green]Yes[/green]" if leave.get("isPaid") else "[red]No[/red]"
|
|
176
|
+
|
|
177
|
+
used = leave.get("used", 0)
|
|
178
|
+
upcoming = leave.get("totalUpcoming", 0)
|
|
179
|
+
total = leave.get("total", leave.get("dayLimit", "∞"))
|
|
180
|
+
unused = leave.get("unused")
|
|
181
|
+
|
|
182
|
+
# If unused is missing, try to calculate or show -
|
|
183
|
+
remaining = fmt(unused) if unused is not None else "∞"
|
|
184
|
+
|
|
185
|
+
next_date = leave.get("nextAccrualDate", "-")
|
|
186
|
+
|
|
187
|
+
table.add_row(
|
|
188
|
+
name,
|
|
189
|
+
is_paid,
|
|
190
|
+
fmt(total),
|
|
191
|
+
fmt(used),
|
|
192
|
+
fmt(upcoming),
|
|
193
|
+
remaining,
|
|
194
|
+
next_date
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
console.print(table)
|
|
198
|
+
console.print("\n[dim]Note: ⭐ indicates Primary Leave Type.[/dim]")
|
|
199
|
+
|
|
200
|
+
except APIError as e:
|
|
201
|
+
console.print(f"[bold red]API Error:[/bold red] {e}")
|
|
202
|
+
raise typer.Exit(1)
|
|
203
|
+
|
|
204
|
+
@app.command(name="terminate")
|
|
205
|
+
def terminate_person(
|
|
206
|
+
person_id: str = typer.Argument(..., help="ID of the person to terminate"),
|
|
207
|
+
termination_date: Optional[str] = typer.Option(
|
|
208
|
+
None,
|
|
209
|
+
help="Termination date (YYYY-MM-DD)",
|
|
210
|
+
show_default=False
|
|
211
|
+
),
|
|
212
|
+
reason: Optional[str] = typer.Option(None, help="Termination reason")
|
|
213
|
+
):
|
|
214
|
+
"""
|
|
215
|
+
Terminate employment of a person.
|
|
216
|
+
"""
|
|
217
|
+
from datetime import datetime
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
client = KolayClient()
|
|
221
|
+
# Pre-check if person exists and is active
|
|
222
|
+
console.print(f"Checking status for person {person_id}...")
|
|
223
|
+
resp = client.get(f"v2/person/view/{person_id}")
|
|
224
|
+
person_data = resp.get("data", {})
|
|
225
|
+
person = person_data.get("person", person_data) if isinstance(person_data, dict) else person_data
|
|
226
|
+
|
|
227
|
+
if not person:
|
|
228
|
+
console.print(f"[red]Error: Person {person_id} not found.[/red]")
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
status = person.get("status")
|
|
232
|
+
if status != "active":
|
|
233
|
+
name = f"{person.get('firstName', '')} {person.get('lastName', '')}".strip()
|
|
234
|
+
console.print(f"[bold red]Stop![/bold red] {name or person_id} is currently [bold yellow]{status}[/bold yellow].")
|
|
235
|
+
console.print("Only active employees can be terminated.")
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
except APIError as e:
|
|
239
|
+
console.print(f"[bold red]Error checking person status:[/bold red] {e}")
|
|
240
|
+
raise typer.Exit(1)
|
|
241
|
+
|
|
242
|
+
# Get today's date as default
|
|
243
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
244
|
+
|
|
245
|
+
if termination_date is None:
|
|
246
|
+
termination_date = typer.prompt("Termination date", default=today)
|
|
247
|
+
|
|
248
|
+
# Try to handle common formats like DD.MM.YYYY
|
|
249
|
+
if "." in termination_date:
|
|
250
|
+
try:
|
|
251
|
+
parts = termination_date.split(".")
|
|
252
|
+
if len(parts) == 3:
|
|
253
|
+
# Convert DD.MM.YYYY to YYYY-MM-DD
|
|
254
|
+
d, m, y = parts
|
|
255
|
+
termination_date = f"{y}-{m.zfill(2)}-{d.zfill(2)}"
|
|
256
|
+
except:
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
REASON_CODES = {
|
|
260
|
+
"01": "Deneme süreli iş sözleşmesinin işverence feshi",
|
|
261
|
+
"02": "Deneme süreli iş sözleşmesinin işçi tarafından feshi",
|
|
262
|
+
"03": "Belirsiz süreli iş sözleşmesinin işçi tarafından feshi (İstifa)",
|
|
263
|
+
"04": "Belirsiz süreli iş sözleşmesinin işveren tarafından haklı sebep bildirilmeden feshi",
|
|
264
|
+
"05": "Belirli süreli iş sözleşmesinin sona ermesi",
|
|
265
|
+
"08": "Emeklilik (Yaşlılık) veya Toptan Ödeme Nedeniyle",
|
|
266
|
+
"09": "Maluliyet Nedeniyle",
|
|
267
|
+
"10": "Ölüm",
|
|
268
|
+
"11": "İş Kazası Sonucu Ölüm",
|
|
269
|
+
"12": "Askerlik",
|
|
270
|
+
"13": "Kadın İşçinin Evlenmesi",
|
|
271
|
+
"14": "Emeklilik İçin Yaş Dışında Diğer Şartların Tamamlanması",
|
|
272
|
+
"15": "Toplu İşçi Çıkarma",
|
|
273
|
+
"17": "İşyerinin Kapanması",
|
|
274
|
+
"18": "İşin Sona Ermesi",
|
|
275
|
+
"19": "Mevsimlik İşin Sona Ermesi",
|
|
276
|
+
"20": "Kampanya İşinin Sona Ermesi",
|
|
277
|
+
"21": "Statü Değişikliği",
|
|
278
|
+
"22": "Diğer Nedenler",
|
|
279
|
+
"23": "İşçi Kuruluşunda Görev Alınması",
|
|
280
|
+
"24": "Sözleşme Başlamadan Fesih",
|
|
281
|
+
"25": "İşçi Tarafından Zorunlu Nedenle Fesih",
|
|
282
|
+
"26": "İşçi Tarafından İşverenin Ahlak ve İyi Niyet Kurallarına Aykırı Davranışı Nedeniyle Fesih",
|
|
283
|
+
"27": "İşveren Tarafından İşçinin Ahlak ve İyi Niyet Kurallarına Aykırı Davranışı Nedeniyle Fesih",
|
|
284
|
+
"28": "Sağlık Nedeniyle İşçi Tarafından Fesih",
|
|
285
|
+
"29": "Sağlık Nedeniyle İşveren Tarafından Fesih",
|
|
286
|
+
"30": "Vize Süresinin Sona Ermesi",
|
|
287
|
+
"31": "Borçlar Kanunu, Sendikalar Kanunu, Grev ve Lokavt Kanunu Kapsamında Fesih",
|
|
288
|
+
"32": "4046 Sayılı Kanun Kapsamında Özelleştirme Nedeniyle Fesih",
|
|
289
|
+
"33": "Gazeteci Tarafından Sözleşmenin Feshi",
|
|
290
|
+
"34": "İşyerinin Devri, İşin veya İşyerinin Niteliğinin Değişmesi Nedeniyle Fesih",
|
|
291
|
+
"36": "KHK ile Kamu Görevinden Çıkarılma",
|
|
292
|
+
"37": "KHK ile Kamu Görevinden İade Edilme",
|
|
293
|
+
"40": "696 Sayılı KHK ile Kamu İşçiliğine Geçiş",
|
|
294
|
+
"41": "696 Sayılı KHK ile Kamu İşçiliğine Geçişte Başarısızlık",
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if reason is None:
|
|
298
|
+
console.print("\n[bold cyan]Turkish Termination (SGK) Reason Codes:[/bold cyan]")
|
|
299
|
+
codes_table = Table(box=None, header_style="bold magenta", padding=(0, 2))
|
|
300
|
+
codes_table.add_column("Code", width=4)
|
|
301
|
+
codes_table.add_column("Description")
|
|
302
|
+
|
|
303
|
+
# Show top common codes first or just a selection
|
|
304
|
+
for c in ["01", "03", "04", "05", "12", "13", "17", "22", "27"]:
|
|
305
|
+
codes_table.add_row(c, REASON_CODES[c])
|
|
306
|
+
codes_table.add_row("..", "Enter 'all' to see all codes")
|
|
307
|
+
|
|
308
|
+
console.print(codes_table)
|
|
309
|
+
|
|
310
|
+
reason_code = typer.prompt("Reason Code", default="03")
|
|
311
|
+
if reason_code.lower() == "all":
|
|
312
|
+
full_table = Table(title="All Reason Codes", header_style="bold magenta")
|
|
313
|
+
full_table.add_column("Code")
|
|
314
|
+
full_table.add_column("Description")
|
|
315
|
+
for c, desc in sorted(REASON_CODES.items()):
|
|
316
|
+
full_table.add_row(c, desc)
|
|
317
|
+
console.print(full_table)
|
|
318
|
+
reason_code = typer.prompt("Reason Code", default="03")
|
|
319
|
+
|
|
320
|
+
details = typer.prompt("Details", default=REASON_CODES.get(reason_code, ""))
|
|
321
|
+
else:
|
|
322
|
+
reason_code = "03"
|
|
323
|
+
details = reason
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
client = KolayClient()
|
|
327
|
+
payload = {
|
|
328
|
+
"personId": person_id,
|
|
329
|
+
"date": termination_date,
|
|
330
|
+
"reasonCode": reason_code,
|
|
331
|
+
"details": details
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
console.print(f"Terminating person {person_id} with date [bold cyan]{termination_date}[/bold cyan] and reason code [bold magenta]{reason_code}[/bold magenta]...")
|
|
335
|
+
client.post("v2/person/terminate", data=payload)
|
|
336
|
+
console.print("[bold green]Success![/bold green] Operation completed.")
|
|
337
|
+
|
|
338
|
+
except APIError as e:
|
|
339
|
+
console.print(f"[bold red]API Error:[/bold red] {e}")
|
|
340
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
CONFIG_DIR = Path.home() / ".config" / "kolay"
|
|
7
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
8
|
+
|
|
9
|
+
def get_config_value(key: str) -> Optional[str]:
|
|
10
|
+
"""Get a value from the config file or environment variables."""
|
|
11
|
+
# Environment variables have precedence
|
|
12
|
+
env_val = os.getenv(f"KOLAY_{key.upper()}")
|
|
13
|
+
if env_val:
|
|
14
|
+
return env_val
|
|
15
|
+
|
|
16
|
+
if not CONFIG_FILE.exists():
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
with open(CONFIG_FILE, "r") as f:
|
|
21
|
+
data = json.load(f)
|
|
22
|
+
return data.get(key)
|
|
23
|
+
except Exception:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
def set_config_value(key: str, value: str):
|
|
27
|
+
"""Set a value in the config file and ensure restricted permissions."""
|
|
28
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
data = {}
|
|
31
|
+
if CONFIG_FILE.exists():
|
|
32
|
+
try:
|
|
33
|
+
with open(CONFIG_FILE, "r") as f:
|
|
34
|
+
data = json.load(f)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
data[key] = value
|
|
39
|
+
|
|
40
|
+
# Create or overwrite file with restricted permissions
|
|
41
|
+
# We use a temp file or just write it then chmod
|
|
42
|
+
with open(CONFIG_FILE, "w") as f:
|
|
43
|
+
json.dump(data, f, indent=4)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
os.chmod(CONFIG_FILE, 0o600)
|
|
47
|
+
except Exception:
|
|
48
|
+
pass # Fallback for environments where chmod might fail
|
|
49
|
+
|
|
50
|
+
def get_api_token() -> Optional[str]:
|
|
51
|
+
return get_config_value("api_token")
|
|
52
|
+
|
|
53
|
+
def get_base_url() -> str:
|
|
54
|
+
return get_config_value("base_url") or "https://api.kolayik.com"
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kolay-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line interface for Kolay IK (https://apidocs.kolayik.com)
|
|
5
|
+
Author: Tunc Aucer
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ezapmar/kolay-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/ezapmar/kolay-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/ezapmar/kolay-cli/issues
|
|
10
|
+
Keywords: kolay,hr,cli,kolayik,human-resources
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Office/Business
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: typer>=0.9.0
|
|
26
|
+
Requires-Dist: requests>=2.31.0
|
|
27
|
+
Requires-Dist: rich>=13.4.2
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Kolay CLI
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
+************
|
|
34
|
+
+*+ **+
|
|
35
|
+
+*+ +*+ %% #%%
|
|
36
|
+
+*+ +*+ %% *#* #%%# #%% #%%#*#*+** **+
|
|
37
|
+
+**+ +*+ %% #%%* %%%##%%% #%% %%%#*%%%% %%# #%%
|
|
38
|
+
**+ +**+ %%%%# %%# %%##%%#%% %%% %%##%%#
|
|
39
|
+
+*+ +*++*+ %%#%%# %%% %%##%%*%% #%% %%%%#
|
|
40
|
+
+*+ ** +*+ %% #%% *%%%%%%# #%% #%%%%%%%% *%%%
|
|
41
|
+
+*+** **+ #%%
|
|
42
|
+
+************ #%%%
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A powerful, user-friendly command-line interface for the [Kolay IK API](https://apidocs.kolayik.com). Manage your HR operations directly from the terminal.
|
|
46
|
+
|
|
47
|
+
## ✨ Features
|
|
48
|
+
|
|
49
|
+
| Module | Commands | Description |
|
|
50
|
+
|--------|----------|-------------|
|
|
51
|
+
| **Auth** | `login`, `status` | Securely store and manage your API token |
|
|
52
|
+
| **Person** | `list`, `view`, `leave-status`, `terminate` | List employees, view structured profiles, check leave balances, terminate with SGK codes |
|
|
53
|
+
| **Leave** | `list`, `view`, `create` | List leave records, view details, create leaves with interactive type picker |
|
|
54
|
+
|
|
55
|
+
### Highlights
|
|
56
|
+
|
|
57
|
+
- 🎨 **Rich terminal output** — Structured tables, panels, and color-coded statuses
|
|
58
|
+
- 🔐 **Secure token storage** — Config file with restricted permissions (`0600`)
|
|
59
|
+
- 🇹🇷 **Turkish HR aware** — SGK termination codes, DD.MM.YYYY date format support
|
|
60
|
+
- ⚡ **Smart prompts** — Leave type picker, today's date defaults, active employee pre-checks
|
|
61
|
+
|
|
62
|
+
## 📦 Installation
|
|
63
|
+
|
|
64
|
+
### Using pipx (Recommended)
|
|
65
|
+
```bash
|
|
66
|
+
pipx install kolay-cli
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### From source
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/ezapmar/kolay-cli.git
|
|
72
|
+
cd kolay-cli
|
|
73
|
+
pip install -e .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 🚀 Quick Start
|
|
77
|
+
|
|
78
|
+
### 1. Authenticate
|
|
79
|
+
Generate an API token from [Kolay Developer Settings](https://app.kolayik.com/settings/developer-settings), then:
|
|
80
|
+
```bash
|
|
81
|
+
kolay auth login
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 2. List your employees
|
|
85
|
+
```bash
|
|
86
|
+
kolay person list
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. View someone's profile
|
|
90
|
+
```bash
|
|
91
|
+
kolay person view <person-id>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. Check leave balances
|
|
95
|
+
```bash
|
|
96
|
+
kolay person leave-status <person-id>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 5. Create a leave request
|
|
100
|
+
```bash
|
|
101
|
+
kolay leave create
|
|
102
|
+
```
|
|
103
|
+
The CLI will interactively show available leave types and let you pick by number.
|
|
104
|
+
|
|
105
|
+
## 📖 Command Reference
|
|
106
|
+
|
|
107
|
+
### Auth
|
|
108
|
+
```bash
|
|
109
|
+
kolay auth login # Save your API token (prompted securely)
|
|
110
|
+
kolay auth status # Check if you're logged in
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Person
|
|
114
|
+
```bash
|
|
115
|
+
kolay person list # List active employees
|
|
116
|
+
kolay person list --status inactive # List inactive employees
|
|
117
|
+
kolay person list --page 2 # Paginate results
|
|
118
|
+
kolay person view <id> # Structured profile view
|
|
119
|
+
kolay person leave-status <id> # Leave balances table
|
|
120
|
+
kolay person terminate <id> # Terminate with SGK reason codes
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Leave
|
|
124
|
+
```bash
|
|
125
|
+
kolay leave list # List approved leaves (current year)
|
|
126
|
+
kolay leave list --status waiting # List pending leaves
|
|
127
|
+
kolay leave list --person-id <id> # Filter by person
|
|
128
|
+
kolay leave list --start-date "2025-01-01 00:00:00" --end-date "2025-12-31 23:59:59"
|
|
129
|
+
kolay leave view <leave-id> # View leave record details
|
|
130
|
+
kolay leave create # Interactive leave creation
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## ⚙️ Configuration
|
|
134
|
+
|
|
135
|
+
The CLI stores its configuration at `~/.config/kolay/config.json` with restricted file permissions.
|
|
136
|
+
|
|
137
|
+
You can also use environment variables (they take precedence):
|
|
138
|
+
```bash
|
|
139
|
+
export KOLAY_API_TOKEN="your-token-here"
|
|
140
|
+
export KOLAY_BASE_URL="https://api.kolayik.com" # optional
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 🛠 Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Clone and setup
|
|
147
|
+
git clone https://github.com/ezapmar/kolay-cli.git
|
|
148
|
+
cd kolay-cli
|
|
149
|
+
python -m venv .venv
|
|
150
|
+
source .venv/bin/activate # or .venv\Scripts\activate on Windows
|
|
151
|
+
pip install -e .
|
|
152
|
+
|
|
153
|
+
# Run
|
|
154
|
+
kolay --help
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 📄 License
|
|
158
|
+
|
|
159
|
+
This project is licensed under the [MIT License](LICENSE) — use it freely, modify it, share it. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/kolay_cli/__init__.py
|
|
5
|
+
src/kolay_cli/api.py
|
|
6
|
+
src/kolay_cli/cli.py
|
|
7
|
+
src/kolay_cli/config.py
|
|
8
|
+
src/kolay_cli.egg-info/PKG-INFO
|
|
9
|
+
src/kolay_cli.egg-info/SOURCES.txt
|
|
10
|
+
src/kolay_cli.egg-info/dependency_links.txt
|
|
11
|
+
src/kolay_cli.egg-info/entry_points.txt
|
|
12
|
+
src/kolay_cli.egg-info/requires.txt
|
|
13
|
+
src/kolay_cli.egg-info/top_level.txt
|
|
14
|
+
src/kolay_cli/commands/__init__.py
|
|
15
|
+
src/kolay_cli/commands/auth.py
|
|
16
|
+
src/kolay_cli/commands/leave.py
|
|
17
|
+
src/kolay_cli/commands/person.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kolay_cli
|