Finishing first version draft. Entrypoints still needs to be properly implemented.
This commit is contained in:
parent
a5603b7724
commit
edf24cd480
|
@ -15,6 +15,7 @@
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.4",
|
"php": ">=7.4",
|
||||||
|
|
||||||
"asika/simple-console": "^1.0",
|
"asika/simple-console": "^1.0",
|
||||||
"kwn/number-to-words": "^1.9",
|
"kwn/number-to-words": "^1.9",
|
||||||
"mpdf/mpdf": "^8.0",
|
"mpdf/mpdf": "^8.0",
|
||||||
|
|
|
@ -1,118 +1,24 @@
|
||||||
configuration:
|
configuration:
|
||||||
date_format:
|
|
||||||
source: ''
|
|
||||||
target: ''
|
|
||||||
locales:
|
locales:
|
||||||
source: ru
|
source: ru
|
||||||
target: en
|
target: en
|
||||||
|
currency_code: 'USD'
|
||||||
images:
|
images:
|
||||||
signature: ''
|
signature: 'images/sign.png'
|
||||||
stamp: ''
|
stamp: 'images/stamp.png'
|
||||||
global_substitutions:
|
|
||||||
# TODO extract to parameters
|
contract:
|
||||||
'%invoice_number%': '999'
|
# Must be set in format parsable by DateTime
|
||||||
'%invoice_date%': '01.01.1970'
|
date_from: '01.01.1970'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
-
|
-
|
||||||
name:
|
name:
|
||||||
source: 'Работа'
|
source: 'Работа на работе по договору от %contract_date% за %period_end_date%'
|
||||||
target: 'Work'
|
target: 'Working on the job for the contract from %contract_date% for %period_end_date%'
|
||||||
amount: 1000
|
amount: 9000
|
||||||
units:
|
units:
|
||||||
source: 'часа'
|
source: 'часы'
|
||||||
target: 'hour'
|
target: 'hours'
|
||||||
price: 1000
|
price: 100
|
||||||
|
|
||||||
translations:
|
|
||||||
source:
|
|
||||||
variables:
|
|
||||||
title: 'Счёт на оплату №%invoice_number% от %invoice_date%'
|
|
||||||
currency: 'EUR'
|
|
||||||
rows:
|
|
||||||
supplier: 'Поставщик'
|
|
||||||
buyer: 'Покупатель'
|
|
||||||
# service table
|
|
||||||
th_number: '№'
|
|
||||||
th_name: 'Наименование работ, услуг'
|
|
||||||
th_amount: 'Кол-во'
|
|
||||||
th_units: 'Ед.'
|
|
||||||
th_price: 'Цена'
|
|
||||||
th_sum: 'Сумма'
|
|
||||||
tf_total: 'Итого'
|
|
||||||
# bank data
|
|
||||||
account_data: 'Банковские реквизиты поставщика'
|
|
||||||
account_number: 'Номер счёта'
|
|
||||||
bank: 'Банк'
|
|
||||||
bank_address: 'Адрес'
|
|
||||||
swift: 'SWIFT'
|
|
||||||
corr_bank: 'Банк-посредник'
|
|
||||||
total_to_pay: 'Всего к оплате'
|
|
||||||
signature: 'Подпись'
|
|
||||||
supplier:
|
|
||||||
title: '123456'
|
|
||||||
short_title: '123'
|
|
||||||
address: 'some address'
|
|
||||||
extra: []
|
|
||||||
bank:
|
|
||||||
account: '9999999999999999999'
|
|
||||||
name: 'Some Bank of some Country'
|
|
||||||
address: 'Some address'
|
|
||||||
swift: 'SOMESWIFT'
|
|
||||||
corr_bank:
|
|
||||||
name: 'SOME CORR BANK'
|
|
||||||
swift: 'SOMESWIFT2'
|
|
||||||
buyer:
|
|
||||||
title: '"Some Client" LLC'
|
|
||||||
address: 'Some Address'
|
|
||||||
extra:
|
|
||||||
- 'VAT: EU12313123'
|
|
||||||
- 'IBAN: EU123131231231232112'
|
|
||||||
static_substitutions:
|
|
||||||
'%contract_date%': '01.01.1970'
|
|
||||||
|
|
||||||
target:
|
|
||||||
variables:
|
|
||||||
title: 'Счёт на оплату №%invoice_number% от %invoice_date%'
|
|
||||||
currency: 'EUR'
|
|
||||||
rows:
|
|
||||||
supplier: 'Поставщик'
|
|
||||||
buyer: 'Покупатель'
|
|
||||||
# service table
|
|
||||||
th_number: '№'
|
|
||||||
th_name: 'Наименование работ, услуг'
|
|
||||||
th_amount: 'Кол-во'
|
|
||||||
th_units: 'Ед.'
|
|
||||||
th_price: 'Цена'
|
|
||||||
th_sum: 'Сумма'
|
|
||||||
tf_total: 'Итого'
|
|
||||||
# bank data
|
|
||||||
account_data: 'Банковские реквизиты поставщика'
|
|
||||||
account_number: 'Номер счёта'
|
|
||||||
bank: 'Банк'
|
|
||||||
bank_address: 'Адрес'
|
|
||||||
swift: 'SWIFT'
|
|
||||||
corr_bank: 'Банк-посредник'
|
|
||||||
total_to_pay: 'Всего к оплате'
|
|
||||||
signature: 'Подпись'
|
|
||||||
supplier:
|
|
||||||
title: '123456'
|
|
||||||
short_title: '123'
|
|
||||||
address: 'some address'
|
|
||||||
extra: []
|
|
||||||
bank:
|
|
||||||
account: '9999999999999999999'
|
|
||||||
name: 'Some Bank of some Country'
|
|
||||||
address: 'Some address'
|
|
||||||
swift: 'SOMESWIFT'
|
|
||||||
corr_bank:
|
|
||||||
name: 'SOME CORR BANK'
|
|
||||||
swift: 'SOMESWIFT2'
|
|
||||||
buyer:
|
|
||||||
title: '"Some Client" LLC'
|
|
||||||
address: 'Some Address'
|
|
||||||
extra:
|
|
||||||
- 'VAT: EU12313123'
|
|
||||||
- 'IBAN: EU123131231231232112'
|
|
||||||
static_substitutions:
|
|
||||||
'%contract_date%': '01.01.1970'
|
|
15
show.php
15
show.php
|
@ -3,17 +3,26 @@
|
||||||
require_once __DIR__.'/vendor/autoload.php';
|
require_once __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
use App\Generator\InvoiceGenerator;
|
use App\Generator\InvoiceGenerator;
|
||||||
|
use App\Invoice\InvoiceData;
|
||||||
|
use App\Kernel;
|
||||||
use Mpdf\Mpdf;
|
use Mpdf\Mpdf;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
$config = Yaml::parseFile(__DIR__.'/config/parameters.yaml');
|
$config = Yaml::parseFile(Kernel::getProjectRoot().'/config/parameters.yaml');
|
||||||
|
|
||||||
|
$invoice = new InvoiceData(
|
||||||
|
123,
|
||||||
|
new \DateTime(),
|
||||||
|
new \DateTime($config['contract']['date_from']),
|
||||||
|
new \DateTime
|
||||||
|
);
|
||||||
|
|
||||||
$pdf = $_GET['pdf'] ?? false;
|
$pdf = $_GET['pdf'] ?? false;
|
||||||
|
|
||||||
if ($pdf) {
|
if ($pdf) {
|
||||||
$mpdf = new Mpdf();
|
$mpdf = new Mpdf();
|
||||||
$mpdf->WriteHTML(InvoiceGenerator::generate($config));
|
$mpdf->WriteHTML(InvoiceGenerator::generate($invoice, $config));
|
||||||
$mpdf->Output();
|
$mpdf->Output();
|
||||||
} else {
|
} else {
|
||||||
echo InvoiceGenerator::generate($config);
|
echo InvoiceGenerator::generate($invoice, $config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,48 +2,32 @@
|
||||||
|
|
||||||
namespace App\Generator;
|
namespace App\Generator;
|
||||||
|
|
||||||
|
use App\Invoice\InvoiceData;
|
||||||
use App\Kernel;
|
use App\Kernel;
|
||||||
use App\Twig\NumberToWordsExtension;
|
use App\Text\PlaceholderProcessor;
|
||||||
use App\Util\StringReplacer;
|
use App\Text\Replacer\{LocalizedDateReplacer, StaticReplacer, LocalizedYearMonthReplacer};
|
||||||
|
use App\Twig\{NumberToWordsExtension, TextProcessingExtension};
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Twig\Environment as Twig;
|
use Twig\Environment as Twig;
|
||||||
use Twig\Extra\{Html\HtmlExtension, Intl\IntlExtension};
|
use Twig\Extra\{Html\HtmlExtension, Intl\IntlExtension};
|
||||||
use Twig\Loader\FilesystemLoader;
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
class InvoiceGenerator
|
class InvoiceGenerator
|
||||||
{
|
{
|
||||||
public static function generate(array $config): string
|
public static function generate(InvoiceData $invoice, array $config): string
|
||||||
{
|
{
|
||||||
$twig = static::createTwig();
|
$locales = $config['configuration']['locales'];
|
||||||
|
|
||||||
$sourceStaticSubstitutions = array_merge(
|
$postprocessor = static::createPostprocessor($invoice);
|
||||||
$config['translations']['source']['static_substitutions'],
|
|
||||||
$config['configuration']['global_substitutions']
|
|
||||||
);
|
|
||||||
$targetStaticSubstitutions = array_merge(
|
|
||||||
$config['translations']['target']['static_substitutions'],
|
|
||||||
$config['configuration']['global_substitutions']
|
|
||||||
);
|
|
||||||
|
|
||||||
$source = StringReplacer::recursiveReplace(
|
$twig = static::createTwig($postprocessor, $locales['source'], $locales['target']);
|
||||||
$config['translations']['source']['variables'],
|
|
||||||
$sourceStaticSubstitutions
|
|
||||||
);
|
|
||||||
$target = StringReplacer::recursiveReplace(
|
|
||||||
$config['translations']['target']['variables'],
|
|
||||||
$targetStaticSubstitutions
|
|
||||||
);
|
|
||||||
|
|
||||||
// @TODO fix multilingual substitution depending on the context
|
|
||||||
$services = StringReplacer::recursiveReplace($config['services'], $sourceStaticSubstitutions);
|
|
||||||
$images = static::getImagesContent($config['configuration']['images']);
|
$images = static::getImagesContent($config['configuration']['images']);
|
||||||
|
|
||||||
return $twig->render('invoice.html.twig', [
|
return $twig->render('invoice.html.twig', [
|
||||||
'configuration' => $config['configuration'],
|
'locales' => $locales,
|
||||||
'trans_data' => [
|
'currency' => $config['configuration']['currency_code'],
|
||||||
'source' => $source,
|
'services' => $config['services'],
|
||||||
'target' => $target,
|
|
||||||
],
|
|
||||||
'services' => $services,
|
|
||||||
'images' => $images,
|
'images' => $images,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -59,15 +43,48 @@ class InvoiceGenerator
|
||||||
return $new;
|
return $new;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function createTwig(): Twig
|
private static function createTwig(
|
||||||
{
|
PlaceholderProcessor $placeholderProcessor,
|
||||||
$loader = new FilesystemLoader(__DIR__.'/../../templates');
|
string $sourceLocale,
|
||||||
|
string $targetLocale
|
||||||
|
): Twig {
|
||||||
|
$loader = new FilesystemLoader(Kernel::getProjectRoot().'/templates');
|
||||||
$twig = new Twig($loader);
|
$twig = new Twig($loader);
|
||||||
|
|
||||||
$twig->addExtension(new NumberToWordsExtension());
|
$twig->addExtension(new NumberToWordsExtension());
|
||||||
$twig->addExtension(new IntlExtension());
|
$twig->addExtension(new IntlExtension());
|
||||||
$twig->addExtension(new HtmlExtension());
|
$twig->addExtension(new HtmlExtension());
|
||||||
|
|
||||||
|
$localeData = static::loadLocaleData($sourceLocale, $targetLocale);
|
||||||
|
$twig->addExtension(new TextProcessingExtension($placeholderProcessor, $localeData));
|
||||||
|
|
||||||
return $twig;
|
return $twig;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private static function createPostprocessor(InvoiceData $invoice): PlaceholderProcessor
|
||||||
|
{
|
||||||
|
$processor = new PlaceholderProcessor();
|
||||||
|
|
||||||
|
$contractDate = new LocalizedDateReplacer($invoice->getContractStartDate());
|
||||||
|
$processor->addReplacer('%contract_date%', $contractDate);
|
||||||
|
|
||||||
|
$invoiceNumber = new StaticReplacer($invoice->getNumber());
|
||||||
|
$processor->addReplacer('%invoice_number%', $invoiceNumber);
|
||||||
|
|
||||||
|
$invoiceDate = new LocalizedDateReplacer($invoice->getIssueDate());
|
||||||
|
$processor->addReplacer('%invoice_date%', $invoiceDate);
|
||||||
|
|
||||||
|
$periodEnd = new LocalizedYearMonthReplacer($invoice->getAccountedMonthDate());
|
||||||
|
$processor->addReplacer('%period_end_date%', $periodEnd);
|
||||||
|
|
||||||
|
return $processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function loadLocaleData(string $source, string $target): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$source => Yaml::parseFile(Kernel::getProjectRoot().'/translation/source.yaml'),
|
||||||
|
$target => Yaml::parseFile(Kernel::getProjectRoot().'/translation/target.yaml'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
43
src/Invoice/InvoiceData.php
Normal file
43
src/Invoice/InvoiceData.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Invoice;
|
||||||
|
|
||||||
|
class InvoiceData
|
||||||
|
{
|
||||||
|
private int $number;
|
||||||
|
private \DateTime $issueDate;
|
||||||
|
private \DateTime $contractStartDate;
|
||||||
|
private \DateTime $accountedMonthDate;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
int $number,
|
||||||
|
\DateTime $issueDate,
|
||||||
|
\DateTime $contractStartDate,
|
||||||
|
\DateTime $accountedMonthDate
|
||||||
|
) {
|
||||||
|
$this->number = $number;
|
||||||
|
$this->issueDate = $issueDate;
|
||||||
|
$this->contractStartDate = $contractStartDate;
|
||||||
|
$this->accountedMonthDate = $accountedMonthDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNumber(): int
|
||||||
|
{
|
||||||
|
return $this->number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIssueDate(): \DateTime
|
||||||
|
{
|
||||||
|
return $this->issueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContractStartDate(): \DateTime
|
||||||
|
{
|
||||||
|
return $this->contractStartDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccountedMonthDate(): \DateTime
|
||||||
|
{
|
||||||
|
return $this->accountedMonthDate;
|
||||||
|
}
|
||||||
|
}
|
49
src/Text/PlaceholderProcessor.php
Normal file
49
src/Text/PlaceholderProcessor.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Text;
|
||||||
|
|
||||||
|
use App\Text\Replacer\ReplacerInterface;
|
||||||
|
|
||||||
|
class PlaceholderProcessor
|
||||||
|
{
|
||||||
|
/** @var ReplacerInterface[] */
|
||||||
|
private array $replacers = [];
|
||||||
|
|
||||||
|
private array $replaceCache = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|callable[] $replacers
|
||||||
|
*/
|
||||||
|
public function __construct(array $replacers = [])
|
||||||
|
{
|
||||||
|
$this->replacers = $replacers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addReplacer(string $placeholder, ReplacerInterface $replacer): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($placeholder, $this->replacers)) {
|
||||||
|
throw new \RuntimeException('Handler for \''.$placeholder.'\' already exists.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->replacers[$placeholder] = $replacer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(
|
||||||
|
string $string,
|
||||||
|
string $locale
|
||||||
|
): string {
|
||||||
|
foreach ($this->replacers as $placeholder => $replacer) {
|
||||||
|
if (!isset($this->replaceCache[$locale][$placeholder])) {
|
||||||
|
$this->replaceCache[$locale][$placeholder] = $replacer->generateReplace($locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = str_replace(
|
||||||
|
$placeholder,
|
||||||
|
$this->replaceCache[$locale][$placeholder],
|
||||||
|
$string
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
}
|
21
src/Text/Replacer/LocalizedDateReplacer.php
Normal file
21
src/Text/Replacer/LocalizedDateReplacer.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Text\Replacer;
|
||||||
|
|
||||||
|
class LocalizedDateReplacer implements ReplacerInterface
|
||||||
|
{
|
||||||
|
private \DateTime $date;
|
||||||
|
|
||||||
|
public function __construct(\DateTime $date = null)
|
||||||
|
{
|
||||||
|
$this->date = $date ?? new \DateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateReplace(string $locale): string
|
||||||
|
{
|
||||||
|
$formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::LONG, \IntlDateFormatter::LONG);
|
||||||
|
$formatter->setPattern('d MMMM yyyy');
|
||||||
|
|
||||||
|
return $formatter->format($this->date);
|
||||||
|
}
|
||||||
|
}
|
21
src/Text/Replacer/LocalizedYearMonthReplacer.php
Normal file
21
src/Text/Replacer/LocalizedYearMonthReplacer.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Text\Replacer;
|
||||||
|
|
||||||
|
class LocalizedYearMonthReplacer implements ReplacerInterface
|
||||||
|
{
|
||||||
|
private \DateTime $date;
|
||||||
|
|
||||||
|
public function __construct(\DateTime $dateInMonth = null)
|
||||||
|
{
|
||||||
|
$this->date = $dateInMonth ?? new \DateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateReplace(string $locale): string
|
||||||
|
{
|
||||||
|
$formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::LONG, \IntlDateFormatter::LONG);
|
||||||
|
$formatter->setPattern('LLLL yyyy');
|
||||||
|
|
||||||
|
return $formatter->format($this->date);
|
||||||
|
}
|
||||||
|
}
|
8
src/Text/Replacer/ReplacerInterface.php
Normal file
8
src/Text/Replacer/ReplacerInterface.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Text\Replacer;
|
||||||
|
|
||||||
|
interface ReplacerInterface
|
||||||
|
{
|
||||||
|
public function generateReplace(string $locale): string;
|
||||||
|
}
|
18
src/Text/Replacer/StaticReplacer.php
Normal file
18
src/Text/Replacer/StaticReplacer.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Text\Replacer;
|
||||||
|
|
||||||
|
class StaticReplacer implements ReplacerInterface
|
||||||
|
{
|
||||||
|
private string $replace;
|
||||||
|
|
||||||
|
public function __construct(string $replace)
|
||||||
|
{
|
||||||
|
$this->replace = $replace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateReplace(string $locale): string
|
||||||
|
{
|
||||||
|
return $this->replace;
|
||||||
|
}
|
||||||
|
}
|
52
src/Twig/TextProcessingExtension.php
Normal file
52
src/Twig/TextProcessingExtension.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig;
|
||||||
|
|
||||||
|
use App\Text\PlaceholderProcessor;
|
||||||
|
use Twig\Extension\AbstractExtension;
|
||||||
|
use Twig\TwigFilter;
|
||||||
|
|
||||||
|
class TextProcessingExtension extends AbstractExtension
|
||||||
|
{
|
||||||
|
private PlaceholderProcessor $replacer;
|
||||||
|
private array $translationsByLocale = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $translationsByLocale ['en' => ['find' => 'replace']]
|
||||||
|
*/
|
||||||
|
public function __construct(PlaceholderProcessor $replacer, array $translationsByLocale)
|
||||||
|
{
|
||||||
|
$this->replacer = $replacer;
|
||||||
|
$this->translationsByLocale = $translationsByLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getFilters(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFilter('trans', [$this, 'translate']),
|
||||||
|
new TwigFilter('process', [$this, 'process']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Translates the message if possible or returns original text. */
|
||||||
|
public function translate(string $message, string $locale, bool $postprocess = false): string
|
||||||
|
{
|
||||||
|
if (!isset($this->translationsByLocale[$locale][$message])) {
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
$text = $this->translationsByLocale[$locale][$message];
|
||||||
|
|
||||||
|
if ($postprocess) {
|
||||||
|
$text = $this->replacer->process($text, $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(string $message, string $locale): string
|
||||||
|
{
|
||||||
|
return $this->replacer->process($message, $locale);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,54 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Util;
|
|
||||||
|
|
||||||
class StringReplacer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param array $variables
|
|
||||||
* @param string[]|array $staticReplaces
|
|
||||||
* @param callable[]|array $dynamicReplaces
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function recursiveReplace(
|
|
||||||
array $variables,
|
|
||||||
array $staticReplaces = [],
|
|
||||||
array $dynamicReplaces = []
|
|
||||||
): array {
|
|
||||||
foreach ($variables as $key => &$value) {
|
|
||||||
if (is_string($value)) {
|
|
||||||
$value = static::replaceString($value, $staticReplaces);
|
|
||||||
} elseif (is_int($value)) {
|
|
||||||
continue;
|
|
||||||
} elseif (is_array($value)) {
|
|
||||||
$value = static::recursiveReplace($value, $staticReplaces);
|
|
||||||
} else {
|
|
||||||
throw new \InvalidArgumentException(sprintf(
|
|
||||||
'Invalid value. string/array allowed, %s (%s) given.',
|
|
||||||
gettype($value),
|
|
||||||
print_r($value, true)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function replaceString(
|
|
||||||
string $string,
|
|
||||||
array $staticReplaces
|
|
||||||
): string {
|
|
||||||
// Process static replaces
|
|
||||||
$string = str_replace(
|
|
||||||
array_keys($staticReplaces),
|
|
||||||
array_values($staticReplaces),
|
|
||||||
$string
|
|
||||||
);
|
|
||||||
|
|
||||||
// TBI
|
|
||||||
// ...
|
|
||||||
|
|
||||||
return $string;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,20 +17,20 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
{{ include ('invoice_side.html.twig', {
|
{{ include ('invoice_side.html.twig', {
|
||||||
t: trans_data.source,
|
|
||||||
services: services,
|
services: services,
|
||||||
images: images,
|
images: images,
|
||||||
context: 'source',
|
context: 'source',
|
||||||
locale: configuration.locales.source
|
locale: locales.source,
|
||||||
|
currency: currency
|
||||||
}, with_context = false) }}
|
}, with_context = false) }}
|
||||||
</td>
|
</td>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
{{ include ('invoice_side.html.twig', {
|
{{ include ('invoice_side.html.twig', {
|
||||||
t: trans_data.target,
|
|
||||||
services: services,
|
services: services,
|
||||||
images: images,
|
images: images,
|
||||||
context: 'target',
|
context: 'target',
|
||||||
locale: configuration.locales.target
|
locale: locales.target,
|
||||||
|
currency: currency
|
||||||
}, with_context = false) }}
|
}, with_context = false) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<h2 class="title">{{ t.title }}</h2>
|
<h2 class="title">{{ 'label_title'|trans(locale, true) }}</h2>
|
||||||
|
|
||||||
<!-- Party attributes -->
|
<!-- Party attributes -->
|
||||||
<table class="bordered fat">
|
<table class="bordered fat">
|
||||||
|
@ -6,36 +6,34 @@
|
||||||
<td colspan="3"> </td>
|
<td colspan="3"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">{{ t.rows.supplier }}:</td>
|
<td rowspan="2">{{ 'label_supplier'|trans(locale) }}:</td>
|
||||||
<td colspan="2">{{ t.supplier.title }}</td>
|
<td colspan="2">{{ 'supplier_title'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">{{ t.supplier.address }}</td>
|
<td colspan="2">{{ 'supplier_address'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="{{ 2 + t.buyer.extra|length }}">{{ t.rows.buyer }}:</td>
|
<td rowspan="3">{{ 'label_buyer'|trans(locale) }}:</td>
|
||||||
<td colspan="2">{{ t.buyer.title }}</td>
|
<td colspan="2">{{ 'buyer_title'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">{{ t.buyer.address }}</td>
|
<td colspan="2">{{ 'buyer_address'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for item in t.buyer.extra %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">{{ item }}</td>
|
<td colspan="2">{{ 'buyer_extra'|trans(locale)|nl2br }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- Services table -->
|
<!-- Services table -->
|
||||||
<table class="bordered bordered-fully fat">
|
<table class="bordered bordered-fully fat">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ t.rows.th_number }}</th>
|
<th>{{ 'label_th_number'|trans(locale) }}</th>
|
||||||
<th>{{ t.rows.th_name }}</th>
|
<th>{{ 'label_th_name'|trans(locale) }}</th>
|
||||||
<th>{{ t.rows.th_amount }}</th>
|
<th>{{ 'label_th_amount'|trans(locale) }}</th>
|
||||||
<th>{{ t.rows.th_units }}</th>
|
<th>{{ 'label_th_units'|trans(locale) }}</th>
|
||||||
<th>{{ t.rows.th_price }}</th>
|
<th>{{ 'label_th_price'|trans(locale) }}</th>
|
||||||
<th>{{ t.rows.th_sum }}</th>
|
<th>{{ 'label_th_sum'|trans(locale) }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -43,20 +41,20 @@
|
||||||
{% for service in services %}
|
{% for service in services %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ loop.index }}</td>
|
<td>{{ loop.index }}</td>
|
||||||
<td width="60%">{{ service.name[context] }}</td>
|
<td width="60%">{{ service.name[context]|process(locale) }}</td>
|
||||||
<td>{{ service.amount }}</td>
|
<td>{{ service.amount }}</td>
|
||||||
<td>{{ service.units[context] }}</td>
|
<td>{{ service.units[context] }}</td>
|
||||||
<td>{{ service.amount }} {{ t.currency }}</td>
|
<td>{{ service.price }} {{ currency }}</td>
|
||||||
{% set sum = service.amount * service.price %}
|
{% set sum = service.amount * service.price %}
|
||||||
<td>{{ sum }} {{ t.currency }}</td>
|
<td>{{ sum }} {{ currency }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% set total = total + sum %}
|
{% set total = total + sum %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" align="right">{{ t.rows.tf_total }}</td>
|
<td colspan="5" align="right">{{ 'label_tf_total'|trans(locale) }}</td>
|
||||||
<td>{{ total }} {{ t.currency }}</td>
|
<td>{{ total }} {{ currency }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
@ -65,7 +63,7 @@
|
||||||
<table class="fat">
|
<table class="fat">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
{{ t.rows.total_to_pay }}: {{ total|ntw(locale) }} {{ t.currency|currency_name(locale) }}.
|
{{ 'label_total_to_pay'|trans(locale) }}: {{ total|ntw(locale) }} {{ currency|currency_name(locale) }}.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -73,33 +71,33 @@
|
||||||
<!-- Bank account data -->
|
<!-- Bank account data -->
|
||||||
<table id="bank-details" class="fat">
|
<table id="bank-details" class="fat">
|
||||||
<tr class="bordered">
|
<tr class="bordered">
|
||||||
<td colspan="3"><strong>{{ t.rows.account_data }}:</strong></td>
|
<td colspan="3"><strong>{{ 'label_supplier_account_data'|trans(locale) }}:</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ t.rows.account_number }}</td>
|
<td>{{ 'label_supplier_account_number'|trans(locale) }}</td>
|
||||||
<td colspan="2">{{ t.supplier.bank.account }}</td>
|
<td colspan="2">{{ 'supplier_bank_account'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">{{ t.rows.bank }}</td>
|
<td colspan="3">{{ 'label_supplier_bank'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">{{ t.supplier.bank.name }}</td>
|
<td colspan="3">{{ 'supplier_bank_name'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ t.rows.bank_address }}</td>
|
<td>{{ 'label_supplier_bank_address'|trans(locale) }}</td>
|
||||||
<td colspan="2">{{ t.supplier.bank.address }}</td>
|
<td colspan="2">{{ 'supplier_bank_address'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ t.rows.swift }}</td>
|
<td>{{ 'label_swift'|trans(locale) }}</td>
|
||||||
<td colspan="2">{{ t.supplier.bank.swift }}</td>
|
<td colspan="2">{{ 'supplier_bank_swift'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{{ t.rows.corr_bank }}</strong></td>
|
<td><strong>{{ 'label_supplier_corr_bank'|trans(locale) }}</strong></td>
|
||||||
<td colspan="2">{{ t.supplier.bank.corr_bank.name }}</td>
|
<td colspan="2">{{ 'supplier_bank_corr_bank_name'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ t.rows.swift }}</td>
|
<td>{{ 'label_swift'|trans(locale) }}</td>
|
||||||
<td colspan="2">{{ t.supplier.bank.corr_bank.swift }}</td>
|
<td colspan="2">{{ 'supplier_bank_corr_bank_swift'|trans(locale) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3"> </td>
|
<td colspan="3"> </td>
|
||||||
|
@ -111,7 +109,7 @@
|
||||||
<td colspan="3"> </td>
|
<td colspan="3"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td height="50px">{{ t.rows.signature }}</td>
|
<td height="50px">{{ 'label_signature'|trans(locale) }}</td>
|
||||||
<td colspan="2" valign="bottom">
|
<td colspan="2" valign="bottom">
|
||||||
<img src="{{ images.signature|data_uri(mime='image/png') }}" alt="Signature">
|
<img src="{{ images.signature|data_uri(mime='image/png') }}" alt="Signature">
|
||||||
</td>
|
</td>
|
||||||
|
|
2
translation/.gitignore
vendored
Normal file
2
translation/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*.yaml
|
||||||
|
!*.yaml.dist
|
41
translation/source.yaml.dist
Normal file
41
translation/source.yaml.dist
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Labels
|
||||||
|
label_title: 'Счёт на оплату №%invoice_number% от %invoice_date%'
|
||||||
|
label_supplier: 'Поставщик'
|
||||||
|
label_buyer: 'Покупатель'
|
||||||
|
label_th_number: '№'
|
||||||
|
label_th_name: 'Наименование работ, услуг'
|
||||||
|
label_th_amount: 'Кол-во'
|
||||||
|
label_th_units: 'Ед.'
|
||||||
|
label_th_price: 'Цена'
|
||||||
|
label_th_sum: 'Сумма'
|
||||||
|
label_tf_total: 'Итого'
|
||||||
|
label_total_to_pay: 'Всего к оплате'
|
||||||
|
label_supplier_account_data: 'Банковские реквизиты поставщика'
|
||||||
|
label_supplier_account_number: 'Номер счёта'
|
||||||
|
label_supplier_bank: 'Банк'
|
||||||
|
label_supplier_bank_address: 'Адрес'
|
||||||
|
label_swift: 'SWIFT'
|
||||||
|
label_supplier_corr_bank: 'Банк-посредник'
|
||||||
|
label_signature: 'Подпись'
|
||||||
|
|
||||||
|
# Generic data
|
||||||
|
|
||||||
|
# Supplier data
|
||||||
|
supplier_title: 'ИП/ООО XXX'
|
||||||
|
supplier_short_title: 'ИП/ООО XXX'
|
||||||
|
supplier_address: 'Россия, г. Мухосранск, ул. Узкая 10'
|
||||||
|
#supplier_extra: ''
|
||||||
|
supplier_bank_account: '1234567890123456'
|
||||||
|
supplier_bank_name: 'BANK OF MOTHER RUSSIA'
|
||||||
|
supplier_bank_address: 'KREMLIN, RUSSIA'
|
||||||
|
supplier_bank_swift: 'COOLSWIFT'
|
||||||
|
supplier_bank_corr_bank_name: 'SOME CORRESPONDENT BANK'
|
||||||
|
supplier_bank_corr_bank_swift: 'SADSWIFT'
|
||||||
|
|
||||||
|
# Buyer data
|
||||||
|
buyer_title: '"Buyer" LLC'
|
||||||
|
buyer_address: 'Washington DC, USA'
|
||||||
|
buyer_extra: "VAT: US12312312312\n
|
||||||
|
IBAN: US1234567890123456"
|
||||||
|
|
||||||
|
|
39
translation/target.yaml.dist
Normal file
39
translation/target.yaml.dist
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Labels
|
||||||
|
label_title: 'Invoice %invoice_number% for %invoice_date%'
|
||||||
|
label_supplier: 'Supplier'
|
||||||
|
label_buyer: 'Buyer'
|
||||||
|
label_th_number: '#'
|
||||||
|
label_th_name: 'Service'
|
||||||
|
label_th_amount: 'Amount'
|
||||||
|
label_th_units: 'Units'
|
||||||
|
label_th_price: 'Price'
|
||||||
|
label_th_sum: 'Sum'
|
||||||
|
label_tf_total: 'Total'
|
||||||
|
label_total_to_pay: 'Total to pay'
|
||||||
|
label_supplier_account_data: 'Supplier bank account'
|
||||||
|
label_supplier_account_number: 'Account number'
|
||||||
|
label_supplier_bank: 'Beneficiary bank'
|
||||||
|
label_supplier_bank_address: 'Bank address'
|
||||||
|
label_swift: 'SWIFT'
|
||||||
|
label_supplier_corr_bank: 'Correspondent Bank'
|
||||||
|
label_signature: 'Подпис'
|
||||||
|
|
||||||
|
# Generic data
|
||||||
|
|
||||||
|
# Supplier data
|
||||||
|
supplier_title: 'IP/LLC XXX'
|
||||||
|
supplier_short_title: 'IP/LLC XXX'
|
||||||
|
supplier_address: 'Russia, Mukhosransk city, Uzkaya st. 10'
|
||||||
|
#supplier_extra: ''
|
||||||
|
supplier_bank_account: '1234567890123456'
|
||||||
|
supplier_bank_name: 'BANK OF MOTHER RUSSIA'
|
||||||
|
supplier_bank_address: 'KREMLIN, RUSSIA'
|
||||||
|
supplier_bank_swift: 'COOLSWIFT'
|
||||||
|
supplier_bank_corr_bank_name: 'SOME CORRESPONDENT BANK'
|
||||||
|
supplier_bank_corr_bank_swift: 'SADSWIFT'
|
||||||
|
|
||||||
|
# Buyer data
|
||||||
|
buyer_title: '"Buyer" LLC'
|
||||||
|
buyer_address: 'Washington DC, USA'
|
||||||
|
buyer_extra: "VAT: US12312312312\n
|
||||||
|
IBAN: US1234567890123456"
|
Loading…
Reference in a new issue