タスクチュートリアルを一新しました。
2022年12月31日土曜日
2022年12月30日金曜日
2022年12月29日木曜日
2022年12月28日水曜日
1フェーズ勤務表
フェーズが1しかないプロジェクトは、シフト勤務表としても実現が可能です。
しかし、シフト数が25を超えるような状態は、求解速度の観点から好ましくありません。
例えば、工場の場合、多くの工程があります。一人のスタッフに着目した場合、全ての工程を担当できることはなく、担当可能な工程は限られていると思われます。そのような性質を持つ場合は、シフトではなくタスクとして実装した方が、求解速度は上がります。
(シフトは、内部的には、全てのスタッフが全てのシフト状態を保持します。シフト数が大きくなると、指数関数的にメモリが大きくなりキャッシュヒット率が低下することが懸念されます。)
スキル定義の例
以下の例は、工場の担当可能な工程作業を表しています。このように、多くのタスクがあるけれども、一人のスタッフが担当可能なタスクは数個に限定される場合は、シフトとして実装するよりもタスクとして実装した方が効果的です。
サンプルは、充填プロジェクトです。
以前は、集積、供給、包装をシフトとして持っていたものを1フェーズタスク勤務表に書き直したプロジェクトになります。
1勤が、日勤で、2勤が夜勤になります。1勤務を、勤務1、供給、集積、包装、目視のタスクに分解定義しています。充填プロジェクトでのタスク数は少ないので、前述の効果はありませんが、タスクの変動(目視検査等の追加)に対しては、強い構造となります。
下は、シフト解とタスク解になります。シフトの1勤だけに、タスク群が割り当てられているのが分かります。
2022年12月26日月曜日
タスク勤務表とは?
1日のシフトが複数の時間区分(以下フェーズと呼称します)、その時間区分毎に、一人のスタッフに一つの仕事(以下タスクと呼称します)を割り当てるタイプの勤務表をいいます。フェーズ数は、いくつでも自由に定義することができます。
1フェーズ勤務表
3フェーズ勤務表の例
タスク勤務表では、シフト解とタスク解、二つの解が出力されます。
シフト解は、シフトに対する割り当ての答えであり、一日に一つのラベルで出力されます。
6フェーズのシフト解の例
シフト解と、タスク解は、右クリック、ビューの切り替えにより、相互に行き来することが出来ます。
シフトは、予定として、既に決まっている場合もあります。その場合は、
シフト予定入力に記述します。
この場合、シフト解は、シフト予定入力そのものになりますから、解として意味があるのはタスク解の方のみです。
シフト勤務表との差異を下表にまとめます。
|
シフト勤務表 |
タスク勤務表 |
解 |
シフト解のみ |
シフト解とタスク解 |
予定 |
シフト 予定入力のみ |
シフト 予定入力とタスク予定入力 |
列最小単位 |
日(Day) |
フェーズ |
シフト |
一日あたり一個 |
1日あたり一個 |
タスク |
なし |
あり(1フェーズあたり1個) |
2022年12月24日土曜日
ハード列基数制約ソフト化オプション廃止
次のリリースで、オプションを廃止します。動作としては、常にDisableの状態に固定します。
ハードConflict解析の邪魔となることが多いので止めることにしました。
<Task集合のNot演算>
これも弊害が多く、NoTaskVarに限定することにしました。
<制約部の形態動作が少し変化します>
殆ど気づかないとは思いますが、他制約をいじって戻ってきた場合に、設定していない制約は、消えてしまうことがあります。今までと、ちょっと違う動きとなります。
<タスクチュートリアル変更>
大幅に見直してチュートリアル変更を予定しています。
2022年12月13日火曜日
2022年12月12日月曜日
Excel定数
Excel定数をいちいち書かなくてもよい方法が分かりました。
下のコードで、import constants as c
で定数を一気にインポートすることが出来ます。
import win32com.client from win32com.client import constants as c xl = win32com.client.gencache.EnsureDispatch('Excel.Application') xl.Visible = True wb = xl.Workbooks.Add() ws = xl.ActiveSheet ws.Range('A1').FormulaR1C1 = 'X' ws.Range('B1').FormulaR1C1 = 'Y' ws.Range('A2').FormulaR1C1 = 1 ws.Range('A3').FormulaR1C1 = 2 ws.Range('A4').FormulaR1C1 = 3 ws.Range('B2').FormulaR1C1 = 4 ws.Range('B3').FormulaR1C1 = 5 ws.Range('B4').FormulaR1C1 = 6 #ws.Range('A1:B4').Select() ch = ws.Shapes.AddChart().Select() xl.ActiveChart.ChartType = c.xlXYScatterLines xl.ActiveChart.SetSourceData(Source=ws.Range("A1:B4")) #ch.Location(10,10) # something like this?上のExcelグラフは、こちらを新規プロジェクトにコピペ、言語制約の使用にチェックをして求解しただけで得ることが出来ます。
2022年12月11日日曜日
2022年12月6日火曜日
2022年12月5日月曜日
Redo/Undoのグレーアウト化
「Redo/Undoでラベルはグレーアウトの方がよい
これにより「ここにあったような気がするんだけど 見当たらないですね」というユーザ同士の行き違いを減らすことができると思いました。」
というご指摘があり実装しました。
現在のところ、適用はストア版のみです。(未だpublicになってはいません)
2022年12月4日日曜日
2022年12月3日土曜日
ソフト制約調整
2022年12月2日金曜日
ストア版永続ライセンスのURL配布
下記に該当する方々に、URLの配布を開始しました。
■現ライセンスを保持している
■開発に貢献があった
■Covid-19関連
■βテスター業務を完了
手作業で1ユーザ毎、順次配信しているので、該当しているが未だ来ないという方は、しばしお待ちください。
E-mailでURLをお送りしています。
インストール手順です。
前提条件:
■microsoft アカウントがあること
■Windows10/11 64bit Versionであること
手順:
受領したURLリンクをクリックするとマイクロソフトアカウントが求められます。
(下記は仮想環境(英語)の為、英語表示になっていますが日本語で出ます)
マイクロソフトアカウントのパスワードを入れますするとマイクロソフトアカウントに入ります。25桁のコードが既に入っている筈です。
あとは、通常通りアプリの起動でOKです。スケジュールナースのUpdateは自動で行われるのでダウンロードする必要はありません。
ストア版永続ライセンスは、マイクロソフトアカウントに紐づいています。なので、
同じマイクロソフトアカウントでログインすれば、他のPCでも10台までお使いになれます。PCを買い換えても、使うことが出来ます。(永続版ストアサイトは、privateですがhttps://www.microsoft.com/store/apps/9NGKL40LLGGT)
2022年12月1日木曜日
ストア版は自動更新される
今日、ストア版とdownload版、両方のupdateを行いました。
で、「この更新を必須にします」にチェックをつけてリリースしました。
数時間後に、何気に起動してみると、しっかり更新されていました。一度インストールすると、Windowsが勝手にパッケージUpdateを察知してインストールしてくれるようです。この間、Windowsのサインアウトはしていません。パッケージ容量は現在80MB程度もあり、結構時間がかかるので、いちいちダウンロードしなくてもよくなるのはうれしいです。(ありがた迷惑になる場面もあるかもしれませんが。)
=>
■12月1日リリースしたものが12月5日
■12月5日リリースしたものが12月5日
になって自動更新されていました。
リリース後、数時間から数日のバラツキがある模様です。
2022年11月25日金曜日
2022年11月24日木曜日
解の部分修正
という動画をアップしました。
解の修正をスケジュールナースの外側で(Excel等)行って頂くことは、問題ないのですが、次の点に留意する必要があります。
■今月解が変わります。
来月勤務表における先月部を編集して実態と合わせる必要があります
■勤務品質が担保されません。
スケジュールナースの管理の外で、修正すると多くの場合解の品質は悪化します。少なくともスケジュールナース上の目的関数値は、悪化する可能性が高いと思われます。スケジュールナースの結果は、最適解もしくは、準最適解ですから、目的関数値最小の組み合わせを出力しています。実際に管理の外で入力した修正を予定制約上で入力して頂くと、悪化を確認できると思います。
上の問題を回避する方法が、上記動画による方法です。
■ポイントは、勤務希望が解で上書きされないようにロックすること
■手修正を補償するために、適当な箇所をブランクにすること
■ブランク範囲が狭いとハードエラーとなります。
やってみると分かりますが、箇所によっては、かなり広範囲にブランクにしないと目的関数値が悪化します。制約は、広範囲に影響し合っている、ということです。
ソフト制約の数がそれほど多くないときに、スケジュールナース外で行うことは、可能かもしれません。しかし、動画で取り上げている規模のソフト制約が入っていた場合、目的関数値を悪化させずに行うのは、人間技ではかなり難しいと思います。
2022年11月23日水曜日
covid-19 勤務表をリリースした後での最小変更
第8波が急速に広がっており、私の知る介護施設でもクラスタが発生しました。
勤務表リリース後に、スタッフが感染すると2週間休むことになるので、多大な損失になります。しかも夜勤等を手当てしつつ、なおかつリリース済みの勤務表に対してなるべく予定変更が最小となるような勤務表を再リリースする必要があります。そのような場合のスケジュールナースの対応の仕方です。
<シナリオ>
次のリリース済み勤務表のど真ん中で、スタッフQがcovid-19に感染し月末まで休みになるとします。
<手順>
1)スタッフの勤務希望は、変えないものとします。
2)予定画面の全てにロックを掛けます
3)解を予定に送ります
4)ロックを掛けた部分は、上書きされません
5)スタッフQの15日~を休みに変更し、ロックします。
6)15日から~の勤務予定を全てソフト制約レベル7に変更します。
(ロック部は、そのままソフト制約・ハード制約に関わらずそのままの状態を維持し変更されません。)
7)スタッフプロパティで、Qに関わる制約をクリアにします。
8)予定制約レベル7の求解重みを変更します。
9)求解します。
10)重みや、回数偏差リミッタを調整する、カット&トライ、 9),10)の繰り返し を行う
するのがミソです。
Youtube https://youtu.be/U4Bg-8SnHP8
2022年11月22日火曜日
サブスクリプション試用と定期請求無効のやりかた
「毎月のサブスクリプション」の試用期間が切れました。毎月のサブスクリプションの試用はもう出来ません。
スケジュールナースのサブスクリプションは二つあり毎月ではなく毎1年間バージョンもあります。今回は、1年間のバージョンを購入します。すぐに、定期請求を無効として、実際には課金されないようにする例を示します。
前回ブログの毎月の試用期間が切れたので、下のダイアログが出ます。(下に隠れている場合もあります)はい、をクリックします。
いいえ、をクリックすると1年間になります。ここからは、スケジュールナースではなく、Microsoft APIが行っています。マイクロソフトのアカウントメールアドレスとパスワードが求められます。
申し込むをクリックします。
サブスクリプション購入ありがとうございました、と出力されて使用可能になります。このダイアログは、スケジュールナースが出しています。
<課金されないようにする>
定期請求を無効にする処理です。確実に課金されないようにするには、有効期限の2日前までに次の処理を行います。
マイクロソフトアカウントに入ります。サブスクリプション1年が登録されています。
管理をクリックします。
有効期限までは、試用期間でありその間は試用可能です。
まとめ
2022年11月17日木曜日
特許年金支払い
特許権の維持は、登録年金として1年度から3年度まで支払い済みです。
特許登録番号が判明しないと、4年度目からの年金を納めることができません。私は、とりあえず、登録証が届いたら10年度まで7年分納めることにしています。
小規模事業者なので、定額の1/3程度で特許権の維持が可能です。11年度以降、維持特許料が一挙、数倍に上がるので、難しいのですが、10年までは、かなり少なく維持することが可能となっています。
2022年11月16日水曜日
特許証が届きました。
新たに出願した特許が、菅原システムズ4件目の特許登録となりました。
菅原システムズの特許査定率(出願に対する特許査定率)は100%です。
<技術解説>
そのいずれも、ナーススケジューリング問題に対するソルバに関する特許です。ナーススケジューリングは、組み合わせ最適化における一分野です。最適化は、連続系最適化問題と離散系(整数系)最適化問題に分けられます。組み合わせ最適化は、整数最適化問題です。連続系最適化なら、例えば人を3.6人配置、という解が出てくるかもしれないのですが、欲しい解は、人をぶった切ることではありません。解は、全て整数である必要があります。そうなると解の様相は、連続系のそれとは、大きく異なる可能性があります。離散系であるグラフ処理と連続系Simplexをハイブリッドに活用することです。
問題を解く核になるエンジンのことをソルバと言います。
実は、ソルバそのものの特許を持つ企業は、国内ではほとんどありません。ソルバの使い方とか、ソルバを使ったアプリの類は、多くの企業が特許出願していますが、核となる問題解決ソルバは、技術深度が極めて深く、日本での出願も少ないのが現状です。
今回の特許は、ここ2年位、数々の国際ナーススケジューリングベンチマーク群の記録更新の元となった技術です。数理ソルバと呼ばれる類の分野の技術で、学術的にはオペレーションリサーチに属します。どちらかと言えば、ベンチマークに強いです。実務的にも、下界が判明するので、最適解かどうか疑われる向きには役に立つ場面があると思います。さらに強みは、マルチコア下でより力を発揮するので、時代が進めば進むほど、より輝く技術となっております。
ちなみに前回の取得特許は、2017年で、原因分析に関する技術です。解がないときのReasoningに関する技術で、学術的には、人口知能(AI)に属します。地味ではあるけれども、前回特許も実務には欠かせない技術です。
オペレーションリサーチとAIの融合は、永続的研究テーマです。前回特許以来、開発に5年を要しましたが、私なりの一つの回答を示せたと思います。
菅原システムズは、ナーススケジューリング問題では、国内トップ、国際的にみても有数の知的所有権保持者であると言えます。
最適化ソルバの特許は、殆どがCplex/Gurobi/Mosek等、米国発だったのですが、最近の傾向として中国系企業が目立ってきました。基本特許力は、国力に比例すると思うのは、私だけでしょうか?
2022年11月15日火曜日
池上先生が退官
シンポジウム 「モデリングを通して見えた世界」 (ikegami-lab.tokyo)
池上先生の過去の論文を検索している途中、遅ればせながら知りました。
講演の先生方も、皆存じ上げている先生方です。
池上先生の研究室にお邪魔したことがあるのですが、ネットワーク表現のお話をお聞きしました。
私のようなアカデミック外の者を、最も権威のある学会に招待していただいたりして、当時色々反発はあったのではないかと、今にして思います。
池上先生が築かれたナーススケジューリングの礎は、きっと日本で発展するものと信じています。
日本のシフトはスケジュールナースが必ず良くします。
2022年11月14日月曜日
2025年度問題
目前に迫る2025年問題とは? 何が起き、どう備えるべきかを徹底解説 | 記事・トピックス一覧 | 法人のお客さま | PERSOL(パーソル)グループ (persol-group.co.jp)
総務省によると、団塊の世代は各年齢200万人。つまり毎年後期高齢者は200万人ずつ増えていく計算で、2025年には2,180万人ほどになると予測されます。日本人の4人に1人が75歳以上という未来があと3年後に迫っていることになります。
シフトで働く全ての方々の為に、スケジュールナースを用いてシフトの品質を改善することを提案しています。
2022年11月13日日曜日
サブスクリプション βテスター募集
サブスクリプションのβテスターを募集します。募集完了しました。
テスターの条件は、
■マイクロソフトアカウントがあること
■マイクロソフトアカウントにクレジットカードが登録されていること
■サブスクリプションのテストにご参加頂き2ヶ月分(サブスクリプション2ヶ月分@480x2)をご負担頂けること
です。
<お礼>
テスト後(2ヶ月)にサブスクリプションをキャンセルして頂きます。
代わりに、買い切りライセンス(永続ライセンス@50000相当)をプレゼントします。
参加頂ける方はサポートまでご連絡ください。(定員となり次第、締め切りします。)
下記は、サブスクリプション購入の様子です。実際に以下のような感じで操作しサブスクリプションを購入して頂きます。
現在は、公開されていませんが、βテスターには、URLをご案内します。
入手をクリックします。ダウンロードが始まります。2-3分かかるようです。サブスクリプションの説明が表示されます。この表示はマイクロソフトアカウントに購入しなかったので、次の表示となります。終了します。
再度、起動します。今度は「はい」をクリックします。
サブスクリプションは、1ヶ月毎と1年毎があります。「はい」で一ヶ月を選択します。
ここからは、スケジュールナースではなくストアAPIが出力しているダイアログになります。マイクロソフトアカウント名を入力します。
アカウントのパスワードを入力します。
次へをクリックします。
私の場合、Amazonでプレペイド@1500を購入しており、それを使いたかったのですが、下の表示では、切れた場合の処置が必須となっており、結局クレジットカードが必要となるようです。
申し込むをクリックします。
申し込むをクリックします。
成功すると「ご購入ありがとうございます。」が表示されます。これは、スケジュールナースがストアAPIの結果より出力しています。
いったん終了し再起動します。
通常は、ここより起動します。
選択すると、次のようなダイアログとなります。
定期請求オフ後、試行期間が残っていれば、操作は、試用期間一杯まで可能です。
試用期間が過ぎた後起動すると、サブスクリプション購入を促すダイアログが出現します。
{ //GetContext(); Debug.Print("Hello"); context = StoreContext.GetDefault(); IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)context; initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle); //context.OfflineLicensesChanged += context_OfflineLicensesChanged; //Task task=SetupSubscriptionInfoAsync();//タスク引き取り待ちで抜ける //AsyncMethod(); CheckLicenseState(); //SetupSubscriptionInfoAsync(); //if (!has_license) //{ // this.Close(); //} } private StoreContext context = null; private StoreProduct subscriptionStoreProductMonth; private StoreProduct subscriptionStoreProductYear; private StoreAppLicense license; #if STORE private bool has_license = false;//NOV032022 #endif private async Task PurchaseNonSubscLicense() { // Get app store product details StoreProductResult productResult = await context.GetStoreProductForCurrentAppAsync(); if (productResult.ExtendedError != null) { // The user may be offline or there might be some other server failure //Debug.Print($"ExtendedError: {productResult.ExtendedError.Message}", NotifyType.ErrorMessage); return; } //rootPage.NotifyUser("Buying the full license...", NotifyType.StatusMessage); //StoreAppLicense license = await storeContext.GetAppLicenseAsync(); //if (license.IsTrial) { StorePurchaseResult result = await productResult.Product.RequestPurchaseAsync(); if (result.ExtendedError != null) { //rootPage.NotifyUser($"Purchase failed: ExtendedError: {result.ExtendedError.Message}", NotifyType.ErrorMessage); return; } switch (result.Status) { case StorePurchaseStatus.AlreadyPurchased: has_license = true; Debug.Print("StorePurchaseStatus.AlreadyPurchased\n"); //rootPage.NotifyUser($"You already bought this app and have a fully-licensed version.", NotifyType.ErrorMessage); break; case StorePurchaseStatus.Succeeded: Debug.Print("StorePurchaseStatus.Succeeded\n"); has_license = true; // License will refresh automatically using the StoreContext.OfflineLicensesChanged event break; case StorePurchaseStatus.NotPurchased: Debug.Print("StorePurchaseStatus.NotPurchased\n"); //rootPage.NotifyUser("Product was not purchased, it may have been canceled.", NotifyType.ErrorMessage); break; case StorePurchaseStatus.NetworkError: Debug.Print("StorePurchaseStatus.NetworkError\n"); //rootPage.NotifyUser("Product was not purchased due to a Network Error.", NotifyType.ErrorMessage); break; case StorePurchaseStatus.ServerError: Debug.Print("StorePurchaseStatus.ServerError\n"); //rootPage.NotifyUser("Product was not purchased due to a Server Error.", NotifyType.ErrorMessage); break; default: Debug.Print("StorePurchaseStatus.default\n"); //rootPage.NotifyUser("Product was not purchased due to an Unknown Error.", NotifyType.ErrorMessage); break; } } } private async Task PromptUserToPurchaseAsync(StoreProduct product) { // Request a purchase of the subscription product. If a trial is available it will be offered // to the customer. Otherwise, the non-trial SKU will be offered. Debug.Print("RequestPurchaseAsync"); StorePurchaseResult result = await product.RequestPurchaseAsync(); // Capture the error message for the operation, if any. string extendedError = string.Empty; if (result.ExtendedError != null) { extendedError = result.ExtendedError.Message; } string stm = extendedError+" "; switch (result.Status) { case StorePurchaseStatus.Succeeded: // Show a UI to acknowledge that the customer has purchased your subscription // and unlock the features of the subscription. has_license = true; stm += rm.GetString("thank_you"); break; case StorePurchaseStatus.NotPurchased: if (!has_license) { stm += rm.GetString("see_you_again"); } has_license = false; System.Diagnostics.Debug.WriteLine("The purchase did not complete. " + "The customer may have cancelled the purchase. ExtendedError: " + extendedError); break; case StorePurchaseStatus.ServerError: case StorePurchaseStatus.NetworkError: if (!has_license) { has_license = false; } stm += rm.GetString("server_error"); System.Diagnostics.Debug.WriteLine("The purchase was unsuccessful due to a server or network error. " + "ExtendedError: " + extendedError); break; case StorePurchaseStatus.AlreadyPurchased: has_license = true; stm += rm.GetString("already_puchased"); System.Diagnostics.Debug.WriteLine("The customer already owns this subscription." + "ExtendedError: " + extendedError); break; } MessageBox.Show(stm); } public string get_date_info(StoreDurationUnit u, uint billingPeriod) { string s = ""; s += billingPeriod.ToString(); switch (u) { case (StoreDurationUnit.Minute): s += rm.GetString("Minute");break; case (StoreDurationUnit.Hour): s += rm.GetString("Hour");break; case (StoreDurationUnit.Day): s += rm.GetString("Day");break; case (StoreDurationUnit.Week): s += rm.GetString("Weeks");break; case (StoreDurationUnit.Month): s += rm.GetString("Month");break; case (StoreDurationUnit.Year): s += rm.GetString("Year");break; } return s; } public async TaskGetSubscriptionsInfo() { //if (context == null) //{ // context = StoreContext.GetDefault(); // // If your app is a desktop app that uses the Desktop Bridge, you // // may need additional code to configure the StoreContext object. // // For more info, see https://aka.ms/storecontext-for-desktop. //} // Subscription add-ons are Durable products. string[] productKinds = { "Durable" }; List filterList = new List (productKinds); StoreProductQueryResult queryResult = await context.GetAssociatedStoreProductsAsync(productKinds); if (queryResult.ExtendedError != null) { // The user may be offline or there might be some other server failure. System.Diagnostics.Debug.WriteLine($"ExtendedError: {queryResult.ExtendedError.Message}"); return null; } string stm = ""; foreach (KeyValuePair item in queryResult.Products) { // Access the Store product info for the add-on. StoreProduct product = item.Value; //subscriptionStoreProduct = product;//productを保存する。 string store_id = product.StoreId; string store_title = product.Title; string store_price = product.Price.FormattedPrice; stm += store_id +" "; stm += store_title + " "; stm += store_price + " "; // For each add-on, the subscription info is available in the SKU objects in the add-on. foreach (StoreSku sku in product.Skus) { if (sku.IsSubscription) { // Use the sku.SubscriptionInfo property to get info about the subscription. // For example, the following code gets the units and duration of the // subscription billing period. StoreDurationUnit billingPeriodUnit = sku.SubscriptionInfo.BillingPeriodUnit; if (billingPeriodUnit == StoreDurationUnit.Year) { subscriptionStoreProductYear = product; }else if (billingPeriodUnit == StoreDurationUnit.Month) { subscriptionStoreProductMonth = product; }else { Debug.Assert(false); } uint billingPeriod = sku.SubscriptionInfo.BillingPeriod; stm += rm.GetString("subscription") + " "; stm +=get_date_info(billingPeriodUnit,billingPeriod ); } if (sku.IsTrial) { stm += rm.GetString("trial"); } } stm += "\n"; } Debug.Print(stm); //MessageBox.Show(stm); return queryResult; } private async Task CheckLicenseState() { //GetContext(); Debug.Print("CheckLicenseState\n"); StoreProductQueryResult sresult = await GetSubscriptionsInfo(); if (subscriptionStoreProductYear==null && subscriptionStoreProductMonth==null)//sresult == null)//NonSubscription { Debug.Print("NonSubscription\n"); StoreAppLicense appLicense = await context.GetAppLicenseAsync(); if (appLicense != null) { if (!appLicense.IsActive) { Debug.Print("!appLicense.IsActive\n"); await PurchaseNonSubscLicense(); } else { Debug.Print("appLicense.IsActive\n"); has_license = true; } }else { Debug.Print("no license is active.\n"); } }else { Debug.Print("CheckSubscriptionAddonStatusesAsync\n"); await CheckSubscriptionAddonStatusesAsync(); if (!has_license) { Debug.Print("purchase_subscription\n"); await purchase_subscription(); } } if (!has_license) { MessageBox.Show(rm.GetString("no_license_detected")); this.Close(); } } private async Task purchase_subscription() { //Dialog サブスクリプションを購入しますか //Yes/No //Dialog サブスクリプションは1か月と1年があります。1か月を購入しますか? //Yes/No/Cancel // DialogResult result = MessageBox.Show(rm.GetString("purchase_subscription_q"), rm.GetString("purchase_subscription"), MessageBoxButtons.YesNo); Debug.Print(rm.GetString("purchase_subscription_q")); if (result == System.Windows.Forms.DialogResult.Yes) { DialogResult result2 = MessageBox.Show(rm.GetString("purchase_subscription_which"), rm.GetString("purchase_subscription"), MessageBoxButtons.YesNoCancel); Debug.Print(rm.GetString("purchase_subscription_which")); if (result2 == System.Windows.Forms.DialogResult.Yes) {//一か月購入 Debug.Print("一か月購入"); await PromptUserToPurchaseAsync(subscriptionStoreProductMonth); } else if (result2 == System.Windows.Forms.DialogResult.No) {//一年購入 Debug.Print("一年購入"); await PromptUserToPurchaseAsync(subscriptionStoreProductYear); } } } public enum LicenseStatus { Unknown = 0, Trial, Full } public async Task CheckSubscriptionAddonStatusesAsync() { //_context ??= StoreContext.GetDefault(); //GetContext(); StoreProductQueryResult queryResult = await context.GetUserCollectionAsync(new[] { "Durable" }); if (queryResult.ExtendedError != null) { MessageBox.Show(rm.GetString("trial_requires_subscription")); return;//ADDON はあって登録subscriptionはない } // throw queryResult.ExtendedError; foreach (KeyValuePair pair in queryResult.Products) { StoreSku sku = pair.Value.Skus.FirstOrDefault(); StoreCollectionData data = sku?.CollectionData; if (data != null) { has_license = true; LicenseStatus status = data.IsTrial ? LicenseStatus.Trial : LicenseStatus.Full; DateTimeOffset startDate = data.StartDate; bool isAutoRenewal = IsAutoRenewal(data.ExtendedJsonData); Debug.WriteLine($"Store ID ------- {pair.Key}"); Debug.WriteLine($"License status - {status}"); Debug.WriteLine($"Start date ----- {startDate:yyyy/MM/dd}"); Debug.WriteLine($"Auto renewal --- {isAutoRenewal}"); } } } private bool IsAutoRenewal(string json) { if (string.IsNullOrEmpty(json)) return false; // For this JSON value, see: // https://docs.microsoft.com/en-us/windows/uwp/monetize/data-schemas-for-store-products var pattern = new Regex(@"""autoRenew"":(? true|false)", RegexOptions.IgnoreCase); var match = pattern.Match(json); return match.Success && bool.TryParse(match.Groups["value"].Value, out bool value) && value; }