Project Overview
DayOfff is a Laravel-based tool that centralizes project budgeting, KPI tracking and team notifications. It streamlines financial planning and performance monitoring across multiple platforms, reducing manual data consolidation and improving visibility.
Problems Solved
- Lack of real-time budget vs. spend insights
- Manual KPI calculation and reporting
- Fragmented data across Bitrix24, VK, Yandex, Afina
- Delayed or inconsistent team notifications
Key Features
- Project Budgeting & KPI Tracking
Define budgets, allocate resources, track actual spend and KPIs in real time - Integrations
• Bitrix24 – pull deals, tasks and time logs
• VK – import ad spend and performance data
• Yandex – sync campaign metrics and costs
• Afina – fetch financial records and reports - Telegram Notifications
Send budget alerts, KPI milestones and daily summaries to team chats
When to Use DayOfff
- You manage multiple projects with changing budgets
- You need consolidated KPI dashboards across sales, marketing and finance
- You rely on Bitrix24, VK, Yandex or Afina for data sources
- You want instant budget and KPI updates via Telegram
Use DayOfff to gain unified financial insights, automate performance reporting and keep your team informed with minimal setup.
Getting Started
Follow these steps to run a local developer instance of RomAlx/dayofff.
1. Prerequisites
- Git
- PHP 8.1+ with extensions: BCMath, Ctype, JSON, Mbstring, OpenSSL, PDO, Tokenizer, XML
- Composer 2.x
- Node.js 16+ and npm 8+
- MySQL 5.7+ or PostgreSQL 12+
2. Clone Repository
git clone https://github.com/RomAlx/dayofff.git
cd dayofff
3. Environment Configuration
- Copy the example environment file and generate an application key:
cp .env.example .env php artisan key:generate
- Open
.env
and set your database and mail credentials:DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=dayofff_dev DB_USERNAME=root DB_PASSWORD=secret MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null
- (Optional) Adjust
APP_ENV
,APP_DEBUG
andAPP_URL
as needed.
4. Install Dependencies
composer install --no-interaction --optimize-autoloader
npm install
5. Database Migration & Seeding
Run migrations and seeders to create tables and sample data:
php artisan migrate
php artisan db:seed
6. Compile Front-end Assets
Start Vite in development mode for hot-reload:
npm run dev
Common scripts available in package.json
:
npm run dev
– start Vite dev servernpm run build
– build production assetsnpm run preview
– preview production build
7. Serve the Laravel Application
Launch the application on http://127.0.0.1:8000
:
php artisan serve
8. Scheduler & Maintenance
- Scheduler: In a separate terminal, trigger scheduled tasks every minute:
php artisan schedule:work
- Optimizations: Use
clear.sh
to cache and clean:
This script caches config, routes, views, optimizes autoloading and cleans sessions../clear.sh
Next Steps
- Visit
/login
to authenticate via JWT. - Explore API routes in
routes/api.php
. - Consult
config/app.php
for environment-specific settings.
System Architecture
This section outlines how the frontend SPA, backend service layers, data flow, and scheduled jobs integrate in the RomAlx/dayofff application.
1. Frontend Single-Page Application
Entry Point & Assets
- Blade Template:
resources/views/app.blade.php
serves the SPA container and injects Vite-built assets. - Vite Config:
vite.config.js
defines inputs (resources/js/app.js
,resources/css/app.css
), Vue plugin, and@
alias →resources/js
.
Client-Side Routing
- Catch-All Route: In
routes/web.php
, all non-/api/*
routes return the Blade view, enabling Vue Router’s history mode.
Mounting Vue
// resources/js/app.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
2. Backend Service Layers
API Routes
// routes/api.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\UserController;
Route::middleware('auth:sanctum')->group(function() {
Route::get('users', [UserController::class, 'index']);
Route::post('users', [UserController::class, 'store']);
});
Controllers
- Validate requests via Form Requests
- Delegate logic to services
- Return JSON resources
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\ListUsersRequest;
use App\Services\UserService;
use App\Http\Resources\UserResource;
class UserController extends Controller
{
public function __construct(private UserService $users) {}
public function index(ListUsersRequest $request)
{
$data = $this->users->getPaginated($request->validated());
return UserResource::collection($data);
}
}
Services
- Encapsulate business rules
- Coordinate multiple repositories or external APIs
namespace App\Services;
use App\Repositories\UserRepository;
class UserService
{
public function __construct(private UserRepository $repo) {}
public function getPaginated(array $filters)
{
return $this->repo->paginateBy($filters, perPage: 15);
}
}
Repositories
- Abstract data access
- Use Eloquent or query builder
namespace App\Repositories;
use App\Models\User;
class UserRepository
{
public function paginateBy(array $filters, int $perPage)
{
return User::filter($filters)->paginate($perPage);
}
}
Models & Scopes
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $fillable = ['name', 'email', 'role'];
public function scopeFilter($query, array $filters)
{
if (isset($filters['role'])) {
$query->where('role', $filters['role']);
}
return $query;
}
}
3. Data Flow
- HTTP Request
Browser/API client → Laravel middleware → route match. - Controller
Validates via Form Request → calls Service. - Service
Applies business logic → invokes Repository. - Repository & Model
Builds query → Eloquent executes → returns results. - Response
Controller wraps with Resource → JSON to client.
4. Scheduled Background Jobs
cron.sh & Crontab
#!/bin/bash
PHP=/opt/php83/bin/php
cd /home/c/co92911/dayofff/public_html
mkdir -p storage/logs
echo "Cron run: $(date)" >> storage/logs/cron.log
$PHP artisan schedule:run >> storage/logs/cron.log 2>&1
Crontab entry (crontab -e
):
* * * * * /home/c/co92911/dayofff/public_html/cron.sh
Defining Scheduled Tasks
All schedules live in routes/console.php
:
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate-daily')
->dailyAt('03:00')
->name('daily_report');
Schedule::call(fn() => app('App\Utilities\CommandRunner')
->runSequentially([
'yadir:update-login-month-metrics --only-active',
'vk:sync-month-login-stats --only-active'
])
)->dailyAt('02:00')->name('monthly_stats_update');
Creating a New Task
- Generate an Artisan command:
php artisan make:command Report:GenerateDaily
- Register it in
routes/console.php
with scheduling methods. - Deploy — the next
cron.sh
run picks it up.
With this architecture, the SPA, REST API, business logic layers, and scheduled tasks operate independently yet cohesively.
Next Steps: Specify Subsection and Context
Please choose one Core Concepts subsection to document and provide its related file summaries or code context. For example:
CommissionCalculator.calculateProjectCommission
• File path (e.g.,src/Utilities/CommissionCalculator.php
)
• Brief summary of internal helper methods or data structures it usesRoleEnum: Role Values and Descriptions
• File path (e.g.,src/Enums/RoleEnum.php
)
• Any existing usages or extension points in the codebase
Once you share the subsection and its file details, I’ll generate focused, practical documentation with examples.
API Reference
This section details REST endpoints grouped by functionality. All endpoints use JSON and require the Authorization: Bearer {token}
header unless noted otherwise. Versioning prefix: /api/v1
.
Authentication
Login
POST /api/v1/auth/login
Request:
POST /api/v1/auth/login HTTP/1.1
Content-Type: application/json
{
"email": "user@example.com",
"password": "secret123"
}
Response (200):
{
"access_token": "eyJ0eXAiOiJKV1Qi...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": 42,
"name": "Jane Doe",
"email": "user@example.com"
}
}
Logout
POST /api/v1/auth/logout
Headers: Authorization: Bearer {token}
Response (204): No content
Refresh Token
POST /api/v1/auth/refresh
Headers: Authorization: Bearer {token}
Response (200):
{
"access_token": "new.jwt.token",
"token_type": "Bearer",
"expires_in": 3600
}
User Management
List Users
GET /api/v1/users
Headers: Authorization: Bearer {token}
Response (200):
[
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
{ "id": 2, "name": "Bob", "email": "bob@example.com" }
]
Get User
GET /api/v1/users/{id}
Response (200):
{ "id": 1, "name": "Alice", "email": "alice@example.com", "roles": ["admin"] }
Create User
POST /api/v1/users
Request:
{
"name": "Charlie",
"email": "charlie@example.com",
"password": "passw0rd"
}
Response (201):
{ "id": 3, "name": "Charlie", "email": "charlie@example.com" }
Update User
PUT /api/v1/users/{id}
Request:
{ "name": "Alice Smith", "roles": ["editor"] }
Response (200):
{ "id": 1, "name": "Alice Smith", "email": "alice@example.com", "roles": ["editor"] }
Delete User
DELETE /api/v1/users/{id}
Response (204): No content
Project Handling
List Projects
GET /api/v1/projects
Response (200):
[
{ "id": 101, "title": "Campaign A", "owner_id": 1 },
{ "id": 102, "title": "Campaign B", "owner_id": 2 }
]
Get Project
GET /api/v1/projects/{id}
Response (200):
{
"id": 101,
"title": "Campaign A",
"owner_id": 1,
"settings": { "budget": 5000, "start_date": "2025-08-01" }
}
Create Project
POST /api/v1/projects
Request:
{
"title": "New Campaign",
"owner_id": 1,
"settings": { "budget": 10000, "start_date": "2025-09-01" }
}
Response (201):
{ "id": 103, "title": "New Campaign", "owner_id": 1 }
Update Project
PUT /api/v1/projects/{id}
Request:
{ "settings": { "budget": 15000 } }
Response (200):
{
"id": 101,
"title": "Campaign A",
"settings": { "budget": 15000, "start_date": "2025-08-01" }
}
Delete Project
DELETE /api/v1/projects/{id}
Response (204): No content
Permissions
List Permissions
GET /api/v1/permissions
Response (200):
[
{ "name": "view_users" },
{ "name": "edit_projects" }
]
Assign Permission
POST /api/v1/permissions
Request:
{ "user_id": 1, "permission": "edit_projects" }
Response (200):
{ "user_id": 1, "permission": "edit_projects", "granted": true }
Integrations
Bitrix24
Get Settings
GET /api/v1/integrations/bitrix24/settings
Response (200):
{ "webhook_url": "https://example.bitrix24.com/rest/1/abcdef/", "active": true }
OAuth Callback
POST /api/v1/integrations/bitrix24/callback
Request receives code
and state
from Bitrix24 OAuth flow.
Response (200):
{ "message": "Bitrix24 connected successfully" }
Yandex.Direct
Get Ads
GET /api/v1/integrations/yandex-direct/ads
Response (200):
[
{ "campaign_id": 2001, "name": "Search Campaign", "status": "active" }
]
Refresh Token
POST /api/v1/integrations/yandex-direct/token
Request:
{ "refresh_token": "ya29.a0Ae..." }
Response (200):
{ "access_token": "yandex.jwt.token", "expires_in": 3600 }
VK Ads
Get Campaigns
GET /api/v1/integrations/vk-ads/campaigns
Response (200):
[
{ "campaign_id": 3001, "name": "VK Promo", "status": "running" }
]
Generate Token
POST /api/v1/integrations/vk-ads/token
Request:
{ "app_id": "1234567", "secret": "vksecret" }
Response (200):
{ "access_token": "vk.jwt.token", "expires_in": 7200 }
Analytics
Campaign Metrics
GET /api/v1/analytics/campaigns
Query params: project_id
, date_from
, date_to
Response (200):
{
"project_id": 101,
"impressions": 15000,
"clicks": 1200,
"cost": 750.50
}
User Activity
GET /api/v1/analytics/users
Query params: user_id
, period
Response (200):
{ "user_id": 1, "actions": 342, "last_login": "2025-08-03T14:22:00Z" }
Internal Statistics
Usage Overview
GET /api/v1/internal/stats/usage
Headers: Admin only
Response (200):
{
"total_users": 125,
"active_projects": 34,
"api_requests_last_hour": 4567
}
External Services
Check Service Status
GET /api/v1/external/{service}/status
Path params: service
(e.g., bitrix24
, yandex-direct
, vk-ads
)
Response (200):
{ "service": "bitrix24", "status": "online", "last_checked": "2025-08-04T10:00:00Z" }
Usage Example (Laravel HTTP Client)
use Illuminate\Support\Facades\Http;
$response = Http::withToken($token)
->get('https://api.example.com/api/v1/projects', ['owner_id' => 1]);
if ($response->successful()) {
$projects = $response->json();
foreach ($projects as $project) {
echo $project['title']."\n";
}
}
## CLI Commands & Tasks
This section catalogs all Artisan commands in the RomAlx/dayofff project, shows usage patterns, and demonstrates how they run on schedules via `routes/console.php`.
### Available Artisan Commands
#### sync:users
Synchronize user data from external systems into the local database.
Usage:
```bash
php artisan sync:users [--force]
Options:
• --force
: Bypass standard interval checks and perform a full sync.
sync:projects
Fetch and update project records.
Usage:
php artisan sync:projects [--force]
Options:
• --force
: Force a complete project sync.
update:logins
Update recent user login statistics.
Usage:
php artisan update:logins
update:campaigns
Refresh campaign statuses and metrics.
Usage:
php artisan update:campaigns
stats:daily
Compute and store daily application statistics.
Usage:
php artisan stats:daily [--date=YYYY-MM-DD]
Options:
• --date
: Run stats for a specific day (default: today).
stats:monthly
Generate and persist monthly summaries.
Usage:
php artisan stats:monthly [--month=YYYY-MM]
Options:
• --month
: Specify target month (format YYYY-MM, default: current month).
notify:telegram
Dispatch queued alerts to Telegram channels.
Usage:
php artisan notify:telegram [--channel=NAME]
Options:
• --channel
: Limit notifications to a specific channel.
Common Workflows
• Full system sync (users + projects):
php artisan sync:users --force
php artisan sync:projects --force
• Generate yesterday’s daily stats and send alerts:
php artisan stats:daily --date=$(date -d "yesterday" +%F)
php artisan notify:telegram
• Backfill monthly data for July 2025:
php artisan stats:monthly --month=2025-07
Scheduling Tasks
All commands register and schedule themselves in routes/console.php
. The scheduler runs via your system’s cron job pointing to php artisan schedule:run
every minute.
Excerpt from routes/console.php
:
<?php
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\Facades\Artisan;
Artisan::command('sync:users', function () {
// … synchronization logic
})->describe('Synchronize user data');
Artisan::command('sync:projects', function () {
// … project sync logic
})->describe('Synchronize project data');
Artisan::command('update:logins', function () {
// … login update logic
})->describe('Update login records');
Artisan::command('update:campaigns', function () {
// … campaign update logic
})->describe('Update campaign data');
Artisan::command('stats:daily', function () {
// … daily stats logic
})->describe('Compute daily statistics');
Artisan::command('stats:monthly', function () {
// … monthly stats logic
})->describe('Compute monthly statistics');
Artisan::command('notify:telegram', function () {
// … Telegram notification logic
})->describe('Dispatch Telegram alerts');
$schedule->command('sync:users')->hourly();
$schedule->command('sync:projects')->hourly();
$schedule->command('update:logins')->everyFiveMinutes();
$schedule->command('update:campaigns')->everyFiveMinutes();
$schedule->command('stats:daily')->dailyAt('01:00');
$schedule->command('stats:monthly')->monthlyOn(1, '02:00');
$schedule->command('notify:telegram')->dailyAt('09:00');
Ensure you include this file in your console kernel:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
require base_path('routes/console.php');
}
Extending Scheduled Commands
- Define a new Artisan command in
routes/console.php
:Artisan::command('your:task', function () { // … task logic })->describe('Description of your task');
- Chain a schedule call:
$schedule->command('your:task')->twiceDaily(2, 14);
- Add usage examples under Available Artisan Commands above.
Configuration & Deployment
This section covers application configuration, CI/CD pipeline setup, automated deployment scripts, and a production readiness checklist for RomAlx/dayofff.
1. Environment Configuration
.env Setup
Add DayOff API and deployment parameters to your .env
:
# DayOff API Integration
DAYOFF_API_URL=https://is.dayofff.ru/api
DAYOFF_API_TOKEN=your_api_token
DAYOFF_TIMEOUT=10
DAYOFF_VERIFY_SSL=true
DAYOFF_CACHE_TTL=3600 # seconds
DAYOFF_RETRIES=3
# Deployment (used in CI/CD)
DEPLOY_SERVER=user@your.server.com
DEPLOY_PATH=/var/www/dayofff
SSH_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----..."
SSH_KNOWN_HOSTS="your.server.com ssh-rsa AAAA..."
config/dayoff.php
return [
'url' => env('DAYOFF_API_URL', 'https://is.dayofff.ru/api'),
'token' => env('DAYOFF_API_TOKEN'),
'timeout' => env('DAYOFF_TIMEOUT', 5),
'verify_ssl' => env('DAYOFF_VERIFY_SSL', true),
'cache_ttl' => env('DAYOFF_CACHE_TTL', 3600),
'retry_count' => env('DAYOFF_RETRIES', 2),
];
These values drive HTTP client settings and caching within the DayOff service integration.
2. CI/CD Pipeline (.gitlab-ci.yml)
Stages & Jobs
stages:
- notifications
- build
- deploy
send_notifications:
stage: notifications
image: php:8.1
script:
- composer install --no-interaction --no-dev
- php artisan schedule:run --only=dayoff:send
only:
- schedules
build:
stage: build
image: node:16
script:
- cd frontend
- npm ci
- npm run build
artifacts:
paths:
- frontend/dist/
deploy:
stage: deploy
image: alpine:latest
variables:
GIT_SUBMODULE_STRATEGY: recursive
before_script:
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
script:
- ssh $DEPLOY_SERVER "cd $DEPLOY_PATH && git pull origin main && composer install --no-dev --optimize-autoloader && bash clear.sh"
dependencies:
- build
only:
- main
- send_notifications runs scheduled Laravel commands.
- build compiles frontend assets and preserves them as artifacts.
- deploy pulls the latest code, installs PHP dependencies, and runs
clear.sh
.
3. Deployment Script (clear.sh
)
clear.sh
automates Laravel optimizations and server permissions:
#!/usr/bin/env bash
# Laravel optimization
php artisan config:cache
php artisan route:cache
php artisan view:cache
composer dump-autoload -o
# Permissions
chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
# Cleanup old sessions
find storage/framework/sessions -type f -mtime +7 -delete
echo "Deployment tasks completed."
Usage (inside CI or SSH session):
cd /var/www/dayofff
bash clear.sh
4. Production Deployment Checklist
Before marking deployment as complete:
- Verify
.env
values on the server (.env.production
). - Ensure
composer install --no-dev --optimize-autoloader
succeeds. - Run
bash clear.sh
and confirm no errors. - Check file ownership and permissions:
storage
,bootstrap/cache
owned by web user- Directories chmod 775, files chmod 664
- Review Laravel logs (
storage/logs/laravel.log
) for exceptions. - Validate frontend assets load correctly in production.
- Perform a health check on the DayOff API endpoint via
php artisan tinker
:dd(app('DayOff\Client')->fetchHolidays());
- Confirm scheduled notifications execute (view GitLab schedule logs).
- Backup database and application files after successful release.
Contribution Guide
This guide covers submitting pull requests, coding standards, running tests, seeding data, and local linting/formatting for RomAlx/dayofff.
1. Pull Request Process
- Fork the repository and create a feature branch from
main
:git checkout -b feature/your-feature-name
- Commit changes with clear messages:
git commit -m "Add calendar integration for user availability"
- Push to your fork and open a PR against
main
. - Reference related issues and include before/after screenshots or logs if applicable.
- Ensure all tests pass and code follows standards below.
2. Coding Standards
We enforce consistency via .editorconfig
. Configure your editor with the EditorConfig plugin.
.editorconfig
Highlights
root = true
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.php]
indent_style = space
indent_size = 4
[*.js,*.json,*.yaml,*.yml]
indent_style = space
indent_size = 2
- Use 4 spaces for PHP, 2 for JS/JSON/YAML.
- No tabs, trailing spaces, or mixed line endings.
- Ensure every file ends with a newline.
3. Running Tests
The PHPUnit configuration (phpunit.xml
) defines two suites: Unit and Feature.
Setup
- Install dependencies:
composer install cp .env.example .env
- Create a testing environment file or rely on PHPUnit’s environment overrides:
<!-- excerpt from phpunit.xml --> <php> <env name="APP_ENV" value="testing"/> <env name="DB_CONNECTION" value="sqlite"/> <env name="CACHE_DRIVER" value="array"/> <!-- other env vars... --> </php>
Commands
- Run all tests:
./vendor/bin/phpunit
- Run only unit tests:
./vendor/bin/phpunit --testsuite Unit
- Run only feature tests:
./vendor/bin/phpunit --testsuite Feature
4. Seeding Data
Leverage Laravel seeders to populate your local database with sample data.
- Run migrations:
php artisan migrate
- Seed the database:
php artisan db:seed --class=DatabaseSeeder
- For specific seeders:
php artisan db:seed --class=UserSeeder
5. Local Linting & Formatting
Ensure code aligns with .editorconfig
and Laravel conventions.
- Use your editor’s EditorConfig plugin to auto-format on save.
- Optionally, install
editorconfig-checker
for CI validation:npm install -g editorconfig-checker editorconfig-checker .
- If using PHP-CS-Fixer, add a script in
composer.json
and run:./vendor/bin/php-cs-fixer fix --dry-run --diff
Following these guidelines ensures consistency and smooth collaboration on RomAlx/dayofff.