Kaynağa Gözat

自動承認まわり対応

develop
sosuke.iwabuchi 2 yıl önce
ebeveyn
işleme
a8970b5d44
31 değiştirilmiş dosya ile 1036 ekleme ve 79 silme
  1. +2
    -0
      package.json
  2. +33
    -0
      src/apps/入金予定結果/index.tsx
  3. +2
    -1
      src/apps/各種申請/index.tsx
  4. +12
    -0
      src/apps/定期申込予約/customize-manifest.json
  5. +62
    -0
      src/apps/定期申込予約/index.tsx
  6. +8
    -0
      src/apps/定期申込予約/初回入金.ts
  7. +251
    -0
      src/apps/定期申込予約/自動承認.ts
  8. +2
    -0
      src/common/appids.ts
  9. +54
    -0
      src/common/datetime.ts
  10. +11
    -2
      src/common/rest-api-client.ts
  11. +9
    -0
      src/config/kintone.ts
  12. +1
    -0
      src/exception/index.ts
  13. +47
    -0
      src/logic/契約状況同期.ts
  14. +14
    -2
      src/middleware/swal.ts
  15. +106
    -0
      src/rest-api/bulk.ts
  16. +53
    -0
      src/rest-api/query.ts
  17. +17
    -0
      src/rest-api/url.ts
  18. +35
    -0
      src/rest-api/自動承認グループ.ts
  19. +34
    -1
      src/rest-api/車室契約情報.ts
  20. +31
    -0
      src/rest-api/車室情報2.ts
  21. +64
    -22
      src/rest-api/顧客マスタ.ts
  22. +7
    -1
      src/types/index.ts
  23. +13
    -7
      src/types/入金予定結果.ts
  24. +10
    -1
      src/types/定期申込予約.ts
  25. +64
    -0
      src/types/定期駐車場プランマスタ.ts
  26. +39
    -0
      src/types/自動承認グループ.ts
  27. +10
    -5
      src/types/車室契約情報.ts
  28. +34
    -0
      src/types/車室情報2.ts
  29. +6
    -3
      src/types/顧客マスタ.ts
  30. +0
    -34
      webpack.config.js
  31. +5
    -0
      yarn.lock

+ 2
- 0
package.json Dosyayı Görüntüle

@@ -46,10 +46,12 @@
},
"dependencies": {
"@kintone/rest-api-client": "^2.0.17",
"@types/lodash": "^4.14.202",
"@types/sprintf": "^0.1.2",
"core-js": "^3.6.4",
"date-fns": "^2.30.0",
"kintone-ui-component": "^1.14.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sprintf": "^0.1.5",


+ 33
- 0
src/apps/入金予定結果/index.tsx Dosyayı Görüntüle

@@ -68,6 +68,16 @@ const getCallbackFillAmount = (record: 入金予定結果) => {
};
};

// クエリパラメータよりフィールドに値をセットする
const setData = (event: any, targets: string[]) => {
const param = new URLSearchParams(location.search.replace("?", ""));
targets.forEach((target) => {
const value = param.get(target);
if (value === null) return;
event.record[target].value = value;
event.record[target].lookup = true;
});
};
(() => {
console.info("script build at " + process.env.BUILD_TIME);

@@ -81,4 +91,27 @@ const getCallbackFillAmount = (record: 入金予定結果) => {
}
}
});

kintone.events.on("app.record.create.show", (event) => {
setData(event, [
入金予定結果フィールド名.車室契約情報レコード番号,
入金予定結果フィールド名.初回振り込み関連申込レコード番号,
]);
return event;
});

kintone.events.on("app.record.create.submit.success", (event) => {
const record = event.record as 入金予定結果;
// 初回請求確定時にはメールを送信する
if (
record[入金予定結果フィールド名.初回振り込み関連申込レコード番号].value
) {
sendEmail(EmailID.申込受付, {
season_ticket_contract_entry_record_no: Number(
record[入金予定結果フィールド名.初回振り込み関連申込レコード番号]
.value
),
});
}
});
})();

+ 2
- 1
src/apps/各種申請/index.tsx Dosyayı Görüntüle

