@@ -19,8 +19,6 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Form\ActivityEditForm;
|
||||
use App\Form\Toolbar\ActivityToolbarForm;
|
||||
use App\Repository\Query\ActivityQuery;
|
||||
@@ -45,47 +43,6 @@ class ActivityController extends AbstractController
|
||||
return $this->getDoctrine()->getRepository(Activity::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return ActivityQuery
|
||||
*/
|
||||
protected function getQueryForRequest(Request $request)
|
||||
{
|
||||
$visibility = $request->get('visibility', ActivityQuery::SHOW_VISIBLE);
|
||||
if (strlen($visibility) == 0 || (int)$visibility != $visibility) {
|
||||
$visibility = ActivityQuery::SHOW_BOTH;
|
||||
}
|
||||
$pageSize = (int) $request->get('pageSize');
|
||||
$customer = $request->get('customer');
|
||||
$customer = !empty(trim($customer)) ? trim($customer) : null;
|
||||
$project = $request->get('project');
|
||||
$project = !empty(trim($project)) ? trim($project) : null;
|
||||
|
||||
if ($project !== null) {
|
||||
$repo = $this->getDoctrine()->getRepository(Project::class);
|
||||
$project = $repo->getById($project);
|
||||
if ($project !== null) {
|
||||
$customer = $project->getCustomer();
|
||||
} else {
|
||||
$customer = null;
|
||||
}
|
||||
} elseif ($customer !== null) {
|
||||
$repo = $this->getDoctrine()->getRepository(Customer::class);
|
||||
$customer = $repo->getById($customer);
|
||||
}
|
||||
|
||||
$query = new ActivityQuery();
|
||||
$query
|
||||
->setPageSize($pageSize)
|
||||
->setVisibility($visibility)
|
||||
->setCustomer($customer)
|
||||
->setProject($project)
|
||||
->setExclusiveVisibility(true)
|
||||
;
|
||||
|
||||
return $query ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", defaults={"page": 1}, name="admin_activity")
|
||||
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_activity_paginated")
|
||||
@@ -94,22 +51,33 @@ class ActivityController extends AbstractController
|
||||
*/
|
||||
public function indexAction($page, Request $request)
|
||||
{
|
||||
$query = $this->getQueryForRequest($request);
|
||||
$query = new ActivityQuery();
|
||||
$query->setExclusiveVisibility(true);
|
||||
$query->setPage($page);
|
||||
|
||||
$form = $this->getToolbarForm($query);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var ActivityQuery $query */
|
||||
$query = $form->getData();
|
||||
}
|
||||
|
||||
/* @var $entries Pagerfanta */
|
||||
$entries = $this->getRepository()->findByQuery($query);
|
||||
|
||||
return $this->render('admin/activity.html.twig', [
|
||||
'entries' => $entries,
|
||||
'query' => $query,
|
||||
'toolbarForm' => $this->getToolbarForm($query)->createView(),
|
||||
'toolbarForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/create", name="admin_activity_create")
|
||||
* @Method({"GET", "POST"})
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function createAction(Request $request)
|
||||
{
|
||||
@@ -120,6 +88,10 @@ class ActivityController extends AbstractController
|
||||
* @Route("/{id}/edit", name="admin_activity_edit")
|
||||
* @Method({"GET", "POST"})
|
||||
* @Security("is_granted('edit', activity)")
|
||||
*
|
||||
* @param Activity $activity
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function editAction(Activity $activity, Request $request)
|
||||
{
|
||||
@@ -204,16 +176,12 @@ class ActivityController extends AbstractController
|
||||
*/
|
||||
protected function getToolbarForm(ActivityQuery $query)
|
||||
{
|
||||
return $this->createForm(
|
||||
ActivityToolbarForm::class,
|
||||
$query,
|
||||
[
|
||||
'action' => $this->generateUrl('admin_activity_paginated', [
|
||||
'page' => $query->getPage(),
|
||||
]),
|
||||
'method' => 'GET',
|
||||
]
|
||||
);
|
||||
return $this->createForm(ActivityToolbarForm::class, $query, [
|
||||
'action' => $this->generateUrl('admin_activity', [
|
||||
'page' => $query->getPage(),
|
||||
]),
|
||||
'method' => 'GET',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,13 +196,9 @@ class ActivityController extends AbstractController
|
||||
$url = $this->generateUrl('admin_activity_edit', ['id' => $activity->getId()]);
|
||||
}
|
||||
|
||||
return $this->createForm(
|
||||
ActivityEditForm::class,
|
||||
$activity,
|
||||
[
|
||||
'action' => $url,
|
||||
'method' => 'POST'
|
||||
]
|
||||
);
|
||||
return $this->createForm(ActivityEditForm::class, $activity, [
|
||||
'action' => $url,
|
||||
'method' => 'POST'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ use App\Entity\Customer;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
|
||||
use App\Form\CustomerEditForm;
|
||||
use App\Form\Toolbar\CustomerToolbarForm;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
@@ -43,44 +42,30 @@ class CustomerController extends AbstractController
|
||||
return $this->getDoctrine()->getRepository(Customer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return CustomerQuery
|
||||
*/
|
||||
protected function getQueryForRequest(Request $request)
|
||||
{
|
||||
$visibility = $request->get('visibility', CustomerQuery::SHOW_VISIBLE);
|
||||
if (strlen($visibility) == 0 || (int)$visibility != $visibility) {
|
||||
$visibility = CustomerQuery::SHOW_BOTH;
|
||||
}
|
||||
$pageSize = (int) $request->get('pageSize');
|
||||
|
||||
$query = new CustomerQuery();
|
||||
$query
|
||||
->setPageSize($pageSize)
|
||||
->setVisibility($visibility);
|
||||
|
||||
return $query ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", defaults={"page": 1}, name="admin_customer")
|
||||
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_customer_paginated")
|
||||
* @Method("GET")
|
||||
* @Cache(smaxage="10")
|
||||
*/
|
||||
public function indexAction($page, Request $request)
|
||||
{
|
||||
$query = $this->getQueryForRequest($request);
|
||||
$query = new CustomerQuery();
|
||||
$query->setPage($page);
|
||||
|
||||
$form = $this->getToolbarForm($query);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var CustomerQuery $query */
|
||||
$query = $form->getData();
|
||||
}
|
||||
|
||||
/* @var $entries Pagerfanta */
|
||||
$entries = $this->getRepository()->findByQuery($query);
|
||||
|
||||
return $this->render('admin/customer.html.twig', [
|
||||
'entries' => $entries,
|
||||
'query' => $query,
|
||||
'toolbarForm' => $this->getToolbarForm($query)->createView(),
|
||||
'toolbarForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,36 +44,6 @@ class ProjectController extends AbstractController
|
||||
return $this->getDoctrine()->getRepository(Project::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return ProjectQuery
|
||||
*/
|
||||
protected function getQueryForRequest(Request $request)
|
||||
{
|
||||
$visibility = $request->get('visibility', ProjectQuery::SHOW_VISIBLE);
|
||||
if (strlen($visibility) == 0 || (int)$visibility != $visibility) {
|
||||
$visibility = ProjectQuery::SHOW_BOTH;
|
||||
}
|
||||
$pageSize = (int) $request->get('pageSize');
|
||||
$customer = $request->get('customer');
|
||||
$customer = !empty(trim($customer)) ? trim($customer) : null;
|
||||
|
||||
if ($customer !== null) {
|
||||
$repo = $this->getDoctrine()->getRepository(Customer::class);
|
||||
$customer = $repo->getById($customer);
|
||||
}
|
||||
|
||||
$query = new ProjectQuery();
|
||||
$query
|
||||
->setPageSize($pageSize)
|
||||
->setVisibility($visibility)
|
||||
->setCustomer($customer)
|
||||
->setExclusiveVisibility(true)
|
||||
;
|
||||
|
||||
return $query ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", defaults={"page": 1}, name="admin_project")
|
||||
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_project_paginated")
|
||||
@@ -82,16 +52,24 @@ class ProjectController extends AbstractController
|
||||
*/
|
||||
public function indexAction($page, Request $request)
|
||||
{
|
||||
$query = $this->getQueryForRequest($request);
|
||||
$query = new ProjectQuery();
|
||||
$query->setExclusiveVisibility(true);
|
||||
$query->setPage($page);
|
||||
|
||||
$form = $this->getToolbarForm($query);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var ProjectQuery $query */
|
||||
$query = $form->getData();
|
||||
}
|
||||
|
||||
/* @var $entries Pagerfanta */
|
||||
$entries = $this->getDoctrine()->getRepository(Project::class)->findByQuery($query);
|
||||
|
||||
return $this->render('admin/project.html.twig', [
|
||||
'entries' => $entries,
|
||||
'query' => $query,
|
||||
'toolbarForm' => $this->getToolbarForm($query)->createView(),
|
||||
'toolbarForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Form\Toolbar\TimesheetAdminToolbarForm;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use App\Controller\TimesheetControllerTrait;
|
||||
@@ -48,9 +50,16 @@ class TimesheetController extends AbstractController
|
||||
*/
|
||||
public function indexAction($page, Request $request)
|
||||
{
|
||||
$query = $this->getQueryForRequest($request);
|
||||
$query = new TimesheetQuery();
|
||||
$query->setPage($page);
|
||||
|
||||
$form = $this->getToolbarForm($query);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var TimesheetQuery $query */
|
||||
$query = $form->getData();
|
||||
}
|
||||
|
||||
/* @var $entries Pagerfanta */
|
||||
$entries = $this->getRepository()->findByQuery($query);
|
||||
|
||||
@@ -58,7 +67,7 @@ class TimesheetController extends AbstractController
|
||||
'entries' => $entries,
|
||||
'page' => $page,
|
||||
'query' => $query,
|
||||
'toolbarForm' => $this->getToolbarForm($query, 'admin_timesheet')->createView(),
|
||||
'toolbarForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -133,15 +142,11 @@ class TimesheetController extends AbstractController
|
||||
*/
|
||||
protected function getCreateForm(Timesheet $entry)
|
||||
{
|
||||
return $this->createForm(
|
||||
TimesheetAdminForm::class,
|
||||
$entry,
|
||||
[
|
||||
'action' => $this->generateUrl('admin_timesheet_create'),
|
||||
'method' => 'POST',
|
||||
'currency' => Customer::DEFAULT_CURRENCY,
|
||||
]
|
||||
);
|
||||
return $this->createForm(TimesheetAdminForm::class, $entry, [
|
||||
'action' => $this->generateUrl('admin_timesheet_create'),
|
||||
'method' => 'POST',
|
||||
'currency' => Customer::DEFAULT_CURRENCY,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,17 +156,27 @@ class TimesheetController extends AbstractController
|
||||
*/
|
||||
protected function getEditForm(Timesheet $entry, $page)
|
||||
{
|
||||
return $this->createForm(
|
||||
TimesheetAdminForm::class,
|
||||
$entry,
|
||||
[
|
||||
'action' => $this->generateUrl('admin_timesheet_edit', [
|
||||
'id' => $entry->getId(),
|
||||
'page' => $page
|
||||
]),
|
||||
'method' => 'POST',
|
||||
'currency' => $entry->getActivity()->getProject()->getCustomer()->getCurrency(),
|
||||
]
|
||||
);
|
||||
return $this->createForm(TimesheetAdminForm::class, $entry, [
|
||||
'action' => $this->generateUrl('admin_timesheet_edit', [
|
||||
'id' => $entry->getId(),
|
||||
'page' => $page
|
||||
]),
|
||||
'method' => 'POST',
|
||||
'currency' => $entry->getActivity()->getProject()->getCustomer()->getCurrency(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetQuery $query
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
protected function getToolbarForm(TimesheetQuery $query)
|
||||
{
|
||||
return $this->createForm(TimesheetAdminToolbarForm::class, $query, [
|
||||
'action' => $this->generateUrl('admin_timesheet', [
|
||||
'page' => $query->getPage(),
|
||||
]),
|
||||
'method' => 'GET',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ use Pagerfanta\Pagerfanta;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
@@ -35,29 +34,6 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return UserQuery
|
||||
*/
|
||||
protected function getQueryForRequest(Request $request)
|
||||
{
|
||||
$visibility = $request->get('visibility', UserQuery::SHOW_VISIBLE);
|
||||
if (strlen($visibility) == 0 || (int)$visibility != $visibility) {
|
||||
$visibility = UserQuery::SHOW_BOTH;
|
||||
}
|
||||
$pageSize = (int) $request->get('pageSize');
|
||||
$userRole = $request->get('role');
|
||||
|
||||
$query = new UserQuery();
|
||||
$query
|
||||
->setPageSize($pageSize)
|
||||
->setVisibility($visibility)
|
||||
->setRole($userRole)
|
||||
;
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", defaults={"page": 1}, name="admin_user")
|
||||
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_user_paginated")
|
||||
@@ -65,16 +41,23 @@ class UserController extends AbstractController
|
||||
*/
|
||||
public function indexAction($page, Request $request)
|
||||
{
|
||||
$query = $this->getQueryForRequest($request);
|
||||
$query = new UserQuery();
|
||||
$query->setPage($page);
|
||||
|
||||
$form = $this->getToolbarForm($query);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var UserQuery $query */
|
||||
$query = $form->getData();
|
||||
}
|
||||
|
||||
/* @var $entries Pagerfanta */
|
||||
$entries = $this->getDoctrine()->getRepository(User::class)->findByQuery($query);
|
||||
|
||||
return $this->render('admin/user.html.twig', [
|
||||
'entries' => $entries,
|
||||
'query' => $query,
|
||||
'toolbarForm' => $this->getToolbarForm($query)->createView(),
|
||||
'toolbarForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -120,16 +103,12 @@ class UserController extends AbstractController
|
||||
*/
|
||||
protected function getToolbarForm(UserQuery $query)
|
||||
{
|
||||
return $this->createForm(
|
||||
UserToolbarForm::class,
|
||||
$query,
|
||||
[
|
||||
'action' => $this->generateUrl('admin_user_paginated', [
|
||||
'page' => $query->getPage(),
|
||||
]),
|
||||
'method' => 'GET',
|
||||
]
|
||||
);
|
||||
return $this->createForm(UserToolbarForm::class, $query, [
|
||||
'action' => $this->generateUrl('admin_user_paginated', [
|
||||
'page' => $query->getPage(),
|
||||
]),
|
||||
'method' => 'GET',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,13 +117,9 @@ class UserController extends AbstractController
|
||||
*/
|
||||
private function createEditForm(User $user)
|
||||
{
|
||||
return $this->createForm(
|
||||
UserCreateType::class,
|
||||
$user,
|
||||
[
|
||||
'action' => $this->generateUrl('admin_user_create'),
|
||||
'method' => 'POST'
|
||||
]
|
||||
);
|
||||
return $this->createForm(UserCreateType::class, $user, [
|
||||
'action' => $this->generateUrl('admin_user_create'),
|
||||
'method' => 'POST'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
263
src/Controller/InvoiceController.php
Normal file
263
src/Controller/InvoiceController.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Form\InvoiceTemplateForm;
|
||||
use App\Form\Toolbar\InvoiceToolbarForm;
|
||||
use App\Invoice\ServiceInvoice;
|
||||
use App\Model\InvoiceModel;
|
||||
use App\Repository\Query\BaseQuery;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Controller used to manage invoices.
|
||||
*
|
||||
* @Route("/invoice")
|
||||
* @Security("has_role('ROLE_TEAMLEAD')")
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceController extends AbstractController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ServiceInvoice
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* InvoiceController constructor.
|
||||
* @param ServiceInvoice $service
|
||||
*/
|
||||
public function __construct(ServiceInvoice $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvoiceQuery
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getDefaultQuery()
|
||||
{
|
||||
$begin = new \DateTime('first day of this month');
|
||||
$end = new \DateTime('last day of this month');
|
||||
|
||||
$query = new InvoiceQuery();
|
||||
$query->setBegin($begin);
|
||||
$query->setEnd($end);
|
||||
$query->setUser($this->getUser());
|
||||
$query->setState(InvoiceQuery::STATE_STOPPED);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \App\Repository\InvoiceTemplateRepository
|
||||
*/
|
||||
protected function getRepository()
|
||||
{
|
||||
return $this->getDoctrine()->getRepository(InvoiceTemplate::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", name="invoice")
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
if (!$this->getRepository()->hasTemplate()) {
|
||||
return $this->redirectToRoute('admin_invoice_template_create');
|
||||
}
|
||||
|
||||
$entries = [];
|
||||
|
||||
$query = $this->getDefaultQuery();
|
||||
$form = $this->getToolbarForm($query);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var InvoiceQuery $query */
|
||||
$query = $form->getData();
|
||||
$query->setResultType(TimesheetQuery::RESULT_TYPE_QUERYBUILDER);
|
||||
|
||||
if ($query->getCustomer() !== null) {
|
||||
$query->getBegin()->setTime(0, 0, 0);
|
||||
$query->getEnd()->setTime(23, 59, 59);
|
||||
|
||||
$queryBuilder = $this->getDoctrine()->getRepository(Timesheet::class)->findByQuery($query);
|
||||
$entries = $queryBuilder->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
$model = new InvoiceModel();
|
||||
$model->setQuery($query);
|
||||
$model->setEntries($entries);
|
||||
$model->setCustomer($query->getCustomer());
|
||||
|
||||
$action = null;
|
||||
if ($query->getTemplate() !== null) {
|
||||
$generator = $this->service->getNumberGeneratorByName($query->getTemplate()->getNumberGenerator());
|
||||
if ($generator === null) {
|
||||
throw new \Exception('Unknown number generator: ' . $query->getTemplate()->getNumberGenerator());
|
||||
}
|
||||
|
||||
$calculator = $this->service->getCalculatorByName($query->getTemplate()->getCalculator());
|
||||
if ($calculator === null) {
|
||||
throw new \Exception('Unknown invoice calculator: ' . $query->getTemplate()->getCalculator());
|
||||
}
|
||||
|
||||
$model->setTemplate($query->getTemplate());
|
||||
$model->setCalculator($calculator);
|
||||
$model->setNumberGenerator($generator);
|
||||
$action = $this->service->getRendererActionByName($query->getTemplate()->getRenderer());
|
||||
}
|
||||
|
||||
return $this->render('invoice/index.html.twig', [
|
||||
'model' => $model,
|
||||
'action' => $action,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/template", defaults={"page": 1}, name="admin_invoice_template")
|
||||
* @Route("/template/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_invoice_template_paginated")
|
||||
* @Method({"GET", "POST"})
|
||||
*
|
||||
* TODO permission
|
||||
*
|
||||
* @param $page
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function listTemplateAction($page, Request $request)
|
||||
{
|
||||
$templates = $this->getRepository()->findByQuery(new BaseQuery());
|
||||
return $this->render('invoice/templates.html.twig', [
|
||||
'entries' => $templates,
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit", name="admin_invoice_template_edit")
|
||||
* @Method({"GET", "POST"})
|
||||
*
|
||||
* TODO permission
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function editTemplateAction(InvoiceTemplate $template, Request $request)
|
||||
{
|
||||
return $this->renderTemplateForm($template, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/create", name="admin_invoice_template_create")
|
||||
* @Method({"GET", "POST"})
|
||||
*
|
||||
* TODO permission
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createTemplateAction(Request $request)
|
||||
{
|
||||
if (!$this->getRepository()->hasTemplate()) {
|
||||
$this->flashWarning('invoice.first_template');
|
||||
}
|
||||
return $this->renderTemplateForm(new InvoiceTemplate(), $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceTemplate $template
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected function renderTemplateForm(InvoiceTemplate $template, Request $request)
|
||||
{
|
||||
$editForm = $this->createEditForm($template);
|
||||
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
$entityManager = $this->getDoctrine()->getManager();
|
||||
$entityManager->persist($template);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->flashSuccess('action.updated_successfully');
|
||||
|
||||
return $this->redirectToRoute('admin_invoice_template');
|
||||
}
|
||||
|
||||
return $this->render('invoice/template_edit.html.twig', [
|
||||
'template' => $template,
|
||||
'form' => $editForm->createView()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function invoiceAction(InvoiceModel $model)
|
||||
{
|
||||
return $this->render('invoice/print.html.twig', [
|
||||
'model' => $model,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
protected function getToolbarForm(InvoiceQuery $query)
|
||||
{
|
||||
return $this->createForm(InvoiceToolbarForm::class, $query, [
|
||||
'action' => $this->generateUrl('invoice', []),
|
||||
'method' => 'GET',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceTemplate $template
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
private function createEditForm(InvoiceTemplate $template)
|
||||
{
|
||||
if ($template->getId() === null) {
|
||||
$url = $this->generateUrl('admin_invoice_template_create');
|
||||
} else {
|
||||
$url = $this->generateUrl('admin_invoice_template_edit', ['id' => $template->getId()]);
|
||||
}
|
||||
|
||||
return $this->createForm(InvoiceTemplateForm::class, $template, [
|
||||
'action' => $url,
|
||||
'method' => 'POST'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Form\Toolbar\TimesheetToolbarForm;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
@@ -43,10 +44,18 @@ class TimesheetController extends AbstractController
|
||||
*/
|
||||
public function indexAction($page, Request $request)
|
||||
{
|
||||
$query = $this->getQueryForRequest($request);
|
||||
$query->setUser($this->getUser());
|
||||
$query = new TimesheetQuery();
|
||||
$query->setPage($page);
|
||||
|
||||
$form = $this->getToolbarForm($query);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var TimesheetQuery $query */
|
||||
$query = $form->getData();
|
||||
}
|
||||
|
||||
$query->setUser($this->getUser());
|
||||
|
||||
/* @var $entries Pagerfanta */
|
||||
$entries = $this->getRepository()->findByQuery($query);
|
||||
|
||||
@@ -54,7 +63,7 @@ class TimesheetController extends AbstractController
|
||||
'entries' => $entries,
|
||||
'page' => $page,
|
||||
'query' => $query,
|
||||
'toolbarForm' => $this->getToolbarForm($query)->createView(),
|
||||
'toolbarForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -201,4 +210,18 @@ class TimesheetController extends AbstractController
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetQuery $query
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
protected function getToolbarForm(TimesheetQuery $query)
|
||||
{
|
||||
return $this->createForm(TimesheetToolbarForm::class, $query, [
|
||||
'action' => $this->generateUrl('timesheet', [
|
||||
'page' => $query->getPage(),
|
||||
]),
|
||||
'method' => 'GET',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,8 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use App\Form\Toolbar\TimesheetToolbarForm;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Repository\TimesheetRepository;
|
||||
|
||||
/**
|
||||
@@ -35,77 +30,6 @@ trait TimesheetControllerTrait
|
||||
return $this->getDoctrine()->getRepository(Timesheet::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return TimesheetQuery
|
||||
*/
|
||||
protected function getQueryForRequest(Request $request)
|
||||
{
|
||||
$activity = $request->get('activity');
|
||||
$activity = !empty(trim($activity)) ? trim($activity) : null;
|
||||
$project = $request->get('project');
|
||||
$project = !empty(trim($project)) ? trim($project) : null;
|
||||
$customer = $request->get('customer');
|
||||
$customer = !empty(trim($customer)) ? trim($customer) : null;
|
||||
$state = $request->get('state');
|
||||
$state = !empty(trim($state)) ? trim($state) : null;
|
||||
$pageSize = (int) $request->get('pageSize');
|
||||
|
||||
if ($activity !== null) {
|
||||
$repo = $this->getDoctrine()->getRepository(Activity::class);
|
||||
$activity = $repo->getById($activity);
|
||||
if ($activity !== null) {
|
||||
$project = $activity->getProject();
|
||||
if ($project !== null) {
|
||||
$customer = $project->getCustomer();
|
||||
}
|
||||
} else {
|
||||
$customer = null;
|
||||
$project = null;
|
||||
}
|
||||
} elseif ($project !== null) {
|
||||
$repo = $this->getDoctrine()->getRepository(Project::class);
|
||||
$project = $repo->getById($project);
|
||||
if ($project !== null) {
|
||||
$customer = $project->getCustomer();
|
||||
} else {
|
||||
$customer = null;
|
||||
}
|
||||
} elseif ($customer !== null) {
|
||||
$repo = $this->getDoctrine()->getRepository(Customer::class);
|
||||
$customer = $repo->getById($customer);
|
||||
}
|
||||
|
||||
$query = new TimesheetQuery();
|
||||
$query
|
||||
->setActivity($activity)
|
||||
->setProject($project)
|
||||
->setCustomer($customer)
|
||||
->setPageSize($pageSize)
|
||||
->setState($state);
|
||||
|
||||
return $query ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetQuery $query
|
||||
* @param string $route
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getToolbarForm(TimesheetQuery $query, $route = 'timesheet')
|
||||
{
|
||||
return $this->createForm(
|
||||
TimesheetToolbarForm::class,
|
||||
$query,
|
||||
[
|
||||
'action' => $this->generateUrl($route, [
|
||||
'page' => $query->getPage(),
|
||||
]),
|
||||
'method' => 'GET',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $entry
|
||||
* @param string $route
|
||||
|
||||
@@ -12,17 +12,23 @@
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Entity\UserPreference;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
|
||||
/**
|
||||
* Sample data to load in the database when running tests or for development
|
||||
* Defines the sample data to load in the database when running the unit and
|
||||
* functional tests or while development.
|
||||
*
|
||||
* Execute this command to load the data:
|
||||
* $ php bin/console doctrine:fixtures:load
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class AppFixtures extends Fixture
|
||||
{
|
||||
use FixturesTrait;
|
||||
|
||||
const DEFAULT_PASSWORD = 'kitten';
|
||||
|
||||
@@ -31,6 +37,10 @@ class AppFixtures extends Fixture
|
||||
*/
|
||||
private $encoder;
|
||||
|
||||
/**
|
||||
* AppFixtures constructor.
|
||||
* @param UserPasswordEncoderInterface $encoder
|
||||
*/
|
||||
public function __construct(UserPasswordEncoderInterface $encoder)
|
||||
{
|
||||
$this->encoder = $encoder;
|
||||
@@ -44,117 +54,53 @@ class AppFixtures extends Fixture
|
||||
$this->loadUsers($manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager
|
||||
*/
|
||||
private function loadUsers(ObjectManager $manager)
|
||||
{
|
||||
$passwordEncoder = $this->encoder;
|
||||
|
||||
$claraCustomer = new User();
|
||||
$claraCustomer
|
||||
->setAlias('Clara Haynes')
|
||||
->setTitle('CFO')
|
||||
->setUsername('clara_customer')
|
||||
->setEmail('clara_customer@example.com')
|
||||
->setRoles(['ROLE_CUSTOMER'])
|
||||
->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=monsterid&f=y')
|
||||
->setPassword($passwordEncoder->encodePassword($claraCustomer, self::DEFAULT_PASSWORD))
|
||||
;
|
||||
$manager->persist($claraCustomer);
|
||||
foreach ($this->getUserDefinition() as $userData) {
|
||||
$user = new User();
|
||||
$user
|
||||
->setAlias($userData[0])
|
||||
->setTitle($userData[1])
|
||||
->setUsername($userData[2])
|
||||
->setEmail($userData[3])
|
||||
->setRoles([$userData[4]])
|
||||
->setAvatar($userData[5])
|
||||
->setActive($userData[6])
|
||||
->setPassword($passwordEncoder->encodePassword($user, self::DEFAULT_PASSWORD))
|
||||
;
|
||||
|
||||
$johnUser = new User();
|
||||
$johnUser
|
||||
->setAlias('John Doe')
|
||||
->setTitle('Developer')
|
||||
->setUsername('john_user')
|
||||
->setEmail('john_user@example.com')
|
||||
->setRoles(['ROLE_USER'])
|
||||
->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y')
|
||||
->setPassword($passwordEncoder->encodePassword($claraCustomer, self::DEFAULT_PASSWORD))
|
||||
;
|
||||
$manager->persist($johnUser);
|
||||
$preference = new UserPreference();
|
||||
$preference->setName(UserPreference::HOURLY_RATE);
|
||||
$preference->setValue(rand(0, 100));
|
||||
$preference->setUser($user);
|
||||
$user->setPreferences([$preference]);
|
||||
|
||||
$deactiveUser = new User();
|
||||
$deactiveUser
|
||||
->setAlias('Chris Deactive')
|
||||
->setTitle('Developer (left company)')
|
||||
->setUsername('chris_user')
|
||||
->setEmail('chris_user@example.com')
|
||||
->setRoles(['ROLE_USER'])
|
||||
->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y')
|
||||
->setPassword($passwordEncoder->encodePassword($claraCustomer, self::DEFAULT_PASSWORD))
|
||||
// inactive for testing user login and UI
|
||||
->setActive(false)
|
||||
;
|
||||
$manager->persist($deactiveUser);
|
||||
|
||||
$tonyTeamlead = new User();
|
||||
$tonyTeamlead
|
||||
->setAlias('Tony Maier')
|
||||
->setTitle('Head of Development')
|
||||
->setUsername('tony_teamlead')
|
||||
->setEmail('tony_teamlead@example.com')
|
||||
->setRoles(['ROLE_TEAMLEAD'])
|
||||
->setAvatar('https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg')
|
||||
->setPassword($passwordEncoder->encodePassword($claraCustomer, self::DEFAULT_PASSWORD))
|
||||
;
|
||||
$manager->persist($tonyTeamlead);
|
||||
|
||||
$annaAdmin = new User();
|
||||
$annaAdmin
|
||||
->setAlias('Anna Smith')
|
||||
->setTitle('Administrator')
|
||||
->setUsername('anna_admin')
|
||||
->setEmail('anna_admin@example.com')
|
||||
->setRoles(['ROLE_ADMIN'])
|
||||
// no avatar to test default image!
|
||||
->setPassword($passwordEncoder->encodePassword($claraCustomer, self::DEFAULT_PASSWORD))
|
||||
;
|
||||
$manager->persist($annaAdmin);
|
||||
|
||||
$susanSuper = new User();
|
||||
$susanSuper
|
||||
// no alias to test the username macros
|
||||
->setTitle('Super Administrator')
|
||||
->setUsername('susan_super')
|
||||
->setEmail('susan_super@example.com')
|
||||
->setRoles(['ROLE_SUPER_ADMIN'])
|
||||
->setAvatar('/bundles/avanzuadmintheme/img/avatar.png')
|
||||
->setPassword($passwordEncoder->encodePassword($claraCustomer, self::DEFAULT_PASSWORD))
|
||||
;
|
||||
$manager->persist($susanSuper);
|
||||
$manager->persist($user);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
protected function getPhrases()
|
||||
/**
|
||||
* @return []
|
||||
*/
|
||||
protected function getUserDefinition()
|
||||
{
|
||||
return [
|
||||
'Lorem ipsum dolor sit amet consectetur adipiscing elit',
|
||||
'Pellentesque vitae velit ex',
|
||||
'Mauris dapibus risus quis suscipit vulputate',
|
||||
'Eros diam egestas libero eu vulputate risus',
|
||||
'In hac habitasse platea dictumst',
|
||||
'Morbi tempus commodo mattis',
|
||||
'Ut suscipit posuere justo at vulputate',
|
||||
'Ut eleifend mauris et risus ultrices egestas',
|
||||
'Aliquam sodales odio id eleifend tristique',
|
||||
'Urna nisl sollicitudin id varius orci quam id turpis',
|
||||
'Nulla porta lobortis ligula vel egestas',
|
||||
'Curabitur aliquam euismod dolor non ornare',
|
||||
'Sed varius a risus eget aliquam',
|
||||
'Nunc viverra elit ac laoreet suscipit',
|
||||
'Pellentesque et sapien pulvinar consectetur',
|
||||
['Clara Haynes', 'CFO', 'clara_customer', 'clara_customer@example.com', 'ROLE_CUSTOMER', 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=monsterid&f=y', true],
|
||||
['John Doe', 'Developer', 'john_user', 'john_user@example.com', 'ROLE_USER', 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', true],
|
||||
// inactive user to test login
|
||||
['Chris Deactive', 'Developer (left company)', 'chris_user', 'chris_user@example.com', 'ROLE_USER', 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', false],
|
||||
['Tony Maier', 'Head of Development', 'tony_teamlead', 'tony_teamlead@example.com', 'ROLE_TEAMLEAD', 'https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg', true],
|
||||
// no avatar to test default image macro
|
||||
['Anna Smith', 'Administrator', 'anna_admin', 'anna_admin@example.com', 'ROLE_ADMIN', null, true],
|
||||
// no alias to test twig username macro
|
||||
[null, 'Super Administrator', 'susan_super', 'susan_super@example.com', 'ROLE_SUPER_ADMIN', '/bundles/avanzuadmintheme/img/avatar.png', true]
|
||||
];
|
||||
}
|
||||
|
||||
protected function getRandomPhrase()
|
||||
{
|
||||
return $this->getRandomPostTitle();
|
||||
}
|
||||
|
||||
private function getRandomPostTitle()
|
||||
{
|
||||
$titles = $this->getPhrases();
|
||||
|
||||
return $titles[array_rand($titles)];
|
||||
}
|
||||
}
|
||||
|
||||
53
src/DataFixtures/FixturesTrait.php
Normal file
53
src/DataFixtures/FixturesTrait.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\DataFixtures;
|
||||
|
||||
/**
|
||||
* Defines reusable sample data.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
trait FixturesTrait
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getPhrases()
|
||||
{
|
||||
return [
|
||||
'Lorem ipsum dolor sit amet consectetur adipiscing elit',
|
||||
'Pellentesque vitae velit ex',
|
||||
'Mauris dapibus risus quis suscipit vulputate',
|
||||
'Eros diam egestas libero eu vulputate risus',
|
||||
'In hac habitasse platea dictumst',
|
||||
'Morbi tempus commodo mattis',
|
||||
'Ut suscipit posuere justo at vulputate',
|
||||
'Ut eleifend mauris et risus ultrices egestas',
|
||||
'Aliquam sodales odio id eleifend tristique',
|
||||
'Urna nisl sollicitudin id varius orci quam id turpis',
|
||||
'Nulla porta lobortis ligula vel egestas',
|
||||
'Curabitur aliquam euismod dolor non ornare',
|
||||
'Sed varius a risus eget aliquam',
|
||||
'Nunc viverra elit ac laoreet suscipit',
|
||||
'Pellentesque et sapien pulvinar consectetur',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getRandomPhrase()
|
||||
{
|
||||
$phrases = $this->getPhrases();
|
||||
return $phrases[array_rand($phrases)];
|
||||
}
|
||||
}
|
||||
65
src/DataFixtures/InvoiceFixtures.php
Normal file
65
src/DataFixtures/InvoiceFixtures.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
|
||||
/**
|
||||
* Defines the sample data to load in the database when running the unit and
|
||||
* functional tests or while development.
|
||||
*
|
||||
* Execute this command to load the data:
|
||||
* $ php bin/console doctrine:fixtures:load
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceFixtures extends Fixture
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$this->loadInvoiceTemplates($manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager
|
||||
*/
|
||||
private function loadInvoiceTemplates(ObjectManager $manager)
|
||||
{
|
||||
$template = new InvoiceTemplate();
|
||||
$template
|
||||
->setName('Invoice')
|
||||
->setTitle('Your company name')
|
||||
->setCompany('Kimai, Inc.')
|
||||
->setVat(19)
|
||||
->setDueDays(14)
|
||||
->setPaymentTerms(
|
||||
'I would like to thank you for your confidence and will gladly be there for you in the future.
|
||||
Please transfer the total amount within 14 days to the given account and use the invoice number as reference.'
|
||||
)
|
||||
->setAddress(
|
||||
'795 Folsom Ave, Suite 600
|
||||
San Francisco, CA 94107
|
||||
Phone: (804) 123-5432
|
||||
Email: info@almasaeedstudio.com'
|
||||
)
|
||||
;
|
||||
|
||||
$manager->persist($template);
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,22 @@ use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
|
||||
/**
|
||||
* Defines the sample data to load in the database when running the unit and
|
||||
* functional tests. Execute this command to load the data:
|
||||
* functional tests or while development.
|
||||
*
|
||||
* $ php bin/console doctrine:fixtures:load
|
||||
* Execute this command to load the data:
|
||||
* $ php bin/console doctrine:fixtures:load
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class TimesheetFixtures extends AppFixtures
|
||||
class TimesheetFixtures extends Fixture
|
||||
{
|
||||
use FixturesTrait;
|
||||
|
||||
const AMOUNT_TIMESHEET = 5000; // timesheet entries total
|
||||
const RATE_MIN = 10; // minimum rate for one hour
|
||||
const RATE_MAX = 80; // maximum rate for one hour
|
||||
@@ -185,11 +189,11 @@ class TimesheetFixtures extends AppFixtures
|
||||
$entry = new Customer();
|
||||
$entry
|
||||
->setCurrency($this->getRandomCurrency())
|
||||
->setVat(rand(0, 30))
|
||||
->setName($customerName . ($visible ? '' : '.'))
|
||||
->setAddress($this->getRandomLocation())
|
||||
->setComment($this->getRandomPhrase())
|
||||
->setVisible($visible)
|
||||
->setNumber('C0815-42-' . $i)
|
||||
->setCountry('DE') // TODO randomize country ?
|
||||
->setTimezone($allTimezones[rand(1, $amountTimezone)]);
|
||||
|
||||
|
||||
68
src/DependencyInjection/AppExtension.php
Normal file
68
src/DependencyInjection/AppExtension.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
|
||||
/**
|
||||
* This is the class that loads and manages your bundle configuration
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class AppExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$configuration = new Configuration();
|
||||
try {
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
} catch (InvalidConfigurationException $e) {
|
||||
trigger_error('Found invalid "kimai" configuration: ' . $e->getMessage());
|
||||
$config = [];
|
||||
}
|
||||
|
||||
$this->createInvoiceParameter($config, $container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
private function createInvoiceParameter(array $config, ContainerBuilder $container)
|
||||
{
|
||||
$keys = ['renderer', 'calculator', 'number_generator'];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (!isset($config['invoice'][$key]) || 0 === count($config['invoice'][$key])) {
|
||||
throw new InvalidDefinitionException('Missing invoice configuration: kimai.invoice.' . $key);
|
||||
}
|
||||
|
||||
$container->setParameter('kimai.invoice.' . $key, $config['invoice'][$key]);
|
||||
}
|
||||
|
||||
$container->setParameter('kimai.invoice', $config['invoice']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAlias()
|
||||
{
|
||||
return 'kimai';
|
||||
}
|
||||
}
|
||||
@@ -29,16 +29,46 @@ class Configuration implements ConfigurationInterface
|
||||
$treeBuilder = new TreeBuilder();
|
||||
$rootNode = $treeBuilder->root('kimai');
|
||||
|
||||
// FIXME add kimai config
|
||||
$rootNode
|
||||
->children()
|
||||
->arrayNode('timesheet')
|
||||
->children()
|
||||
->integerNode('round_record')->end()
|
||||
->children()
|
||||
->integerNode('rounding')->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('invoice')
|
||||
->children()
|
||||
->arrayNode('renderer')
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->defaultValue(array(
|
||||
'default' => 'App\Controller\InvoiceController::invoiceAction',
|
||||
))
|
||||
->end()
|
||||
->arrayNode('calculator')
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->defaultValue(array(
|
||||
'default' => 'App\Invoice\DefaultCalculator',
|
||||
))
|
||||
->end()
|
||||
->arrayNode('number_generator')
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->defaultValue(array(
|
||||
'default' => 'App\Invoice\DateNumberGenerator',
|
||||
))
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
->end();
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
|
||||
@@ -60,23 +60,25 @@ class TimesheetSubscriber implements EventSubscriber
|
||||
$entity = $args->getObject();
|
||||
|
||||
if ($entity instanceof Timesheet) {
|
||||
$duration = 0;
|
||||
if ($entity->getEnd() !== null) {
|
||||
$duration = $entity->getEnd()->getTimestamp() - $entity->getBegin()->getTimestamp();
|
||||
$entity->setDuration($duration);
|
||||
|
||||
// TODO allow to set hourly rate on activity, project and customer and prefer these
|
||||
|
||||
$hourlyRate = 0;
|
||||
foreach ($entity->getUser()->getPreferences() as $preference) {
|
||||
if ($preference->getName() == UserPreference::HOURLY_RATE) {
|
||||
$hourlyRate = (int) $preference->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
$rate = $hourlyRate * ($duration / 3600);
|
||||
$rate = $this->calculateRate($entity);
|
||||
$entity->setRate($rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $entity
|
||||
* @return float
|
||||
*/
|
||||
protected function calculateRate(Timesheet $entity)
|
||||
{
|
||||
$hourlyRate = (float) $entity->getUser()->getPreferenceValue(UserPreference::HOURLY_RATE, 0);
|
||||
return (float) $hourlyRate * ($entity->getDuration() / 3600);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,14 @@ class Customer
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="number", type="string", length=50, nullable=false)
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $number;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
@@ -73,15 +81,6 @@ class Customer
|
||||
*/
|
||||
private $company;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(name="vat", type="integer", length=2, nullable=true)
|
||||
* @Assert\Range(min = 0, max = 99)
|
||||
*
|
||||
*/
|
||||
private $vat;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
@@ -92,7 +91,7 @@ class Customer
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="street", type="text", length=65535, nullable=true)
|
||||
* @ORM\Column(name="address", type="text", length=65535, nullable=true)
|
||||
*/
|
||||
private $address;
|
||||
|
||||
@@ -167,13 +166,11 @@ class Customer
|
||||
* Set name
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -182,22 +179,38 @@ class Customer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $number
|
||||
* @return Customer
|
||||
*/
|
||||
public function setNumber(string $number)
|
||||
{
|
||||
$this->number = $number;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNumber(): ?string
|
||||
{
|
||||
return $this->number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comment
|
||||
*
|
||||
* @param string $comment
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -215,13 +228,11 @@ class Customer
|
||||
* Set visible
|
||||
*
|
||||
* @param boolean $visible
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setVisible($visible)
|
||||
{
|
||||
$this->visible = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -239,13 +250,11 @@ class Customer
|
||||
* Set company
|
||||
*
|
||||
* @param string $company
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setCompany($company)
|
||||
{
|
||||
$this->company = $company;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -259,41 +268,15 @@ class Customer
|
||||
return $this->company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set vat
|
||||
*
|
||||
* @param integer $vat
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setVat($vat)
|
||||
{
|
||||
$this->vat = $vat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get vat
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getVat()
|
||||
{
|
||||
return $this->vat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set contact
|
||||
*
|
||||
* @param string $contact
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setContact($contact)
|
||||
{
|
||||
$this->contact = $contact;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -307,6 +290,16 @@ class Customer
|
||||
return $this->contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address
|
||||
* @return Customer
|
||||
*/
|
||||
public function setAddress($address)
|
||||
{
|
||||
$this->address = $address;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -315,29 +308,15 @@ class Customer
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setAddress($address)
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set country
|
||||
*
|
||||
* @param string $country
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setCountry($country)
|
||||
{
|
||||
$this->country = $country;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -351,6 +330,16 @@ class Customer
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $currency
|
||||
* @return Customer
|
||||
*/
|
||||
public function setCurrency($currency)
|
||||
{
|
||||
$this->currency = $currency;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -359,28 +348,15 @@ class Customer
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $currency
|
||||
* @return $this
|
||||
*/
|
||||
public function setCurrency($currency)
|
||||
{
|
||||
$this->currency = $currency;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set phone
|
||||
*
|
||||
* @param string $phone
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setPhone($phone)
|
||||
{
|
||||
$this->phone = $phone;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -398,13 +374,11 @@ class Customer
|
||||
* Set fax
|
||||
*
|
||||
* @param string $fax
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setFax($fax)
|
||||
{
|
||||
$this->fax = $fax;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -422,13 +396,11 @@ class Customer
|
||||
* Set mobile
|
||||
*
|
||||
* @param string $mobile
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setMobile($mobile)
|
||||
{
|
||||
$this->mobile = $mobile;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -446,13 +418,11 @@ class Customer
|
||||
* Set mail
|
||||
*
|
||||
* @param string $mail
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setMail($mail)
|
||||
{
|
||||
$this->mail = $mail;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -470,13 +440,11 @@ class Customer
|
||||
* Set homepage
|
||||
*
|
||||
* @param string $homepage
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setHomepage($homepage)
|
||||
{
|
||||
$this->homepage = $homepage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -494,13 +462,11 @@ class Customer
|
||||
* Set timezone
|
||||
*
|
||||
* @param string $timezone
|
||||
*
|
||||
* @return Customer
|
||||
*/
|
||||
public function setTimezone($timezone)
|
||||
{
|
||||
$this->timezone = $timezone;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -516,12 +482,11 @@ class Customer
|
||||
|
||||
/**
|
||||
* @param Project[] $projects
|
||||
* @return $this
|
||||
* @return Customer
|
||||
*/
|
||||
public function setProjects($projects)
|
||||
{
|
||||
$this->projects = $projects;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
317
src/Entity/InvoiceTemplate.php
Normal file
317
src/Entity/InvoiceTemplate.php
Normal file
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* InvoiceTemplate
|
||||
*
|
||||
* @ORM\Entity(repositoryClass="App\Repository\InvoiceTemplateRepository")
|
||||
* @ORM\Table(
|
||||
* name="invoice_templates",
|
||||
* uniqueConstraints={
|
||||
* @ORM\UniqueConstraint(columns={"name"})
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceTemplate
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="name", type="string", length=255, nullable=false)
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="title", type="string", length=255, nullable=false)
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="company", type="string", length=255, nullable=false)
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $company;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="address", type="text", length=65535, nullable=true)
|
||||
*/
|
||||
private $address;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="due_days", type="integer", length=3, nullable=false)
|
||||
* @Assert\Range(min = 0, max = 999)
|
||||
*/
|
||||
private $dueDays = 30;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*
|
||||
* @ORM\Column(name="vat", type="integer", length=2, nullable=true)
|
||||
* @Assert\Range(min = 0, max = 99)
|
||||
*/
|
||||
private $vat = 0.00;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="calculator", type="string", length=20, nullable=false)
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $calculator = 'default';
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="number_generator", type="string", length=20, nullable=false)
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $numberGenerator = 'default';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="renderer", type="string", length=20, nullable=false)
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $renderer = 'default';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="payment_terms", type="text", length=65535, nullable=true)
|
||||
*/
|
||||
private $paymentTerms;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setTitle(string $title)
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAddress(): ?string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setAddress(string $address)
|
||||
{
|
||||
$this->address = $address;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNumberGenerator(): ?string
|
||||
{
|
||||
return $this->numberGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $numberGenerator
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setNumberGenerator(string $numberGenerator)
|
||||
{
|
||||
$this->numberGenerator = $numberGenerator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDueDays(): ?int
|
||||
{
|
||||
return $this->dueDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dueDays
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setDueDays(int $dueDays)
|
||||
{
|
||||
$this->dueDays = $dueDays;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getVat(): ?float
|
||||
{
|
||||
return $this->vat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $vat
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setVat(float $vat)
|
||||
{
|
||||
$this->vat = $vat;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCompany(): ?string
|
||||
{
|
||||
return $this->company;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $company
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setCompany(string $company)
|
||||
{
|
||||
$this->company = $company;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRenderer(): string
|
||||
{
|
||||
return $this->renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $renderer
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setRenderer(string $renderer)
|
||||
{
|
||||
$this->renderer = $renderer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCalculator(): string
|
||||
{
|
||||
return $this->calculator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $calculator
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setCalculator(string $calculator)
|
||||
{
|
||||
$this->calculator = $calculator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPaymentTerms(): ?string
|
||||
{
|
||||
return $this->paymentTerms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $paymentTerms
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setPaymentTerms(string $paymentTerms)
|
||||
{
|
||||
$this->paymentTerms = $paymentTerms;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ class Timesheet
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var float
|
||||
*
|
||||
* @ORM\Column(name="rate", type="decimal", precision=10, scale=2, nullable=false)
|
||||
*/
|
||||
@@ -150,7 +150,6 @@ class Timesheet
|
||||
* Set duration
|
||||
*
|
||||
* @param integer $duration
|
||||
*
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setDuration($duration)
|
||||
@@ -182,7 +181,6 @@ class Timesheet
|
||||
* Set user
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
@@ -205,7 +203,6 @@ class Timesheet
|
||||
* Set activity
|
||||
*
|
||||
* @param Activity $activity
|
||||
*
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setActivity($activity)
|
||||
@@ -228,7 +225,6 @@ class Timesheet
|
||||
* Set description
|
||||
*
|
||||
* @param string $description
|
||||
*
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setDescription($description)
|
||||
@@ -250,8 +246,7 @@ class Timesheet
|
||||
/**
|
||||
* Set rate
|
||||
*
|
||||
* @param string $rate
|
||||
*
|
||||
* @param float $rate
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setRate($rate)
|
||||
@@ -263,7 +258,7 @@ class Timesheet
|
||||
/**
|
||||
* Get rate
|
||||
*
|
||||
* @return string
|
||||
* @return float
|
||||
*/
|
||||
public function getRate()
|
||||
{
|
||||
|
||||
@@ -369,6 +369,35 @@ class User implements UserInterface, AdvancedUserInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return UserPreference|null
|
||||
*/
|
||||
public function getPreference(string $name)
|
||||
{
|
||||
foreach ($this->preferences as $preference) {
|
||||
if ($preference->getName() == $name) {
|
||||
return $preference;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param null $default
|
||||
* @return bool|int|null|string
|
||||
*/
|
||||
public function getPreferenceValue($name, $default = null)
|
||||
{
|
||||
$preference = $this->getPreference($name);
|
||||
if ($preference === null) {
|
||||
return $default;
|
||||
}
|
||||
return $preference->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserPreference $preference
|
||||
* @return User
|
||||
|
||||
@@ -59,6 +59,7 @@ class MenuSubscriber implements EventSubscriberInterface
|
||||
|
||||
$isLoggedIn = $auth->isGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
$isUser = $isLoggedIn && $auth->isGranted('ROLE_USER');
|
||||
$isTeamlead = $isLoggedIn && $auth->isGranted('ROLE_USER');
|
||||
|
||||
if (!$isLoggedIn || !$isUser) {
|
||||
return;
|
||||
@@ -68,6 +69,14 @@ class MenuSubscriber implements EventSubscriberInterface
|
||||
$menu->addItem(
|
||||
new MenuItemModel('timesheet', 'menu.timesheet', 'timesheet', [], 'fa fa-clock-o')
|
||||
);
|
||||
|
||||
if (!$isTeamlead) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu->addItem(
|
||||
new MenuItemModel('invoice', 'menu.invoice', 'invoice', [], 'fa fa-print')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,6 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CountryType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PercentType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TelType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
@@ -40,84 +39,57 @@ class CustomerEditForm extends AbstractType
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
// string - length 255
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
])
|
||||
// text
|
||||
->add('number', TextType::class, [
|
||||
'label' => 'label.customer_number',
|
||||
])
|
||||
->add('comment', TextareaType::class, [
|
||||
'label' => 'label.comment',
|
||||
'required' => false,
|
||||
])
|
||||
// do not allow project selection:
|
||||
// 1. it is a bad UX
|
||||
// 2. what should happen if they are detached?
|
||||
/*
|
||||
->add('projects', EntityType::class, [
|
||||
'label' => 'label.project',
|
||||
'class' => 'Kimai:Project',
|
||||
'multiple' => true,
|
||||
'expanded' => true
|
||||
])
|
||||
*/
|
||||
// boolean
|
||||
->add('visible', VisibilityType::class, [
|
||||
'label' => 'label.visible',
|
||||
])
|
||||
// string - length 255
|
||||
->add('company', TextType::class, [
|
||||
'label' => 'label.company',
|
||||
'required' => false,
|
||||
])
|
||||
// string - length 255
|
||||
->add('vat', PercentType::class, [
|
||||
'label' => 'label.vat',
|
||||
'type' => 'integer',
|
||||
])
|
||||
// string - length 255
|
||||
->add('contact', TextType::class, [
|
||||
'label' => 'label.contact',
|
||||
'required' => false,
|
||||
])
|
||||
// string - length 255
|
||||
->add('address', TextareaType::class, [
|
||||
'label' => 'label.address',
|
||||
'required' => false,
|
||||
])
|
||||
// string - length 2
|
||||
->add('country', CountryType::class, [
|
||||
'label' => 'label.country',
|
||||
])
|
||||
// string - length 3
|
||||
->add('currency', CurrencyType::class, [
|
||||
'label' => 'label.currency',
|
||||
])
|
||||
// string - length 255
|
||||
->add('phone', TelType::class, [
|
||||
'label' => 'label.phone',
|
||||
'required' => false,
|
||||
])
|
||||
// string - length 255
|
||||
->add('fax', TelType::class, [
|
||||
'label' => 'label.fax',
|
||||
'required' => false,
|
||||
])
|
||||
// string - length 255
|
||||
->add('mobile', TelType::class, [
|
||||
'label' => 'label.mobile',
|
||||
'required' => false,
|
||||
])
|
||||
// string - length 255
|
||||
->add('mail', EmailType::class, [
|
||||
'label' => 'label.email',
|
||||
'required' => false,
|
||||
])
|
||||
// string - length 255
|
||||
->add('homepage', UrlType::class, [
|
||||
'label' => 'label.homepage',
|
||||
'required' => false,
|
||||
])
|
||||
// string - length 255
|
||||
->add('timezone', TimezoneType::class, [
|
||||
'label' => 'label.timezone',
|
||||
])
|
||||
|
||||
81
src/Form/InvoiceTemplateForm.php
Normal file
81
src/Form/InvoiceTemplateForm.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Form\Type\InvoiceCalculatorType;
|
||||
use App\Form\Type\InvoiceNumberGeneratorType;
|
||||
use App\Form\Type\InvoiceRendererType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PercentType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used to manipulate invoice templates.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceTemplateForm extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
])
|
||||
->add('title', TextType::class, [
|
||||
'label' => 'label.title',
|
||||
])
|
||||
->add('company', TextType::class, [
|
||||
'label' => 'label.company',
|
||||
])
|
||||
->add('address', TextareaType::class, [
|
||||
'label' => 'label.address',
|
||||
'required' => false,
|
||||
])
|
||||
->add('paymentTerms', TextareaType::class, [
|
||||
'label' => 'label.payment_terms',
|
||||
'required' => false,
|
||||
])
|
||||
->add('dueDays', TextType::class, [
|
||||
'label' => 'label.due_days',
|
||||
])
|
||||
->add('vat', PercentType::class, [
|
||||
'label' => 'label.vat',
|
||||
'type' => 'integer',
|
||||
])
|
||||
->add('renderer', InvoiceRendererType::class, [])
|
||||
->add('calculator', InvoiceCalculatorType::class, [])
|
||||
->add('numberGenerator', InvoiceNumberGeneratorType::class, [])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => InvoiceTemplate::class,
|
||||
'csrf_protection' => true,
|
||||
'csrf_field_name' => '_token',
|
||||
'csrf_token_id' => 'admin_invoice_template',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,33 @@
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\ActivityType;
|
||||
use App\Form\Type\CustomerType;
|
||||
use App\Form\Type\PageSizeType;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\UserRoleType;
|
||||
use App\Form\Type\UserType;
|
||||
use App\Form\Type\VisibilityType;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
|
||||
/**
|
||||
* Defines the base form used for all toolbars.
|
||||
*
|
||||
* Extend this class and stack the elements defined here, they are coupled to each other and with the toolbar.js.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
abstract class AbstractToolbarForm extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* Dirty hack to enable easy handling of GET form in controller and javascript.
|
||||
*Cleans up the name of all form elents (and unfortunately of the form itself).
|
||||
@@ -30,4 +48,135 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addUserChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('user', UserType::class, [
|
||||
'label' => 'label.user',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addCustomerChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('customer', CustomerType::class, [
|
||||
'required' => false,
|
||||
'query_builder' => function (CustomerRepository $repo) {
|
||||
$query = new CustomerQuery();
|
||||
$query->setVisibility(CustomerQuery::SHOW_BOTH); // this field is the reason for the query here
|
||||
$query->setResultType(CustomerQuery::RESULT_TYPE_QUERYBUILDER);
|
||||
return $repo->findByQuery($query);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addVisibilityChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('visibility', VisibilityType::class, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addPageSizeChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('pageSize', PageSizeType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addUserRoleChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('role', UserRoleType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addStartDateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('begin', DateType::class, [
|
||||
'label' => 'label.begin',
|
||||
'widget' => 'single_text',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addEndDateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('end', DateType::class, [
|
||||
'label' => 'label.end',
|
||||
'widget' => 'single_text',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addProjectChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
$data = $event->getData();
|
||||
if (!isset($data['customer']) || empty($data['customer'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->getForm()->add('project', ProjectType::class, array(
|
||||
'group_by' => null,
|
||||
'required' => false,
|
||||
'query_builder' => function (ProjectRepository $repo) use ($data) {
|
||||
$qb = $repo->builderForEntityType();
|
||||
$qb->where('p.customer = :customer')->setParameter('customer', $data['customer']);
|
||||
return $qb;
|
||||
},
|
||||
));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addActivityChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
$data = $event->getData();
|
||||
if (!isset($data['project']) || empty($data['project'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->getForm()->add('activity', ActivityType::class, array(
|
||||
'group_by' => null,
|
||||
'required' => false,
|
||||
'query_builder' => function (ActivityRepository $repo) use ($data) {
|
||||
$qb = $repo->builderForEntityType();
|
||||
$qb->where('a.project = :project')->setParameter('project', $data['project']);
|
||||
return $qb;
|
||||
},
|
||||
));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace App\Form\Toolbar;
|
||||
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Repository\Query\ActivityQuery;
|
||||
|
||||
/**
|
||||
@@ -21,7 +20,7 @@ use App\Repository\Query\ActivityQuery;
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class ActivityToolbarForm extends ProjectToolbarForm
|
||||
class ActivityToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -29,25 +28,10 @@ class ActivityToolbarForm extends ProjectToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
/** @var ActivityQuery $query */
|
||||
$query = $options['data'];
|
||||
|
||||
if ($query->getCustomer() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$choices = [];
|
||||
foreach ($query->getCustomer()->getProjects() as $project) {
|
||||
$choices[] = $project;
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('project', ProjectType::class, [
|
||||
'required' => false,
|
||||
'choices' => $choices,
|
||||
]);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addCustomerChoice($builder);
|
||||
$this->addProjectChoice($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Toolbar\VisibilityToolbarForm;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
|
||||
@@ -20,9 +20,18 @@ use App\Repository\Query\CustomerQuery;
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class CustomerToolbarForm extends VisibilityToolbarForm
|
||||
class CustomerToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
58
src/Form/Toolbar/InvoiceToolbarForm.php
Normal file
58
src/Form/Toolbar/InvoiceToolbarForm.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\InvoiceTemplateType;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering timesheet entries for invoices.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addTemplateChoice($builder);
|
||||
$this->addUserChoice($builder);
|
||||
$this->addStartDateChoice($builder);
|
||||
$this->addEndDateChoice($builder);
|
||||
$this->addCustomerChoice($builder);
|
||||
$this->addProjectChoice($builder);
|
||||
$this->addActivityChoice($builder);
|
||||
}
|
||||
|
||||
protected function addTemplateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('template', InvoiceTemplateType::class, [
|
||||
'required' => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => InvoiceQuery::class,
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\PageSizeType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
|
||||
/**
|
||||
* Defines the base form used for all toolbars with pageSizes.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class PagedToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
/** @var CustomerQuery $query */
|
||||
$query = $options['data'];
|
||||
|
||||
$builder
|
||||
->add('pageSize', PageSizeType::class, [
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,8 @@
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Toolbar\VisibilityToolbarForm;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use App\Form\Type\CustomerType;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use App\Repository\Query\ProjectQuery;
|
||||
|
||||
/**
|
||||
@@ -24,7 +20,7 @@ use App\Repository\Query\ProjectQuery;
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class ProjectToolbarForm extends VisibilityToolbarForm
|
||||
class ProjectToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -32,19 +28,9 @@ class ProjectToolbarForm extends VisibilityToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder
|
||||
->add('customer', CustomerType::class, [
|
||||
'required' => false,
|
||||
'query_builder' => function (CustomerRepository $repo) {
|
||||
$query = new CustomerQuery();
|
||||
$query->setVisibility(CustomerQuery::SHOW_BOTH); // this field is the reason for the query here
|
||||
$query->setResultType(CustomerQuery::RESULT_TYPE_QUERYBUILDER);
|
||||
return $repo->findByQuery($query);
|
||||
},
|
||||
])
|
||||
;
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addCustomerChoice($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,28 +11,26 @@
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\VisibilityType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering entities with a "visibility" field.
|
||||
* Defines the form used for filtering the admin timesheet.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class VisibilityToolbarForm extends PagedToolbarForm
|
||||
class TimesheetAdminToolbarForm extends TimesheetToolbarForm
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder
|
||||
->add('visibility', VisibilityType::class, [
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
$this->addTimesheetStateChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addUserChoice($builder);
|
||||
$this->addCustomerChoice($builder);
|
||||
$this->addProjectChoice($builder);
|
||||
$this->addActivityChoice($builder);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ namespace App\Form\Toolbar;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use App\Form\Type\ActivityType;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
|
||||
/**
|
||||
@@ -22,7 +21,7 @@ use App\Repository\Query\TimesheetQuery;
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class TimesheetToolbarForm extends ActivityToolbarForm
|
||||
class TimesheetToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -30,42 +29,26 @@ class TimesheetToolbarForm extends ActivityToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('state', ChoiceType::class, [
|
||||
'label' => 'label.entryState',
|
||||
'choices' => [
|
||||
'entryState.all' => TimesheetQuery::STATE_ALL,
|
||||
'entryState.running' => TimesheetQuery::STATE_RUNNING,
|
||||
'entryState.stopped' => TimesheetQuery::STATE_STOPPED
|
||||
],
|
||||
])
|
||||
;
|
||||
parent::buildForm($builder, $options);
|
||||
$this->addActivityChoice($builder, $options['data']);
|
||||
|
||||
$builder->remove('visibility');
|
||||
$this->addTimesheetStateChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addCustomerChoice($builder);
|
||||
$this->addProjectChoice($builder);
|
||||
$this->addActivityChoice($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
* @param TimesheetQuery $query
|
||||
*/
|
||||
protected function addActivityChoice(FormBuilderInterface $builder, TimesheetQuery $query)
|
||||
protected function addTimesheetStateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
if ($query->getProject() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$choices = [];
|
||||
foreach ($query->getProject()->getActivities() as $activity) {
|
||||
$choices[] = $activity;
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('activity', ActivityType::class, [
|
||||
'required' => false,
|
||||
'choices' => $choices,
|
||||
]);
|
||||
$builder->add('state', ChoiceType::class, [
|
||||
'label' => 'label.entryState',
|
||||
'choices' => [
|
||||
'entryState.all' => TimesheetQuery::STATE_ALL,
|
||||
'entryState.running' => TimesheetQuery::STATE_RUNNING,
|
||||
'entryState.stopped' => TimesheetQuery::STATE_STOPPED
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\UserRoleType;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@@ -21,7 +20,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class UserToolbarForm extends VisibilityToolbarForm
|
||||
class UserToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -29,13 +28,9 @@ class UserToolbarForm extends VisibilityToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder
|
||||
->add('role', UserRoleType::class, [
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addUserRoleChoice($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
66
src/Form/Type/InvoiceCalculatorType.php
Normal file
66
src/Form/Type/InvoiceCalculatorType.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Invoice\ServiceInvoice;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select an invoice calculator.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceCalculatorType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var ServiceInvoice
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* InvoiceCalculatorType constructor.
|
||||
* @param ServiceInvoice $service
|
||||
*/
|
||||
public function __construct(ServiceInvoice $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$renderer = [];
|
||||
foreach ($this->service->getCalculator() as $name => $class) {
|
||||
$renderer[$name] = $name;
|
||||
}
|
||||
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.invoice_calculator',
|
||||
'choices' => $renderer,
|
||||
'choice_label' => function($renderer) {
|
||||
return 'invoice_calculator.' . $renderer;
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
}
|
||||
66
src/Form/Type/InvoiceNumberGeneratorType.php
Normal file
66
src/Form/Type/InvoiceNumberGeneratorType.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Invoice\ServiceInvoice;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select an invoice number generator.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceNumberGeneratorType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var ServiceInvoice
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* InvoiceNumberGeneratorType constructor.
|
||||
* @param ServiceInvoice $service
|
||||
*/
|
||||
public function __construct(ServiceInvoice $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$renderer = [];
|
||||
foreach ($this->service->getNumberGenerator() as $name => $class) {
|
||||
$renderer[$name] = $name;
|
||||
}
|
||||
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.invoice_number_generator',
|
||||
'choices' => $renderer,
|
||||
'choice_label' => function($renderer) {
|
||||
return 'invoice_number_generator.' . $renderer;
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
}
|
||||
66
src/Form/Type/InvoiceRendererType.php
Normal file
66
src/Form/Type/InvoiceRendererType.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Invoice\ServiceInvoice;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select an invoice renderer.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceRendererType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var ServiceInvoice
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* InvoiceRendererType constructor.
|
||||
* @param ServiceInvoice $service
|
||||
*/
|
||||
public function __construct(ServiceInvoice $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$renderer = [];
|
||||
foreach ($this->service->getRenderer() as $name => $action) {
|
||||
$renderer[$name] = $name;
|
||||
}
|
||||
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.invoice_renderer',
|
||||
'choices' => $renderer,
|
||||
'choice_label' => function($renderer) {
|
||||
return 'invoice_renderer.' . $renderer;
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
}
|
||||
45
src/Form/Type/InvoiceTemplateType.php
Normal file
45
src/Form/Type/InvoiceTemplateType.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select an invoice template.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceTemplateType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.template',
|
||||
'class' => InvoiceTemplate::class,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return EntityType::class;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use App\Entity\Project;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\ProjectQuery;
|
||||
|
||||
/**
|
||||
* Custom form field type to select a project.
|
||||
@@ -33,7 +32,7 @@ class ProjectType extends AbstractType
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.project',
|
||||
'class' => 'Kimai:Project',
|
||||
'class' => Project::class,
|
||||
'choice_label' => 'name',
|
||||
'group_by' => function (Project $project, $key, $index) {
|
||||
return $project->getCustomer()->getName();
|
||||
|
||||
@@ -32,6 +32,7 @@ class VisibilityType extends AbstractType
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.visible',
|
||||
'choices' => [
|
||||
'both' => VisibilityQuery::SHOW_BOTH,
|
||||
'yes' => VisibilityQuery::SHOW_VISIBLE,
|
||||
'no' => VisibilityQuery::SHOW_HIDDEN,
|
||||
],
|
||||
|
||||
48
src/Invoice/CalculatorInterface.php
Normal file
48
src/Invoice/CalculatorInterface.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Model\InvoiceModel;
|
||||
|
||||
/**
|
||||
* CalculatorInterface defines all methods for any invoice price calculator.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
interface CalculatorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model);
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getSubtotal(): float;
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getTax(): float;
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getTotal(): float;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrency(): string;
|
||||
}
|
||||
44
src/Invoice/DateNumberGenerator.php
Normal file
44
src/Invoice/DateNumberGenerator.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Model\InvoiceModel;
|
||||
|
||||
/**
|
||||
* Class DateNumberGenerator generates the invoice number based on the current day.
|
||||
* It will create duplicate IDs if you create multiple invoices per day.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class DateNumberGenerator implements NumberGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInvoiceNumber(): string
|
||||
{
|
||||
return date('ymd');
|
||||
}
|
||||
}
|
||||
94
src/Invoice/DefaultCalculator.php
Normal file
94
src/Invoice/DefaultCalculator.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Model\InvoiceModel;
|
||||
|
||||
/**
|
||||
* Class DefaultCalculator works on all given entries using:
|
||||
* - the customers currency
|
||||
* - the invoice template vat rate
|
||||
* - the entries rate
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class DefaultCalculator implements CalculatorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $currency;
|
||||
|
||||
/**
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getSubtotal(): float
|
||||
{
|
||||
$amount = 0;
|
||||
foreach ($this->model->getEntries() as $entry) {
|
||||
$amount += $entry->getRate();
|
||||
}
|
||||
return round($amount, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getVat()
|
||||
{
|
||||
return $this->model->getTemplate()->getVat();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getTax(): float
|
||||
{
|
||||
$vat = $this->getVat();
|
||||
if ($vat == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$percent = $vat / 100.00;
|
||||
return round($this->getSubtotal() * $percent, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getTotal(): float
|
||||
{
|
||||
return $this->getSubtotal() + $this->getTax();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->model->getCustomer()->getCurrency();
|
||||
}
|
||||
}
|
||||
33
src/Invoice/NumberGeneratorInterface.php
Normal file
33
src/Invoice/NumberGeneratorInterface.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Model\InvoiceModel;
|
||||
|
||||
/**
|
||||
* Class NumberGeneratorInterface defines all methods that invoice number generator have to implement.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
interface NumberGeneratorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInvoiceNumber(): string;
|
||||
}
|
||||
43
src/Invoice/RandomNumberGenerator.php
Normal file
43
src/Invoice/RandomNumberGenerator.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Model\InvoiceModel;
|
||||
|
||||
/**
|
||||
* Class RandomNumberGenerator is meant for testing purpose only.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class RandomNumberGenerator implements NumberGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInvoiceNumber(): string
|
||||
{
|
||||
return rand(1000000, 9999999);
|
||||
}
|
||||
}
|
||||
106
src/Invoice/ServiceInvoice.php
Normal file
106
src/Invoice/ServiceInvoice.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
/**
|
||||
* A service to manage the invoice configuration:
|
||||
* - invoice number generator
|
||||
* - invoice sum calculator
|
||||
* - template renderer
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class ServiceInvoice
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* ServiceInvoice constructor.
|
||||
* @param array $invoiceConfig
|
||||
*/
|
||||
public function __construct(array $invoiceConfig)
|
||||
{
|
||||
$this->config = $invoiceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getNumberGenerator()
|
||||
{
|
||||
return $this->config['number_generator'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return NumberGeneratorInterface|null
|
||||
*/
|
||||
public function getNumberGeneratorByName(string $name)
|
||||
{
|
||||
foreach ($this->getNumberGenerator() as $key => $class) {
|
||||
if ($key === $name) {
|
||||
return new $class();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCalculator()
|
||||
{
|
||||
return $this->config['calculator'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return CalculatorInterface|null
|
||||
*/
|
||||
public function getCalculatorByName(string $name)
|
||||
{
|
||||
foreach ($this->getCalculator() as $key => $class) {
|
||||
if ($key === $name) {
|
||||
return new $class();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of invoice renderer, which will consist of a unique name and a controller action.
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getRenderer()
|
||||
{
|
||||
return $this->config['renderer'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $renderer
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRendererActionByName($renderer)
|
||||
{
|
||||
foreach ($this->config['renderer'] as $name => $action) {
|
||||
if ($name == $renderer) {
|
||||
return $action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\DependencyInjection\AppExtension;
|
||||
use App\DependencyInjection\Compiler\DoctrineCompilerPass;
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
@@ -38,6 +39,8 @@ class Kernel extends BaseKernel
|
||||
|
||||
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
|
||||
{
|
||||
$container->registerExtension(new AppExtension());
|
||||
|
||||
$container->setParameter('container.autowiring.strict_mode', true);
|
||||
$container->setParameter('container.dumper.inline_class_loader', true);
|
||||
$confDir = $this->getProjectDir().'/config';
|
||||
|
||||
186
src/Model/InvoiceModel.php
Normal file
186
src/Model/InvoiceModel.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\DateNumberGenerator;
|
||||
use App\Invoice\DefaultCalculator;
|
||||
use App\Invoice\NumberGeneratorInterface;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
|
||||
/**
|
||||
* Class InvoiceModel is the ONLY value that a renderer template receives for generating the invoice.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceModel
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Customer
|
||||
*/
|
||||
protected $customer;
|
||||
|
||||
/**
|
||||
* @var InvoiceQuery
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var Timesheet[]
|
||||
*/
|
||||
protected $entries;
|
||||
|
||||
/**
|
||||
* @var InvoiceTemplate
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* @var CalculatorInterface
|
||||
*/
|
||||
protected $calculator;
|
||||
|
||||
/**
|
||||
* @var NumberGeneratorInterface
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
/**
|
||||
* @return InvoiceQuery
|
||||
*/
|
||||
public function getQuery(): InvoiceQuery
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setQuery(InvoiceQuery $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Timesheet[]
|
||||
*/
|
||||
public function getEntries(): array
|
||||
{
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $entries
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setEntries(array $entries)
|
||||
{
|
||||
$this->entries = $entries;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function getTemplate(): ?InvoiceTemplate
|
||||
{
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceTemplate $template
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setTemplate($template)
|
||||
{
|
||||
$this->template = $template;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
*/
|
||||
public function getCustomer()
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer $customer
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setCustomer($customer)
|
||||
{
|
||||
$this->customer = $customer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDueDate(): \DateTime
|
||||
{
|
||||
return new \DateTime('+'.$this->getTemplate()->getDueDays().' days');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getInvoiceDate(): \DateTime
|
||||
{
|
||||
return new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CalculatorInterface $calculator
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setCalculator(CalculatorInterface $calculator)
|
||||
{
|
||||
$this->calculator = $calculator;
|
||||
$this->calculator->setModel($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NumberGeneratorInterface $generator
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setNumberGenerator(NumberGeneratorInterface $generator)
|
||||
{
|
||||
$this->generator = $generator;
|
||||
$this->generator->setModel($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NumberGeneratorInterface
|
||||
*/
|
||||
public function getNumberGenerator(): NumberGeneratorInterface
|
||||
{
|
||||
return $this->generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CalculatorInterface
|
||||
*/
|
||||
public function getCalculator(): CalculatorInterface
|
||||
{
|
||||
return $this->calculator;
|
||||
}
|
||||
}
|
||||
60
src/Repository/InvoiceTemplateRepository.php
Normal file
60
src/Repository/InvoiceTemplateRepository.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Repository\Query\BaseQuery;
|
||||
use Doctrine\ORM\Query;
|
||||
|
||||
/**
|
||||
* Class InvoiceTemplateRepository
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceTemplateRepository extends AbstractRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTemplate()
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->select('COUNT(t.id) as totalRecords')
|
||||
->from(InvoiceTemplate::class, 't')
|
||||
;
|
||||
|
||||
$result = $qb->getQuery()->execute([], Query::HYDRATE_ARRAY);
|
||||
|
||||
if (!isset($result[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result[0]['totalRecords'] > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BaseQuery $query
|
||||
* @return \Doctrine\ORM\QueryBuilder|\Pagerfanta\Pagerfanta
|
||||
*/
|
||||
public function findByQuery(BaseQuery $query)
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->select('t')
|
||||
->from(InvoiceTemplate::class, 't')
|
||||
->orderBy('t.id');
|
||||
|
||||
return $this->getBaseQueryResult($qb, $query);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ use App\Entity\Project;
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class ActivityQuery extends VisibilityQuery
|
||||
class ActivityQuery extends ProjectQuery
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -27,29 +27,6 @@ class ActivityQuery extends VisibilityQuery
|
||||
*/
|
||||
protected $project;
|
||||
|
||||
/**
|
||||
* @var Customer
|
||||
*/
|
||||
protected $customer;
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
*/
|
||||
public function getCustomer()
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer $customer
|
||||
* @return $this
|
||||
*/
|
||||
public function setCustomer(Customer $customer = null)
|
||||
{
|
||||
$this->customer = $customer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project
|
||||
*/
|
||||
|
||||
69
src/Repository/Query/InvoiceQuery.php
Normal file
69
src/Repository/Query/InvoiceQuery.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai package.
|
||||
*
|
||||
* (c) Kevin Papst <kevin@kevinpapst.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Repository\Query;
|
||||
|
||||
use App\Entity\InvoiceTemplate;
|
||||
|
||||
/**
|
||||
* Can be used for invoice queries.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class InvoiceQuery extends TimesheetQuery
|
||||
{
|
||||
|
||||
/**
|
||||
* @var InvoiceTemplate
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* @var InvoiceTemplate[]
|
||||
*/
|
||||
protected $templates = [];
|
||||
|
||||
/**
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function getTemplate()
|
||||
{
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceTemplate $template
|
||||
* @return InvoiceQuery
|
||||
*/
|
||||
public function setTemplate($template)
|
||||
{
|
||||
$this->template = $template;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvoiceTemplate[]
|
||||
*/
|
||||
public function getTemplates(): array
|
||||
{
|
||||
return $this->templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceTemplate[] $templates
|
||||
* @return InvoiceQuery
|
||||
*/
|
||||
public function setTemplates(array $templates)
|
||||
{
|
||||
$this->templates = $templates;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ use App\Entity\Project;
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class TimesheetQuery extends BaseQuery
|
||||
class TimesheetQuery extends ActivityQuery
|
||||
{
|
||||
|
||||
const STATE_ALL = 1;
|
||||
@@ -47,18 +47,18 @@ class TimesheetQuery extends BaseQuery
|
||||
* @var Activity
|
||||
*/
|
||||
protected $activity;
|
||||
/**
|
||||
* @var Project
|
||||
*/
|
||||
protected $project;
|
||||
/**
|
||||
* @var Customer
|
||||
*/
|
||||
protected $customer;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $state = self::STATE_ALL;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $begin;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $end;
|
||||
|
||||
/**
|
||||
* @return User
|
||||
@@ -98,48 +98,6 @@ class TimesheetQuery extends BaseQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project
|
||||
*/
|
||||
public function getProject()
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project overwrites: setCustomer()
|
||||
* Is overwritten by: setActivity()
|
||||
*
|
||||
* @param Project $project
|
||||
* @return TimesheetQuery
|
||||
*/
|
||||
public function setProject(Project $project = null)
|
||||
{
|
||||
$this->project = $project;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
*/
|
||||
public function getCustomer()
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project overwrites: none
|
||||
* Is overwritten by: setActivity() and setProject()
|
||||
*
|
||||
* @param Customer $customer
|
||||
* @return TimesheetQuery
|
||||
*/
|
||||
public function setCustomer(Customer $customer = null)
|
||||
{
|
||||
$this->customer = $customer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
@@ -165,4 +123,40 @@ class TimesheetQuery extends BaseQuery
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getBegin()
|
||||
{
|
||||
return $this->begin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $begin
|
||||
* @return TimesheetQuery
|
||||
*/
|
||||
public function setBegin($begin)
|
||||
{
|
||||
$this->begin = $begin;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getEnd()
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $end
|
||||
* @return TimesheetQuery
|
||||
*/
|
||||
public function setEnd($end)
|
||||
{
|
||||
$this->end = $end;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Entity\User;
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Timesheet;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use App\Model\Statistic\Month;
|
||||
use App\Model\Statistic\Year;
|
||||
@@ -266,7 +267,7 @@ class TimesheetRepository extends AbstractRepository
|
||||
|
||||
/**
|
||||
* @param TimesheetQuery $query
|
||||
* @return Pagerfanta
|
||||
* @return QueryBuilder|Pagerfanta
|
||||
*/
|
||||
public function findByQuery(TimesheetQuery $query)
|
||||
{
|
||||
@@ -291,6 +292,15 @@ class TimesheetRepository extends AbstractRepository
|
||||
$qb->andWhere($qb->expr()->isNotNull('t.end'));
|
||||
}
|
||||
|
||||
if ($query->getBegin() !== null) {
|
||||
$qb->andWhere('t.begin >= :begin')
|
||||
->setParameter('begin', $query->getBegin());
|
||||
}
|
||||
if ($query->getEnd() !== null) {
|
||||
$qb->andWhere('t.end <= :end')
|
||||
->setParameter('end', $query->getEnd());
|
||||
}
|
||||
|
||||
if ($query->getActivity() !== null) {
|
||||
$qb->andWhere('t.activity = :activity')
|
||||
->setParameter('activity', $query->getActivity());
|
||||
|
||||
@@ -23,6 +23,14 @@ use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
|
||||
*/
|
||||
class UserRepository extends AbstractRepository implements UserLoaderInterface
|
||||
{
|
||||
/**
|
||||
* @param $id
|
||||
* @return null|User
|
||||
*/
|
||||
public function getById($id)
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return statistic data for all user.
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use App\Utils\Markdown;
|
||||
use Symfony\Component\Intl\Intl;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Timesheet;
|
||||
|
||||
/**
|
||||
@@ -123,8 +121,11 @@ class Extensions extends \Twig_Extension
|
||||
*/
|
||||
public function money($amount, $currency = null)
|
||||
{
|
||||
$currency = $currency ?: Customer::DEFAULT_CURRENCY;
|
||||
return round($amount) . ' ' . Intl::getCurrencyBundle()->getCurrencySymbol($currency);
|
||||
$result = round($amount, 2);
|
||||
if ($currency !== null) {
|
||||
$result .= ' ' . Intl::getCurrencyBundle()->getCurrencySymbol($currency);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user