Просмотр исходного кода

領収証対応 たたき台

master
sosuke.iwabuchi 2 лет назад
Родитель
Сommit
ee0150b150
10 измененных файлов: 494 добавлений и 71 удалений
  1. +47
    -0
      app/Files/PDF/PDFFile.php
  2. +21
    -0
      app/Files/PDF/Receipt.php
  3. +26
    -4
      app/Files/TmpFile.php
  4. +14
    -4
      app/Kintone/KintoneAccess.php
  5. +152
    -63
      app/Kintone/Models/KintoneModel.php
  6. +51
    -0
      app/Kintone/Models/Receipt.php
  7. +12
    -0
      app/Kintone/Models/SubTable/SubTableData.php
  8. +70
    -0
      app/Logic/ReceiptManager.php
  9. +34
    -0
      resources/views/pdf/receipt.blade.php
  10. +67
    -0
      resources/views/pdf/receipt.css

+ 47
- 0
app/Files/PDF/PDFFile.php Просмотреть файл

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

namespace App\Files\PDF;

use App\Files\TmpFile;

abstract class PDFFile extends TmpFile
{
protected const DIR = ['pdf'];

private string $appFileName = "";

public function __construct(?string $id = null)
{
parent::__construct($id);
}

/**
* @override
*/
public function getFileExtension(): string
{
return "pdf";
}

/**
* @override
*/
public function getMimeType(): string
{
return "application/pdf";
}

/**
* @override
*/
public function getAppFileName()
{
return $this->appFileName;
}

public function setAppFileName(string $fileName)
{
$this->appFileName = $fileName;
return $this;
}
}

+ 21
- 0
app/Files/PDF/Receipt.php Просмотреть файл

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

namespace App\Files\PDF;


class Receipt extends PDFFile
{

protected const DIR = [
...parent::DIR,
'receipt'
];

/**
* @override
*/
protected function getFileTypeName(): string
{
return "ReceiptPDF";
}
}

+ 26
- 4
app/Files/TmpFile.php Просмотреть файл

@@ -35,6 +35,8 @@ class TmpFile

protected string $uuid;

protected string $content;


public function __construct(?string $id = null)
{
@@ -59,16 +61,26 @@ class TmpFile
return "tmp";
}

protected function getFileExtension(): string
public function getFileExtension(): string
{
return "tmp";
}

final protected function getFileName(): string
public function getMimeType(): string
{
return "txt/plain";
}

final public function getFileName(): string
{
return sprintf("%s_%s.%s", $this->getFileTypeName(), $this->uuid, $this->getFileExtension());
}

public function getAppFileName()
{
return $this->getFileName();
}

