Browse Source

領収証対応

master
sosuke.iwabuchi 2 years ago
parent
commit
eca5ec7c9f
16 changed files with 456 additions and 76 deletions
  1. +2
    -19
      app/Email/BaseEmailer.php
  2. +49
    -0
      app/Http/Controllers/Web/Receipt/ReceiptDownloadController.php
  3. +20
    -0
      app/Http/Controllers/Web/Receipt/ReceiptDownloadParam.php
  4. +46
    -0
      app/Http/Controllers/Web/Receipt/ReceiptsController.php
  5. +9
    -0
      app/Http/Controllers/Web/Receipt/ReceiptsParam.php
  6. +12
    -0
      app/Kintone/Models/Receipt.php
  7. +28
    -0
      app/Kintone/Repositories/ReceiptRepository.php
  8. +20
    -0
      app/Kintone/Repositories/ReceiptRepositoryData.php
  9. +32
    -6
      app/Logic/ReceiptManager.php
  10. +5
    -0
      app/Util/RouteHelper.php
  11. +36
    -0
      app/Util/UrlUtil.php
  12. +32
    -0
      resources/views/pdf/common.css
  13. +89
    -21
      resources/views/pdf/receipt.blade.php
  14. +72
    -30
      resources/views/pdf/receipt.css
  15. BIN
      resources/views/pdf/stamp.png
  16. +4
    -0
      routes/api.php

+ 2
- 19
app/Email/BaseEmailer.php View File

@@ -8,6 +8,7 @@ use App\Models\Email;
use App\Models\EmailAttachment;
use App\Models\User;
use App\Util\DateUtil;
use App\Util\UrlUtil;
use Illuminate\Bus\Queueable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Mail\Mailable;
@@ -180,24 +181,6 @@ abstract class BaseEmailer extends Mailable
*/
protected function getAppUrl(array|string $path, array $query = []): string
{
$elements = [config("app.url")];
if (is_array($path)) {
$elements = array_merge($elements, $path);
} else {
$elements[] = $path;
}

$url = implode("/", $elements);

if (!!$query) {
$url .= "?";
$queryStrList = [];
foreach ($query as $key => $value) {
$queryStrList[] = sprintf("%s=%s", $key, $value);
}
$url .= implode("&", $queryStrList);
}

return $url;
return UrlUtil::getAppUrl($path, $query);
}
}

+ 49
- 0
app/Http/Controllers/Web/Receipt/ReceiptDownloadController.php View File

@@ -0,0 +1,49 @@
<?php

namespace App\Http\Controllers\Web\Receipt;

use App\Exceptions\AppCommonException;
use App\Http\Controllers\Web\WebController;
use App\Logic\ReceiptManager;
use Auth;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class ReceiptDownloadController extends WebController
{

public function name(): string
{
return "領収証PDF取得";
}

public function description(): string
{
return "領収証PDFを取得する";
}


public function __construct(protected ReceiptDownloadParam $param)
{
parent::__construct();
$this->middleware('auth:sanctum');
}

protected function run(Request $request): Response
{
$param = $this->param;
$manager = new ReceiptManager($param->recordNo);

if ($manager->getReceipt()->receiptNo !== $param->receiptNo) {
throw new AppCommonException("パラメータ不正");
}

$customerCode = $manager->getReceipt()->customerCode;
$customerCodeAuth = Auth::user()->kintone_customer_code;
if ($customerCode != $customerCodeAuth) {
throw new AppCommonException("パラメータ不正");
}

return $manager->getPdf();
}
}

+ 20
- 0
app/Http/Controllers/Web/Receipt/ReceiptDownloadParam.php View File

@@ -0,0 +1,20 @@
<?php

namespace App\Http\Controllers\Web\Receipt;

use App\Http\Controllers\Web\BaseParam;

/**
* @property int recordNo
* @property string receiptNo
*/
class ReceiptDownloadParam extends BaseParam
{
function rules(): array
{
return [
'record_no' => $this->str(),
'receipt_no' => $this->str(),
];
}
}

+ 46
- 0
app/Http/Controllers/Web/Receipt/ReceiptsController.php View File

@@ -0,0 +1,46 @@
<?php

namespace App\Http\Controllers\Web\Receipt;

use App\Http\Controllers\Web\WebController;
use App\Kintone\Repositories\ReceiptRepository;
use App\Kintone\Repositories\SeasonTicketContractRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class ReceiptsController extends WebController
{

public function name(): string
{
return "領収証一覧取得";
}

public function description(): string
{
return "領収証の一覧を取得する";
}


public function __construct(protected ReceiptsParam $param)
{
parent::__construct();
$this->middleware('auth:sanctum');
}

protected function run(Request $request): JsonResponse
{
$user = Auth::user();

$list = ReceiptRepository::get($user->kintone_customer_code);

$result = [];

foreach ($list as $ele) {
$result[] = $ele->toArray();
}

return $this->successResponse($result);
}
}

