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": {
|
||||
"php": ">=7.4",
|
||||
|
||||
"asika/simple-console": "^1.0",
|
||||
"kwn/number-to-words": "^1.9",
|
||||
"mpdf/mpdf": "^8.0",
|
||||
|
|
|
@ -1,118 +1,24 @@
|
|||
configuration:
|
||||
date_format:
|
||||
source: ''
|
||||
target: ''
|
||||
locales:
|
||||
source: ru
|
||||
target: en
|
||||
currency_code: 'USD'
|
||||
images:
|
||||
signature: ''
|
||||
stamp: ''
|
||||
global_substitutions:
|
||||
# TODO extract to parameters
|
||||
'%invoice_number%': '999'
|
||||
'%invoice_date%': '01.01.1970'
|
||||
signature: 'images/sign.png'
|
||||
stamp: 'images/stamp.png'
|
||||
|
||||
contract:
|
||||
# Must be set in format parsable by DateTime
|
||||
date_from: '01.01.1970'
|
||||
|
||||
services:
|
||||
-
|
||||
name:
|
||||
source: 'Работа'
|
||||
target: 'Work'
|
||||
amount: 1000
|
||||
source: 'Работа на работе по договору от %contract_date% за %period_end_date%'
|
||||
target: 'Working on the job for the contract from %contract_date% for %period_end_date%'
|
||||
amount: 9000
|
||||
units:
|
||||
source: 'часа'
|
||||
target: 'hour'
|
||||
price: 1000
|
||||
source: 'часы'
|
||||
target: 'hours'
|
||||
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';
|
||||
|
||||
use App\Generator\InvoiceGenerator;
|
||||
use App\Invoice\InvoiceData;
|
||||
use App\Kernel;
|
||||
use Mpdf\Mpdf;
|
||||
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;
|
||||
|
||||
if ($pdf) {
|
||||
$mpdf = new Mpdf();
|
||||
$mpdf->WriteHTML(InvoiceGenerator::generate($config));
|
||||
$mpdf->WriteHTML(InvoiceGenerator::generate($invoice, $config));
|
||||
$mpdf->Output();
|
||||
} else {
|
||||
echo InvoiceGenerator::generate($config);
|
||||
echo InvoiceGenerator::generate($invoice, $config);
|
||||
}
|
||||
|
|
|
@ -2,48 +2,32 @@
|
|||
|
||||
namespace App\Generator;
|
||||
|
||||
use App\Invoice\InvoiceData;
|
||||
use App\Kernel;
|
||||
use App\Twig\NumberToWordsExtension;
|
||||
use App\Util\StringReplacer;
|
||||
use App\Text\PlaceholderProcessor;
|
||||
use App\Text\Replacer\{LocalizedDateReplacer, StaticReplacer, LocalizedYearMonthReplacer};
|
||||
use App\Twig\{NumberToWordsExtension, TextProcessingExtension};
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Twig\Environment as Twig;
|
||||
use Twig\Extra\{Html\HtmlExtension, Intl\IntlExtension};
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
|
||||
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(
|
||||
$config['translations']['source']['static_substitutions'],
|
||||
$config['configuration']['global_substitutions']
|
||||
);
|
||||
$targetStaticSubstitutions = array_merge(
|
||||
$config['translations']['target']['static_substitutions'],
|
||||
$config['configuration']['global_substitutions']
|
||||
);
|
||||
$postprocessor = static::createPostprocessor($invoice);
|
||||
|
||||
$source = StringReplacer::recursiveReplace(
|
||||
$config['translations']['source']['variables'],
|
||||
$sourceStaticSubstitutions
|
||||
);
|
||||
$target = StringReplacer::recursiveReplace(
|
||||
$config['translations']['target']['variables'],
|
||||
$targetStaticSubstitutions
|
||||
);
|
||||
$twig = static::createTwig($postprocessor, $locales['source'], $locales['target']);
|
||||
|
||||
// @TODO fix multilingual substitution depending on the context
|
||||
$services = StringReplacer::recursiveReplace($config['services'], $sourceStaticSubstitutions);
|
||||
$images = static::getImagesContent($config['configuration']['images']);
|
||||
|
||||
return $twig->render('invoice.html.twig', [
|
||||
'configuration' => $config['configuration'],
|
||||
'trans_data' => [
|
||||
'source' => $source,
|
||||
'target' => $target,
|
||||
],
|
||||
'services' => $services,
|
||||
'locales' => $locales,
|
||||
'currency' => $config['configuration']['currency_code'],
|
||||
'services' => $config['services'],
|
||||
'images' => $images,
|
||||
]);
|
||||
}
|
||||
|
@ -59,15 +43,48 @@ class InvoiceGenerator
|
|||
return $new;
|
||||
}
|
||||
|
||||
private static function createTwig(): Twig
|
||||
{
|
||||
$loader = new FilesystemLoader(__DIR__.'/../../templates');
|
||||
private static function createTwig(
|
||||
PlaceholderProcessor $placeholderProcessor,
|
||||
string $sourceLocale,
|
||||
string $targetLocale
|
||||
): Twig {
|
||||
$loader = new FilesystemLoader(Kernel::getProjectRoot().'/templates');
|
||||
$twig = new Twig($loader);
|
||||
|
||||
$twig->addExtension(new NumberToWordsExtension());
|
||||
$twig->addExtension(new IntlExtension());
|
||||
$twig->addExtension(new HtmlExtension());
|
||||
|
||||
$localeData = static::loadLocaleData($sourceLocale, $targetLocale);
|
||||
$twig->addExtension(new TextProcessingExtension($placeholderProcessor, $localeData));
|
||||
|
||||
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>
|
||||
<td width="50%">
|
||||
{{ include ('invoice_side.html.twig', {
|
||||
t: trans_data.source,
|
||||
services: services,
|
||||
images: images,
|
||||
context: 'source',
|
||||
locale: configuration.locales.source
|
||||
locale: locales.source,
|
||||
currency: currency
|
||||
}, with_context = false) }}
|
||||
</td>
|
||||
<td width="50%">
|
||||
{{ include ('invoice_side.html.twig', {
|
||||
t: trans_data.target,
|
||||
services: services,
|
||||
images: images,
|
||||
context: 'target',
|
||||
locale: configuration.locales.target
|
||||
locale: locales.target,
|
||||
currency: currency
|
||||
}, with_context = false) }}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<h2 class="title">{{ t.title }}</h2>
|
||||
<h2 class="title">{{ 'label_title'|trans(locale, true) }}</h2>
|
||||
|
||||
<!-- Party attributes -->
|
||||
<table class="bordered fat">
|
||||
|
@ -6,36 +6,34 @@
|
|||
<td colspan="3"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">{{ t.rows.supplier }}:</td>
|
||||
<td colspan="2">{{ t.supplier.title }}</td>
|
||||
<td rowspan="2">{{ 'label_supplier'|trans(locale) }}:</td>
|
||||
<td colspan="2">{{ 'supplier_title'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">{{ t.supplier.address }}</td>
|
||||
<td colspan="2">{{ 'supplier_address'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="{{ 2 + t.buyer.extra|length }}">{{ t.rows.buyer }}:</td>
|
||||
<td colspan="2">{{ t.buyer.title }}</td>
|
||||
<td rowspan="3">{{ 'label_buyer'|trans(locale) }}:</td>
|
||||
<td colspan="2">{{ 'buyer_title'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">{{ t.buyer.address }}</td>
|
||||
<td colspan="2">{{ 'buyer_address'|trans(locale) }}</td>
|
||||
</tr>
|
||||
{% for item in t.buyer.extra %}
|
||||
<tr>
|
||||
<td colspan="2">{{ item }}</td>
|
||||
<td colspan="2">{{ 'buyer_extra'|trans(locale)|nl2br }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<!-- Services table -->
|
||||
<table class="bordered bordered-fully fat">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ t.rows.th_number }}</th>
|
||||
<th>{{ t.rows.th_name }}</th>
|
||||
<th>{{ t.rows.th_amount }}</th>
|
||||
<th>{{ t.rows.th_units }}</th>
|
||||
<th>{{ t.rows.th_price }}</th>
|
||||
<th>{{ t.rows.th_sum }}</th>
|
||||
<th>{{ 'label_th_number'|trans(locale) }}</th>
|
||||
<th>{{ 'label_th_name'|trans(locale) }}</th>
|
||||
<th>{{ 'label_th_amount'|trans(locale) }}</th>
|
||||
<th>{{ 'label_th_units'|trans(locale) }}</th>
|
||||
<th>{{ 'label_th_price'|trans(locale) }}</th>
|
||||
<th>{{ 'label_th_sum'|trans(locale) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -43,20 +41,20 @@
|
|||
{% for service in services %}
|
||||
<tr>
|
||||
<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.units[context] }}</td>
|
||||
<td>{{ service.amount }} {{ t.currency }}</td>
|
||||
<td>{{ service.price }} {{ currency }}</td>
|
||||
{% set sum = service.amount * service.price %}
|
||||
<td>{{ sum }} {{ t.currency }}</td>
|
||||
<td>{{ sum }} {{ currency }}</td>
|
||||
</tr>
|
||||
{% set total = total + sum %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="5" align="right">{{ t.rows.tf_total }}</td>
|
||||
<td>{{ total }} {{ t.currency }}</td>
|
||||
<td colspan="5" align="right">{{ 'label_tf_total'|trans(locale) }}</td>
|
||||
<td>{{ total }} {{ currency }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
@ -65,7 +63,7 @@
|
|||
<table class="fat">
|
||||
<tr>
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -73,33 +71,33 @@
|
|||
<!-- Bank account data -->
|
||||
<table id="bank-details" class="fat">
|
||||
<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>
|
||||
<td>{{ t.rows.account_number }}</td>
|
||||
<td colspan="2">{{ t.supplier.bank.account }}</td>
|
||||
<td>{{ 'label_supplier_account_number'|trans(locale) }}</td>
|
||||
<td colspan="2">{{ 'supplier_bank_account'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">{{ t.rows.bank }}</td>
|
||||
<td colspan="3">{{ 'label_supplier_bank'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">{{ t.supplier.bank.name }}</td>
|
||||
<td colspan="3">{{ 'supplier_bank_name'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ t.rows.bank_address }}</td>
|
||||
<td colspan="2">{{ t.supplier.bank.address }}</td>
|
||||
<td>{{ 'label_supplier_bank_address'|trans(locale) }}</td>
|
||||
<td colspan="2">{{ 'supplier_bank_address'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ t.rows.swift }}</td>
|
||||
<td colspan="2">{{ t.supplier.bank.swift }}</td>
|
||||
<td>{{ 'label_swift'|trans(locale) }}</td>
|
||||
<td colspan="2">{{ 'supplier_bank_swift'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{{ t.rows.corr_bank }}</strong></td>
|
||||
<td colspan="2">{{ t.supplier.bank.corr_bank.name }}</td>
|
||||
<td><strong>{{ 'label_supplier_corr_bank'|trans(locale) }}</strong></td>
|
||||
<td colspan="2">{{ 'supplier_bank_corr_bank_name'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ t.rows.swift }}</td>
|
||||
<td colspan="2">{{ t.supplier.bank.corr_bank.swift }}</td>
|
||||
<td>{{ 'label_swift'|trans(locale) }}</td>
|
||||
<td colspan="2">{{ 'supplier_bank_corr_bank_swift'|trans(locale) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"> </td>
|
||||
|
@ -111,7 +109,7 @@
|
|||
<td colspan="3"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="50px">{{ t.rows.signature }}</td>
|
||||
<td height="50px">{{ 'label_signature'|trans(locale) }}</td>
|
||||
<td colspan="2" valign="bottom">
|
||||
<img src="{{ images.signature|data_uri(mime='image/png') }}" alt="Signature">
|
||||
</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