public function getId(): string
{
return $this->uuid;
@@ -93,16 +105,25 @@ class TmpFile
public function put(string $content)
{
Storage::put($this->getPath(), $content);
$this->content = $content;
return $this;
}

public function get()
public function load()
{
return Storage::get($this->getPath());
$this->content = Storage::get($this->getPath());
return $this;
}

public function append(string $content)
{
Storage::append($this->getPath(), $content);
$this->content .= $content;
return $this;
}
public function get(): string
{
return $this->content;
}

public function download(string $name = "download")
@@ -122,6 +143,7 @@ class TmpFile
if ($ret) info(sprintf("ファイル削除:%s ", $this->getFullPath()));
return;
} else {
$this->content = "";
DeleteFile::dispatch($this)
->delay($delay);
return;


+ 14
- 4
app/Kintone/KintoneAccess.php Просмотреть файл

@@ -4,6 +4,7 @@ namespace App\Kintone;

use App\Exceptions\AppCommonException;
use App\Exceptions\ConfigException;
use App\Files\TmpFile;
use App\Kintone\Models\KintoneModel;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -403,16 +404,25 @@ class KintoneAccess
/**
* ファイルアップロード
*
* @param UploadedFile $file
* @param UploadedFile|TmpFile $file
* @return string fileKey
*/
public function filePut(UploadedFile $file): string
public function filePut(UploadedFile|TmpFile $file): string
{
$content = file_get_contents($file);
$content = "";
$sendFileName = "";
if ($file instanceof UploadedFile) {
$content = file_get_contents($file);
$sendFileName = sprintf("file.%s", $file->extension());
} else if ($file instanceof TmpFile) {
$content = $file->get();
$sendFileName = $file->getAppFileName();
}

$response = Http::withHeaders([
"X-Cybozu-API-Token" => $this->apiToken,
])
->attach("file", $content, sprintf("file.%s", $file->extension()))
->attach("file", $content, $sendFileName)
->post($this->getFileUrl());

if ($response->failed()) {


+ 152
- 63
app/Kintone/Models/KintoneModel.php Просмотреть файл

@@ -2,15 +2,18 @@

namespace App\Kintone\Models;

use App\Exceptions\AppCommonException;
use App\Exceptions\ConfigException;
use App\Files\TmpFile;
use App\Kintone\File;
use App\Kintone\KintoneAccess;
use App\Kintone\KintoneAccessStore;
use App\Kintone\KintoneRecordQuery;
use App\Kintone\Models\SubTable\SubTableData;
use App\Util\DateUtil;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use LogicException;
use stdClass;
@@ -89,6 +92,13 @@ abstract class KintoneModel
{
return static::getAccess()->find($recordId);
}
/**
* @return static
*/
public static function first(?KintoneRecordQuery $query = null)
{
return static::getAccess()->first($query);
}

public static function getDropDownOptions(string $fieldCode): array
{
@@ -112,8 +122,12 @@ abstract class KintoneModel
protected ?stdClass $dataOrigin = null;
protected stdClass $data;

const FIELD_RECORD_NO = "レコード番号";

protected const FIELDS = [];

protected const SUB_TABLES = [];

protected const FIELD_NAMES = [];

protected const RELATIONS = [];
@@ -148,36 +162,15 @@ abstract class KintoneModel

public function set(string $fieldCode, $value)
{
$field = Arr::get(static::FIELDS, $fieldCode);
if ($field instanceof FieldType) {
$this->setData($fieldCode, $field, $value);
} else if (is_array($field)) {
$this->setTable($fieldCode, $value);
$type = $this->getFieldType($fieldCode);
if ($type) {
$this->setData($fieldCode, $value);
data_set($this->changed, $fieldCode, true);
}

data_set($this->changed, $fieldCode, true);
return $this;
}

private function setTable(string $fieldCode, array $table)
{
foreach ($table as $index => $row) {
$row = data_get($row, "value");
$this->setTableRow($fieldCode, $index, $row);
}
}

private function setTableRow(string $fieldCode, int $index, array $row)
{
foreach ($row as $columnFieldCode => $column) {
$value = $column["value"];
$type = static::FIELDS[$fieldCode][$columnFieldCode];
$insertKey = sprintf("%s.%d.%s", $fieldCode, $index, $columnFieldCode);
$this->setData($insertKey, $type, $value);
}
}

private function setData(string $path, FieldType $type, $value)
private function setData(string $path, $value)
{
data_set($this->data, $path, $value);

@@ -207,43 +200,73 @@ abstract class KintoneModel
continue;
}

$type = FieldType::tryFrom($type);
if ($type === null) continue;
data_set($this->data, $fieldCode, $this->readData($ele, $fieldCode));
}

if ($this->recordId === null) {
throw new LogicException(sprintf("レコード番号取得失敗 :%s", static::class));
}

$ret = $this->setDataCustom($data);
if ($ret) {
$this->clean();
}
return $ret;
}

private function readData(array $data, string $fieldCode = "")
{
$type = data_get($data, "type");
$value = data_get($data, "value");
if ($type === null || $value === null) {
return null;
}

$type = FieldType::tryFrom($type);
if ($type !== null) {
if (in_array($type, [FieldType::DATETIME, FieldType::DATE])) {
if ($value) {
data_set($this->data, $fieldCode, DateUtil::parse($value));
return DateUtil::parse($value);
} else {
data_set($this->data, $fieldCode, null);
return null;
}
continue;
}
if ($type === FieldType::NUMBER) {
return intval($value);
}
if ($type === FieldType::FILE) {
$ret = [];
foreach ($value as $f) {
$ret[] = new File($f);
}
data_set($this->data, $fieldCode, $ret);
continue;
return $ret;
}
if ($type === FieldType::SUBTABLE) {
continue;
if ($type === FieldType::SUBTABLE && $this->getFieldType($fieldCode)) {
$ret = collect();
foreach ($value as $v) {
$rowRet =
$rowArray = data_get($v, 'value');
foreach ($rowArray as $subFieldCode => $ele) {
$rowRet[$subFieldCode] = $this->readData($ele);
}
$ret->push($this->getSubTableData($fieldCode, $rowRet));
}
return $ret;
}


// 以外はそのまま格納
data_set($this->data, $fieldCode, $value);
return $value;
}

if ($this->recordId === null) {
throw new LogicException(sprintf("レコード番号取得失敗 :%s", static::class));
}
return null;
}

$ret = $this->setDataCustom($data);
if ($ret) {
$this->clean();
private function getSubTableData(string $fieldCode, array $data): SubTableData
{
$className = data_get(static::SUB_TABLES, $fieldCode, "");
if (class_exists($className)) {
return new $className($data);
}
return $ret;
throw new LogicException("キントーン設定不備");
}


@@ -273,28 +296,54 @@ abstract class KintoneModel
}

$path = sprintf("%s.value", $fieldCode);
if ($type === FieldType::DATETIME) {
$data = $this->getDate($fieldCode);
if ($data) {
data_set($ret, $path, $data->toIso8601ZuluString());
} else {
data_set($ret, $path, "");
}
continue;
data_set($ret, $path, $this->getApiLayoutValue($fieldCode));
}

return array_merge($ret, $this->getApiLayoutCustom());
}

private function getApiLayoutValue(string $fieldCode)
{

$ret = [];
$data = $this->get($fieldCode);

$type = $this->getFieldType($fieldCode);

if ($type === FieldType::DATETIME) {
$data = $this->getDate($fieldCode);
if ($data) {
return $data->toIso8601ZuluString();
} else {
return "";
}
if ($type === FieldType::DATE) {
$data = $this->getDate($fieldCode);
if ($data) {
data_set($ret, $path, $data->toDateString());
} else {
data_set($ret, $path, "");
}
if ($type === FieldType::DATE) {
$data = $this->getDate($fieldCode);
if ($data) {
return $data->toDateString();
} else {
return "";
}
}

if ($data instanceof Collection) {
$ret = [];
foreach ($data as $ele) {
if ($ele instanceof SubTableData) {
$obj = [];
$obj['value'] = $ele->toArray();
$ret[] = $obj;
}
continue;
}
data_set($ret, $path, data_get($this->data, $fieldCode));
return $ret;
}
return data_get($this->data, $fieldCode);
}

return array_merge($ret, $this->getApiLayoutCustom());
private function getFieldType(string $fieldCode): ?FieldType
{
return data_get(static::FIELDS, $fieldCode);
}

public function get(string $key)
@@ -346,6 +395,46 @@ abstract class KintoneModel
return $this->createdAt;
}

/**
* @param string $fieldCode
* @param Collection<int, UploadedFile|TmpFile> $files
* @param bool $override
* @return static
*/
public function setFiles(string $fieldCode, Collection $files, bool $override = true): static
{
if ($override) {
$this->set($fieldCode, []);
}
foreach ($files as $file) {
$this->addFile($fieldCode, $file);
}
return $this;
}

public function addFile(string $fieldCode, UploadedFile|TmpFile $file)
{
if ($file instanceof UploadedFile) {
$name = sprintf("image_%d.%s", Str::uuid(), $file->extension());
$contentType = $file->getClientMimeType();
} else {
$name = $file->getAppFileName();
$contentType = $file->getMimeType();
}
$access = $this->getAccess();

$field = $this->get($fieldCode);

array_push($field, [
'fileKey' => $access->filePut($file),
'name' => $name,
'contentType' => $contentType,
]);

$this->set($fieldCode, $field);
return $this;
}

public function save()
{
$access = static::getAccess();


+ 51
- 0
app/Kintone/Models/Receipt.php Просмотреть файл

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

namespace App\Kintone\Models;

use App\Kintone\Models\SubTable\SeasonTicketContractReserve\TargetRoom;
use Illuminate\Support\Collection;

/**
* アプリ名 領収証
* @property string receiptNo
* @property int customerCode
* @property string customerName
* @property string parkingName
* @property string receiptCustomerName
* @property string receiptPurpose
* @property int receiptTotalAmount
*/
class Receipt extends KintoneModel
{
const CONFIG_KEY = "KINTONE_APP_RECEIPT";

const FIELD_RECEIPT_NO = "領収証番号";
const FIELD_CUSTOMER_CODE = "顧客コード";
const FIELD_CUSTOMER_NAME = "顧客名";
const FIELD_RECEIPT_CUSTOMER_NAME = "宛名";
const FIELD_RECEIPT_PURPOSE = "但し書き";
const FIELD_RECEIPT_TOTAL_AMOUNT = "合計";


const FIELD_RECEIPT_PDF_FILE = "領収証PDF";

protected const FIELDS = [
...parent::FIELDS,
self::FIELD_RECEIPT_NO => FieldType::SINGLE_LINE_TEXT,
self::FIELD_CUSTOMER_CODE => FieldType::NUMBER,
self::FIELD_CUSTOMER_NAME => FieldType::SINGLE_LINE_TEXT,
self::FIELD_RECEIPT_CUSTOMER_NAME => FieldType::SINGLE_LINE_TEXT,
self::FIELD_RECEIPT_PURPOSE => FieldType::SINGLE_LINE_TEXT,
self::FIELD_RECEIPT_TOTAL_AMOUNT => FieldType::NUMBER,
self::FIELD_RECEIPT_PDF_FILE => FieldType::FILE,
];

protected const FIELD_NAMES = [
...parent::FIELD_NAMES,
];

protected const RELATIONS = [
Customer::class,
PaymentPlan::class,
];
}

+ 12
- 0
app/Kintone/Models/SubTable/SubTableData.php Просмотреть файл

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

namespace App\Kintone\Models\SubTable;

abstract class SubTableData
{
public function __construct(protected array $data)
{
}

abstract public function toArray(): array;
}

+ 70
- 0
app/Logic/ReceiptManager.php Просмотреть файл

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

namespace App\Logic;

use App\Files\PDF\Receipt as ReceiptReceipt;
use App\Kintone\Models\Receipt;
use PDF;

class ReceiptManager
{
private ?Receipt $receipt = null;

public function __construct(?int $recordNo = null)
{
if ($recordNo) {
$this->load($recordNo);
} else {
$this->receipt = new Receipt();
}
}

private function load(int $recordNo)
{
$this->receipt = Receipt::find($recordNo);
}

public function getReceipt(): Receipt
{
return $this->receipt;
}

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


$file = new ReceiptReceipt();
$file->setAppFileName($this->makeFileName($file))
->put($pdf->output());

return $file;
}

private function makeFileName(ReceiptReceipt $file)
{
return sprintf(
"領収証_%s_%s_%s.%s",
$this->receipt->receiptNo,
$this->receipt->customerName,
$this->receipt->receiptPurpose,
$file->getFileExtension(),
);
}

private function getPdfData()
{
return [
'receiptDate' => "2023/10/17",
'receiptCustomerName' => $this->receipt->receiptCustomerName,
'receiptTotalAmount' => number_format($this->receipt->receiptTotalAmount),
'taxTotalAmount' => number_format(100),
'receiptPurpose' => $this->receipt->receiptPurpose,
];
}
}

+ 34
- 0
resources/views/pdf/receipt.blade.php Просмотреть файл

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="ja">

<head>
<meta charset="utf-8">
<title>領収証</title>
<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>
</body>

</html>

+ 67
- 0
resources/views/pdf/receipt.css Просмотреть файл

@@ -0,0 +1,67 @@

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

body div {
position: absolute;
}

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

.receiptCustomerName {
top : 30mm;
}
#receiptCustomerName {
left: 100mm;
}
#receiptDate {
left: 150mm;
}

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

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

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

top: 65mm;
left: 140mm;
width: 50mm;
}

#receiptPurpose {
top: 70mm;
left: 20mm;
}

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




Загрузка…
Отмена
Сохранить