Upload invoice documents via UI (#1495)
This commit is contained in:
@@ -22,6 +22,7 @@ Permission changes:
|
|||||||
- `comments_create_teamlead_project` - NEW: permission that allows to add new comments for a teamlead of the current project
|
- `comments_create_teamlead_project` - NEW: permission that allows to add new comments for a teamlead of the current project
|
||||||
- `edit_teamlead_project` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
|
- `edit_teamlead_project` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
|
||||||
- `edit_teamlead_customer` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
|
- `edit_teamlead_customer` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
|
||||||
|
- `upload_invoice_template` - NEW: permission that allows to upload invoice documents from the UI
|
||||||
|
|
||||||
## [1.7](https://github.com/kevinpapst/kimai2/releases/tag/1.7)
|
## [1.7](https://github.com/kevinpapst/kimai2/releases/tag/1.7)
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ kimai:
|
|||||||
SINGLE_USER: ['view_team_member','budget_team_project']
|
SINGLE_USER: ['view_team_member','budget_team_project']
|
||||||
SINGLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member']
|
SINGLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member']
|
||||||
SINGLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member']
|
SINGLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member']
|
||||||
SINGLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member']
|
SINGLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template']
|
||||||
# link above sets to one complete set for each user role
|
# link above sets to one complete set for each user role
|
||||||
ROLE_USER: ['@TIMESHEET','@PROFILE','@SINGLE_USER']
|
ROLE_USER: ['@TIMESHEET','@PROFILE','@SINGLE_USER']
|
||||||
ROLE_TEAMLEAD: ['@ACTIVITIES_TEAMLEAD','@PROJECTS_TEAMLEAD','@CUSTOMERS_TEAMLEAD','@TIMESHEET_OTHER','@INVOICE','@TIMESHEET','@PROFILE','@EXPORT','@TAGS','@SINGLE_TEAMLEAD']
|
ROLE_TEAMLEAD: ['@ACTIVITIES_TEAMLEAD','@PROJECTS_TEAMLEAD','@CUSTOMERS_TEAMLEAD','@TIMESHEET_OTHER','@INVOICE','@TIMESHEET','@PROFILE','@EXPORT','@TAGS','@SINGLE_TEAMLEAD']
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ namespace App\Controller;
|
|||||||
use App\Entity\InvoiceTemplate;
|
use App\Entity\InvoiceTemplate;
|
||||||
use App\Event\InvoicePostRenderEvent;
|
use App\Event\InvoicePostRenderEvent;
|
||||||
use App\Event\InvoicePreRenderEvent;
|
use App\Event\InvoicePreRenderEvent;
|
||||||
|
use App\Form\InvoiceDocumentUploadForm;
|
||||||
use App\Form\InvoiceTemplateForm;
|
use App\Form\InvoiceTemplateForm;
|
||||||
use App\Form\Toolbar\InvoiceToolbarForm;
|
use App\Form\Toolbar\InvoiceToolbarForm;
|
||||||
use App\Invoice\InvoiceFormatter;
|
use App\Invoice\InvoiceFormatter;
|
||||||
use App\Invoice\InvoiceItemInterface;
|
use App\Invoice\InvoiceItemInterface;
|
||||||
use App\Invoice\InvoiceModel;
|
use App\Invoice\InvoiceModel;
|
||||||
use App\Invoice\ServiceInvoice;
|
use App\Invoice\ServiceInvoice;
|
||||||
|
use App\Repository\InvoiceDocumentRepository;
|
||||||
use App\Repository\InvoiceTemplateRepository;
|
use App\Repository\InvoiceTemplateRepository;
|
||||||
use App\Repository\Query\BaseQuery;
|
use App\Repository\Query\BaseQuery;
|
||||||
use App\Repository\Query\InvoiceQuery;
|
use App\Repository\Query\InvoiceQuery;
|
||||||
@@ -25,6 +27,7 @@ use App\Timesheet\UserDateTimeFactory;
|
|||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\Form\SubmitButton;
|
use Symfony\Component\Form\SubmitButton;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
@@ -289,6 +292,68 @@ final class InvoiceController extends AbstractController
|
|||||||
return $this->renderTemplateForm($template, $request);
|
return $this->renderTemplateForm($template, $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(path="/document_upload", name="admin_invoice_document_upload", methods={"GET", "POST"})
|
||||||
|
* @Security("is_granted('upload_invoice_template')")
|
||||||
|
*/
|
||||||
|
public function uploadDocumentAction(Request $request, string $projectDirectory, InvoiceDocumentRepository $documentRepository)
|
||||||
|
{
|
||||||
|
$dir = $documentRepository->getCustomInvoiceDirectory();
|
||||||
|
$invoiceDir = $projectDirectory . DIRECTORY_SEPARATOR . $dir;
|
||||||
|
$canUpload = true;
|
||||||
|
$form = null;
|
||||||
|
|
||||||
|
if (!file_exists($invoiceDir)) {
|
||||||
|
@mkdir($invoiceDir);
|
||||||
|
}
|
||||||
|
if (!file_exists($invoiceDir)) {
|
||||||
|
$this->flashError(sprintf('Invoice directory is not existing and could not be created: %s', $dir));
|
||||||
|
$canUpload = false;
|
||||||
|
}
|
||||||
|
if (!is_writable($invoiceDir)) {
|
||||||
|
$this->flashError(sprintf('Invoice directory cannot be written: %s', $dir));
|
||||||
|
$canUpload = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($canUpload) {
|
||||||
|
$form = $this->createForm(InvoiceDocumentUploadForm::class, null, [
|
||||||
|
'action' => $this->generateUrl('admin_invoice_document_upload', []),
|
||||||
|
'method' => 'POST'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
/** @var UploadedFile $uploadedFile */
|
||||||
|
$uploadedFile = $form->get('document')->getData();
|
||||||
|
|
||||||
|
$originalFilename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
|
||||||
|
$safeFilename = transliterator_transliterate(
|
||||||
|
'Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()',
|
||||||
|
$originalFilename
|
||||||
|
);
|
||||||
|
$newFilename = $safeFilename . '.' . $uploadedFile->guessExtension();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$uploadedFile->move($invoiceDir, $newFilename);
|
||||||
|
$this->flashSuccess('action.update.success');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_invoice_document_upload');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->flashError(
|
||||||
|
sprintf('Failed uploading invoice document: %e', $e->getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('invoice/document_upload.html.twig', [
|
||||||
|
'form' => (null !== $form) ? $form->createView() : null,
|
||||||
|
'documents' => $this->service->getDocuments(),
|
||||||
|
'baseDirectory' => $projectDirectory . DIRECTORY_SEPARATOR,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route(path="/template/create", name="admin_invoice_template_create", methods={"GET", "POST"})
|
* @Route(path="/template/create", name="admin_invoice_template_create", methods={"GET", "POST"})
|
||||||
* @Route(path="/template/create/{id}", name="admin_invoice_template_copy", methods={"GET", "POST"})
|
* @Route(path="/template/create/{id}", name="admin_invoice_template_copy", methods={"GET", "POST"})
|
||||||
|
|||||||
@@ -42,4 +42,9 @@ final class InvoiceDocument
|
|||||||
{
|
{
|
||||||
return $this->file->getExtension();
|
return $this->file->getExtension();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getLastChange(): int
|
||||||
|
{
|
||||||
|
return $this->file->getMTime();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/Form/InvoiceDocumentUploadForm.php
Normal file
62
src/Form/InvoiceDocumentUploadForm.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\File;
|
||||||
|
|
||||||
|
class InvoiceDocumentUploadForm extends AbstractType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('document', FileType::class, [
|
||||||
|
'label' => 'label.invoice_renderer',
|
||||||
|
'translation_domain' => 'invoice-renderer',
|
||||||
|
'help' => 'help.upload',
|
||||||
|
'mapped' => false,
|
||||||
|
'required' => true,
|
||||||
|
'constraints' => [
|
||||||
|
new File([
|
||||||
|
'mimeTypes' => [
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet',
|
||||||
|
],
|
||||||
|
'mimeTypesMessage' => 'This file type is not allowed',
|
||||||
|
])
|
||||||
|
],
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'csrf_protection' => true,
|
||||||
|
'csrf_field_name' => '_token',
|
||||||
|
'csrf_token_id' => 'admin_invoice_document_upload',
|
||||||
|
'attr' => [
|
||||||
|
'data-form-event' => 'kimai.invoiceTemplateUpdate',
|
||||||
|
'data-msg-success' => 'action.update.success',
|
||||||
|
'data-msg-error' => 'action.update.error',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,26 +12,24 @@ namespace App\Repository;
|
|||||||
use App\Entity\InvoiceDocument;
|
use App\Entity\InvoiceDocument;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
|
||||||
class InvoiceDocumentRepository
|
final class InvoiceDocumentRepository
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $documentDirs = [];
|
private $documentDirs = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $directories
|
|
||||||
*/
|
|
||||||
public function __construct(array $directories)
|
public function __construct(array $directories)
|
||||||
{
|
{
|
||||||
$this->documentDirs = $directories;
|
$this->documentDirs = $directories;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getCustomInvoiceDirectory(): string
|
||||||
* @param string $name
|
{
|
||||||
* @return InvoiceDocument|null
|
return $this->documentDirs[0];
|
||||||
*/
|
}
|
||||||
public function findByName(string $name)
|
|
||||||
|
public function findByName(string $name): ?InvoiceDocument
|
||||||
{
|
{
|
||||||
foreach ($this->findAll() as $document) {
|
foreach ($this->findAll() as $document) {
|
||||||
if ($document->getId() === $name) {
|
if ($document->getId() === $name) {
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ final class IconExtension extends AbstractExtension
|
|||||||
'timesheet-team' => 'fas fa-user-clock',
|
'timesheet-team' => 'fas fa-user-clock',
|
||||||
'trash' => 'far fa-trash-alt',
|
'trash' => 'far fa-trash-alt',
|
||||||
'unlocked' => 'fas fa-unlock-alt',
|
'unlocked' => 'fas fa-unlock-alt',
|
||||||
|
'upload' => 'fas fa-upload',
|
||||||
'user' => 'fas fa-user-friends',
|
'user' => 'fas fa-user-friends',
|
||||||
'visibility' => 'far fa-eye',
|
'visibility' => 'far fa-eye',
|
||||||
'warning' => 'fas fa-exclamation-triangle',
|
'warning' => 'fas fa-exclamation-triangle',
|
||||||
|
|||||||
@@ -26,12 +26,31 @@
|
|||||||
{% set actions = actions|merge({'create': path('admin_invoice_template_create')}) %}
|
{% set actions = actions|merge({'create': path('admin_invoice_template_create')}) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if is_granted('upload_invoice_template') %}
|
||||||
|
{# File upload does not work in a modal right now #}
|
||||||
|
{% set actions = actions|merge({'upload': {'url': path('admin_invoice_document_upload')}}) %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% set actions = actions|merge({'help': {'url': 'invoices.html'|docu_link, 'target': '_blank'}}) %}
|
{% set actions = actions|merge({'help': {'url': 'invoices.html'|docu_link, 'target': '_blank'}}) %}
|
||||||
|
|
||||||
{% set event = trigger('actions.invoice_templates', {'actions': actions, 'view': 'index'}) %}
|
{% set event = trigger('actions.invoice_templates', {'actions': actions, 'view': 'index'}) %}
|
||||||
{{ widgets.page_actions(actions) }}
|
{{ widgets.page_actions(actions) }}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro invoice_upload(view) %}
|
||||||
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
|
||||||
|
{% set actions = {} %}
|
||||||
|
{% if view == 'index' and is_granted('manage_invoice_template') %}
|
||||||
|
{% set actions = actions|merge({'back': path('admin_invoice_template')}) %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set actions = actions|merge({'help': {'url': 'invoices.html'|docu_link, 'target': '_blank'}}) %}
|
||||||
|
|
||||||
|
{% set event = trigger('actions.invoice_upload', {'actions': actions, 'view': 'index'}) %}
|
||||||
|
{{ widgets.page_actions(actions) }}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro invoice_template(template, view) %}
|
{% macro invoice_template(template, view) %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
|
||||||
|
|||||||
49
templates/invoice/document_upload.html.twig
Normal file
49
templates/invoice/document_upload.html.twig
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||||
|
{% import "invoice/actions.html.twig" as actions %}
|
||||||
|
|
||||||
|
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
||||||
|
{% block page_actions %}{{ actions.invoice_upload('index') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% if form is not null %}
|
||||||
|
{% form_theme form '@AdminLTE/layout/form-theme-horizontal.html.twig' %}
|
||||||
|
{% set formEditTemplate = app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||||
|
{% set formOptions = {
|
||||||
|
'title': 'upload'|trans,
|
||||||
|
'form': form,
|
||||||
|
'back': path('admin_invoice_template')
|
||||||
|
} %}
|
||||||
|
|
||||||
|
{% embed formEditTemplate with formOptions %}
|
||||||
|
{% block form_body %}
|
||||||
|
{{ form_row(form.document) }}
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
{% endblock %}
|
||||||
|
{% endembed %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if documents|length > 0 %}
|
||||||
|
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'documents': documents} %}
|
||||||
|
{% import "project/actions.html.twig" as actions %}
|
||||||
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
{% block box_title %}{{ 'label.invoice_renderer'|trans({}, 'invoice-renderer') }}{% endblock %}
|
||||||
|
{% block box_attributes %}
|
||||||
|
id="invoice_document_list"
|
||||||
|
{% endblock %}
|
||||||
|
{% block box_body_class %}no-padding{% endblock %}
|
||||||
|
{% block box_body %}
|
||||||
|
<table class="table table-hover dataTable">
|
||||||
|
<tbody>
|
||||||
|
{% for document in documents %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ document.id }}</td>
|
||||||
|
<td>{{ document.lastChange|date }}</td>
|
||||||
|
<td>{{ document.filename|replace({(baseDirectory): ''}) }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
{% endembed %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -224,4 +224,20 @@ class InvoiceControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$this->assertEquals(0, $em->getRepository(InvoiceTemplate::class)->count([]));
|
$this->assertEquals(0, $em->getRepository(InvoiceTemplate::class)->count([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testUploadDocumentAction()
|
||||||
|
{
|
||||||
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||||
|
|
||||||
|
$em = static::$kernel->getContainer()->get('doctrine.orm.entity_manager');
|
||||||
|
$fixture = new InvoiceFixtures();
|
||||||
|
$this->importFixture($client, $fixture);
|
||||||
|
|
||||||
|
$this->request($client, '/invoice/document_upload');
|
||||||
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
|
$node = $client->getCrawler()->filter('div.box#invoice_document_list');
|
||||||
|
self::assertEquals(1, $node->count());
|
||||||
|
// we do not test the upload here, just make sure that the action can be rendered properly
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class PermissionControllerTest extends ControllerBaseTest
|
|||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||||
$this->assertAccessIsGranted($client, '/admin/permissions');
|
$this->assertAccessIsGranted($client, '/admin/permissions');
|
||||||
$this->assertHasDataTable($client);
|
$this->assertHasDataTable($client);
|
||||||
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 107);
|
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 108);
|
||||||
$this->assertPageActions($client, [
|
$this->assertPageActions($client, [
|
||||||
'back' => $this->createUrl('/admin/user/'),
|
'back' => $this->createUrl('/admin/user/'),
|
||||||
'roles modal-ajax-form' => $this->createUrl('/admin/permissions/roles/create'),
|
'roles modal-ajax-form' => $this->createUrl('/admin/permissions/roles/create'),
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ class InvoiceDocumentTest extends TestCase
|
|||||||
self::assertStringContainsString('templates/invoice/renderer/default.html.twig', $sut->getFilename());
|
self::assertStringContainsString('templates/invoice/renderer/default.html.twig', $sut->getFilename());
|
||||||
self::assertEquals('default', $sut->getId());
|
self::assertEquals('default', $sut->getId());
|
||||||
self::assertEquals('default.html.twig', $sut->getName());
|
self::assertEquals('default.html.twig', $sut->getName());
|
||||||
|
self::assertIsInt($sut->getLastChange());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,10 @@
|
|||||||
<source>company</source>
|
<source>company</source>
|
||||||
<target>Firmen-Rechnung</target>
|
<target>Firmen-Rechnung</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="help.upload">
|
||||||
|
<source>help.upload</source>
|
||||||
|
<target>Achtung: existierende Dateien werden übschrieben. Erlaubte Dateitypen sind: DOCX, ODS, XLSX</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -34,6 +34,10 @@
|
|||||||
<source>company</source>
|
<source>company</source>
|
||||||
<target>Company invoice</target>
|
<target>Company invoice</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="help.upload">
|
||||||
|
<source>help.upload</source>
|
||||||
|
<target>Attention: existing files will be overwritten. Allowed file types are: DOCX, ODS, XLSX.</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -52,6 +52,10 @@
|
|||||||
<source>confirm</source>
|
<source>confirm</source>
|
||||||
<target>Bestätigen</target>
|
<target>Bestätigen</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="upload">
|
||||||
|
<source>upload</source>
|
||||||
|
<target>Hochladen</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="search">
|
<trans-unit id="search">
|
||||||
<source>search</source>
|
<source>search</source>
|
||||||
<target>Suchen</target>
|
<target>Suchen</target>
|
||||||
|
|||||||
@@ -52,6 +52,10 @@
|
|||||||
<source>confirm</source>
|
<source>confirm</source>
|
||||||
<target>Confirm</target>
|
<target>Confirm</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="upload">
|
||||||
|
<source>upload</source>
|
||||||
|
<target>Upload</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="search">
|
<trans-unit id="search">
|
||||||
<source>search</source>
|
<source>search</source>
|
||||||
<target>Search</target>
|
<target>Search</target>
|
||||||
|
|||||||
Reference in New Issue
Block a user