@@ -44,6 +44,7 @@ const 口座変更申請承認 = async (record: 各種申請) => {
const customer = await get顧客マスタ({
顧客コード: Number(record.顧客コード.value),
});
if (customer === null) throw new Error("顧客不正");

// 顧客マスタへの反映
await client.record.updateRecord({
@@ -141,7 +142,7 @@ const 利用者情報変更申請承認 = async (record: 各種申請) => {
const customer = await get顧客マスタ({
顧客コード: Number(record.顧客コード.value),
});
if (customer === null) throw new Error("顧客不正");
await client.record.updateRecord({
app: AppID.顧客マスタ,
id: customer.$id.value,


+ 12
- 0
src/apps/定期申込予約/customize-manifest.json Dosyayı Görüntüle

@@ -0,0 +1,12 @@
{
"app": "",
"scope": "ALL",
"desktop": {
"js": ["dist/定期申込予約.js"],
"css":[]
},
"mobile": {
"js": [],
"css":[]
}
}

+ 62
- 0
src/apps/定期申込予約/index.tsx Dosyayı Görüntüle

@@ -0,0 +1,62 @@
import { AppID } from "@/common/appids";
import { setHeaderButton } from "@/common/header-button";
import { CancelError } from "@/exception";
import {
ShowConfirmDialog,
SuccessDialog,
WarningDialog,
} from "@/middleware/swal";
import { getCreateUrl } from "@/rest-api/url";
import { 入金予定結果フィールド名 } from "@/types/入金予定結果";
import { 定期申込予約 } from "@/types/定期申込予約";
import { 申込 } from "./自動承認";

(() => {
console.info("script build at " + process.env.BUILD_TIME);

kintone.events.on("app.record.detail.show", (event) => {
const record: 定期申込予約 = event.record;

if (!record.auto_confirm_status.value) {
setHeaderButton("自動承認", async () => {
const confirm = await ShowConfirmDialog({
text: "承認しますか",
});
if (!confirm.isConfirmed) return;

try {
const entry = new 申込(record);
await entry.初期化();
await entry.選定();

await SuccessDialog.fire();

await WarningDialog.fire({
timer: 2000,
timerProgressBar: true,
text: "初回請求データを作成してください",
});
const 契約 = entry.作成後契約取得();
if (!契約) throw new Error();

const param = new URLSearchParams({
[入金予定結果フィールド名.車室契約情報レコード番号]: 契約.$id.value,
[入金予定結果フィールド名.初回振り込み関連申込レコード番号]:
record.$id.value,
});
const url = getCreateUrl(AppID.入金予定結果, param);
console.log(url, param);
window.open(url, "_blank");

location.reload();
} catch (e) {
if (e instanceof CancelError) {
console.log("canceled");
} else {
throw e;
}
}
});
}
});
})();

+ 8
- 0
src/apps/定期申込予約/初回入金.ts Dosyayı Görüntüle

@@ -0,0 +1,8 @@
import { dateParse, now } from "@/common/datetime";
import { 車室契約情報 } from "@/types/車室契約情報";

export const 初回請求情報取得 = (車室契約情報: 車室契約情報) => {
const 利用開始日 = dateParse(車室契約情報.契約日.value);
const 今日 = now();
今日.setHours(0, 0, 0, 0);
};

+ 251
- 0
src/apps/定期申込予約/自動承認.ts Dosyayı Görüntüle

@@ -0,0 +1,251 @@
import { AppID } from "@/common/appids";
import { makeRecordData } from "@/common/rest-api-client";
import { 契約状況同期 } from "@/logic/契約状況同期";
import {
ConfirmDialog,
ShowConfirmDialog,
showLoadingDialog,
} from "@/middleware/swal";
import { EmailID, sendEmail } from "@/mypage/メール";
import { BulkRequest } from "@/rest-api/bulk";
import { getDetailUrl } from "@/rest-api/url";
import { get自動承認グループ } from "@/rest-api/自動承認グループ";
import { get車室契約情報, get車室契約情報一覧 } from "@/rest-api/車室契約情報";
import { get車室情報一覧 } from "@/rest-api/車室情報2";
import {
getNextSMBC番号,
getNext顧客コード,
get顧客マスタ,
} from "@/rest-api/顧客マスタ";
import {
定期申込予約,
定期申込予約フィールド名,
状態Dropdown,
自動承認ステータスDropdown,
} from "@/types/定期申込予約";
import { 自動承認グループ } from "@/types/自動承認グループ";
import { 車室契約情報, 車室契約情報フィールド名 } from "@/types/車室契約情報";
import { 車室情報2 } from "@/types/車室情報2";
import { 顧客マスタフィールド名 } from "@/types/顧客マスタ";
import { sprintf } from "sprintf";
import { CancelError } from "../../exception";
import { 入金予定結果フィールド名 } from "@/types/入金予定結果";
import { dateParse, now } from "@/common/datetime";

export class 申込 {
private 定期申込予約: 定期申込予約;
private 初期化済み: boolean = false;

private 顧客情報: {
顧客コード: number;
} | null = null;

private 車室一覧: 車室情報2[] = [];
private 契約一覧: 車室契約情報[] = [];
private 自動承認グループ: 自動承認グループ | null = null;

private 作成後契約: 車室契約情報 | null = null;

private requests: BulkRequest = new BulkRequest();

constructor(定期申込予約: 定期申込予約) {
this.定期申込予約 = 定期申込予約;
}

async 初期化() {
// 車室情報の取得
await this.契約対象車室取得();
// 対象のプランデータ取得
await this.契約情報取得();
// 自動承認データの取得
await this.自動承認グループ取得();

this.初期化済み = true;
return this;
}

async 選定() {
// 空き車室の特定
const target = this.選定情報取得();
// 顧客マスタの作成/取得
await this.顧客マスタ取得();
// 契約の作成
await this.契約情報作成(target);
// 申込データの更新
await this.申込情報完了();

// データ保存
await this.save();

await 契約状況同期(
this.定期申込予約.駐車場.value,
Number(target.車室番号.value)
);
}

選定情報取得() {
return this.対象車室取得();
}

作成後契約取得() {
return this.作成後契約;
}

private async 契約対象車室取得() {
if (!this.定期申込予約.駐車場.value) {
throw new Error("駐車場名の設定をしてください");
}

this.車室一覧 = await get車室情報一覧({
駐車場名: this.定期申込予約.駐車場.value,
});
}
private async 契約情報取得() {
if (!this.定期申込予約.駐車場.value) {
throw new Error("駐車場名の設定をしてください");
}
this.契約一覧 = await get車室契約情報一覧({
駐車場名: this.定期申込予約.駐車場.value,
契約中のみ: true,
});
}
private async 自動承認グループ取得() {
if (!this.定期申込予約.定期駐車場プラン.value) {
throw new Error("プラン名の設定をしてください");
}
this.自動承認グループ = await get自動承認グループ(
this.定期申込予約.定期駐車場プラン.value
);
}

private 対象車室取得() {
if (!this.初期化済み) {
throw new Error("実装エラー 未初期化");
}
const target = this.自動承認グループ?.対象車室番号.value
.filter(({ value: 定義 }) => {
// 自動承認車室でない場合は対象外とする
if (定義.自動承認スキップ.value.length !== 0) return false;
// 契約中の車室は対象外とする
const 同一車室の契約中情報: 車室契約情報 | undefined =
this.契約一覧.find((契約) => {
return 契約.車室番号.value === 定義.車室番号.value;
});
if (!!同一車室の契約中情報) return false;

return true;
})
.sort((a, b) => {
return Number(a.value.割当順.value) < Number(b.value.割当順.value)
? -1
: 1;
})
.find(() => true);

if (!target) {
throw new Error("空き車室がありません");
}

const 車室番号 = Number(target.value.車室番号.value);
const 車室 = this.車室一覧.find((room) => {
return Number(room.車室番号.value) === 車室番号;
});
if (!車室) {
throw new Error("車室がありません");
}

return 車室;
}

private async 顧客マスタ取得() {
const customer = await get顧客マスタ({
メールアドレス: this.定期申込予約.メールアドレス.value,
必須: false,
});
if (customer) {
const confirm = await ShowConfirmDialog({
html: sprintf(
"<p>既存顧客<a href='%s' target='_blank'>[%s]</a>様へ契約を追加しますか</p>",
getDetailUrl(AppID.顧客マスタ, customer.$id.value),
customer.CustomerName.value
),
});

if (!confirm.isConfirmed) throw new CancelError();

this.顧客情報 = {
顧客コード: Number(customer[顧客マスタフィールド名.顧客コード].value),
};
} else {
this.顧客マスタ作成();
}
}

private async 顧客マスタ作成() {
const F = 顧客マスタフィールド名;
const 顧客コード = await getNext顧客コード();

this.requests.create({
app: AppID.顧客マスタ,
record: makeRecordData({
[F.顧客コード]: String(顧客コード),
[F.顧客名]: this.定期申込予約.氏名.value,
[F.顧客名カナ]: this.定期申込予約.フリガナ.value,
[F.電話番号]: this.定期申込予約.電話番号.value,
[F.メールアドレス]: this.定期申込予約.メールアドレス.value,
[F.SMBC契約番号]: String(await getNextSMBC番号()),
[F.支払方法]: "その他",
}),
});

this.顧客情報 = {
顧客コード,
};
}

private async 契約情報作成(対象車室: 車室情報2) {
if (!this.顧客情報) throw new Error("顧客情報不正");
if (!this.定期申込予約) throw new Error("定期申込予約不正");
const F = 車室契約情報フィールド名;

this.requests.create(
{
app: AppID.車室契約情報,
record: makeRecordData({
[F.顧客コード]: String(this.顧客情報.顧客コード),
[F.契約日]: this.get契約開始日(),
[F.車両番号]: this.定期申込予約.車両番号.value,
[F.車室番号]: 対象車室.車室番号.value,
[F.プラン名]: this.定期申込予約.定期駐車場プラン.value,
}),
},
async ({ id }) => {
if (!id) throw new Error();
this.作成後契約 = await get車室契約情報(Number(id));
}
);
}

private async 申込情報完了() {
const F = 定期申込予約フィールド名;
this.requests.update({
app: AppID.定期申込予約,
id: this.定期申込予約.$id.value,
record: makeRecordData({
[F.状態]: 状態Dropdown.承認_自動承認,
[F.自動承認ステータス]: 自動承認ステータスDropdown.承認済,
}),
});
}

private get契約開始日() {
const 利用開始希望日 = this.定期申込予約.利用開始希望日.value;
if (!利用開始希望日) throw new Error("利用開始希望日不正");
return 利用開始希望日;
}

private async save() {
this.requests.debug();
await this.requests.save();
}
}

+ 2
- 0
src/common/appids.ts Dosyayı Görüntüle

@@ -10,5 +10,7 @@ export const AppID = {
入金予定結果: APP_ID.入金予定結果,
各種申請: APP_ID.各種申請,
車室契約情報: APP_ID.車室契約情報,
車室情報2: APP_ID.車室情報2,
自動承認グループ: APP_ID.自動承認グループ,
} as const;
export type AppID = (typeof AppID)[keyof typeof AppID];

+ 54
- 0
src/common/datetime.ts Dosyayı Görüntüle

@@ -0,0 +1,54 @@
import { format, isValid, parse, parseISO } from "date-fns";

export const DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
export const DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss";
export const DEFAULT_DATE_TIME_FORMAT_ANOTHER1 = "yyyy-MM-dd HH:mm:ss";
export const DEFAULT_YYYYMM_FORMAT = "yyyyMM";

type Input = Date | string | null | undefined;

export const formatDateStr = (source: Input) => {
return formatToStr(source, DEFAULT_DATE_FORMAT);
};

export const formatDateTimeStr = (source: Date | string | null | undefined) => {
return formatToStr(source, DEFAULT_DATE_TIME_FORMAT);
};
export const formatYYYYMMStr = (source: Date | string | null | undefined) => {
return formatToStr(source, DEFAULT_YYYYMM_FORMAT);
};

const formatToStr = (source: Input, formatStr: string) => {
if (source === null || source === undefined) return "";
if (source instanceof Date) return format(source, formatStr);
return format(parseISO(source), formatStr);
};

export const now = () => {
return new Date();
};
export const nowStr = (): string => {
return formatDateTimeStr(now());
};

export const dateParse = (source: Input): Date | null => {
return parseFromFormat(source, DEFAULT_DATE_FORMAT);
};

export const dateTimeParse = (source: Input): Date | null => {
return (
parseFromFormat(source, DEFAULT_DATE_TIME_FORMAT) ??
parseFromFormat(source, DEFAULT_DATE_TIME_FORMAT_ANOTHER1)
);
};

const parseFromFormat = (source: Input, format: string): Date | null => {
if (source === null || source === undefined) return null;
if (source instanceof Date) return source;

const ret = parse(source, format, new Date());
if (isValid(ret)) {
return ret;
}
return null;
};

+ 11
- 2
src/common/rest-api-client.ts Dosyayı Görüntüle

@@ -1,4 +1,13 @@
export const setFieldValue = (fieldCode: string, value: string | object) => {
export type Field = {
[fieldCode: string]: {
value: unknown;
};
};

export const setFieldValue = (
fieldCode: string,
value: string | object
): Field => {
return {
[fieldCode]: {
value,
@@ -8,7 +17,7 @@ export const setFieldValue = (fieldCode: string, value: string | object) => {

export const makeRecordData = (data: {
[fieldCode: string]: string | object;
}) => {
}): Field => {
let ret = {};
Object.keys(data).forEach((fieldCode) => {
ret = {


+ 9
- 0
src/config/kintone.ts Dosyayı Görüntüle

@@ -7,6 +7,9 @@ export type KintoneConfig = {
入金予定結果: number;
各種申請: number;
車室契約情報: number;
車室情報2: number;
自動承認グループ: number;
定期駐車場プランマスタ: number;
};
};

@@ -21,6 +24,9 @@ export const kintoneConfig = (): KintoneConfig => {
入金予定結果: 272,
各種申請: 294,
車室契約情報: 253,
車室情報2: 279,
自動承認グループ: 286,
定期駐車場プランマスタ: 257,
},
};
} else {
@@ -34,6 +40,9 @@ export const kintoneConfig = (): KintoneConfig => {
入金予定結果: 272,
各種申請: 294,
車室契約情報: 253,
車室情報2: 279,
自動承認グループ: 286,
定期駐車場プランマスタ: 257,
},
};
}


+ 1
- 0
src/exception/index.ts Dosyayı Görüntüle

@@ -0,0 +1 @@
export class CancelError extends Error {}

+ 47
- 0
src/logic/契約状況同期.ts Dosyayı Görüntüle

@@ -0,0 +1,47 @@
import { AppID } from "@/common/appids";
import { makeRecordData } from "@/common/rest-api-client";
import { BulkRequest } from "@/rest-api/bulk";
import { get車室契約情報一覧 } from "@/rest-api/車室契約情報";
import { get車室情報一覧 } from "@/rest-api/車室情報2";
import { 状態Dropdown, 車室情報2フィールド名 } from "@/types/車室情報2";

export const 契約状況同期 = async (駐車場名: string, 車室番号?: number) => {
await 車室情報契約項目同期(駐車場名, 車室番号);
};

const 車室情報契約項目同期 = async (駐車場名: string, 車室番号?: number) => {
const requests = new BulkRequest();

const 契約一覧 = await get車室契約情報一覧({
駐車場名,
車室番号,
契約中のみ: true,
});

const 車室一覧 = await get車室情報一覧({
駐車場名,
車室番号,
});

console.log({ 契約一覧, 車室一覧 });

車室一覧.forEach((車室) => {
const 契約 = 契約一覧.find((c) => c.車室番号.value === 車室.車室番号.value);

requests.update({
app: AppID.車室情報2,
id: 車室.$id.value,
record: makeRecordData({
[車室情報2フィールド名.状態]: !!契約
? 状態Dropdown.契約中
: 状態Dropdown.空き,
[車室情報2フィールド名.契約レコード番号]: 契約?.$id.value ?? "",
}),
});
});
// 保存
if (!requests.isSaved()) {
requests.debug();
await requests.save();
}
};

+ 14
- 2
src/middleware/swal.ts Dosyayı Görüntüle

@@ -1,5 +1,5 @@
import { delay } from "@/common/timer";
import Swal from "sweetalert2";
import Swal, { SweetAlertOptions } from "sweetalert2";

export const SuccessDialog = Swal.mixin({
icon: "success",
@@ -12,6 +12,9 @@ export const ErrorDialog = Swal.mixin({
icon: "error",
title: "エラーが発生しました",
});
export const WarningDialog = Swal.mixin({
icon: "warning",
});

export const ConfirmDialog = Swal.mixin({
icon: "info",
@@ -20,6 +23,15 @@ export const ConfirmDialog = Swal.mixin({
cancelButtonText: "キャンセル",
});

export const ShowConfirmDialog = (param: SweetAlertOptions) => {
return ConfirmDialog.fire(param).then((result) => {
if (result.isConfirmed) {
showLoadingDialog();
}
return result;
});
};

export const LoadingDialog = Swal.mixin({
title: "実行中...",
text: "画面を閉じないでください",
@@ -35,7 +47,7 @@ export const LoadingDialog = Swal.mixin({
* 必ずawaitを入れて同期処理にすること
* @param timer
*/
export const showLoadingDialog = async (timer: number = 5000) => {
export const showLoadingDialog = async (timer: number = 10 * 1000) => {
LoadingDialog.fire({
timer,
}).then((result) => {


+ 106
- 0
src/rest-api/bulk.ts Dosyayı Görüntüle

@@ -0,0 +1,106 @@
import { AppID } from "@/common/appids";
import { Field } from "@/common/rest-api-client";
import { delay } from "@/common/timer";
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import { chunk } from "lodash";

const UPDATE_PARAM = {
method: "PUT",
api: "/k/v1/record.json",
} as const;
const CREATE_PARAM = {
method: "POST",
api: "/k/v1/record.json",
} as const;

type Payload = {
app: AppID;
id?: string | number;
query?: string;
record: Field;
};

type BulkRequestResponse = { id?: string; revision?: string };

export type RequestParam = {
method: "POST" | "PUT";
api: string;
payload: Payload;
};

type Callback = (result: BulkRequestResponse) => Promise<void>;

export class BulkRequest {
private client: KintoneRestAPIClient;

private requests: Array<RequestParam> = [];

private callbacks: {
[index: number]: Callback;
} = {};

constructor() {
this.client = new KintoneRestAPIClient({
baseUrl: location.origin,
});
}

update(payload: Payload, callback?: Callback) {
const request: RequestParam = {
...UPDATE_PARAM,
payload,
};
this.requests.push(request);

if (callback) {
this.callbacks[this.requests.length - 1] = callback;
}
}

create(payload: Payload, callback?: Callback) {
const request: RequestParam = {
...CREATE_PARAM,
payload,
};
this.requests.push(request);

if (callback) {
this.callbacks[this.requests.length - 1] = callback;
}
}

isSaved() {
return this.requests.length === 0;
}

async save() {
let results: BulkRequestResponse[] = [];
for (const requests of chunk(this.requests, 20)) {
const res = await this.client.bulkRequest({ requests });

results = [...results, ...res.results];
}
await this.execCallback(results);
this.clear();

return results;
}

private async execCallback(results: BulkRequestResponse[]) {
for (const [index, result] of results.entries()) {
if (index in this.callbacks) {
console.log("exec callback", this.requests.at(index));
const callback = this.callbacks[index];
await callback(result);
}
}
}

clear() {
this.requests = [];
}

debug() {
console.log("BulkRequest debug", this, this.requests);
}
}

+ 53
- 0
src/rest-api/query.ts Dosyayı Görüntüle

@@ -0,0 +1,53 @@
import { sprintf } from "sprintf";

type ORDER = "asc" | "desc";
type OrderCondition = {
カラム: string;
並び: ORDER;
};
export class QueryBuilder {
private whereConditions: string[] = [];
private order: OrderCondition[] = [];
private _limit: number | null = null;

where(カラム: string, 条件: string | number, 比較文字列: string = "=") {
if (typeof 条件 === "string") {
this.whereConditions.push(
sprintf('%s %s "%s"', カラム, 比較文字列, 条件)
);
} else {
this.whereConditions.push(sprintf("%s %s %d", カラム, 比較文字列, 条件));
}
return this;
}

orderBy(カラム: string, 並び: ORDER = "asc") {
this.order.push({
カラム,
並び,
});

return this;
}

limit(limit: number) {
this._limit = limit;
return this;
}

build(): string {
let query = "";
query += this.whereConditions.join(" and ") + " ";

this.order.forEach(({ カラム, 並び }) => {
query += sprintf(" order by %s %s ", カラム, 並び);
});

if (this._limit !== null) {
query += sprintf(" limit %d ", this._limit);
}

console.log("QueryBuilder", query);
return query;
}
}

+ 17
- 0
src/rest-api/url.ts Dosyayı Görüntüle

@@ -0,0 +1,17 @@
import { AppID } from "@/common/appids";
import { sprintf } from "sprintf";

export const getDetailUrl = (app: AppID, レコード番号: string | number) => {
if (typeof レコード番号 === "string") {
レコード番号 = Number(レコード番号);
}
return sprintf("%s/k/%d/show#record=%d", location.origin, app, レコード番号);
};
export const getCreateUrl = (app: AppID, param?: URLSearchParams) => {
return sprintf(
"%s/k/%d/edit?%s",
location.origin,
app,
!!param ? param.toString() : ""
);
};

+ 35
- 0
src/rest-api/自動承認グループ.ts Dosyayı Görüntüle

@@ -0,0 +1,35 @@
import { AppID } from "@/common/appids";
import {
自動承認グループ,
自動承認グループフィールド名,
} from "@/types/自動承認グループ";
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import { sprintf } from "sprintf";

const client = new KintoneRestAPIClient();

export const get自動承認グループ = async (
プラン名: string
): Promise<自動承認グループ> => {
const query = sprintf(
sprintf('%s = "%s"', 自動承認グループフィールド名.プラン名, プラン名)
);

const { records } = await client.record.getRecords<自動承認グループ>({
app: AppID.自動承認グループ,
query,
});

if (records.length !== 1) {
throw new Error(
sprintf(
"自動承認グループ取得数数エラー expect %d, actual %d query:%s",
1,
records.length,
query
)
);
}

return records[0];
};

+ 34
- 1
src/rest-api/車室契約情報.ts Dosyayı Görüntüle

@@ -1,6 +1,8 @@
import { AppID } from "@/common/appids";
import { 車室契約情報 } from "@/types/車室契約情報";
import { formatDateStr, now } from "@/common/datetime";
import { 車室契約情報, 車室契約情報フィールド名 } from "@/types/車室契約情報";
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import { QueryBuilder } from "./query";

const client = new KintoneRestAPIClient();

@@ -14,3 +16,34 @@ export const get車室契約情報 = async (

return record;
};

type get車室契約情報一覧オプション = {
駐車場名?: string;
車室番号?: number | string;
契約中のみ?: boolean;
};
export const get車室契約情報一覧 = async ({
駐車場名,
車室番号,
契約中のみ,
}: get車室契約情報一覧オプション): Promise<車室契約情報[]> => {
const builder = new QueryBuilder();

if (駐車場名) {
builder.where(車室契約情報フィールド名.定期駐車場名, 駐車場名);
}
if (車室番号) {
builder.where(車室契約情報フィールド名.車室番号, 車室番号);
}
if (契約中のみ) {
const todayStr = formatDateStr(now());
builder.where(車室契約情報フィールド名.解約日, todayStr, ">");
}

const record = await client.record.getAllRecords<車室契約情報>({
app: AppID.車室契約情報,
condition: builder.build(),
});

return record;
};

+ 31
- 0
src/rest-api/車室情報2.ts Dosyayı Görüntüle

@@ -0,0 +1,31 @@
import { AppID } from "@/common/appids";
import { 車室情報2, 車室情報2フィールド名 } from "@/types/車室情報2";
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import { sprintf } from "sprintf";
import { QueryBuilder } from "./query";

const client = new KintoneRestAPIClient();

type get車室情報一覧オプション = {
駐車場名?: string;
車室番号?: number | string;
};
export const get車室情報一覧 = async ({
駐車場名,
車室番号,
}: get車室情報一覧オプション): Promise<車室情報2[]> => {
const builder = new QueryBuilder();
if (駐車場名) {
builder.where(車室情報2フィールド名.駐車場名, 駐車場名);
}
if (車室番号) {
builder.where(車室情報2フィールド名.車室番号, 車室番号);
}

const { records } = await client.record.getRecords<車室情報2>({
app: AppID.車室情報2,
query: builder.build(),
});

return records;
};

+ 64
- 22
src/rest-api/顧客マスタ.ts Dosyayı Görüntüle

@@ -1,47 +1,89 @@
import { AppID } from "@/common/appids";
import { フィールド名 } from "@/types";
import { 顧客マスタ, 顧客マスタフィールド名 } from "@/types/顧客マスタ";
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import { sprintf } from "sprintf";
import { QueryBuilder } from "./query";

const client = new KintoneRestAPIClient();

type Props = {
レコード番号?: number;
顧客コード?: number;
メールアドレス?: string;
電話番号?: string;
必須?: boolean;
};
export const get顧客マスタ = async ({
レコード番号,
顧客コード,
}: Props): Promise<顧客マスタ> => {
if (!!レコード番号) {
const { record } = await client.record.getRecord<顧客マスタ>({
app: AppID.顧客マスタ,
id: レコード番号,
});
メールアドレス,
電話番号,
必須 = true,
}: Props): Promise<顧客マスタ | null> => {
const builder = new QueryBuilder();

return record;
if (!!レコード番号) {
builder.where(フィールド名.レコード番号, レコード番号);
}
if (!!顧客コード) {
const query = sprintf(
sprintf("%s = %d", 顧客マスタフィールド名.顧客コード, 顧客コード)
);
builder.where(顧客マスタフィールド名.顧客コード, 顧客コード);
}
if (!!メールアドレス) {
builder.where(顧客マスタフィールド名.メールアドレス, メールアドレス);
}
if (!!電話番号) {
builder.where(顧客マスタフィールド名.電話番号, 電話番号);
}

const { records } = await client.record.getRecords<顧客マスタ>({
app: AppID.顧客マスタ,
query,
});
const { records } = await client.record.getRecords<顧客マスタ>({
app: AppID.顧客マスタ,
query: builder.build(),
});

if (records.length !== 1) {
if (records.length === 0) {
if (必須) {
throw new Error(
sprintf(
"顧客マスタ取得数エラー expect %d, actual %d",
1,
records.length
)
sprintf("顧客マスタ取得数エラー expect %d, actual %d", 1, 0)
);
}
return records[0];
return null;
}

if (records.length !== 1) {
throw new Error(
sprintf("顧客マスタ取得数エラー expect %d, actual %d", 1, records.length)
);
}
return records[0];
};

throw new Error("get顧客マスタ 引数エラー");
export const getNext顧客コード = async () => {
const query = sprintf(
"order by %s desc limit 1",
顧客マスタフィールド名.顧客コード
);
const { records } = await client.record.getRecords<顧客マスタ>({
app: AppID.顧客マスタ,
query,
});
if (records.length === 0) {
return 1;
}
return Number(records[0][顧客マスタフィールド名.顧客コード].value) + 1;
};

export const getNextSMBC番号 = async () => {
const query = sprintf(
"order by %s desc limit 1",
顧客マスタフィールド名.SMBC契約番号
);
const { records } = await client.record.getRecords<顧客マスタ>({
app: AppID.顧客マスタ,
query,
});
if (records.length === 0) {
return 1;
}
return Number(records[0][顧客マスタフィールド名.SMBC契約番号].value) + 1;
};

+ 7
- 1
src/types/index.ts Dosyayı Görüntüle

@@ -1,7 +1,13 @@
import { KintoneRecordField } from "@kintone/rest-api-client";

export const フィールド名 = {
レコード番号: "$id",
} as const;

const F = フィールド名;

export type AppRecord = {
$id: KintoneRecordField.ID;
[F.レコード番号]: KintoneRecordField.ID;
$revision: KintoneRecordField.Revision;
作成者: KintoneRecordField.Creator;
レコード番号: KintoneRecordField.RecordNumber;


+ 13
- 7
src/types/入金予定結果.ts Dosyayı Görüntüle

@@ -2,9 +2,15 @@ import { KintoneRecordField } from "@kintone/rest-api-client";
import { AppRecord } from ".";

const F = {
車室契約情報レコード番号: "contract_record_number",
支払対象_利用_年: "target_year",
支払対象_利用_月: "target_month",
支払対象_利用_月間数: "target_term_month",
支払予定日: "payment_plan_date",
支払予定金額: "payment_plan_amount",
入金予定コード: "payment_plan_code",
入金日: "appropriation_date",
入金額: "appropriation_amount",
支払予定金額: "payment_plan_amount",
残金: "remaining_amount",
初回振り込み関連申込レコード番号: "first_payment_entry_record_no",
} as const;
@@ -23,18 +29,18 @@ export type 支払種別Dropdown =
export const 入金予定結果フィールド名 = F;

export type 入金予定結果 = AppRecord & {
contract_record_number: KintoneRecordField.Number;
payment_plan_date: KintoneRecordField.Date;
payment_plan_code: KintoneRecordField.SingleLineText;
[F.車室契約情報レコード番号]: KintoneRecordField.Number;
[F.支払予定日]: KintoneRecordField.Date;
[F.入金予定コード]: KintoneRecordField.SingleLineText;
[F.残金]: KintoneRecordField.Number;
[F.入金日]: KintoneRecordField.Date;
target_year: KintoneRecordField.SingleLineText;
target_month: KintoneRecordField.SingleLineText;
[F.支払対象_利用_年]: KintoneRecordField.SingleLineText;
[F.支払対象_利用_月]: KintoneRecordField.SingleLineText;
[F.入金額]: KintoneRecordField.Number;
payment_type: KintoneRecordField.Dropdown;
customer_name_kana: KintoneRecordField.SingleLineText;
[F.支払予定金額]: KintoneRecordField.Number;
target_term_month: KintoneRecordField.SingleLineText;
[F.支払対象_利用_月間数]: KintoneRecordField.SingleLineText;
customer_name: KintoneRecordField.SingleLineText;
parking_name: KintoneRecordField.SingleLineText;
customer_code: KintoneRecordField.Number;


+ 10
- 1
src/types/定期申込予約.ts Dosyayı Görüntüle

@@ -3,6 +3,8 @@ import { AppRecord } from ".";

const F = {
状態: "status",
自動承認ステータス: "auto_confirm_status",
利用開始希望日: "利用開始希望日",
} as const;

export const 状態Dropdown = {
@@ -18,6 +20,13 @@ export const 状態Dropdown = {
} as const;
export type 状態Dropdown = (typeof 状態Dropdown)[keyof typeof 状態Dropdown];

export const 自動承認ステータスDropdown = {
NONE: "",
承認済: "承認済",
} as const;
export type 自動承認ステータスDropdown =
(typeof 自動承認ステータスDropdown)[keyof typeof 自動承認ステータスDropdown];

export const 定期申込予約フィールド名 = F;

export type 定期申込予約 = AppRecord & {
@@ -54,6 +63,6 @@ export type 定期申込予約 = AppRecord & {
ParkingNaviプラン: KintoneRecordField.SingleLineText;
日割り分_月: KintoneRecordField.Number;
支払方法: KintoneRecordField.Dropdown;
auto_confirm_status: KintoneRecordField.SingleLineText;
[F.自動承認ステータス]: KintoneRecordField.SingleLineText;
[F.状態]: KintoneRecordField.Dropdown;
};

+ 64
- 0
src/types/定期駐車場プランマスタ.ts Dosyayı Görüntüle

@@ -0,0 +1,64 @@
import { KintoneRecordField } from "@kintone/rest-api-client";
import { AppRecord } from ".";

const F = {
プラン名: "key",
} as const;

// export const 支払種別Dropdown = {
// 定期料金: "定期料金",
// 保証金: "保証金",
// 証明書郵送代: "証明書郵送代",
// 事務手数料: "事務手数料",
// 延滞金: "延滞金",
// 余剰金: "余剰金",
// } as const;
// export type 支払種別Dropdown =
// (typeof 支払種別Dropdown)[keyof typeof 支払種別Dropdown];

export const 定期駐車場プランマスタフィールド名 = F;

export type 対象車室番号行データ = {
割当順: KintoneRecordField.Number;
車室番号: KintoneRecordField.SingleLineText;
利用中: KintoneRecordField.CheckBox;
自動承認スキップ: KintoneRecordField.CheckBox;
};
export type 定期駐車場プランマスタ = AppRecord & {
備考: KintoneRecordField.MultiLineText;
状況: KintoneRecordField.RadioButton;
支払パターン: KintoneRecordField.Dropdown;
保証金: KintoneRecordField.Number;
収容台数: KintoneRecordField.Number;
住所_駐車場マスタ: KintoneRecordField.SingleLineText;
パスカード保証金: KintoneRecordField.Number;
定期駐車場名: KintoneRecordField.SingleLineText;
駐車場名: KintoneRecordField.SingleLineText;
key: KintoneRecordField.SingleLineText;
その他収入: KintoneRecordField.Number;
住所_手入力: KintoneRecordField.SingleLineText;
担当: KintoneRecordField.SingleLineText;
内税: KintoneRecordField.Number;
定期_駐車場名: KintoneRecordField.SingleLineText;
契約金額: KintoneRecordField.Number;
利用者へ表示するプラン名: KintoneRecordField.SingleLineText;
税率: KintoneRecordField.Number;
種別: KintoneRecordField.Dropdown;
住所_使用: KintoneRecordField.SingleLineText;
駐車場備考: KintoneRecordField.Dropdown;
表示順: KintoneRecordField.Number;
プラン: KintoneRecordField.Dropdown;
チェーンゲート保証金: KintoneRecordField.Number;
年額: KintoneRecordField.Calc;
保証金合計額: KintoneRecordField.Calc;
送付物: KintoneRecordField.CheckBox;
プラン変更申請にて変更可能なプラン一覧: {
type: "SUBTABLE";
value: {
id: string;
value: {
プラン変更申請にて変更可能なプラン一覧_定期_駐車場名_月額金額_駐車場備考_プラン_種別_支払パターン: KintoneRecordField.SingleLineText;
};
}[];
};
};

+ 39
- 0
src/types/自動承認グループ.ts Dosyayı Görüntüle

@@ -0,0 +1,39 @@
import { KintoneRecordField } from "@kintone/rest-api-client";
import { AppRecord } from ".";

const F = {
プラン名: "定期駐車場プラン",
自動承認グループ名: "自動承認グループ名",
対象車室番号: "対象車室番号",
} as const;

// export const 支払種別Dropdown = {
// 定期料金: "定期料金",
// 保証金: "保証金",
// 証明書郵送代: "証明書郵送代",
// 事務手数料: "事務手数料",
// 延滞金: "延滞金",
// 余剰金: "余剰金",
// } as const;
// export type 支払種別Dropdown =
// (typeof 支払種別Dropdown)[keyof typeof 支払種別Dropdown];

export const 自動承認グループフィールド名 = F;

export type 対象車室番号行データ = {
割当順: KintoneRecordField.Number;
車室番号: KintoneRecordField.SingleLineText;
利用中: KintoneRecordField.CheckBox;
自動承認スキップ: KintoneRecordField.CheckBox;
};
export type 自動承認グループ = AppRecord & {
[F.プラン名]: KintoneRecordField.SingleLineText;
[F.自動承認グループ名]: KintoneRecordField.SingleLineText;
[F.対象車室番号]: {
type: "SUBTABLE";
value: {
id: string;
value: 対象車室番号行データ;
}[];
};
};

+ 10
- 5
src/types/車室契約情報.ts Dosyayı Görüntüle

@@ -2,9 +2,14 @@ import { KintoneRecordField } from "@kintone/rest-api-client";
import { AppRecord } from ".";

const F = {
契約日: "契約日",
解約日: "解約日",
車両番号: "車両番号",
防犯登録番号: "防犯登録番号",
定期駐車場名: "定期駐車場名",
顧客コード: "顧客コード",
車室番号: "車室番号",
プラン名: "契約駐車場_0",
} as const;

// export const 支払種別Dropdown = {
@@ -22,14 +27,14 @@ export const 車室契約情報フィールド名 = F;

export type 車室契約情報 = AppRecord & {
定期券番号_0: KintoneRecordField.SingleLineText;
契約駐車場_0: KintoneRecordField.SingleLineText;
[F.プラン名]: KintoneRecordField.SingleLineText;
[F.防犯登録番号]: KintoneRecordField.SingleLineText;
定額_3月: KintoneRecordField.Number;
入金日: KintoneRecordField.Date;
定期駐車場名: KintoneRecordField.SingleLineText;
[F.定期駐車場名]: KintoneRecordField.SingleLineText;
担当: KintoneRecordField.SingleLineText;
車室番号: KintoneRecordField.SingleLineText;
契約日: KintoneRecordField.Date;
[F.車室番号]: KintoneRecordField.SingleLineText;
[F.契約日]: KintoneRecordField.Date;
定額_5月: KintoneRecordField.Number;
契約金額: KintoneRecordField.Number;
障がい者手帳有効期限: KintoneRecordField.Date;
@@ -37,7 +42,7 @@ export type 車室契約情報 = AppRecord & {
定額_4月: KintoneRecordField.Number;
駐車場備考: KintoneRecordField.SingleLineText;
障がい者手帳画像更新日時: KintoneRecordField.DateTime;
顧客コード: KintoneRecordField.Number;
[F.顧客コード]: KintoneRecordField.Number;
解約減額: KintoneRecordField.Number;
顧客名カナ_マスタ: KintoneRecordField.SingleLineText;
定額_6月: KintoneRecordField.Number;


+ 34
- 0
src/types/車室情報2.ts Dosyayı Görüntüle

@@ -0,0 +1,34 @@
import { KintoneRecordField } from "@kintone/rest-api-client";
import { AppRecord } from ".";

const F = {
駐車場名: "定期駐車場",
車室番号: "車室番号",
状態: "状態",
契約レコード番号: "契約情報",
} as const;

export const 状態Dropdown = {
空き: "空き",
契約中: "契約中",
} as const;
export type 状態Dropdown = (typeof 状態Dropdown)[keyof typeof 状態Dropdown];

export const 車室情報2フィールド名 = F;

export type 車室情報2 = AppRecord & {
契約開始日: KintoneRecordField.Date;
[F.契約レコード番号]: KintoneRecordField.Number;
メモ: KintoneRecordField.MultiLineText;
[F.車室番号]: KintoneRecordField.SingleLineText;
定期駐車場プラン: KintoneRecordField.SingleLineText;
車室タイプ: KintoneRecordField.Dropdown;
顧客名: KintoneRecordField.SingleLineText;
顧客名カナ: KintoneRecordField.SingleLineText;
定期券番号: KintoneRecordField.SingleLineText;
[F.状態]: KintoneRecordField.Dropdown;
契約終了日: KintoneRecordField.Date;
定期駐車場: KintoneRecordField.SingleLineText;
契約駐車場プラン: KintoneRecordField.SingleLineText;
自動承認: KintoneRecordField.CheckBox;
};

+ 6
- 3
src/types/顧客マスタ.ts Dosyayı Görüntüle

@@ -16,6 +16,9 @@ const F = {
契約者_郵便番号: "契約者_郵便番号",
住所: "住所",
電話番号: "電話番号",
メールアドレス: "メールアドレス",
SMBC契約番号: "ContractNo",
支払方法: "支払方法",
} as const;

// export const 支払種別Dropdown = {
@@ -40,7 +43,7 @@ export type 顧客マスタ = AppRecord & {
ChargedBranchCode: KintoneRecordField.SingleLineText;
ChargedBranchName: KintoneRecordField.SingleLineText;
[F.引落預金種別]: KintoneRecordField.Number;
ContractNo: KintoneRecordField.SingleLineText;
[F.SMBC契約番号]: KintoneRecordField.SingleLineText;
[F.顧客コード]: KintoneRecordField.Number;
[F.顧客名]: KintoneRecordField.SingleLineText;
SMBC口座名義_1: KintoneRecordField.SingleLineText;
@@ -50,7 +53,7 @@ export type 顧客マスタ = AppRecord & {
[F.ゆうちょ口座番号]: KintoneRecordField.Number;
クレジット入金額: KintoneRecordField.Number;
コンビニ払入金額: KintoneRecordField.Number;
メールアドレス: KintoneRecordField.Link;
[F.メールアドレス]: KintoneRecordField.Link;
ゆうちょ口座名義_0: KintoneRecordField.SingleLineText;
ゆうちょ口座名義_1: KintoneRecordField.SingleLineText;
ゆうちょ口座名義: KintoneRecordField.SingleLineText;
@@ -60,7 +63,7 @@ export type 顧客マスタ = AppRecord & {
[F.顧客名カナ]: KintoneRecordField.SingleLineText;
口座振替依頼書: KintoneRecordField.File;
口座登録催促予定日時: KintoneRecordField.DateTime;
支払方法: KintoneRecordField.SingleLineText;
[F.支払方法]: KintoneRecordField.SingleLineText;
[F.住所]: KintoneRecordField.SingleLineText;
送付方法: KintoneRecordField.Dropdown;
台数: KintoneRecordField.Number;


+ 0
- 34
webpack.config.js Dosyayı Görüntüle

@@ -70,40 +70,6 @@ module.exports = {
// new Dotenv({ systemvars: true }),
new ForkTsCheckerWebpackPlugin(),
// new BundleAnalyzerPlugin(),
{
// watchモードのとき再ビルドされたものをアップロードする
apply: (compiler) => {
compiler.hooks.afterEmit.tapPromise(
'upload javascript files',
(compilation) => {
if (!compiler.options.watch) return Promise.resolve();

const emittedFiles = Object.keys(compilation.assets)
.filter((file) => {
const source = compilation.assets[file];
return source.emitted && source.existsAt;
})
.map((file) => file.replace('.js', ''));

const processes = glob
.sync(`@(${emittedFiles.join('|')})/customize-manifest.json`, {
cwd: basePath,
})
.map((file) => {
console.log('\nuploading... ', file);
return exec(
`yarn upload ${path.resolve(basePath, file)}`,
(err, stdout, stderr) => {
if (stdout) process.stdout.write(stdout);
if (stderr) process.stderr.write(stderr);
}
);
});
return Promise.all(processes);
}
);
},
},
new webpack.DefinePlugin({
'process.env.BUILD_TIME': JSON.stringify(format(new Date(), 'yyyy-MM-dd HH:mm:ss')),
}),


+ 5
- 0
yarn.lock Dosyayı Görüntüle

@@ -1264,6 +1264,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==

"@types/lodash@^4.14.202":
version "4.14.202"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8"
integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==

"@types/mime@*":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45"


Yükleniyor…
İptal
Kaydet