+ 9
- 0
app/Http/Controllers/Web/Receipt/ReceiptsParam.php View File

@@ -0,0 +1,9 @@
<?php

namespace App\Http\Controllers\Web\Receipt;

use App\Http\Controllers\Web\NoneParams;

class ReceiptsParam extends NoneParams
{
}

+ 12
- 0
app/Kintone/Models/Receipt.php View File

@@ -2,10 +2,12 @@

namespace App\Kintone\Models;

use App\Http\Controllers\Web\Receipt\ReceiptDownloadController;
use App\Kintone\Models\PaymentPlan as ModelsPaymentPlan;
use App\Kintone\Models\SubTable\Receipt\PaymentPlan;
use App\Kintone\Models\SubTable\Receipt\ReceiptDetail;
use App\Kintone\Models\SubTable\Receipt\TaxDetail;
use App\Util\RouteHelper;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

@@ -86,6 +88,9 @@ class Receipt extends KintoneModel

protected const FIELD_NAMES = [
...parent::FIELD_NAMES,
self::FIELD_RECEIPT_DATE => 'receipt_date',
self::FIELD_RECEIPT_NAME => 'receipt_name',
self::FIELD_RECEIPT_TOTAL_AMOUNT => 'total_amount',
];

protected const SUB_TABLES = [
@@ -98,4 +103,11 @@ class Receipt extends KintoneModel
Customer::class,
ModelsPaymentPlan::class,
];

protected function toArrayCustom(): array
{
return [
'pdf_url' => RouteHelper::getPath(ReceiptDownloadController::class, ['receipt_no' => $this->receiptNo, 'record_no' => $this->getRecordId()]),
];
}
}

+ 28
- 0
app/Kintone/Repositories/ReceiptRepository.php View File

@@ -0,0 +1,28 @@
<?php

namespace App\Kintone\Repositories;

use App\Kintone\Models\Receipt;
use Illuminate\Support\Collection;

class ReceiptRepository
{
/**
* キーは車室情報管理のレコード番号
* @param string $customerCode
* @return Collection<int, ReceiptRepositoryData>
*/
static function get(int $customerCode): Collection
{

$ret = collect();
$query = Receipt::getQuery()->where(Receipt::FIELD_CUSTOMER_CODE, $customerCode)->orderByDesc(Receipt::FIELD_RECEIPT_DATE);
$all = Receipt::getAccess()->all($query);

$all->map(function (Receipt $receipt) use ($ret) {
$ret->push(new ReceiptRepositoryData($receipt));
});

return $ret;
}
}

+ 20
- 0
app/Kintone/Repositories/ReceiptRepositoryData.php View File

@@ -0,0 +1,20 @@
<?php

namespace App\Kintone\Repositories;

use App\Kintone\Models\Receipt;

class ReceiptRepositoryData
{
public function __construct(
public Receipt $receipt,
) {
}

public function toArray(): array
{
return [
...$this->receipt->toArray(),
];
}
}

+ 32
- 6
app/Logic/ReceiptManager.php View File

@@ -181,9 +181,9 @@ class ReceiptManager
public function makePdf(): ReceiptReceipt
{
$pdf = PDF::loadView("pdf/receipt", $this->getPdfData())
// ->setPaper("A4")
->setOption('page-width', 210)
->setOption('page-height', 148)
->setPaper("A4")
// ->setOption('page-width', 210)
// ->setOption('page-height', 148)
->setOrientation("Portrait")
->setOption('encoding', 'utf-8');

@@ -195,6 +195,23 @@ class ReceiptManager
return $file;
}

public function getPdf()
{
$pdf = PDF::loadView("pdf/receipt", $this->getPdfData())
->setPaper("A4")
// ->setOption('page-width', 210)
// ->setOption('page-height', 148)
->setOrientation("Portrait")
->setOption('encoding', 'utf-8');

return $pdf->inline();
}

public function getHtml()
{
return response()->view("pdf/receipt", ["forHtml" => true, ...$this->getPdfData()]);
}

