diff --git a/Admin/Install/Media/bill.pdf.php b/Admin/Install/Media/bill.pdf.php index 41e4829..07223e1 100755 --- a/Admin/Install/Media/bill.pdf.php +++ b/Admin/Install/Media/bill.pdf.php @@ -14,6 +14,7 @@ declare(strict_types=1); use Modules\Billing\Models\NullBill; use phpOMS\Localization\ISO3166NameEnum; +use phpOMS\Localization\ISO3166TwoEnum; use phpOMS\Localization\Money; /** @var \phpOMS\Views\View $this */ @@ -70,11 +71,14 @@ $billTypeName = \strtoupper($bill->type->getL11n()); // @todo: depending on amount of lines, there is a solution (html, or use backtracking of tcpdf) // Address -$pdf->setY(55); -$pdf->setFont('helvetica', '', 8); +$pdf->setY(50); +$pdf->setFont('helvetica', '', 10); -$countries = ISO3166NameEnum::getConstants(); -$toCountry = isset($countries[$bill->billCountry]) ? $countries[$bill->billCountry] : ''; +$countries = ISO3166NameEnum::getConstants(); +$countryEnumName = ISO3166TwoEnum::getName($bill->billCountry); +$toCountry = \is_string($countryEnumName) && ($country = ISO3166NameEnum::getByName($countryEnumName)) !== null + ? $country + : ''; $addressString = \trim( $bill->billTo . "\n" @@ -96,11 +100,11 @@ $pdf->Write( $lineHeight = ($lineHeight - $pdf->getY()) / $addressLineCount; // Bill head -$pdf->setFont('helvetica', 'B', 20); -$titleWidth = $pdf->getStringWidth($billTypeName, 'helvetica', 'B', 20); +$pdf->setFont('helvetica', 'B', 16); +$titleWidth = $pdf->getStringWidth($billTypeName, 'helvetica', 'B', 16); $pdf->setXY( - $rightPos = ($pdf->getPageWidth() - $titleWidth - ($titleWidth < 55 ? 55 : 35) + 15), + $rightPos = ($pdf->getPageWidth() - $titleWidth - \max(60 - $titleWidth, 0) - 15 - 2), $topPos + 50 + $lineHeight * $addressLineCount - 38, true ); @@ -109,12 +113,12 @@ $pdf->setTextColor(255, 255, 255); $pdf->setFillColor(255, 162, 7); $pdf->Cell($pdf->getPageWidth() - $rightPos - 15, 0, $billTypeName, 0, 0, 'L', true); -$pdf->setFont('helvetica', '', 8); +$pdf->setFont('helvetica', '', 10); $pdf->setTextColor(255, 162, 7); $pdf->setXY($rightPos, $tempY = $pdf->getY() + 10, true); $pdf->MultiCell( - 23, 30, + 26, 30, $lang[$pdf->language]['InvoiceNo'] . "\n" . $lang[$pdf->language]['InvoiceDate'] . "\n" . $lang[$pdf->language]['ServiceDate'] . "\n" @@ -124,10 +128,10 @@ $pdf->MultiCell( 0, 'L' ); -$pdf->setFont('helvetica', '', 8); +$pdf->setFont('helvetica', '', 10); $pdf->setTextColor(0, 0, 0); -$pdf->setXY($rightPos + 23 + 2, $tempY, true); +$pdf->setXY($rightPos + 26 + 2, $tempY, true); $pdf->MultiCell( 25, 30, $bill->number . "\n" @@ -151,7 +155,7 @@ $pdf->writeHTMLCell( $pdf->Ln(); */ -$pdf->setY($pdf->getY() + 5); +$pdf->setY($pdf->getY() + 10); $header = [ $lang[$pdf->language]['Item'], @@ -180,7 +184,7 @@ foreach($lines as $line) { $pdf->setTextColor(255); $pdf->setDrawColor(255, 162, 7); //$pdf->SetLineWidth(0.3); - $pdf->setFont('helvetica', 'B', 8); + $pdf->setFont('helvetica', 'B', 10); if (!$first/* || $row === null*/) { $pdf->AddPage(); @@ -194,7 +198,7 @@ foreach($lines as $line) { $pdf->Ln(); $pdf->setFillColor(245, 245, 245); $pdf->setTextColor(0); - $pdf->setFont('helvetica', '', 8); + $pdf->setFont('helvetica', '', 10); $first = false; } @@ -230,7 +234,7 @@ if ($pdf->getY() > $pdf->getPageHeight() - 40) { $pdf->setFillColor(240, 240, 240); $pdf->setTextColor(0); $pdf->setDrawColor(240, 240, 240); -$pdf->setFont('helvetica', 'B', 8); +$pdf->setFont('helvetica', 'B', 10); $tempY = $pdf->getY(); @@ -255,7 +259,7 @@ foreach ($taxes as $rate => $tax) { $pdf->setFillColor(255, 162, 7); $pdf->setTextColor(255); $pdf->setDrawColor(255, 162, 7); -$pdf->setFont('helvetica', 'B', 8); +$pdf->setFont('helvetica', 'B', 10); $grossSales = Money::fromFloatInt($bill->grossSales); @@ -279,6 +283,11 @@ $pdf->Ln(); // @todo: fix terms $pdf->setFont('helvetica', 'B', 8); $pdf->Write(0, $lang[$pdf->language]['Terms'] . ': https://jingga.app/terms', '', 0, 'L', false, 0, false, false, 0); +$pdf->Ln(); + +$pdf->setFont('helvetica', 'B', 8); +$pdf->Write(0, $lang[$pdf->language]['Currency'] . ': ' . $bill->currency, '', 0, 'L', false, 0, false, false, 0); +$pdf->Ln(); $pdf->setFont('helvetica', '', 8); $pdf->Write(0, $bill->termsText, '', 0, 'L', false, 0, false, false, 0); diff --git a/Admin/Install/Media/lang.php b/Admin/Install/Media/lang.php index a5ae0f2..15bff84 100755 --- a/Admin/Install/Media/lang.php +++ b/Admin/Install/Media/lang.php @@ -23,6 +23,7 @@ return [ 'PO' => 'PO', 'DueDate' => 'Due Date', 'Item' => 'Item', + 'Currency' => 'Currency', 'Quantity' => 'Quantity', 'UnitPrice' => 'Unit Price', 'Total' => 'Total', @@ -41,6 +42,7 @@ return [ 'PO' => 'Kundenreferenz', 'DueDate' => 'Fälligkeitsdatum', 'Item' => 'Artikel', + 'Currency' => 'Währung', 'Quantity' => 'Menge', 'UnitPrice' => 'Einzelpreis', 'Total' => 'Gesamt', diff --git a/Admin/Install/types.json b/Admin/Install/types.json index 202996e..165483c 100755 --- a/Admin/Install/types.json +++ b/Admin/Install/types.json @@ -18,7 +18,7 @@ "isTemplate": false, "l11n": { "en": "Order Confirmation", - "de": "Auftragsbestaetigung" + "de": "Auftragsbestätigung" } }, { @@ -95,7 +95,7 @@ "isTemplate": false, "l11n": { "en": "Order Confirmation", - "de": "Auftragsbestaetigung" + "de": "Auftragsbestätigung" } }, { diff --git a/Admin/Routes/Web/Api.php b/Admin/Routes/Web/Api.php index fbfa045..a36354b 100755 --- a/Admin/Routes/Web/Api.php +++ b/Admin/Routes/Web/Api.php @@ -20,7 +20,7 @@ use phpOMS\Router\RouteVerb; return [ '^.*/bill/render.*$' => [ [ - 'dest' => '\Modules\Billing\Controller\ApiController:apiPreviewRender', + 'dest' => '\Modules\Billing\Controller\ApiBillController:apiMediaRender', 'verb' => RouteVerb::GET, 'permission' => [ 'module' => BackendController::NAME, diff --git a/Controller/ApiBillController.php b/Controller/ApiBillController.php index 04692ca..b08288e 100755 --- a/Controller/ApiBillController.php +++ b/Controller/ApiBillController.php @@ -36,6 +36,7 @@ use Modules\Media\Models\PathSettings; use Modules\Media\Models\UploadStatus; use Modules\Messages\Models\EmailMapper; use Modules\SupplierManagement\Models\NullSupplier; +use Modules\SupplierManagement\Models\Supplier; use Modules\SupplierManagement\Models\SupplierMapper; use phpOMS\Autoloader; use phpOMS\Localization\ISO3166TwoEnum; @@ -85,9 +86,9 @@ final class ApiBillController extends Controller /** @var \Modules\Billing\Models\Bill $old */ $old = BillMapper::get()->where('id', (int) $request->getData('bill')); $new = $this->updateBillFromRequest($request, $response, $data); - $this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill', $request->getOrigin()); - $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Bill', 'Bill successfully created.', $new); + $this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill', $request->getOrigin()); + $this->createStandardUpdateResponse($request, $response, $new); } /** @@ -153,7 +154,7 @@ final class ApiBillController extends Controller $bill = $this->createBillFromRequest($request, $response, $data); $this->createBillDatabaseEntry($bill, $request); - $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Bill', 'Bill successfully created.', $bill); + $this->createStandardCreateResponse($request, $response, $bill); } /** @@ -200,16 +201,16 @@ final class ApiBillController extends Controller * * @since 1.0.0 */ - public function createBaseBill(Client $client, RequestAbstract $request) : Bill + public function createBaseBill(Client | Supplier $account, RequestAbstract $request) : Bill { - // @todo: validate vat before creation + // @todo: validate vat before creation for clients $bill = new Bill(); $bill->createdBy = new NullAccount($request->header->account); - $bill->unit = $client->unit ?? $this->app->unitId; + $bill->unit = $account->unit ?? $this->app->unitId; $bill->billDate = new \DateTime('now'); // @todo: Date of payment - $bill->performanceDate = new \DateTime('now'); // @todo: Date of payment - $bill->accountNumber = $client->number; - $bill->setStatus(BillStatus::DRAFT); + $bill->performanceDate = $request->getDataDateTime('performancedate') ?? new \DateTime('now'); // @todo: Date of payment + $bill->accountNumber = $account->number; + $bill->setStatus($request->getDataInt('status') ?? BillStatus::DRAFT); $bill->shipping = 0; $bill->shippingText = ''; @@ -217,17 +218,18 @@ final class ApiBillController extends Controller $bill->payment = 0; $bill->paymentText = ''; - $bill->type = BillTypeMapper::get() - ->where('name', 'sales_invoice') - ->execute(); + if ($account instanceof Client) { + $bill->client = $account; + } else { + $bill->supplier = $account; + } // @todo: use bill and shipping address instead of main address if available - $bill->client = $client; - $bill->billTo = $client->account->name1; - $bill->billAddress = $client->mainAddress->address; - $bill->billCity = $client->mainAddress->city; - $bill->billZip = $client->mainAddress->postal; - $bill->billCountry = $client->mainAddress->getCountry(); + $bill->billTo = $request->getDataString('billto') ?? $account->account->name1; + $bill->billAddress = $request->getDataString('billaddress') ?? $account->mainAddress->address; + $bill->billCity = $request->getDataString('billtocity') ?? $account->mainAddress->city; + $bill->billZip = $request->getDataString('billtopostal') ?? $account->mainAddress->postal; + $bill->billCountry = $request->getDataString('billtocountry') ?? $account->mainAddress->getCountry(); $bill->setCurrency(ISO4217CharEnum::_EUR); @@ -248,7 +250,7 @@ final class ApiBillController extends Controller } $validLanguages = []; - if (!empty($settings)) { + if (!empty($settings) && !empty($settings->content)) { $validLanguages = \json_decode($settings->content, true); } else { $validLanguages = [ @@ -258,20 +260,33 @@ final class ApiBillController extends Controller $billLanguage = $validLanguages[0] ?? ISO639x1Enum::_EN; - $clientBillLanguage = $client->getAttribute('bill_language')->value->valueStr; - if (!empty($clientBillLanguage) && \in_array($clientBillLanguage, $validLanguages)) { - $billLanguage = $clientBillLanguage; + $accountBillLanguage = $account->getAttribute('bill_language')->value->valueStr; + if (!empty($accountBillLanguage) && \in_array($accountBillLanguage, $validLanguages)) { + $billLanguage = $accountBillLanguage; } else { - $clientLanguages = ISO639x1Enum::languageFromCountry($client->mainAddress->getCountry()); - $clientLanguage = !empty($clientLanguages) ? $clientLanguages[0] : ''; + $accountLanguages = ISO639x1Enum::languageFromCountry($account->mainAddress->getCountry()); + $accountLanguage = !empty($accountLanguages) ? $accountLanguages[0] : ''; - if (\in_array($clientLanguage, $validLanguages)) { - $billLanguage = $clientLanguage; + if (\in_array($accountLanguage, $validLanguages)) { + $billLanguage = $accountLanguage; } } $bill->setLanguage($billLanguage); + $typeMapper = BillTypeMapper::get() + ->with('l11n') + ->where('l11n/langauge', $billLanguage) + ->limit(1); + + if ($request->hasData('type')) { + $typeMapper->where('id', $request->getDataInt('type')); + } else { + $typeMapper->where('name', 'sales_invoice'); + } + + $bill->type = $typeMapper->execute(); + return $bill; } @@ -335,31 +350,7 @@ final class ApiBillController extends Controller ->execute(); } - /** @var \Modules\Billing\Models\BillType $billType */ - $billType = BillTypeMapper::get() - ->where('id', $request->getDataInt('type') ?? 1) - ->execute(); - - // @todo: use defaultInvoiceAddress or mainAddress. also consider to use billto1, billto2, billto3 (for multiple lines e.g. name2, fao etc.) - /** @var \Modules\SupplierManagement\Models\Supplier|\Modules\ClientManagement\Models\Client $account */ - $bill = new Bill(); - $bill->unit = $account->unit ?? $this->app->unitId; - $bill->createdBy = new NullAccount($request->header->account); - $bill->type = $billType; - $bill->billTo = $request->getDataString('billto') ?? ( - $account->account->name1 . (!empty($account->account->name2) - ? ', ' . $account->account->name2 - : '' - )); - $bill->billAddress = (string) ($request->getDataString('billaddress') ?? $account->mainAddress->address); - $bill->billZip = (string) ($request->getDataString('billtopostal') ?? $account->mainAddress->postal); - $bill->billCity = (string) ($request->getDataString('billtocity') ?? $account->mainAddress->city); - $bill->billCountry = $request->getDataString('billtocountry') ?? ( - ($country = $account->mainAddress->getCountry()) === ISO3166TwoEnum::_XXX ? '' : $country); - $bill->client = !$request->hasData('client') ? null : $account; - $bill->supplier = !$request->hasData('supplier') ? null : $account; - $bill->performanceDate = $request->getDataDateTime('performancedate') ?? new \DateTime('now'); - $bill->setStatus($request->getDataInt('status') ?? BillStatus::ACTIVE); + $bill = $this->createBaseBill($account, $request); return $bill; } @@ -435,7 +426,7 @@ final class ApiBillController extends Controller $bill->id, $media->id, BillMapper::class, - 'media', + 'files', '', $request->getOrigin() ); @@ -483,7 +474,7 @@ final class ApiBillController extends Controller $bill->id, (int) $media, BillMapper::class, - 'media', + 'files', '', $request->getOrigin() ); @@ -570,9 +561,9 @@ final class ApiBillController extends Controller $new = clone $old; $new->addElement($element); - $this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill_element', $request->getOrigin()); - $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Bill element', 'Bill element successfully created.', $element); + $this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill_element', $request->getOrigin()); + $this->createStandardCreateResponse($request, $response, $element); } /** @@ -631,6 +622,12 @@ final class ApiBillController extends Controller return []; } + public function apiMediaRender(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void + { + // @todo: check if has permission + $this->app->moduleManager->get('Media', 'Api')->apiMediaExport($request, $response, ['ignorePermission' => true]); + } + /** * Api method to create a bill preview * @@ -789,24 +786,29 @@ final class ApiBillController extends Controller { Autoloader::addPath(__DIR__ . '/../../../Resources/'); + /** @var \Modules\Billing\Models\Bill $bill */ + $bill = BillMapper::get() + ->where('id', $request->getDataInt('bill') ?? 0) + ->execute(); + + // @todo: This is stupid to do twice but I need to get the langauge. + // For the future it should just be a join on the bill langauge!!! + // The problem is the where here is a model where and not a query + // builder where meaning it is always considered a value and not a column. + /** @var \Modules\Billing\Models\Bill $bill */ $bill = BillMapper::get() ->with('type') ->with('type/l11n') + ->with('type/defaultTemplate') ->with('elements') ->where('id', $request->getDataInt('bill') ?? 0) + ->where('type/l11n/language', $bill->getLanguage()) ->execute(); $templateId = $request->getDataInt('bill_template'); if ($templateId === null) { - $billTypeId = $bill->type->id; - - /** @var \Modules\Billing\Models\BillType $billType */ - $billType = BillTypeMapper::get() - ->where('id', $billTypeId) - ->execute(); - - $templateId = $billType->defaultTemplate?->id; + $templateId = $bill->type->defaultTemplate?->id; } /** @var \Modules\Media\Models\Collection $template */ @@ -920,24 +922,34 @@ final class ApiBillController extends Controller $this->sendBillEmail($media, $email, $response->getLanguage()); } + // Add type to media + $originalType = $this->app->appSettings->get( + names: SettingsEnum::ORIGINAL_MEDIA_TYPE, + module: self::NAME + ); + + $this->createModelRelation( + $request->header->account, + $media->id, + (int) $originalType->content, + MediaMapper::class, + 'types', + '', + $request->getOrigin() + ); + + // Add media to bill $this->createModelRelation( $request->header->account, $bill->id, $media->id, BillMapper::class, - 'media', + 'files', '', $request->getOrigin() ); - $this->fillJsonResponse( - $request, - $response, - NotificationLevel::OK, - 'PDF', - 'Bill Pdf successfully created.', - $media - ); + $this->createStandardCreateResponse($request, $response, $media); } public function sendBillEmail(Media $media, string $email, string $language = 'en') : void diff --git a/Controller/BackendController.php b/Controller/BackendController.php index e06864c..0472377 100755 --- a/Controller/BackendController.php +++ b/Controller/BackendController.php @@ -68,6 +68,7 @@ final class BackendController extends Controller ->with('type/l11n') ->with('client') ->where('type/transferType', BillTransferType::SALES) + ->where('type/l11n/language', $response->getLanguage()) ->sort('id', OrderType::DESC) ->limit(25); @@ -76,21 +77,18 @@ final class BackendController extends Controller $mapperQuery ->where('id', $request->getDataInt('id') ?? 0, '<') ->where('client', null, '!=') - ->where('type/l11n/language', $response->getLanguage()) ->execute() ); } elseif ($request->getData('ptype') === 'n') { $view->setData('bills', $mapperQuery->where('id', $request->getDataInt('id') ?? 0, '>') ->where('client', null, '!=') - ->where('type/l11n/language', $response->getLanguage()) ->execute() ); } else { $view->setData('bills', $mapperQuery->where('id', 0, '>') ->where('client', null, '!=') - ->where('type/l11n/language', $response->getLanguage()) ->execute() ); } @@ -119,7 +117,8 @@ final class BackendController extends Controller /** @var \Modules\Billing\Models\Bill $bill */ $bill = SalesBillMapper::get() ->with('elements') - ->with('media') + ->with('files') + ->with('files/types') ->with('notes') ->where('id', (int) $request->getData('id')) ->execute(); @@ -298,8 +297,8 @@ final class BackendController extends Controller $bill = PurchaseBillMapper::get() ->with('elements') - ->with('media') - ->with('media/types') + ->with('files') + ->with('files/types') ->with('notes') ->where('id', (int) $request->getData('id')) ->execute(); @@ -806,8 +805,8 @@ final class BackendController extends Controller $bill = PurchaseBillMapper::get() ->with('elements') - ->with('media') - ->with('media/types') + ->with('files') + ->with('files/types') ->with('notes') ->where('id', (int) $request->getData('id')) ->execute(); diff --git a/Models/Bill.php b/Models/Bill.php index 9775a5a..49405a7 100755 --- a/Models/Bill.php +++ b/Models/Bill.php @@ -339,7 +339,7 @@ class Bill implements \JsonSerializable * @var string * @since 1.0.0 */ - private string $currency = ISO4217CharEnum::_EUR; + public string $currency = ISO4217CharEnum::_EUR; /** * Info text. @@ -445,14 +445,6 @@ class Bill implements \JsonSerializable */ public int $reference = 0; - /** - * Media files - * - * @var array - * @since 1.0.0 - */ - protected array $media = []; - /** * Attributes. * @@ -761,77 +753,6 @@ class Bill implements \JsonSerializable return $this->notes; } - /** - * Get all media - * - * @return Media[] - * - * @since 1.0.0 - */ - public function getMedia() : array - { - return $this->media; - } - - /** - * Add media - * - * @param Media $media Media to add - * - * @return void - * - * @since 1.0.0 - */ - public function addMedia(Media $media) : void - { - $this->media[] = $media; - } - - /** - * Get media file by type - * - * @param null|int $type Media type - * - * @return array - * - * @since 1.0.0 - */ - public function getMediaByType(int $type = null) : array - { - if ($type === null) { - return $this->media; - } - - $files = []; - foreach ($this->media as $file) { - if ($file->type !== null && $file->type->id === $type) { - $files[] = $file; - } - } - - return $files; - } - - /** - * Get media file by type - * - * @param int $type Media type - * - * @return Media - * - * @since 1.0.0 - */ - public function getFileByType(int $type) : Media - { - foreach ($this->media as $file) { - if ($file->hasMediaTypeId($type)) { - return $file; - } - } - - return new NullMedia(); - } - /** * {@inheritdoc} */ @@ -865,4 +786,5 @@ class Bill implements \JsonSerializable } use \Modules\Attribute\Models\AttributeHolderTrait; + use \Modules\Media\Models\MediaListTrait; } diff --git a/Models/BillMapper.php b/Models/BillMapper.php index 6b394e3..2fcd066 100755 --- a/Models/BillMapper.php +++ b/Models/BillMapper.php @@ -109,7 +109,7 @@ class BillMapper extends DataMapperFactory 'self' => 'billing_bill_element_bill', 'external' => null, ], - 'media' => [ + 'files' => [ 'mapper' => MediaMapper::class, 'table' => 'billing_bill_media', 'external' => 'billing_bill_media_dst', diff --git a/Models/SettingsEnum.php b/Models/SettingsEnum.php index c81b267..1585ffe 100755 --- a/Models/SettingsEnum.php +++ b/Models/SettingsEnum.php @@ -28,7 +28,7 @@ abstract class SettingsEnum extends Enum { public const PREVIEW_MEDIA_TYPE = '1005100001'; // internally generated preview - public const ORIGINAL_MEDIA_TYPE = '1005100002'; // original document (mostly supplier invoice/delivery note) + public const ORIGINAL_MEDIA_TYPE = '1005100002'; // original document (client = invoice sent to client, supplier = invoice from supplier) public const VALID_BILL_LANGUAGES = '1005100003'; // List of valid languages for bills diff --git a/Theme/Backend/Lang/en.lang.php b/Theme/Backend/Lang/en.lang.php index 742f38c..5a04c24 100755 --- a/Theme/Backend/Lang/en.lang.php +++ b/Theme/Backend/Lang/en.lang.php @@ -14,6 +14,7 @@ declare(strict_types=1); return ['Billing' => [ 'Amount' => 'Amount', + 'Archive' => 'Archive', 'Address' => 'Address', 'Addresses' => 'Addresses', 'Africa' => 'Africa', diff --git a/Theme/Backend/bill-create.tpl.php b/Theme/Backend/bill-create.tpl.php index d9c97bc..20d3f9e 100755 --- a/Theme/Backend/bill-create.tpl.php +++ b/Theme/Backend/bill-create.tpl.php @@ -36,6 +36,8 @@ $elements = $bill->getElements(); $billTypes = $this->getData('billtypes') ?? []; +$archive = $bill->getFileByTypeName('original'); + /** @var \Modules\Auditor\Models\Audit */ $logs = $this->getData('logs') ?? []; @@ -49,10 +51,11 @@ echo $this->getData('nav')->render(); ?>