| @@ -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); | |||
| } | |||
| } | |||
| @@ -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(); | |||
| } | |||
| } | |||
| @@ -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(), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Receipt; | |||
| use App\Http\Controllers\Web\NoneParams; | |||
| class ReceiptsParam extends NoneParams | |||
| { | |||
| } | |||
| @@ -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()]), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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(), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| @@ -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); | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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> | |||
| @@ -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; | |||
| } | |||
| @@ -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); | |||