private function makeFileName(ReceiptReceipt $file)
{
return sprintf(
@@ -209,14 +226,23 @@ class ReceiptManager
private function getPdfData()
{
return [
'receiptDate' => "2023/10/17",
'receiptDate' => $this->receipt->receiptDate->format('Y/m/d'),
'receiptCustomerName' => $this->receipt->receiptCustomerName,
'receiptTotalAmount' => number_format($this->receipt->receiptTotalAmount),
'taxTotalAmount' => number_format(100),
'receiptPurpose' => $this->receipt->receiptPurpose,
'taxTotalAmount' => $this->getTaxAmount10(),
'detail' => $this->receipt->receiptDetail,
];
}

private function getTaxAmount10(): int
{
$receipt = $this->receipt->taxDetail->first(function (TaxDetail $tax) {
return $tax->taxRate === 10;
});

return $receipt ? $receipt->taxAmount : 0;
}

private function getReceiptNo(): string
{



+ 5
- 0
app/Util/RouteHelper.php View File

@@ -34,6 +34,11 @@ class RouteHelper
return $routeName;
}

static public function getPath(string $controllerClassName, array $param = [])
{
return route(self::routeName($controllerClassName), $param);
}

static public function webRoute(string $route)
{
return Str::replaceFirst('/api', '', $route);


+ 36
- 0
app/Util/UrlUtil.php View File

@@ -0,0 +1,36 @@
<?php

namespace App\Util;

class UrlUtil
{

/**
* 画面のURLを生成する
*
* @param array|string $path
* @return string
*/
public static function getAppUrl(array|string $path, array $query = []): string
{
$elements = [config("app.url")];
if (is_array($path)) {
$elements = array_merge($elements, $path);
} else {
$elements[] = $path;
}

$url = implode("/", $elements);

if (!!$query) {
$url .= "?";
$queryStrList = [];
foreach ($query as $key => $value) {
$queryStrList[] = sprintf("%s=%s", $key, $value);
}
$url .= implode("&", $queryStrList);
}

return $url;
}
}

+ 32
- 0
resources/views/pdf/common.css View File

@@ -0,0 +1,32 @@
.flex {
display: flex;
display: -webkit-box;
}

.flex-start {
display: flex;
display: -webkit-box;
justify-content: start;
-webkit-box-pack: start;
}

.flex-center {
display: flex;
display: -webkit-box;
justify-content: center;
-webkit-box-pack: center;
}

.flex-end {
display: flex;
display: -webkit-box;
justify-content: end;
-webkit-box-pack: end;
}

.flex-space-between {
display: flex;
display: -webkit-box;
justify-content: space-between;
-webkit-box-pack: justify;
}

+ 89
- 21
resources/views/pdf/receipt.blade.php View File

@@ -4,30 +4,98 @@
<head>
<meta charset="utf-8">
<title>領収証</title>
<link rel="stylesheet" href="{{ resource_path('views/pdf/receipt.css') }}">
<link rel="stylesheet" href="{{ resource_path('views/pdf/common.css') }}" />
<link rel="stylesheet" href="{{ resource_path('views/pdf/receipt.css') }}" />
</head>

<body>
<div id="title">
領収証
</div>
<div id="receiptCustomerName" class="receiptCustomerName">{{ $receiptCustomerName }}様</div>
<div id="receiptDate" class="receiptCustomerName">領収日 {{ $receiptDate }}</div>
<div id="receiptTotalAmount">金額{{ $receiptTotalAmount }}(税込)</div>
<div id="taxTotalAmount">税率10% ¥{{ $taxTotalAmount }}</div>
<div id="receiptPurpose">
<p>
但し {{ $receiptPurpose }} として
</p>
<p>
上記正に領収いたしました
</p>
</div>
<div id="company">
<p class="name">一般財団法人 京都市都市整備公社</p>
<p class="zipCode">〒600-8421</p>
<p class="address1">京都市下京区小道通烏丸西入童侍者町167番</p>
<p class="invoiceNo">登録番号 T7 1300 0501 2806</p>
<div id="content">

<div id="title" class="flex-center">
<div>
領収書
</div>
</div>



<div id="receiptCustomerName" class="flex-end">

<div>
{{ $receiptCustomerName }}様
</div>
<div id="receiptDate">
領収日 {{ $receiptDate }}
</div>

</div>
<div id="receiptTotalAmount" class="flex-space-between">



<div>
金額
</div>
<div>
¥{{ $receiptTotalAmount }}-
</div>
<div style="font-weight: normal;">
(税込)

</div>
</div>

<div id="taxTotalAmount" class="flex-end">
<div class="flex-space-between">
<div>

税率10%
</div>
<div>
¥{{ number_format($taxTotalAmount) }}
</div>
</div>
</div>


<div id="detailTable">

<table>
<thead>
<th class="parkingName">駐車場名</th>
<th class="amount">金額</th>
<th class="targetMonth">対象月</th>
<th class="memo">備考</th>
</thead>
<tbody>
@foreach($detail as $ele)
<tr>
<td>{{ $ele->parkingName }}</td>
<td>¥{{ number_format($ele->amount) }}</td>
<td>{{ sprintf("%d月分", $ele->targetMonth) }}</td>
<td>{{ $ele->memo }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>

<div style="margin-top: 10mm;">
上記正に領収いたしました。
</div>


<div id="company" class="flex-end">
<div>
<p class="name">一般財団法人 京都市都市整備公社</p>
<p class="zipCode">〒600-8421</p>
<p class="address1">京都市下京区小道通烏丸西入童侍者町167番</p>
<p class="invoiceNo">登録番号 T7 1300 0501 2806</p>
</div>
<img id="stamp" width="50mm" height="50mm" src="{{ resource_path('views/pdf/stamp.png') }}" />
</div>


</div>
</body>


+ 72
- 30
resources/views/pdf/receipt.css View File

@@ -1,67 +1,109 @@

body {
position: relative;
font-size: 5mm
font-size: 3.5mm
}

body div {
position: absolute;
#content {
padding: 20mm;
padding-top: 10mm
}


#title {
top : 10mm;
left: 100mm;
font-size: 10mm;
font-size: 7mm;
font-weight: bold;
letter-spacing: 10mm;
letter-spacing: 5mm;
}

.receiptCustomerName {
top : 30mm;
}
#receiptCustomerName {
left: 100mm;
font-size: 5mm;
padding-top: 10mm;
}

#receiptDate {
left: 150mm;
margin-left: 50mm;
}

#receiptTotalAmount {
border-top: solid 1px;
border-bottom: solid 1px;
padding-top: 2mm;;
padding-top: 2mm;
padding-bottom: 2mm;
margin-top: 10mm;
margin-bottom: 2mm;

font-size: 5mm;
font-weight: bold;

width: 150mm;

top: 50mm;
left: 20mm;
width: 170mm;
}

