Compare commits

..

18 Commits

Author SHA1 Message Date
deploy 562ea93d78 add jwt secret to prod env file
Build and Deploy Docker / build (push) Successful in 5m31s
Build and Deploy Docker / deploy (push) Successful in 47s
2026-07-14 16:40:46 +02:00
deploy 30f50dcfb7 change config from mariadb to mysql
Build and Deploy Docker / build (push) Successful in 5m48s
Build and Deploy Docker / deploy (push) Successful in 46s
2026-07-14 15:42:57 +02:00
deploy d8022ab205 change docker container name to backend
Build and Deploy Docker / build (push) Successful in 8m35s
Build and Deploy Docker / deploy (push) Successful in 39s
2026-07-14 14:53:45 +02:00
deploy 71c14b0625 add env prod 2026-07-14 14:14:42 +02:00
deploy d9bf86598b add cicd
Build and Deploy Docker / build (push) Failing after 14m19s
Build and Deploy Docker / deploy (push) Has been skipped
2026-07-14 08:05:08 +02:00
deploy 4388a7ec93 add client from rest api 2026-07-14 07:56:35 +02:00
deploy 22da85872d initial rest api for react.js 2026-06-29 18:54:40 +02:00
deploy 2eb770a328 add highlight report 2025-09-27 21:09:59 +02:00
deploy ed1d991a84 minor fixes 2025-03-29 20:35:54 +01:00
deploy b454bbe77b Merge branch 'master' into invoices 2024-10-15 12:28:39 +02:00
deploy aa759af8c5 add mailto to reports 2024-10-15 12:15:57 +02:00
deploy fb85ac565f test invoices 2024-10-08 23:47:04 +02:00
ryjek 598acfd77c major fixes 2024-09-25 16:55:03 +02:00
ryjek e1cb68766e set docker tag 2024-08-09 15:22:35 +02:00
ryjek bd746e18cc Merge branch 'master' into tryUpgradeView 2024-08-09 14:21:56 +02:00
ryjek 7af9eeab61 fix change template mode; fix schedule view; minor fixes 2024-08-09 14:16:51 +02:00
ryjek 354aa9c3be add darkmode, minimize js/css 2024-08-09 10:53:16 +02:00
ryjek 2c8e32deb8 SPRA-26 add redit- to test 2024-08-02 16:07:05 +02:00
66 changed files with 3717 additions and 1106 deletions
+52
View File
@@ -0,0 +1,52 @@
name: Build and Deploy Docker
on:
push:
branches:
- masterApi
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: git.rhost.ovh
username: ${{ github.actor }}
password: ${{ secrets.TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./services/www/Dockerfile
push: true
tags: git.rhost.ovh/${{ github.repository }}:latest
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SERVER_PORT }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /home/ryjek/docker/sprawozdania
docker compose pull backend
docker compose up -d backend
docker compose exec --workdir /var/www/html backend APP_RUNTIME_ENV=prod php bin/console secrets:decrypt-to-local --force
docker compose exec --workdir /var/www/html backend php ./bin/console doctrine:migration:migrate --dry-run
docker compose exec --workdir /var/www/html backend php ./bin/console lexik:jwt:generate-keypair --skip-if-exists
docker compose exec --workdir /var/www/html backend php ./bin/console cache:clear
+2 -11
View File
@@ -14,8 +14,9 @@ services:
- internal - internal
- myadmin - myadmin
www: www:
image: registry.docker.rhost.ovh/sprawozdania:0.1.8 image: registry.docker.rhost.ovh/sprawozdania:0.2.5
restart: on-failure restart: on-failure
platform: linux/x86_64
build: build:
context: . context: .
dockerfile: services/www/Dockerfile dockerfile: services/www/Dockerfile
@@ -37,16 +38,6 @@ services:
- internal - internal
volumes: volumes:
- ./storage/redis:/data - ./storage/redis:/data
# wait_for_it:
# build:
# context: .
# dockerfile: services/wait_for_it/Dockerfile
# networks:
# - internal
# depends_on:
# - database
# command: sh -c "/wait -t 30 database:5432 -- echo 123;"
networks: networks:
internal: internal:
internal: true internal: true
+35
View File
@@ -0,0 +1,35 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=prod
APP_SECRET=$env(APP_SECRET)$
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
#DATABASE_URL="mysql://app:%env(DATABASE_PASSWORD)%@mariadb:3306/app?serverVersion=8.0.32&charset=utf8mb4"
DATABASE_URL="mysql://app:%env(DATABASE_PASSWORD)%@mysql:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
#DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
###< doctrine/doctrine-bundle ###
REDIS_URL=redis://redis:6379?timeout=5
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=%env(JWT_PASSPHRASE)%
+2 -1
View File
@@ -1,11 +1,12 @@
###> symfony/framework-bundle ### ###> symfony/framework-bundle ###
.env .env
.env.prod
/.env.local /.env.local
/.env.local.php /.env.local.php
/.env.*.local /.env.*.local
/config/secrets/prod/prod.decrypt.private.php /config/secrets/prod/prod.decrypt.private.php
config/jwt
!config/jwt/.gitkeep
/public/bundles/ /public/bundles/
/var/ /var/
/vendor/ /vendor/
+4 -1
View File
@@ -14,4 +14,7 @@ import './styles/main.less';
global.jQuery = global.$ = require('jquery'); global.jQuery = global.$ = require('jquery');
require('@fortawesome/fontawesome-free/css/all.min.css'); require('@fortawesome/fontawesome-free/css/all.min.css');
require('bootstrap'); require('bootstrap');
$('[data-toggle="tooltip"]').tooltip();
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
+2 -1
View File
@@ -1,4 +1,5 @@
import { startStimulusApp } from '@symfony/stimulus-bridge'; import { startStimulusApp } from '@symfony/stimulus-bridge';
import {tmp} from "./services/theme_controller";
// Registers Stimulus controllers from controllers.json and in the controllers/ directory // Registers Stimulus controllers from controllers.json and in the controllers/ directory
export const app = startStimulusApp(require.context( export const app = startStimulusApp(require.context(
@@ -6,5 +7,5 @@ export const app = startStimulusApp(require.context(
true, true,
/\.[jt]sx?$/ /\.[jt]sx?$/
)); ));
new tmp()
// register any custom, 3rd party controllers here // register any custom, 3rd party controllers here
// app.register('some_controller_name', SomeImportedController);
@@ -0,0 +1,35 @@
import { Controller } from '@hotwired/stimulus';
/*
* This is an example Stimulus controller!
*
* Any element with a data-controller="hello" attribute will cause
* this controller to be executed. The name "hello" comes from the filename:
* hello_controller.js -> "hello"
*
* Delete this file or adapt it for your use!
*/
export default class extends Controller {
allCardsDiv;
connect() {
this.allCardsDiv = $('#allCardsReport .report-one');
}
showModal(){
const reportId = event.params.reportid, date = event.params.date;
$("#showReportModal").find('.modal-body').html(
this.findReport(reportId)
);
$("#showReportModal").find('.modal-title').html(date)
$("#showReportModal").modal('show');
}
findReport(id) {
for (const allCardsDivKey of this.allCardsDiv) {
const tmp = $(allCardsDivKey);
if (tmp.data('report_id') === id) {
return tmp;
}
}
}
}
@@ -0,0 +1,31 @@
import { Controller } from '@hotwired/stimulus';
import { tmp } from '../services/theme_controller'
export default class extends Controller {
modes;
currentState;
connect() {
this.modes = $(this.element).find('p');
this.currentState = localStorage.getItem('themeMode');
this.changeState()
var tmp1 = new tmp();
tmp1.update();
}
switch(event) {
var mode = event.params.mode;
this.changeState(mode);
}
changeState(newState) {
if (typeof newState !== 'undefined') {
localStorage.setItem("themeMode", newState);
this.currentState = newState;
}
// $('html').attr('data-bs-theme', this.currentState);
this.modes.filter((e,tmp)=>{
const obj = $(tmp);
obj.toggle(!obj.hasClass(this.currentState));
})
}
}
@@ -0,0 +1,73 @@
export class tmp {
constructor() {
const getStoredTheme = () => localStorage.getItem('theme')
const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
if (storedTheme) {
return storedTheme
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
this.setTheme(getPreferredTheme())
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
this.setTheme(getPreferredTheme())
}
})
window.addEventListener('DOMContentLoaded', () => {
this.showActiveTheme(getPreferredTheme())
this.update();
})
}
setTheme = theme => {
if (theme === 'auto') {
document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'))
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
setStoredTheme = theme => localStorage.setItem('theme', theme)
showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector('#bd-theme')
if (!themeSwitcher) {
return
}
const themeSwitcherText = document.querySelector('#bd-theme-text')
const activeThemeIcon = document.querySelector('.theme-icon-active use')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href')
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
activeThemeIcon.setAttribute('href', svgOfActiveBtn)
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
if (focus) {
themeSwitcher.focus()
}
}
update() {
document.querySelectorAll('[data-bs-theme-value]')
.forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value')
this.setStoredTheme(theme)
this.setTheme(theme)
this.showActiveTheme(theme, true)
})
})
}
}
+22 -3
View File
@@ -1,10 +1,11 @@
body { body {
background-color: #e6e6e6; //background-color: #e6e6e6;
} }
.reports-all { .reports-all {
.col { .col {
margin-bottom: 20px; margin-bottom: 20px;
} }
}
.report-one { .report-one {
.card-header { .card-header {
display: flex; display: flex;
@@ -51,7 +52,6 @@ body {
} }
} }
} }
}
.reports-months-all { .reports-months-all {
margin-bottom: 20px; margin-bottom: 20px;
} }
@@ -59,5 +59,24 @@ body {
/* background-color: lightgray;*/ /* background-color: lightgray;*/
/*}*/ /*}*/
a { a {
text-decoration: none !important; //text-decoration: none !important;
}
.spinner-grow.overdue {
position: absolute;
top: 2px;
right: 2px;
}
.card-footer {
.edit-client-group {
display: inline-flex;
margin: auto;
>div {
padding: 10px;
}
}
}
#allCardsReport{
display: none;
opacity: 0;
} }
+4
View File
@@ -12,13 +12,17 @@
"doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.2", "doctrine/orm": "^3.2",
"league/commonmark": "^2.4", "league/commonmark": "^2.4",
"lexik/jwt-authentication-bundle": "^3.2",
"nelmio/cors-bundle": "^2.6",
"predis/predis": "^2.2", "predis/predis": "^2.2",
"symfony/console": "6.4.*", "symfony/console": "6.4.*",
"symfony/dotenv": "6.4.*", "symfony/dotenv": "6.4.*",
"symfony/form": "6.4.*", "symfony/form": "6.4.*",
"symfony/framework-bundle": "6.4.*", "symfony/framework-bundle": "6.4.*",
"symfony/http-client": "^7.1",
"symfony/runtime": "6.4.*", "symfony/runtime": "6.4.*",
"symfony/security-bundle": "6.4.*", "symfony/security-bundle": "6.4.*",
"symfony/serializer-pack": "^1.3",
"symfony/stimulus-bundle": "^2.18", "symfony/stimulus-bundle": "^2.18",
"symfony/twig-bundle": "6.4.*", "symfony/twig-bundle": "6.4.*",
"symfony/ux-turbo": "^2.18", "symfony/ux-turbo": "^2.18",
+2039 -710
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -12,4 +12,6 @@ return [
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
]; ];
View File
+4
View File
@@ -10,6 +10,10 @@ framework:
# Redis # Redis
app: cache.adapter.redis app: cache.adapter.redis
system: cache.adapter.redis system: cache.adapter.redis
default_redis_provider: '%env(REDIS_URL)%'
pools:
database.cache:
adapter: cache.adapter.redis
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu #app: cache.adapter.apcu
+18
View File
@@ -15,6 +15,24 @@ doctrine:
validate_xml_mapping: true validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true auto_mapping: true
second_level_cache:
enabled: true
regions:
user_cache:
lifetime: 8640000
cache_driver: { type: service, id: database.cache }
schedule_cache:
lifetime: 864000
cache_driver: { type: service, id: database.cache }
report_cache:
lifetime: 864000
cache_driver: { type: service, id: database.cache }
client_cache:
lifetime: 864000
cache_driver: { type: service, id: database.cache }
default_cache:
lifetime: 864000
cache_driver: { type: service, id: database.cache }
mappings: mappings:
App: App:
type: attribute type: attribute
@@ -0,0 +1,5 @@
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
clock_skew: 0
+8
View File
@@ -0,0 +1,8 @@
nelmio_cors:
defaults:
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_headers: ['Content-Type', 'Authorization']
allow_methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
max_age: 3600
paths:
'^/api/': ~
+16 -11
View File
@@ -13,6 +13,20 @@ security:
dev: dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/ pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false security: false
login:
pattern: ^/api/auth/login
stateless: true
json_login:
check_path: /api/auth/login
username_path: username
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
jwt: ~
main: main:
lazy: true lazy: true
provider: app_user_provider provider: app_user_provider
@@ -24,22 +38,13 @@ security:
always_use_default_target_path: true always_use_default_target_path: true
logout: logout:
path: app_logout path: app_logout
# where to redirect after logout
# target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site # Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used # Note: Only the *first* access control that matches will be used
access_control: access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/user/activate, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: ROLE_USER } - { path: ^/, roles: ROLE_USER }
- { path: ^/api/auth, roles: PUBLIC_ACCESS }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
when@test: when@test:
security: security:
@@ -0,0 +1,3 @@
<?php // dev.API_FAKTUROWNIA.f73a67 on Fri, 01 Nov 2024 22:52:28 +0100
return "\xA9c\xD0\xC5\xD1\x3Aek\xE6\x24\x16\x15b\x3A\xDF\x83\x9B\xED\xE8n\x04_d\x5B\xAE8\x1B\xAC\x02\x2C\x9CA\xFB\x8Ee\x99\xFC3\xDE\xC34f\x28\x08\x26\xC8\x26o\x88\xF9\xF4z";
@@ -0,0 +1,3 @@
<?php // dev.JWT_PASSPHRASE.0269d8 on Tue, 14 Jul 2026 16:39:52 +0200
return "\x5D\x0FW\xCF\xA8\x28\xCDN\x8Fk\xE2\x17\xA3\x5Cd\xF7\x15\xA7KeJ\x1A\x27\xF4\x10\xEB\x1A\xFF6U\xAFx\x3C\x84\x5B\xE7\x9D\x01f\xF7\x90\xAC\xAAJ\x82\xA9\xBE\xEDBS2\x16\xA4\x95\x24\x1D\x218\xDF\xF33\x93\xB9\x9A\xAF\xB3\xE7\x02\x22\xABC\xE8\xEDM\x98\x5B\xF7\xFD\x0E\xE8s\x8D\x06\x00\x40m\x9B_\x8F\x27\xF5t3\x97\x8E\xF6y\xAA\xE0\xB7\xF4\xD67\x06\xD0Q\x0B\xFF\x90\x1A\xAC\xA1\x2B\xDA\xA2\x8F6\xF5n\x18.\xDA\x18\x1A\x18\xAC3\xE5\xA28\x9C\xE7\x09\xFF\xCC\x99\x88\x16\x83\x06\x2C\x09z\xF2\x9D\x18\xACT2\xBCqZD\xE9\x93\x96\xCF\xCC\xDDwnp\xFE\xE1o\xA9R\x21\x9B\x5E\x0F\x7C\x0B\xA4\xC9\x11\xCF";
+2
View File
@@ -1,5 +1,7 @@
<?php <?php
return [ return [
'API_FAKTUROWNIA' => null,
'DATABASE_PASSWORD' => null, 'DATABASE_PASSWORD' => null,
'JWT_PASSPHRASE' => null,
]; ];
@@ -1,3 +1,3 @@
<?php // prod.DATABASE_PASSWORD.b77cc3 on Thu, 25 Jul 2024 14:54:39 +0200 <?php // prod.DATABASE_PASSWORD.b77cc3 on Wed, 21 Aug 2024 11:10:09 +0200
return "\x0FA\x82\x24\x0E\xC5I\x1Ep7\xA5A\x1Cx\x80\x18m\xB0\x17\x1B\x82B\x92\x99\xAF\x9APi\x24\x9B\x009\x15\xA4\x25k\xBE\x20\xE8t\xF9\xD1\xF9W\xD3\xC4\x25\xFFU\x7D\xAF\x04\xE9\xF9j\x7F\xFBFZ\x81\x0A\x254\x12"; return "u\x2C\x1D\xF4\x2CKm\x98\xCA\x01\x7C\xABJ\x3A\xA7\xCA\x1B\x94R\x98\xF5D\xC9\x25\xCDY\xDF\x5C\x23\x8DLK\xBD\x03jew\x5C9\x7B\x7F\x05\x5EE\x21Z\x9A\xB0\x87\xC9\xB8\xCES\xB7N\xE6\xFF\xA4\xB6\xDA\xFC\xC8\xB3\x94";
@@ -0,0 +1,3 @@
<?php // prod.JWT_PASSPHRASE.0269d8 on Tue, 14 Jul 2026 16:40:05 +0200
return "\xAF\xFA\xD0CAH\x1B\x27\xB0\x12\x3B\x05\x06\xF5\x18\x90q\xBCr\xE7\x2A\x830\x1A\x19\x02\x2F\xCE\x8CR\xC7\x1C\xA5V\xBA\xC0\xC3\xAC-\x83\x5D\xCB\x87~\xD1\x1E\x95\x2AY\xB8\xA1\x02\xAF\xE7\x60.\xBF\x2F\xD1\xCF3\xB1\xE3\x842\x92\x136\x91\xD1\xFC\xAD\xEE\x2FsC\xFC\x0B\x93\x8AQ\x18\x8F\x93\xBC\x00q\xA3\x93jD\xBF\x1DI\xF3\x98\x94~\x3F\x27\xBB\x9B\x84\x0B\xB0\xBDB\x19\xCC7\xDE\xCF\x2A2\x1C\xAC\x04\xB9\x86\x11\x83C\x3B\x18\xE8\xBBdQ\xC0\xA4\x15\xBCM\x28~\xF4\x0BG\x26\x5Dp\x04\xA5B\xCE\x0F\x5D~\xD8H\xC2\xBE\x18mfep\x07\x1CZ\x2C\x8B7\xBAz\x7C\xF3\x8CuY~\xB5y\xFD\x12\xC7\x9F";
@@ -3,4 +3,5 @@
return [ return [
'APP_SECRET' => null, 'APP_SECRET' => null,
'DATABASE_PASSWORD' => null, 'DATABASE_PASSWORD' => null,
'JWT_PASSPHRASE' => null,
]; ];
+2
View File
@@ -4,6 +4,7 @@
# Put parameters here that don't need to change on each machine where the app is deployed # Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters: parameters:
FAKTUROWNIA_API: '%env(API_FAKTUROWNIA)%'
services: services:
# default configuration for services in *this* file # default configuration for services in *this* file
@@ -24,5 +25,6 @@ services:
resource: '../src/Services/' resource: '../src/Services/'
arguments: arguments:
- '@doctrine.orm.entity_manager' - '@doctrine.orm.entity_manager'
- '@Symfony\Contracts\HttpClient\HttpClientInterface'
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones # please note that last definitions always *replace* previous ones
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250329215035 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE reports ADD highlight INT DEFAULT 0 NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE reports DROP highlight');
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250330100207 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE clients ADD highlight INT DEFAULT 0 NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE clients DROP highlight');
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260507143656 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE UNIQUE INDEX report_todo ON reports_todo (report_id, name)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX report_todo ON reports_todo');
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Controller\Api;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class AuthController extends AbstractController
{
#[Route('/api/auth/login', name: 'api_login', methods: ['POST'])]
public function login(): JsonResponse
{
throw new \LogicException('Intercepted by firewall.');
}
}
@@ -0,0 +1,98 @@
<?php
namespace App\Controller\Api;
use App\Entity\Clients;
use App\Repository\ClientsRepository;
use App\Services\AddReports;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/clients', name: 'api_')]
class ClientsController extends AbstractController
{
public function __construct(
private ClientsRepository $repo,
private SerializerInterface $serializer,
) {}
#[Route('', name: 'list', methods: ['GET'])]
public function list(): JsonResponse
{
$products = $this->repo->findAll();
$prepare = [];
foreach ($products as $product) {
$prepare[] = [
'id' => $product->getId(),
'strName' => $product->getStrName(),
'strAddress' => $product->getStrAddress(),
'dateLegitimacy' => $product->getDateLegitimacy()->format('Y-m-d'),
'dateProclamation' => $product->getDateProclamation()->format('Y-m-d'),
'extendInfo' => [
'intNip' => $product->getIntNIP(),
'intPhone' => $product->getIntPhone(),
'strEmail' => $product->getStrEmail(),
]
];
}
return $this->json($prepare, context: ['groups' => ['client:read']]);
}
#[Route('/add', name: 'add_client', methods: ['POST'])]
public function add(Request $request, EntityManagerInterface $entityManager, AddReports $addReports): JsonResponse
{
$need = [
"strName" => $request->getPayload()->get('name'),
"strAddress" => $request->getPayload()->get('address'),
"dateLegitimacy" => $request->getPayload()->get('legitimacy'),
"dateProclamation" => $request->getPayload()->get('proclamation'),
"intNIP" => $request->getPayload()->get('nip'),
"intPhone" => $request->getPayload()->get('phone'),
"strEmail" => $request->getPayload()->get('email'),
];
$newClient = new Clients();
foreach ($need as $key => $value) {
$cleared = $this->clearType($key, $value);
$method = 'set'.ucfirst($key);
$newClient->{$method}($cleared);
}
$entityManager->persist($newClient);
$entityManager->flush();
$entityManager->getCache()->evictEntityRegion(Clients::class);
$addReports->setClient($newClient);
$addReports->init();
return $this->json(['success' => true]);
}
private function clearType(string $key, mixed $value)
{
if (str_contains($key, 'str')) {
return addslashes($value);
}
if (str_contains($key, 'int')) {
return intval($value);
}
if (str_contains($key, 'date')) {
return new \DateTime($value);
}
}
// #[Route('/{id}', name: 'show', methods: ['GET'])]
// public function show(Product $product): JsonResponse
// {
// return $this->json($product, context: ['groups' => ['product:read']]);
// }
//
// #[Route('', name: 'create', methods: ['POST'])]
// public function create(Request $request): JsonResponse
// {
// $data = $request->toArray();
// // walidacja + zapis...
// return $this->json($product, 201, context: ['groups' => ['product:read']]);
// }
}
@@ -0,0 +1,72 @@
<?php
namespace App\Controller\Api;
use App\Entity\Reports;
use App\Entity\ReportsTodo;
use App\Enum\ReportStatus;
use App\Enum\ReportTodoEnum;
use App\Helper\MonthHelper;
use App\Repository\ReportsRepository;
use App\Services\AddReports;
use App\Services\OverdueReports;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/reports', name: 'api_reports')]
class ReportsController extends AbstractController
{
public function __construct(
private readonly ReportsRepository $repo,
private readonly OverdueReports $overdueReports
)
{
}
#[Route('/{monthId}', name: 'api_reports_all', methods: ['GET'])]
public function getReports(int $monthId): JsonResponse
{
$monthHelper = new MonthHelper($this->overdueReports);
$clientId=0;
$monthCurrentDate = $monthHelper->getCurrentMonthDate($monthId);
$reportsAll = $this->repo->findAllInMonth($monthCurrentDate,$clientId);
return $this->json($reportsAll, context: ['groups' => ['reportsAll:read','reportstodo:read']]);
}
#[Route('/todo', name: 'api_reports_todo', methods: ['POST'])]
public function updateReportTodo(Request $request, AddReports $reportsService, EntityManagerInterface $entityManager): JsonResponse
{
$reportId = $request->toArray()['reportId'];
$todoName = $request->toArray()['todoName'];
$nameObj = ReportTodoEnum::tryFromNameStatic($todoName);
if (!$nameObj) {
throw $this->createNotFoundException();
}
$reportReference = $entityManager->getReference(Reports::class, $reportId);
$todo = $entityManager->getRepository(ReportsTodo::class)->findOneBy(['name' => $nameObj, 'report' => $reportReference]);
if($todo){
$todo->setDeleted(!$todo->isDeleted());
} else {
$todo = new ReportsTodo();
$todo->setName($nameObj);
$todo->setReport($reportReference);
}
$reportReference->addReportsTodo($todo);
$entityManager->persist($todo);
$entityManager->flush();
$entityManager->getCache()->evictCollection(Reports::class,"reportsTodos",$reportReference->getId());
return $this->json([
'reportId' => $reportId,
'reportStatusTodo' => $reportReference->getStatusTodo(),
'todoItem' => $todo
], context: ['groups' => ['reportstodo:read']]);
}
}
+39 -16
View File
@@ -8,7 +8,9 @@ use App\Entity\ReportsTodo;
use App\Enum\Months; use App\Enum\Months;
use App\Enum\ReportStatus; use App\Enum\ReportStatus;
use App\Enum\ReportTodoEnum; use App\Enum\ReportTodoEnum;
use App\Enum\ReportHighlight;
use App\Helper\MonthHelper; use App\Helper\MonthHelper;
use App\Services\OverdueReports;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -23,33 +25,39 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Cache\CacheInterface;
class BaseController extends AbstractController class BaseController extends AbstractController
{ {
private MonthHelper $monthHelper;
public function __construct(){
$this->monthHelper = new MonthHelper();
}
#[Route('/', name: 'index')] #[Route('/', name: 'index')]
public function index(): Response public function index(): Response
{ {
if (!$this->isGranted('reportView')) { if ($this->isGranted('reportView')) {
return $this->redirectToRoute('app_base');
}
if ($this->isGranted('scheduleView')) {
return $this->redirectToRoute('app_schedule'); return $this->redirectToRoute('app_schedule');
} }
if ($this->isGranted('clientView')) {
return $this->redirectToRoute('app_customers_list');
}
return $this->redirectToRoute('app_base'); return $this->redirectToRoute('app_base');
} }
#[Route('/reports/{monthId}', name: 'app_base')] #[Route('/reports/{monthId}/{clientId}', name: 'app_base')]
#[IsGranted('reportView')] #[IsGranted('reportView')]
public function reportsIndex(EntityManagerInterface $entityManager, int $monthId = 0) : Response public function reportsIndex(EntityManagerInterface $entityManager, CacheInterface $cache, OverdueReports $overdueReports, int $monthId = 0, int $clientId = 0) : Response
{ {
$client = null;
if ($clientId) {
$client = $entityManager->getRepository(Clients::class)->find($clientId);
}
$overdueReports->setClient($client);
$monthHelper = new MonthHelper($overdueReports);
$isOverdue = false; $isOverdue = false;
if ($monthId > -1) { $monthCurrentDate = $monthHelper->getCurrentMonthDate($monthId);
$monthCurrent = $this->monthHelper->getCurrentMonth($monthId); $reportsAll = $this->takeReportsAll($entityManager, $clientId, $monthCurrentDate);
$reportsAll = $entityManager->getRepository(Reports::class)->findAllInMonth($monthCurrent);
} else { if ($monthId < 0) {
$reportsAll = $entityManager->getRepository(Reports::class)->findAllOverdue();
$isOverdue = true; $isOverdue = true;
} }
@@ -67,10 +75,25 @@ class BaseController extends AbstractController
ksort($reports); ksort($reports);
return $this->render('reports/list.html.twig', [ return $this->render('reports/list.html.twig', [
'reportsGroup' => $reports, 'reportsGroup' => $reports,
'months' => $this->monthHelper->takeMonths(), 'months' => $monthHelper->takeMonths(),
'currentMonth' => $monthCurrent ?? null, 'currentMonth' => $monthCurrentDate ?? null,
'reportToDoList' => ReportTodoEnum::cases(), 'reportToDoList' => ReportTodoEnum::cases(),
'isOverdue' => $isOverdue, 'isOverdue' => $isOverdue,
'countOverdueLeft' => $overdueReports->getLeftCount(),
'client' => $client,
'clientId' => $clientId,
'highlightStatus' => ReportHighlight::cases()
]); ]);
} }
private function takeReportsAll(EntityManagerInterface $entityManager, int $clientId, ?\DateTime $monthCurrent = null) : array
{
if ($monthCurrent !== null) {
$ret = $entityManager->getRepository(Reports::class)->findAllInMonth($monthCurrent,$clientId);
} else {
$ret = $entityManager->getRepository(Reports::class)->findAllOverdue($clientId);
}
return $ret;
}
} }
+22 -2
View File
@@ -6,6 +6,9 @@ use App\Entity\Clients;
use App\Entity\Notes; use App\Entity\Notes;
use App\Entity\Reports; use App\Entity\Reports;
use App\Entity\Schedule; use App\Entity\Schedule;
use App\Enum\ReportHighlight;
use App\Enum\ReportStatus;
use App\Enum\ReportTodoEnum;
use App\Services\AddReports; use App\Services\AddReports;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -50,13 +53,15 @@ class ClientController extends AbstractController
$schedules = $entityManager->getRepository(Schedule::class)->findOneBy(["client" => $clients->getId()]); $schedules = $entityManager->getRepository(Schedule::class)->findOneBy(["client" => $clients->getId()]);
$reports = $entityManager->getRepository(Reports::class)->findBy(["client" => $clients->getId()],["number"=>"DESC"]); $reports = $entityManager->getRepository(Reports::class)->findBy(["client" => $clients->getId()],["number"=>"DESC"]);
$notes = $entityManager->getRepository(Notes::class)->findOneBy(["clientId" => $clients->getId()]); $notes = $entityManager->getRepository(Notes::class)->findOneBy(["clientId" => $clients->getId()]);
return $this->render('customers/show.html.twig', [ return $this->render('customers/show.html.twig', [
'client' => $clients, 'client' => $clients,
'schedules' => $schedules, 'schedules' => $schedules,
'reports' => $reports, 'reports' => $reports,
'notes' => $notes, 'notes' => $notes,
'backUrl' => $request->headers->get('referer') 'backUrl' => $request->headers->get('referer'),
'reportToDoList' => ReportTodoEnum::cases(),
'reportStatus' => ReportStatus::INSERT,
'highlightStatus' => ReportHighlight::cases()
]); ]);
} }
#[Route('/dodaj_klienta/{id}', name: 'app_add_client', defaults: ['id'=>0])] #[Route('/dodaj_klienta/{id}', name: 'app_add_client', defaults: ['id'=>0])]
@@ -76,6 +81,7 @@ class ClientController extends AbstractController
$newClient = $form->getData(); $newClient = $form->getData();
$entityManager->persist($newClient); $entityManager->persist($newClient);
$entityManager->flush(); $entityManager->flush();
$entityManager->getCache()->evictEntityRegion(Clients::class);
if (!$isEdit) { if (!$isEdit) {
$addReports->setClient($newClient); $addReports->setClient($newClient);
$addReports->init(); $addReports->init();
@@ -161,4 +167,18 @@ class ClientController extends AbstractController
->add('submitAndRedirect', SubmitType::class, ) ->add('submitAndRedirect', SubmitType::class, )
->getForm(); ->getForm();
} }
#[Route('/client_highlight/{id}', name: 'app_client_highlight', methods: ['POST'])]
public function updateHighlight(EntityManagerInterface $entityManager, Request $request, Clients $client): Response
{
$token = $request->get("token");
if ($this->isCsrfTokenValid('client-highlight', $token)) {
$highlightId = intval($request->get("highlight_id"));
$highlight = ReportHighlight::tryFrom($highlightId);
$client->setHighlight($highlight);
$entityManager->persist($client);
$entityManager->flush();
}
return $this->redirectToRoute('app_client', ['id'=>$client->getId()]);
}
} }
+43 -9
View File
@@ -4,8 +4,11 @@ namespace App\Controller;
use App\Entity\Clients; use App\Entity\Clients;
use App\Entity\Reports; use App\Entity\Reports;
use App\Entity\ReportsTodo; use App\Entity\ReportsTodo;
use App\Entity\Schedule;
use App\Enum\ReportTodoEnum; use App\Enum\ReportTodoEnum;
use App\Enum\ReportHighlight;
use App\Services\AddReports; use App\Services\AddReports;
use App\Services\InvoiceServices;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -17,12 +20,11 @@ class ReportsController extends AbstractController
{ {
#[Route('/set-status-todo/{id}', name: 'app_set_status_todo', methods: ['POST'])] #[Route('/set-status-todo/{id}', name: 'app_set_status_todo', methods: ['POST'])]
#[IsGranted('reportEdit')] #[IsGranted('reportEdit')]
public function setStatusTodo(EntityManagerInterface $entityManager, AddReports $reportsService, Request $request, Reports $report): Response public function setStatusTodo(EntityManagerInterface $entityManager, AddReports $reportsService, InvoiceServices $invoiceServices, Request $request, Reports $report): Response
{ {
$token = $request->get("token"); $token = $request->get("token");
if ($this->isCsrfTokenValid('report-status', $token)) { if ($this->isCsrfTokenValid('report-status', $token)) {
$reportsService->setClient($report->getClient()); $reportsService->setClient($report->getClient());
$isDone = false;
if ($request->get("unDone")){ if ($request->get("unDone")){
$todo = $entityManager->getRepository(ReportsTodo::class)->findOneBy([ $todo = $entityManager->getRepository(ReportsTodo::class)->findOneBy([
"report" => $report->getId(), "report" => $report->getId(),
@@ -30,8 +32,12 @@ class ReportsController extends AbstractController
]); ]);
$todo->setDeleted(true); $todo->setDeleted(true);
$entityManager->persist($todo); $entityManager->persist($todo);
$report->setDone(false);
} else { } else {
foreach (ReportTodoEnum::cases() as $case) { foreach (ReportTodoEnum::cases() as $case) {
if (!$request->get($case->value)) {
continue;
}
$todo = $entityManager->getRepository(ReportsTodo::class)->findOneBy([ $todo = $entityManager->getRepository(ReportsTodo::class)->findOneBy([
"report" => $report->getId(), "report" => $report->getId(),
"name" => $case->value "name" => $case->value
@@ -40,21 +46,29 @@ class ReportsController extends AbstractController
$todo = new ReportsTodo(); $todo = new ReportsTodo();
$todo->setReport($report); $todo->setReport($report);
$todo->setName($case); $todo->setName($case);
}
if ($request->get($case->value) !== null) {
$todo->setDeleted(false);
} else { } else {
$todo->setDeleted(true); $todo->setDeleted(!$todo->isDeleted());
} }
if ($todo->getName() === ReportTodoEnum::COMPLETE && !$todo->isDeleted()) { if ($todo->getName() === ReportTodoEnum::COMPLETE) {
$isDone = true; if ($todo->isDeleted()) {
$report->setDone(false);
} else {
$report->setDone(true);
} }
}
// if ($todo->getName() === ReportTodoEnum::INVOICE_SEND && !$todo->isDeleted()) {
// $invoiceServices->setApiKey($this->getParameter('FAKTUROWNIA_API'));
// $invoiceServices->setClients($report->getClient());
// $invoiceServices->setReportDate($report->getDateStart());
// $invoiceServices->send();
// }
$entityManager->persist($todo); $entityManager->persist($todo);
} }
} }
$report->setDone($isDone);
$entityManager->persist($report); $entityManager->persist($report);
$entityManager->flush(); $entityManager->flush();
$entityManager->getCache()->evictCollection(Reports::class,"reportsTodos",$report->getId());
$reportsService->tryAddNextReport(); $reportsService->tryAddNextReport();
} }
return $this->redirect($request->headers->get('referer')); return $this->redirect($request->headers->get('referer'));
@@ -96,4 +110,24 @@ class ReportsController extends AbstractController
} }
return $this->redirectToRoute('app_client', ['id'=>$clientId]); return $this->redirectToRoute('app_client', ['id'=>$clientId]);
} }
#[Route('/testapi')]
public function test()
{
return new Response(var_export($_POST,true));
}
#[Route('/reports_highlight/{id}', name: 'app_report_highlight', methods: ['POST'])]
public function updateHighlight(EntityManagerInterface $entityManager, Request $request, Reports $reports): Response
{
$token = $request->get("token");
if ($this->isCsrfTokenValid('schedule-highlight', $token)) {
$highlightId = intval($request->get("highlight_id"));
$highlight = ReportHighlight::tryFrom($highlightId);
$reports->setHighlight($highlight);
$entityManager->persist($reports);
$entityManager->flush();
}
return $this->redirect($request->headers->get('referer') ?? $this->generateUrl('app_schedule'));
}
} }
@@ -10,14 +10,17 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class ScheduleController extends AbstractController class ScheduleController extends AbstractController
{ {
#[Route('/schedule/{typeId}', name: 'app_schedule', defaults: ['typeId' => 1])] #[Route('/schedule/{typeId}', name: 'app_schedule', defaults: ['typeId' => 1])]
#[IsGranted('scheduleView')]
public function index(EntityManagerInterface $entityManager, int $typeId = 1): Response public function index(EntityManagerInterface $entityManager, int $typeId = 1): Response
{ {
if ($typeId == 1) { if ($typeId == 1) {
$schedulesTmp = $entityManager->getRepository(Schedule::class)->findAllCurrent(); $schedulesTmp = $entityManager->getRepository(Schedule::class)->findAllCurrent();
$isOverdue = $entityManager->getRepository(Schedule::class)->findAllOverdue(true);
} else { } else {
$schedulesTmp = $entityManager->getRepository(Schedule::class)->findAllOverdue(); $schedulesTmp = $entityManager->getRepository(Schedule::class)->findAllOverdue();
} }
@@ -25,16 +28,16 @@ class ScheduleController extends AbstractController
return $this->render('schedule/index.html.twig', [ return $this->render('schedule/index.html.twig', [
'typeId' => $typeId, 'typeId' => $typeId,
'schedules' => $schedulesTmp, 'schedules' => $schedulesTmp,
'isOverdue' => $isOverdue ?? false,
]); ]);
} }
#[Route('/schedule_status', name: 'app_schedule_status')] #[Route('/schedule_status/{id}', name: 'app_schedule_status', methods: ['POST'])]
public function setDone(EntityManagerInterface $entityManager, Request $request): Response #[IsGranted('scheduleEdit')]
public function setDone(EntityManagerInterface $entityManager, Request $request, Schedule $schedule): Response
{ {
$scheduleId = $request->getPayload()->get('scheduleId'); $token = $request->get("token");
$token = $request->getPayload()->get("token"); if ($this->isCsrfTokenValid('schedule-status', $token)) {
if ($scheduleId && $this->isCsrfTokenValid('schedule-status', $token)) {
$schedule = $entityManager->getRepository(Schedule::class)->find($scheduleId);
$isDone = true; $isDone = true;
if ($schedule->isDone()) { if ($schedule->isDone()) {
$isDone = false; $isDone = false;
+31
View File
@@ -2,53 +2,72 @@
namespace App\Entity; namespace App\Entity;
use App\Enum\ReportHighlight;
use App\Repository\ClientsRepository; use App\Repository\ClientsRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity(repositoryClass: ClientsRepository::class)] #[ORM\Entity(repositoryClass: ClientsRepository::class)]
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'client_cache')]
class Clients class Clients
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
#[Groups(['client:read'])]
private ?int $id = null; private ?int $id = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Groups(['client:read','reportsAll:read'])]
private ?string $strName = null; private ?string $strName = null;
#[ORM\Column(type: Types::BIGINT)] #[ORM\Column(type: Types::BIGINT)]
#[Groups(['client:read'])]
private ?int $intNIP = null; private ?int $intNIP = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Groups(['client:read'])]
private ?string $strAddress = null; private ?string $strAddress = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Groups(['client:read'])]
private ?string $strEmail = null; private ?string $strEmail = null;
#[ORM\Column(type: Types::DATE_MUTABLE)] #[ORM\Column(type: Types::DATE_MUTABLE)]
#[Groups(['client:read'])]
private ?\DateTimeInterface $dateLegitimacy = null; private ?\DateTimeInterface $dateLegitimacy = null;
#[ORM\Column(type: Types::DATE_MUTABLE)] #[ORM\Column(type: Types::DATE_MUTABLE)]
#[Groups(['client:read'])]
private ?\DateTimeInterface $dateProclamation = null; private ?\DateTimeInterface $dateProclamation = null;
/** /**
* @var Collection<int, Schedule> * @var Collection<int, Schedule>
*/ */
#[Groups(['client:read'])]
#[ORM\OneToMany(targetEntity: Schedule::class, mappedBy: 'client')] #[ORM\OneToMany(targetEntity: Schedule::class, mappedBy: 'client')]
private Collection $schedules; private Collection $schedules;
#[Groups(['client:read'])]
#[ORM\Column(options: ["default" => "0"])] #[ORM\Column(options: ["default" => "0"])]
private bool $isDeleted = false; private bool $isDeleted = false;
#[Groups(['client:read'])]
#[ORM\Column(options: ["default" => "0"])] #[ORM\Column(options: ["default" => "0"])]
private bool $isComplete = false; private bool $isComplete = false;
#[Groups(['client:read'])]
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
private ?int $intPhone = null; private ?int $intPhone = null;
#[Groups(['client:read'])]
#[ORM\Column(enumType: ReportHighlight::class, options: ["default" => "0"])]
private ReportHighlight $highlight = ReportHighlight::STANDARD;
public function __construct() public function __construct()
{ {
$this->schedules = new ArrayCollection(); $this->schedules = new ArrayCollection();
@@ -197,4 +216,16 @@ class Clients
return $this; return $this;
} }
public function getHighlight(): ReportHighlight
{
return $this->highlight;
}
public function setHighlight(ReportHighlight $highlight): static
{
$this->highlight = $highlight;
return $this;
}
} }
+1
View File
@@ -7,6 +7,7 @@ use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: NotesRepository::class)] #[ORM\Entity(repositoryClass: NotesRepository::class)]
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'default_cache')]
class Notes class Notes
{ {
#[ORM\Id] #[ORM\Id]
+62
View File
@@ -2,47 +2,66 @@
namespace App\Entity; namespace App\Entity;
use App\Enum\ReportHighlight;
use App\Enum\ReportStatus; use App\Enum\ReportStatus;
use App\Enum\ReportTodoEnum; use App\Enum\ReportTodoEnum;
use App\Helper\MonthHelper;
use App\Repository\ReportsRepository; use App\Repository\ReportsRepository;
use App\Services\AddReports;
use App\Services\MailtoServices;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity(repositoryClass: ReportsRepository::class)] #[ORM\Entity(repositoryClass: ReportsRepository::class)]
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'report_cache')]
class Reports class Reports
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
#[Groups(['report:read','reportsAll:read'])]
private ?int $id = null; private ?int $id = null;
#[ORM\ManyToOne] #[ORM\ManyToOne]
#[ORM\JoinColumn(referencedColumnName: "id", nullable: false)] #[ORM\JoinColumn(referencedColumnName: "id", nullable: false)]
#[Groups(['report:read','reportsAll:read'])]
private ?Clients $client = null; private ?Clients $client = null;
#[ORM\Column(type: Types::DATE_MUTABLE)] #[ORM\Column(type: Types::DATE_MUTABLE)]
#[Groups(['report:read'])]
private ?\DateTimeInterface $dateStart = null; private ?\DateTimeInterface $dateStart = null;
/** /**
* @var Collection<int, ReportsTodo> * @var Collection<int, ReportsTodo>
*/ */
#[ORM\OneToMany(targetEntity: ReportsTodo::class, mappedBy: 'report', orphanRemoval: true)] #[ORM\OneToMany(targetEntity: ReportsTodo::class, mappedBy: 'report', orphanRemoval: true)]
#[ORM\Cache(usage: 'READ_ONLY', region: 'report_cache')]
#[Groups(['report:read','reportsAll:read'])]
private Collection $reportsTodos; private Collection $reportsTodos;
#[ORM\Column(options: ["default" => "0"])] #[ORM\Column(options: ["default" => "0"])]
#[Groups(['report:read'])]
private bool $isDelete = false; private bool $isDelete = false;
private int $numberOfReport = 0; private int $numberOfReport = 0;
#[ORM\Column(options: ["default" => "0"])] #[ORM\Column(options: ["default" => "0"])]
#[Groups(['report:read'])]
private bool $isDone = false; private bool $isDone = false;
#[ORM\Column] #[ORM\Column]
#[Groups(['report:read','reportsAll:read'])]
private ?int $number = null; private ?int $number = null;
#[ORM\Column(enumType: ReportHighlight::class, options: ["default" => "0"])]
#[Groups(['report:read'])]
private ReportHighlight $highlight = ReportHighlight::STANDARD;
public function __construct() public function __construct()
{ {
$this->reportsTodos = new ArrayCollection(); $this->reportsTodos = new ArrayCollection();
@@ -124,6 +143,11 @@ class Reports
return $this->isReportsTodo(ReportTodoEnum::COMPLETE); return $this->isReportsTodo(ReportTodoEnum::COMPLETE);
} }
public function isEmailSend(): bool
{
return $this->isReportsTodo(ReportTodoEnum::SENT_EMAIL);
}
public function countTodoReports(): int public function countTodoReports(): int
{ {
$count = 0; $count = 0;
@@ -135,6 +159,25 @@ class Reports
return $count; return $count;
} }
#[Groups(['reportsAll:read'])]
public function getStatusTodo(): ReportStatus
{
if ($this->countTodoReports() === 0) {
return ReportStatus::INSERT;
}
$status = ReportStatus::WORKING;
foreach ($this->getReportsTodos() as $reportsTodo) {
if ($reportsTodo->isDeleted()) {
continue;
}
if ($reportsTodo->getName() === ReportTodoEnum::COMPLETE) {
$status = ReportStatus::COMPLETE;
break;
}
}
return $status;
}
public function takeStatusTodo(): ReportStatus public function takeStatusTodo(): ReportStatus
{ {
if ($this->countTodoReports() === 0) { if ($this->countTodoReports() === 0) {
@@ -194,4 +237,23 @@ class Reports
return $this; return $this;
} }
public function takeMailToUrl(): string
{
$service = new MailtoServices();
return $service->takeUrl($this);
}
public function getHighlight(): ReportHighlight
{
return $this->highlight;
}
public function setHighlight(ReportHighlight $highlight): static
{
$this->highlight = $highlight;
return $this;
}
} }
+23 -1
View File
@@ -7,25 +7,42 @@ use App\Enum\ReportTodoEnum;
use App\Repository\ReportsTodoRepository; use App\Repository\ReportsTodoRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity(repositoryClass: ReportsTodoRepository::class)] #[ORM\Entity(repositoryClass: ReportsTodoRepository::class)]
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'report_cache')]
#[ORM\Table(
name: 'reports_todo',
uniqueConstraints: [
new ORM\UniqueConstraint(
name: 'report_todo',
columns: ['report_id', 'name'],
)
]
)]
class ReportsTodo class ReportsTodo
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
#[Groups(['reportstodo:read'])]
private ?int $id = null; private ?int $id = null;
#[ORM\Column(type: Types::SMALLINT, enumType: ReportTodoEnum::class)] #[ORM\Column(type: Types::SMALLINT, enumType: ReportTodoEnum::class)]
#[Groups(['reportstodo:read'])]
private ?ReportTodoEnum $name = null; private ?ReportTodoEnum $name = null;
#[ORM\ManyToOne] #[ORM\ManyToOne(inversedBy: 'reportsTodos')]
#[ORM\JoinColumn(referencedColumnName: 'id', nullable: false)] #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false)]
private ?Reports $report = null; private ?Reports $report = null;
#[ORM\Column(options: ["default" => false])] #[ORM\Column(options: ["default" => false])]
#[Groups(['reportstodo:read'])]
private ?bool $deleted = false; private ?bool $deleted = false;
#[Groups(['reportstodo:read'])]
private ?string $nameString = null;
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@@ -66,4 +83,9 @@ class ReportsTodo
return $this; return $this;
} }
public function getNameString(): ?string
{
return $this->name->name;
}
} }
+1
View File
@@ -8,6 +8,7 @@ use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ScheduleRepository::class)] #[ORM\Entity(repositoryClass: ScheduleRepository::class)]
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'schedule_cache')]
class Schedule class Schedule
{ {
#[ORM\Id] #[ORM\Id]
+1
View File
@@ -11,6 +11,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])]
#[ORM\Cache(usage: 'READ_ONLY', region: 'user_cache')]
class User implements UserInterface, PasswordAuthenticatedUserInterface class User implements UserInterface, PasswordAuthenticatedUserInterface
{ {
#[ORM\Id] #[ORM\Id]
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Enum;
enum ReportHighlight: int
{
case STANDARD = 0;
case INFO = 1;
case WARNING = 5;
case DANGER = 10;
public function borderColor(): string
{
return match ($this)
{
ReportHighlight::STANDARD => '',
ReportHighlight::WARNING => 'warning',
ReportHighlight::INFO => 'info',
ReportHighlight::DANGER => 'danger',
};
}
public function buttonLang(): string
{
return match ($this)
{
ReportHighlight::STANDARD => 'Brak',
ReportHighlight::WARNING => 'Ostrzeżenie',
ReportHighlight::INFO => 'Informacja',
ReportHighlight::DANGER => 'Ważne',
};
}
}
+4
View File
@@ -36,6 +36,10 @@ enum ReportTodoEnum: int {
} }
public function tryFromName(string $name): ?ReportTodoEnum public function tryFromName(string $name): ?ReportTodoEnum
{
return self::tryFromNameStatic($name);
}
public static function tryFromNameStatic(string $name): ?ReportTodoEnum
{ {
$name = strtoupper($name); $name = strtoupper($name);
foreach (self::cases() as $v) { foreach (self::cases() as $v) {
+11
View File
@@ -18,4 +18,15 @@ enum ScheduleStatus: int
ScheduleStatus::COMPLETED => 'success', ScheduleStatus::COMPLETED => 'success',
}; };
} }
public function borderColor(): string
{
return match ($this) {
ScheduleStatus::TODO => '',
ScheduleStatus::INCOMING => 'warning',
ScheduleStatus::OVERDUE => 'danger',
ScheduleStatus::COMPLETED => 'success',
};
}
} }
+49 -5
View File
@@ -1,38 +1,82 @@
<?php <?php
namespace App\Helper; namespace App\Helper;
use App\Services\OverdueReports;
use Symfony\Component\Validator\Constraints\Date;
class MonthHelper class MonthHelper
{ {
public function __construct(private readonly OverdueReports $overdueReports){}
public function takeMonths() : array public function takeMonths() : array
{ {
$startDate = new \DateTime("-6 months"); $startDate = new \DateTime("-6 months");
$startDate->modify("first day of this month"); $startDate->modify("first day of this month");
$return = []; $return = [];
$overdueCounts = $this->overdueReports->getList();
for ($i=1; $i<=11; $i++) { for ($i=1; $i<=11; $i++) {
$tmp = clone $startDate; $tmp = clone $startDate;
$tmp->modify("+$i month"); $tmp->modify("+$i month");
$return[$i] = $tmp; $return[$i] = [
'date' => $tmp,
'overdueCount' => 0
];
$key = $tmp->format('Y-m');
if (isset($overdueCounts[$key])) {
$return[$i]['overdueCount'] = $overdueCounts[$key];
unset($overdueCounts[$key]);
}
}
if (count($overdueCounts) > 0) {
$this->overdueReports->setLeftCount(array_sum($overdueCounts));
} }
return $return; return $return;
} }
public function getCurrentMonth(int $monthId) : \DateTime public function getCurrentMonthDate(int $monthId) : ?\DateTime
{ {
if ($monthId < 0) {
return null;
}
$months = $this->takeMonths(); $months = $this->takeMonths();
if ($monthId==0) { if ($monthId==0) {
$currentMonth = intval(date("n")); $currentMonth = intval(date("n"));
foreach ($months as $mId => $monthObj) { foreach ($months as $mId => $monthObj) {
if ($monthObj->format("m") == $currentMonth) { $date = $monthObj['date']->format('m');
if ($date == $currentMonth) {
$monthCurrent = $monthObj; $monthCurrent = $monthObj;
$monthId = $mId;
break; break;
} }
} }
} else { } else {
$monthCurrent = $months[$monthId]; $monthCurrent = $months[$monthId];
} }
return $monthCurrent; return $monthCurrent['date'];
}
public static function takeMonthNameForEmail(int $month) : string
{
$months = [
"stycznia", "lutego", "marca", "kwietnia", "maja", "czerwca", "lipca", "sierpnia", "września", "października", "listopada", "grudnia"
];
return $months[$month - 1] ?? "";
}
public static function takeDateFormatWithPolishMonths(\DateTimeInterface $date, bool $showDays = true): string
{
$day = $date->format('d');
$month = $date->format('n');
$year = $date->format('Y');
$monthName = self::takeMonthNameForEmail($month);
$stringReturn = "";
if ($showDays) {
$stringReturn .= "$day ";
}
$stringReturn .= "$monthName $year";
return $stringReturn;
} }
} }
+16 -8
View File
@@ -26,7 +26,7 @@ class ReportsRepository extends ServiceEntityRepository
return parent::findBy($criteria, $orderBy, $limit, $offset); // TODO: Change the autogenerated stub return parent::findBy($criteria, $orderBy, $limit, $offset); // TODO: Change the autogenerated stub
} }
public function findAllOverdue() public function findAllOverdue(int $clientId = 0)
{ {
$dateEnd = new \DateTime(); $dateEnd = new \DateTime();
$dateEnd->modify("last day of previous month"); $dateEnd->modify("last day of previous month");
@@ -36,7 +36,12 @@ class ReportsRepository extends ServiceEntityRepository
->andWhere('r.isDone = :isDone') ->andWhere('r.isDone = :isDone')
->setParameter('isDone', 0) ->setParameter('isDone', 0)
->setParameter('isDelete', 0) ->setParameter('isDelete', 0)
->setParameter('dateEnd', $dateEnd) ->setParameter('dateEnd', $dateEnd);
if ($clientId > 0) {
$reports->andWhere('r.client = :client');
$reports->setParameter('client', $clientId);
}
$reports = $reports
->orderBy("r.number","ASC") ->orderBy("r.number","ASC")
->getQuery() ->getQuery()
->getResult(); ->getResult();
@@ -48,8 +53,9 @@ class ReportsRepository extends ServiceEntityRepository
/** /**
* @return Reports[] Returns an array of Reports objects * @return Reports[] Returns an array of Reports objects
* @throws \DateMalformedStringException
*/ */
public function findAllInMonth(\DateTime $date): array public function findAllInMonth(\DateTime $date, int $clientId = 0): array
{ {
$dateStart = clone $date; $dateStart = clone $date;
$dateStart->modify("first day of this month"); $dateStart->modify("first day of this month");
@@ -61,13 +67,15 @@ class ReportsRepository extends ServiceEntityRepository
->andWhere('r.isDelete = :deleted') ->andWhere('r.isDelete = :deleted')
->setParameter('dateStart', $dateStart->format('Y-m-d')) ->setParameter('dateStart', $dateStart->format('Y-m-d'))
->setParameter('dateEnd', $dateEnd->format('Y-m-d')) ->setParameter('dateEnd', $dateEnd->format('Y-m-d'))
->setParameter('deleted', 0) ->setParameter('deleted', 0);
if ($clientId > 0) {
$reports->andWhere('r.client = :client');
$reports->setParameter('client', $clientId);
}
$reports = $reports
->orderBy("r.number","ASC") ->orderBy("r.number","ASC")
// ->setMaxResults(10)
->getQuery() ->getQuery()
->getResult() ->getResult();
;
// $this->countReport($reports); // $this->countReport($reports);
return $reports; return $reports;
+29 -5
View File
@@ -11,25 +11,49 @@ use Doctrine\Persistence\ManagerRegistry;
*/ */
class ScheduleRepository extends ServiceEntityRepository class ScheduleRepository extends ServiceEntityRepository
{ {
private const _OVERDUE_DAYS_START = 15;
private const _OVERDUE_DAYS_END = 30;
public function __construct(ManagerRegistry $registry) public function __construct(ManagerRegistry $registry)
{ {
parent::__construct($registry, Schedule::class); parent::__construct($registry, Schedule::class);
} }
public function findAllOverdue() public function findAllOverdue(bool $onlyCount = false) : array|int
{ {
$dateEnd = new \DateTime("now"); $dateEnd = new \DateTime("now");
$dateEnd->modify("-".self::_OVERDUE_DAYS_START." days");
$reports = $this->createQueryBuilder('s') $reports = $this->createQueryBuilder('s')
->where('s.isDone = :isDone') ->where('s.isDone = :isDone')
->andWhere('s.dateEnd < :dateEnd') ->andWhere('s.dateEnd < :dateEnd')
// ->andWhere('s.isDelete = :isDelete') // ->andWhere('s.isDelete = :isDelete')
->setParameter('isDone', 0) ->setParameter('isDone', 0)
// ->setParameter('isDelete', 0)
->setParameter('dateEnd', $dateEnd);
if ($onlyCount) {
$reports->select('COUNT(s.id)');
return $reports->getQuery()->getSingleScalarResult();
}
return $reports->getQuery()->getResult();
}
public function isOverdue(): bool
{
$dateEnd = new \DateTime("now");
$dateEnd->modify("-".self::_OVERDUE_DAYS_START." days");
$query = $this->createQueryBuilder('s')
->select('count(s.id)')
->where('s.isDone = :isDone')
->andWhere('s.dateEnd < :dateEnd')
// ->andWhere('s.isDelete = :isDelete')
->setParameter('isDone', 0)
// ->setParameter('isDelete', 0) // ->setParameter('isDelete', 0)
->setParameter('dateEnd', $dateEnd) ->setParameter('dateEnd', $dateEnd)
->getQuery() ->getQuery()
->getResult(); ->getSingleScalarResult();
return $reports; trigger_error(var_export($query,true));
} }
/** /**
@@ -39,9 +63,9 @@ class ScheduleRepository extends ServiceEntityRepository
{ {
$date = new \DateTime(); $date = new \DateTime();
$dateStart = clone $date; $dateStart = clone $date;
$dateStart->modify("-15 days"); $dateStart->modify("-".self::_OVERDUE_DAYS_START." days");
$dateEnd = clone $date; $dateEnd = clone $date;
$dateEnd->modify("+30 days"); $dateEnd->modify("+".self::_OVERDUE_DAYS_END." days");
return $this->createQueryBuilder('r') return $this->createQueryBuilder('r')
->where('r.dateEnd >= :dateStart') ->where('r.dateEnd >= :dateStart')
->andWhere('r.dateEnd <= :dateEnd') ->andWhere('r.dateEnd <= :dateEnd')
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace App\Security;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class SchedulesVoter extends Voter
{
const VIEW = "scheduleView";
const EDIT = "scheduleEdit";
public function __construct(readonly private Security $security)
{
}
protected function supports(string $attribute, mixed $subject): bool
{
if (!in_array($attribute, [self::VIEW, self::EDIT])) {
return false;
}
return true; }
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
return match ($attribute) {
self::VIEW => $this->canView(),
self::EDIT => $this->canEdit()
};
}
private function canView(): bool
{
if ($this->canAll()) {
return true;
}
return $this->security->isGranted('ROLE_SCHEDULE_VIEW');
}
private function canEdit(): bool
{
if ($this->canAll()) {
return true;
}
return $this->canView();
}
private function canAll(): bool
{
return $this->security->isGranted('ROLE_ACCESS_ALL');
}
}
+2
View File
@@ -84,6 +84,7 @@ class AddReports
$report->setNumber($numberOfReports + 1); $report->setNumber($numberOfReports + 1);
$this->manager->persist($report); $this->manager->persist($report);
$this->manager->flush(); $this->manager->flush();
$this->manager->getCache()->evictEntityRegion(Reports::class);
} }
return true; return true;
} }
@@ -107,6 +108,7 @@ class AddReports
$report->setDateStart($dateToReport->modify("+".self::NEXT_REPORT_MONTH." months")); $report->setDateStart($dateToReport->modify("+".self::NEXT_REPORT_MONTH." months"));
$this->manager->persist($report); $this->manager->persist($report);
$this->manager->flush(); $this->manager->flush();
$this->manager->getCache()->evictEntityRegion(Reports::class);
return true; return true;
} }
} }
+109
View File
@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Entity\Clients;
use Doctrine\ORM\EntityManager;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class InvoiceServices
{
private Clients $clients;
private \DateTimeInterface $reportDate;
private string $apiKey;
public function __construct(
private readonly EntityManager $entityManager,
private readonly HttpClientInterface $curl){}
/**
* @param Clients $clients
*/
public function setClients(Clients $clients): void
{
$this->clients = $clients;
}
public function setApiKey(string $api): void
{
$this->apiKey = $api;
}
/**
* @param \DateTimeInterface $reportDate
*/
public function setReportDate(\DateTimeInterface $reportDate): void
{
$this->reportDate = $reportDate->modify('-1 month');
}
public function send(): void
{
$this->curl->request(
'POST',
// 'https://kancelariakolczynski.fakturownia.pl/invoices.json',
'http://sprawozdania.docker/testapi',
[
'headers' => [
'Accept' => 'application/json',
],
'json' => $this->preparePost()
]
);
}
private function postToJson(): string
{
return json_encode($this->preparePost());
}
private function preparePost() : array
{
if (!isset($this->clients) || !isset($this->reportDate)) {
return [];
}
$todayObj = new \DateTime("now");
$todayStr = $todayObj->format('Y-m-d');
$paymentToObj = new \DateTime("+7 days");
$paymentToStr = $paymentToObj->format('Y-m-d');
$monthStartPeriod = (clone $this->reportDate)->modify("-2 months")->format('m');
$periodStr = $monthStartPeriod . ' - ' . $this->reportDate->format('m') . '/' . $this->reportDate->format('Y');
$invoice = [
'kinds' => ["vat","proforma"],
'number' => null,
'sell_date' => $todayStr,
'issue_date' => $todayStr,
'payment_to' => $paymentToStr,
'buyer_name' => $this->clients->getStrName(),
'buyer_email' => $this->clients->getStrEmail(),
'buyer_tax_no' => $this->clients->getIntNIP(),
'positions' => [
[
'name' => "Przygotowanie sprawozdanie z wykonania układu za okres $periodStr",
'tax' => 23,
'total_price_gross' => 553.5
]
]
];
// "kind":"vat",
// "number": null,
// "sell_date": "2013-01-16",
// "issue_date": "2013-01-16",
// "payment_to": "2013-01-23",
// "seller_name": "Wystawca Sp. z o.o.",
// "seller_tax_no": "6272616681",
// "buyer_name": "Klient1 Sp. z o.o.",
// "buyer_email": "buyer@testemail.pl",
// "buyer_tax_no": "6272616681",
// "positions":[
// {"name":"Produkt A1", "tax":23, "total_price_gross":10.23, "quantity":1},
// {"name":"Produkt A2", "tax":0, "total_price_gross":50, "quantity":3}
// ]
return [
'api_token' => $this->apiKey,
'invoice' => $invoice,
];
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Entity\Clients;
use App\Entity\Reports;
use App\Helper\MonthHelper;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Twig\Environment;
class MailtoServices extends AbstractController
{
public function takeUrl(Reports $reports): string
{
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Entity\Clients;
use App\Entity\Reports;
use Doctrine\ORM\EntityManager;
class OverdueReports
{
private array $list;
private int $leftCount = 0;
private ?Clients $client = null;
public function __construct(private readonly EntityManager $entity){}
public function getList(): array
{
if(!isset($this->list)){
$this->list = $this->takeOverdueList();
}
return $this->list;
}
public function setLeftCount(int $leftCount): void
{
$this->leftCount = $leftCount;
}
public function setClient(?Clients $client): void
{
$this->client = $client;
}
public function getLeftCount(): int
{
return $this->leftCount;
}
private function takeOverdueList() : array
{
$clientId = 0;
if($this->client){
$clientId = $this->client->getId();
}
$reports = $this->entity->getRepository(Reports::class)->findAllOverdue($clientId);
if (!$reports) {
return [];
}
$toReturn = [];
/** @var Reports $report */
foreach ($reports as $report) {
$date = $report->getDateStart()->format('Y-m');
if (!array_key_exists($date, $toReturn)) {
$toReturn[$date] = 1;
} else {
$toReturn[$date]++;
}
}
return $toReturn;
}
}
+35
View File
@@ -2,6 +2,10 @@
// src/Twig/AppExtension.php // src/Twig/AppExtension.php
namespace App\Twig; namespace App\Twig;
use App\Entity\Clients;
use App\Entity\Reports;
use App\Helper\MonthHelper;
use App\Services\AddReports;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
use Twig\TwigFilter; use Twig\TwigFilter;
use Twig\TwigFunction; use Twig\TwigFunction;
@@ -19,6 +23,8 @@ class AppExtension extends AbstractExtension
{ {
return [ return [
new TwigFunction('enum', [$this, 'enumChecker']), new TwigFunction('enum', [$this, 'enumChecker']),
new TwigFunction('is_route_active', [$this, 'getActualRoute']),
new TwigFunction('getMailtoLink', [$this, 'getMailtoLink']),
]; ];
} }
@@ -35,4 +41,33 @@ class AppExtension extends AbstractExtension
{ {
return $currentEnum->name === $needEnumName; return $currentEnum->name === $needEnumName;
} }
public function getActualRoute(string $value, string $route): string
{
return $value === $route ? 'active' : '';
}
public function getMailtoLink(Reports $reports): string
{
$subject = rawurlencode("Sprawozdanie z wykonania układu nr {$reports->getNumber()}");
$dateBase = (clone $reports->getDateStart())->modify("-1 month");
$template = "email/report.html.twig";
$dateStart = (clone $dateBase)->modify("-".(AddReports::NEXT_REPORT_MONTH-1)." months")->modify("first day of this month");
if ($reports->getNumber() === 1){
$template = "email/firstReport.html.twig";
$dateStart = $reports->getClient()->getDateLegitimacy();
}
$view = sprintf(
file_get_contents($_SERVER['PWD']."/templates/".$template),
MonthHelper::takeDateFormatWithPolishMonths($dateStart),
MonthHelper::takeDateFormatWithPolishMonths($dateBase->modify("last day of this month"))
);
$content = rawurlencode($view);
/** @var Clients $client */
$client = $reports->getClient();
return "mailto:{$client->getStrEmail()}?subject=$subject&body=$content";
}
} }
+2 -3
View File
@@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="pl"> <html lang="pl" data-bs-theme="">
<head> <head>
{% if app.environment == "prod" %} {% if app.environment == "prod" %}
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" /> <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" />
@@ -7,9 +7,8 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="turbo-prefetch" content="false">
<title>{% block title %}Sprawozdania{% endblock %}</title> <title>{% block title %}Sprawozdania{% endblock %}</title>
{% block stylesheets %} {% block stylesheets %}
{# 'app' must match the first argument to addEntry() in webpack.config.js #} {# 'app' must match the first argument to addEntry() in webpack.config.js #}
{{ encore_entry_link_tags('app') }} {{ encore_entry_link_tags('app') }}
+61 -7
View File
@@ -1,5 +1,7 @@
{% import 'reports/listInclude.html.twig' as sets %}
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block body %} {% block body %}
<div data-controller="customer-show">
{% if client.isComplete %} {% if client.isComplete %}
<div class="alert alert-warning"><i class="fa-solid fa-circle-info"></i> Zakończono generowanie sprawozdań!</div> <div class="alert alert-warning"><i class="fa-solid fa-circle-info"></i> Zakończono generowanie sprawozdań!</div>
{% endif %} {% endif %}
@@ -27,12 +29,19 @@
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
<div class="card-footer" style="display: inline-flex;margin: auto;"> <div class="card-footer">
<div class='edit-client-group'>
{% if is_granted('reportView') %}
<div>
<a href="{{ path('app_base',{clientId: client.id}) }}" class="btn btn-outline-info">Wyświetl sprawozdania</a>
</div>
{% endif %}
{% if is_granted('reportEdit') %} {% if is_granted('reportEdit') %}
<form action="{{ path('app_set_complete',{id: client.id}) }}" method="post" style="padding: 0 5px;"> <div>
<form action="{{ path('app_set_complete',{id: client.id}) }}" method="post">
<input type="hidden" name="isComplete" value="1"> <input type="hidden" name="isComplete" value="1">
<input type="hidden" name="token" value="{{ csrf_token("set-complete-item") }}"> <input type="hidden" name="token" value="{{ csrf_token("set-complete-item") }}">
<button class="btn btn-warning" type="submit"> <button class="btn btn-outline-warning" type="submit">
{% if client.isComplete %} {% if client.isComplete %}
Wznów Wznów
{% else %} {% else %}
@@ -40,16 +49,22 @@
{% endif %} {% endif %}
</button> </button>
</form> </form>
</div>
{% endif %} {% endif %}
{% if not client.isComplete and is_granted('clientEdit') %} {% if not client.isComplete and is_granted('clientEdit') %}
<a href="{{ path('app_add_client',{id: client.id}) }}" class="btn btn-success">Edytuj</a> <div>
<a href="{{ path('app_add_client',{id: client.id}) }}" class="btn btn-outline-success">Edytuj</a>
</div>
{% endif %} {% endif %}
{% if is_granted('clientDelete') %} {% if is_granted('clientDelete') %}
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#removeConfirmModal">Usuń</button> <div>
<button class="btn btn-outline-danger" type="button" data-bs-toggle="modal" data-bs-target="#removeConfirmModal">Usuń</button>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
{% if is_granted('reportView') %} {% if is_granted('reportView') %}
<div class="card text-center bg-light-subtle"> <div class="card text-center bg-light-subtle">
<div class="card-header"> <div class="card-header">
@@ -58,14 +73,18 @@
<form action="{{ path('app_add_next_report') }}" method="post"> <form action="{{ path('app_add_next_report') }}" method="post">
<input type="hidden" name="clientId" value="{{ client.id }}"> <input type="hidden" name="clientId" value="{{ client.id }}">
<input type="hidden" name="token" value="{{ csrf_token('add-next-report') }}"> <input type="hidden" name="token" value="{{ csrf_token('add-next-report') }}">
<button type="submit" class="btn btn-primary">Dodaj następne sprawozdanie</button> <button type="submit" class="btn btn-outline-primary">Dodaj następne sprawozdanie</button>
</form> </form>
{% endif %} {% endif %}
</div> </div>
<div class="card-body"> <div class="card-body">
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
{% for report in reports %} {% for report in reports %}
<li class="list-group-item d-flex align-items-center"> <li class="list-group-item d-flex align-items-center"
data-action="click->customer-show#showModal"
data-customer-show-reportid-param="{{ report.id }}"
data-customer-show-date-param="{{ report.getDateStart | date("Y-m-d") }}"
>
<span class="badge rounded-pill text-bg-primary">{{ report.number }}</span> <span class="badge rounded-pill text-bg-primary">{{ report.number }}</span>
<span class="badge rounded-pill text-bg-light">{{ report.getDateStart | date("Y-m-d") }}</span> <span class="badge rounded-pill text-bg-light">{{ report.getDateStart | date("Y-m-d") }}</span>
@@ -101,6 +120,21 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="card">
<div class="card-header text-center"><h2>Oznaczenie</h2></div>
<div class="card-body">
<form action="{{ path('app_client_highlight',{id: client.id}) }}" method="post">
<input type="hidden" name="token" value="{{ csrf_token('client-highlight') }}">
{% for status in highlightStatus %}
<button type="submit" name="highlight_id" value="{{ status.value }}" class="btn btn-{{ status.borderColor }}">
{{ status.buttonLang }}
</button>
{% endfor %}
</form>
</div>
</div>
</div> </div>
</div> </div>
@@ -129,4 +163,24 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="showReportModal" tabindex="-1" aria-labelledby="showReportModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="showReportModalLabel"></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
<div id="allCardsReport">
{% for report in reports %}
{{ sets.cardView(report, reportStatus, reportToDoList) }}
{% endfor %}
</div>
</div>
{% endblock %} {% endblock %}
@@ -0,0 +1,17 @@
Szanowni Państwo,
w związku z prawomocnym zatwierdzeniem układu sąd ustanowił nadzorcą wykonania układu Pana Michała Kolczyńskiego. Wiąże się to z koniecznością składania do sądu kwartalnych sprawozdań z wykonania układu i realizacji płatności.
Co za tym idzie, zwracam się z uprzejmą prośbą o przesłanie potwierdzeń dokonanych przelewów rat układowych za okres od %s roku do %s roku
oraz o odpowiedź na następujące pytania:
1) Czy w okresie sprawozdawczym nastąpiły zwolnienia lub zatrudnienia pracowników;
2) Czy zbyty lub nabyty został jakiś majątek?
3) Proszę wymienić najważniejsze wydarzenia w działalności. (Czy udało się pozyskać nowych kontrahentów? Rozszerzenie rynku zbytu, nowe usługi, obniżenie kosztów utrzymania, nowi dostawcy?)
4) Czy nastąpiły jakieś zmiany organizacyjne?
Proszę o przekazanie niniejszych informacji oraz dokumentów w nieprzekraczalnym terminie 5 dni od otrzymania wiadomości.
W razie pytań lub wątpliwości proszę o kontakt z asystentem prowadzącym postępowanie.
+15
View File
@@ -0,0 +1,15 @@
Szanowni Państwo,
zwracam się z uprzejmą prośbą o przesłanie potwierdzeń dokonanych przelewów rat układowych za okres od %s roku do %s roku oraz o odpowiedź na następujące pytania:
1) Czy w okresie sprawozdawczym nastąpiły zwolnienia lub zatrudnienia pracowników;
2) Czy zbyty lub nabyty został jakiś majątek?
3) Proszę wymienić najważniejsze wydarzenia w działalności. (Czy udało się pozyskać nowych kontrahentów? Rozszerzenie rynku zbytu, nowe usługi, obniżenie kosztów utrzymania, nowi dostawcy?)
4) Czy nastąpiły jakieś zmiany organizacyjne?
Proszę o przekazanie niniejszych informacji oraz dokumentów w nieprzekraczalnym terminie 5 dni od otrzymania wiadomości.
W razie pytań lub wątpliwości proszę o kontakt z asystentem prowadzącym postępowanie.
@@ -1,40 +1,42 @@
{% macro month_menu(routeName, months, currentMonth) %} {% macro month_menu(routeName, months, currentMonth, countOverdueLeft, clientId, client) %}
{% if client %}
<div class="row justify-content-center">
<div class="alert alert-info col-3 text-center " role="alert">
<h5 class="alert-heading"><strong>{{ client.strName }}</strong></h5>
<p>
<a href="{{ path(routeName,{monthId: 0}) }}" class="btn btn-outline-warning">Wróć do wszystkich</a>
</p>
</div>
</div>
{% endif %}
<div class="row reports-months-all"> <div class="row reports-months-all">
<div class="col"> <ul class="nav nav-underline nav-fill">
<div class="card"> <li class="nav-item position-relative">
<a href="{{ path(routeName, {monthId:-1}) }}" class="btn {% if currentMonth is null %}btn-success{% else %}btn-secondary{% endif %}">Zaległe</a> <a href="{{ path(routeName, {monthId:-1, clientId:clientId}) }}" class="nav-link {% if currentMonth is null %}active{% endif %}">Zaległe</a>
</div> {% if countOverdueLeft > 0 %}
</div> <div class="spinner-grow spinner-grow-sm text-danger overdue"></div>
{% for id,month in months %} {% endif %}
</li>
{% for id,obj in months %}
{% set month = obj.date %}
{% set isCurrent = currentMonth is not null and month | date("Y-m") == currentMonth | date("Y-m") %} {% set isCurrent = currentMonth is not null and month | date("Y-m") == currentMonth | date("Y-m") %}
<div class="col col-xxl-1 col-xl-2 col-lg-2 col-md-3 col-sm-3 col-4"> <li class="nav-item position-relative"
<div class="card"> {% if obj.overdueCount > 0 %}
<a href="{{ path(routeName,{monthId: id}) }}" class="btn {% if isCurrent %}btn-success{% else %}btn-secondary{% endif %}"> data-toggle="tooltip" data-bs-placement="bottom" title="Zaległe sprawozdania do zrobienia"
{% endif %}
>
<a class="nav-link {% if isCurrent %} active{% endif %}" href="{{ path(routeName,{monthId: id, clientId:clientId}) }}">
{% if routeName=='app_schedule' %} {% if routeName=='app_schedule' %}
{{ id }} {{ id }}
{% else %} {% else %}
{{ month | date("Y-m") }} {{ month | date("Y-m") }}
{% endif %} {% endif %}
</a> </a>
</div> {% if obj.overdueCount > 0 %}
</div> <div class="spinner-grow spinner-grow-sm text-danger overdue"></div>
{% endif %}
</li>
{% endfor %} {% endfor %}
{# <ul class="nav nav-tabs">#} </ul>
{# <li class="nav-item">#}
{# <a class="nav-link {% if currentMonth is null %}active{% endif %}" aria-current="page" href="{{ path(routeName, {monthId:-1}) }}">Zaległe</a>#}
{# </li>#}
{# {% for id,month in months %}#}
{# {% set isCurrent = currentMonth is not null and month | date("Y-m") == currentMonth | date("Y-m") %}#}
{# <li class="nav-item">#}
{# <a class="nav-link {% if isCurrent %}active{% endif %}" href="{{ path(routeName,{monthId: id}) }}" aria-current="page">#}
{# {% if routeName=='app_schedule' %}#}
{# {{ id }}#}
{# {% else %}#}
{# {{ month | date("Y-m") }}#}
{# {% endif %}#}
{# </a>#}
{# </li>#}
{# {% endfor %}#}
{# </ul>#}
</div> </div>
{% endmacro %} {% endmacro %}
+36 -49
View File
@@ -1,69 +1,56 @@
{#<nav class="navbar navbar-inverse">#} {% set route = app.request.attributes.get('_route') %}
{# <div class="container-fluid">#} <nav class="navbar navbar-light navbar-expand-md justify-content-md-center justify-content-start">
{# <div class="navbar-header">#}
{# <a class="navbar-brand" href="{{ path('app_base') }}">WebSiteName</a>#}
{# </div>#}
{# <ul class="nav navbar-nav">#}
{# <li class="active"><a href="{{ path('app_base') }}">Home</a></li>#}
{# <li class="dropdown">#}
{# <a class="dropdown-toggle" data-toggle="dropdown" href="#">Klienci#}
{# <span class="caret"></span></a>#}
{# <ul class="dropdown-menu">#}
{# <li><a href="{{ path('app_customers_list') }}">Lista</a></li>#}
{# <li><a href="{{ path('app_base') }}">Dodaj klienta</a></li>#}
{# <li><a href="#">Page 1-3</a></li>#}
{# </ul>#}
{# </li>#}
{# <li><a href="#">Page 2</a></li>#}
{# <li><a href="#">Page 3</a></li>#}
{# </ul>#}
{# </div>#}
{#</nav>#}
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="{{ path('index') }}">CRM</a> <a class="navbar-brand" href="{{ path('index') }}">CRM</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="navbar-collapse collapse justify-content-between align-items-center w-100" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav">
{% if is_granted('reportView') %} {% if is_granted('reportView') %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ path('app_base') }}">Sprawozdania</a> <a class="nav-link {{ is_route_active('app_base', route) }}" aria-current="page" href="{{ path('app_base') }}">Sprawozdania</a>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('scheduleView') %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ path('app_schedule') }}">Harmonogramy</a> <a class="nav-link {{ is_route_active('app_schedule', route) }}" aria-current="page" href="{{ path('app_schedule') }}">Harmonogramy</a>
</li> </li>
{% endif %}
{% if is_granted('clientView') %} {% if is_granted('clientView') %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ path('app_customers_list') }}">Klienci</a> <a class="nav-link {{ is_route_active('app_customers_list', route) }}" aria-current="page" href="{{ path('app_customers_list') }}">Klienci</a>
</li> </li>
{% endif %} {% endif %}
{# <li class="nav-item dropdown">#}
{# <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">#}
{# Klienci#}
{# </a>#}
{# <ul class="dropdown-menu" aria-labelledby="navbarDropdown">#}
{# <li><a class="dropdown-item" href="{{ path('app_customers_list') }}">Lista</a></li>#}
{# <li><a class="dropdown-item" href="{{ path('app_add_client') }}">Dodaj klienta</a></li>#}
{# <li><hr class="dropdown-divider"></li>#}
{# <li><a class="dropdown-item" href="#">Something else here</a></li>#}
{# </ul>#}
{# </li>#}
{# <li class="nav-item">#}
{# <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>#}
{# </li>#}
</ul> </ul>
{% if is_granted('clientAdd') %} <ul class="navbar-nav">
<div class="d-flex"> <li class="nav-item">
<a href="{{ path('app_add_client') }}" class="btn btn-success btn-outline-dark" type="submit">Dodaj klienta</a> <a href="{{ path('app_add_client') }}" class="btn btn-outline-success" type="submit"><i class="fa-regular fa-id-card"></i> Dodaj klienta</a>
</div> </li>
{% endif %} <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-circle-user"></i>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li data-controller="thememode">
<p class="nav-link dark" data-action="click->thememode#switch" data-thememode-mode-param="dark" data-bs-theme-value="dark"><i class="fa-solid fa-moon"></i> Tryb ciemny</p>
<p class="nav-link light" data-action="click->thememode#switch" data-thememode-mode-param="light" data-bs-theme-value="light"><i class="fa-regular fa-sun"></i> Tryb jasny</p>
</li>
<li><hr class="dropdown-divider"></li>
<li><a class="nav-link" href="{{ path('app_logout') }}"><i class="fa-solid fa-right-from-bracket"></i> Wyloguj</a></li>
</ul>
</li>
</ul>
{# {% if is_granted('clientAdd') %}#}
{# <div class="d-flex">#}
{# <a href="{{ path('app_add_client') }}" class="btn btn-outline-success" type="submit">Dodaj klienta</a>#}
{# </div>#}
{# {% endif %}#}
{# <div class="d-flex">#}
{# <a href="{{ path('app_logout') }}" class="btn btn-outline-primary" type="submit">Wyloguj</a>#}
{# </div>#}
<div class="d-flex">
<a href="{{ path('app_logout') }}" class="btn btn-primary btn-outline-dark" type="submit">Wyloguj</a>
</div>
{# <form class="d-flex">#} {# <form class="d-flex">#}
{# <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">#} {# <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">#}
{# <button class="btn btn-outline-success" type="submit">Search</button>#} {# <button class="btn btn-outline-success" type="submit">Search</button>#}
+2 -38
View File
@@ -2,7 +2,7 @@
{% import 'navigation/switch_months.html.twig' as navigation %} {% import 'navigation/switch_months.html.twig' as navigation %}
{% import 'reports/listInclude.html.twig' as sets %} {% import 'reports/listInclude.html.twig' as sets %}
{% block body %} {% block body %}
{{ navigation.month_menu('app_base',months,currentMonth) }} {{ navigation.month_menu('app_base',months,currentMonth,countOverdueLeft,clientId,client) }}
<div class=" <div class="
row row
d-flex d-flex
@@ -17,43 +17,7 @@
<h3 class="card-header">{{ reports.status.takeLang }}</h3> <h3 class="card-header">{{ reports.status.takeLang }}</h3>
<div class="card-body"> <div class="card-body">
{% for report in reports.reports %} {% for report in reports.reports %}
<div class="card text-{{ reports.status.textColor }} border-dark report-one {{ reports.status.name | lower }}"> {{ sets.cardView(report, reports.status, reportToDoList,highlightStatus) }}
<div class="report-number">
<div class="badge bg-{{ reports.status.takeBadgeBg }} text-dark" data-toggle="tooltip" data-placement="bottom" title="Numer sprawozdania">{{ report.number }}</div>
</div>
{% if enum(reports.status, 'COMPLETE') %}
<div class="report-mark-uncompleted">
{# <div class="badge bg-danger text-dark" data-toggle="tooltip" data-placement="bottom" title="Oznacz jako nie zakończone">#}
<form action="{{ path('app_set_status_todo',{id:report.id}) }}" method="post">
<input type="hidden" name="token" value="{{ csrf_token('report-status') }}">
<input type="hidden" name="unDone" value="1">
<button type="submit" class="badge bg-danger text-dark" data-toggle="tooltip" data-placement="bottom" title="Oznacz jako nie zakończone"><i class="fa fa-solid fa-remove"></i></button>
</form>
{# </div>#}
</div>
{% endif %}
<div class="card-header">
<h5 class="card-title">
{% if is_granted('clientView') %}
<a href="{{ path('app_client',{id: report.client.id}) }}" class="link-{{ reports.status.linkColor }}">{{ report.client.strName }}</a>
{% else %}
{{ report.client.strName }}
{% endif %}
</h5>
</div>
{% if not enum(reports.status, 'COMPLETE') %}
<div class="card-body">
<p class="card-text">{{ sets.statusForm(report, reportToDoList, reports.status) }}</p>
</div>
{% endif %}
{% if enum(reports.status, 'WORKING') %}
<div class="card-footer bg-transparent">
{{ sets.progress(report, reportToDoList|length) }}
</div>
{% endif %}
</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
+72 -12
View File
@@ -3,20 +3,14 @@
<input type="hidden" name="token" value="{{ csrf_token('report-status') }}"> <input type="hidden" name="token" value="{{ csrf_token('report-status') }}">
<div class="btn-group" role="group" aria-label="Todos"> <div class="btn-group" role="group" aria-label="Todos">
{% for todo in reportToDoList %} {% for todo in reportToDoList %}
<input onchange="this.form.submit()" <button type="submit" class="btn-check" name="{{ todo.value }}" value='1' id="checkboxToDo{{ todo.value }}Id{{ report.id }}"></button>
name="{{ todo.value }}" <label class="btn
type="checkbox"
class="btn-check"
id="checkboxToDo{{ todo.value }}Id{{ report.id }}"
{% if report.isReportsTodo(todo) %} {% if report.isReportsTodo(todo) %}
checked="checked" btn-{{ reportEnum.checkboxColor }}
{% else %}
btn-outline-{{ reportEnum.checkboxColor }}
{% endif %} {% endif %}
{% if not is_granted('reportEdit') %} d-flex align-items-center"
disabled
{% endif %}
autocomplete="off"
>
<label class="btn btn-outline-{{ reportEnum.checkboxColor }} d-flex align-items-center"
for="checkboxToDo{{ todo.value }}Id{{ report.id }}" for="checkboxToDo{{ todo.value }}Id{{ report.id }}"
data-toggle="tooltip" data-placement="bottom" title="{{ todo.takeLang }}"> data-toggle="tooltip" data-placement="bottom" title="{{ todo.takeLang }}">
<i class="fa fa-solid fa-{{ todo.takeIcon }}"> </i> <i class="fa fa-solid fa-{{ todo.takeIcon }}"> </i>
@@ -40,3 +34,69 @@
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro cardView(report, groupStatus, reportToDoList, highlightStatus) %}
<div class="card text-{{ groupStatus.textColor }} report-one {{ groupStatus.name | lower }} border-{{ report.client.highlight.borderColor }}" data-report_id="{{ report.id }}">
<div class="report-number" data-bs-toggle="modal" data-bs-target="#editSchedule{{ report.id }}">
<div class="badge bg-{{ groupStatus.takeBadgeBg }} text-dark" data-toggle="tooltip" data-placement="bottom" title="Numer sprawozdania">{{ report.number }}</div>
</div>
{% if enum(groupStatus, 'COMPLETE') and false %}
<div class="report-mark-uncompleted">
<form action="{{ path('app_set_status_todo',{id:report.id}) }}" method="post">
<input type="hidden" name="token" value="{{ csrf_token('report-status') }}">
<input type="hidden" name="unDone" value="1">
<button type="submit" class="badge bg-danger" data-toggle="tooltip" data-placement="bottom" title="Oznacz jako nie zakończone"><i class="fa fa-solid fa-remove"></i></button>
</form>
</div>
{% endif %}
<div class="card-header bg-{{ report.highlight.borderColor }}">
<h5 class="card-title">
{% if is_granted('clientView') %}
<a href="{{ path('app_client',{id: report.client.id}) }}" >{{ report.client.strName }}</a>
{% else %}
{{ report.client.strName }}
{% endif %}
</h5>
</div>
{# {% if not enum(groupStatus, 'COMPLETE') %}#}
<div class="card-body">
<p class="card-text">{{ _self.statusForm(report, reportToDoList, groupStatus) }}</p>
{% if is_granted('reportEdit') and not report.isEmailSend %}
<a class="btn btn-sm btn-outline-primary" href="{{ getMailtoLink(report) }}">Wyślij maila</a>
{% endif %}
</div>
{# {% endif %}#}
{% if enum(groupStatus, 'WORKING') %}
<div class="card-footer bg-transparent">
{{ _self.progress(report, reportToDoList|length) }}
</div>
{% endif %}
</div>
<!-- Modal -->
<div class="modal fade" id="editSchedule{{ report.id }}" tabindex="-1" aria-labelledby="editSchedule{{ report.id }}Label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Ustaw wyróżnienie sprawozdania</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="{{ path('app_report_highlight',{id: report.id}) }}" method="post">
<input type="hidden" name="token" value="{{ csrf_token('schedule-highlight') }}">
{% for status in highlightStatus %}
<button type="submit" name="highlight_id" value="{{ status.value }}" class="btn btn-{{ status.borderColor }}">
{{ status.buttonLang }}</button>
{% endfor %}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Zamknij</button>
{# <button type="button" class="btn btn-primary">Save changes</button>#}
</div>
</div>
</div>
</div>
{% endmacro %}
+22 -24
View File
@@ -2,44 +2,42 @@
{% import 'navigation/switch_months.html.twig' as navigation %} {% import 'navigation/switch_months.html.twig' as navigation %}
{% block title %}Harmonogramy{% endblock %} {% block title %}Harmonogramy{% endblock %}
{% block body %} {% block body %}
{# {{ navigation.month_menu('app_schedule',months,currentMonth) }}#} <div class="row reports-months-all">
<div class="row row-cols-2 reports-months-all"> <ul class="nav nav-underline nav-fill">
<div class="col"> <li class="nav-item position-relative">
<a href="{{ path('app_schedule', {typeId:2}) }}" class="card text-center text-white {% if typeId == 2 %}bg-primary{% else %}bg-secondary{% endif %}"> <a href="{{ path('app_schedule', {typeId:2}) }}" class="nav-link {% if typeId == 2 %}active{% endif %}">Zaległe</a>
<div class="card-title"><h2>Zaległe</h2></div> {% if isOverdue %}
</a> <div class="spinner-grow spinner-grow-sm text-danger overdue"></div>
{# <a href="{{ path('app_schedule', {monthId:-1}) }}" class="btn {% if currentMonth is null %}btn-success{% else %}btn-secondary{% endif %}">Zaległe</a>#} {% endif %}
</li>
<li class="nav-item">
<a href="{{ path('app_schedule', {typeId:1}) }}" class="nav-link {% if typeId == 1 %}active{% endif %}">Aktualne</a>
</li>
</ul>
</div> </div>
<div class="col"> <div class="row row-cols-auto schedule-all">
<a href="{{ path('app_schedule', {typeId:1}) }}" class="card text-center text-white {% if typeId == 1 %}bg-primary{% else %}bg-secondary{% endif %}">
<div class="card-title"><h2>Aktualne</h2></div>
</a>
{# <a href="{{ path('app_schedule', {monthId:-1}) }}" class="btn {% if currentMonth is null %}btn-success{% else %}btn-secondary{% endif %}">Zaległe</a>#}
</div>
{# <div class="col">#}
{# <a href="{{ path('app_schedule', {monthId:1}) }}" class="btn {% if currentMonth is null %}btn-success{% else %}btn-secondary{% endif %}">Aktualne</a>#}
{# </div>#}
</div>
<div class="row row-cols-auto reports-all">
{% for schedule in schedules %} {% for schedule in schedules %}
<div class="col"> <div class="col">
<div class="card text-white bg-{{ schedule.getStatus.backgroundColor }}"> <div class="card border-{{ schedule.getStatus.borderColor }}">
<div class="card-body"> <div class="card-body">
<h5 class="card-title"> <h5 class="card-title">
{% if is_granted('clientView') %} {% if is_granted('clientView') %}
<a href="{{ path('app_client',{id: schedule.client.id}) }}" class="link-dark">{{ schedule.client.strName }}</a> <a href="{{ path('app_client',{id: schedule.client.id}) }}">{{ schedule.client.strName }}</a>
{% else %} {% else %}
<div class="link-dark">{{ schedule.client.strName }}</div> <div class="link-dark">{{ schedule.client.strName }}</div>
{% endif %} {% endif %}
</h5> </h5>
{% if enum(schedule.getStatus, 'OVERDUE') %}
<h6 class="card-subtitle text-muted text-danger-emphasis">Czas minął: {{ schedule.getTimeLeft }} dni</h6>
<div class="spinner-grow spinner-grow-sm text-danger overdue"></div>
{% else %}
<h6 class="card-subtitle text-muted">Pozostały czas: {{ schedule.getTimeLeft }} dni</h6> <h6 class="card-subtitle text-muted">Pozostały czas: {{ schedule.getTimeLeft }} dni</h6>
{% endif %}
</div> </div>
<div class="card-footer"> <div class="card-footer">
<form action="{{ path('app_schedule_status') }}" method="post"> <form action="{{ path('app_schedule_status',{id:schedule.id}) }}" method="post">
<input type="hidden" name="token" value="{{ csrf_token('schedule-status') }}"> <input type="hidden" name="token" value="{{ csrf_token('schedule-status') }}">
<input type="hidden" name="scheduleId" value="{{ schedule.id }}"> <button type="submit" class="btn btn-outline-success w-100">Wysłano</button>
<button type="submit" class="btn btn-success">Wysłano</button>
</form> </form>
</div> </div>
</div> </div>
+8
View File
@@ -72,6 +72,14 @@ Encore
// uncomment if you're having problems with a jQuery plugin // uncomment if you're having problems with a jQuery plugin
//.autoProvidejQuery() //.autoProvidejQuery()
.configureTerserPlugin(options => {
options.terserOptions = {
output: {
comments: false
},
sourceMap: true
}
})
; ;
module.exports = Encore.getWebpackConfig(); module.exports = Encore.getWebpackConfig();
+4 -5
View File
@@ -18,8 +18,9 @@ RUN apt-get install -y \
libwebp-dev \ libwebp-dev \
libfreetype6-dev \ libfreetype6-dev \
libonig-dev \ libonig-dev \
npm \ npm
&& docker-php-ext-configure zip \
RUN docker-php-ext-configure zip \
&& docker-php-ext-install zip intl && docker-php-ext-install zip intl
RUN apt-get install -y libgd3 libgd-dev && rm -rf /var/lib/apt/lists/* RUN apt-get install -y libgd3 libgd-dev && rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-configure gd --with-jpeg RUN docker-php-ext-configure gd --with-jpeg
@@ -37,6 +38,7 @@ COPY --from=composer /usr/bin/composer /usr/bin/composer
COPY --chown=www-data:www-data ./project /var/www/html COPY --chown=www-data:www-data ./project /var/www/html
WORKDIR /var/www/html WORKDIR /var/www/html
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
RUN npm install RUN npm install
@@ -44,10 +46,7 @@ RUN npm run build
RUN apt-get purge --auto-remove -y npm RUN apt-get purge --auto-remove -y npm
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN composer dump-autoload --optimize RUN composer dump-autoload --optimize
RUN mv .env.prod .env RUN mv .env.prod .env
COPY services/config/nginx-site.conf /etc/nginx/sites-enabled/default COPY services/config/nginx-site.conf /etc/nginx/sites-enabled/default