#receiptTotalAmount :nth-of-type(1) {
margin-left: 10mm;
}

#receiptTotalAmount :nth-last-of-type(1) {
margin-right: 6mm;
}

#taxTotalAmount {
border-bottom: solid 1px;
padding-bottom: 2mm;
width: 150mm;
}

#taxTotalAmount>div {
width: 60mm;
border-bottom: solid 0.5px;
}

top: 65mm;
left: 140mm;
width: 50mm;
#taxTotalAmount>div :nth-of-type(1) {
margin-left: 10mm;
}

#receiptPurpose {
top: 70mm;
left: 20mm;
#taxTotalAmount>div :nth-of-type(2) {
margin-right: 10mm;
}


#detailTable {
margin-top: 5mm;
}

#detailTable table {
border-collapse: collapse;
/* セルの線を重ねる */
width: 150mm;
text-align: left;
}

#detailTable th,
td {
border: solid 0.5px;
/* 枠線指定 */
}

#detailTable th.amount {
width: 20mm;
}

#detailTable th.targetMonth {
width: 15mm;
}


#company {
top: 130mm;
left: 150mm;
font-size: 4mm;
margin-top: 5mm;
}


#company p {
margin: 1mm;
}

#company .name {
font-size: 5mm;
}



#stamp {
top: 100mm;
}

BIN
resources/views/pdf/stamp.png View File

Before After
Width: 236  |  Height: 245  |  Size: 163KB

+ 4
- 0
routes/api.php View File

@@ -36,6 +36,10 @@ RouteHelper::post('/season-ticket-contract/entry/cancel', App\Http\Controllers\W
RouteHelper::get('/season-ticket-contract/selection/info', App\Http\Controllers\Web\SeasonTicketContract\Selection\SelectionInfoController::class);
RouteHelper::post('/season-ticket-contract/selection/entry', App\Http\Controllers\Web\SeasonTicketContract\Selection\EntryController::class);

RouteHelper::get('/receipts', App\Http\Controllers\Web\Receipt\ReceiptsController::class);
RouteHelper::get('/receipt/download', App\Http\Controllers\Web\Receipt\ReceiptDownloadController::class);


RouteHelper::get('/faq', App\Http\Controllers\Web\FAQ\FAQsController::class);
RouteHelper::get('/faq/genres', App\Http\Controllers\Web\FAQ\FAQGenresController::class);
RouteHelper::post('/ask', App\Http\Controllers\Web\FAQ\AskController::class);


Loading…
Cancel
Save