FLEXSCHEバージョン19
FLEXSCHEバージョン19.0を2019年12月27日にリリースしました。174項目にもおよぶ機能拡張が施されています。そのうちの主だったものをご紹介します。(バージョン18.0とバージョン19.0の差分)
FLEXSCHE WebViewer
コストをかけずにシンプルなガントチャートをウェブブラウザで閲覧できるようになりました。
生産スケジューラで立案した結果を製造現場へ提示する方法としては、個々の作業員や各設備への局所的な「作業指示」であることが多いですが、一方ではガントチャートなどを使って全体を俯瞰したい場合もあるでしょう。また管理者(本社や工場長など)や営業部門が工場の状況を随時把握できるようにすることも求められるでしょう。
従来このようなニーズに対しては、FLEXSCHE Viewerをご提供してきました。これは計画立案者が使うFLEXSCHE GPと完全に同レベルの高度な画面表示機能を備えており、さまざまな切り口からの計画の視覚化を可能にしますが、クライアントごとにライセンスを用意する必要があるため、クライアント数が多くなるとコストがかさんでしまうことが難点でした。
そこで今回のバージョン19のリリースと同時に、FLEXSCHE WebViewer(略称はFWV)のご提供を開始することにしました。FLEXSCHE WebViewerは、ウェブブラウザ上で動作する簡易なガントチャートビューワーです。表示できる情報やできることは限られますが、「簡単でよいから全体を把握したい」という要望に応える製品です。
FLEXSCHE WebViewerは無償でご利用いただけますが、有効な保守契約のあるFLEXSCHE GPあるいはFLEXSCHE CarryOutのライセンスから出力されたデータのみを表示することができます。
WebViewerデータの出力
有効な保守契約があるFLEXSCHE GP上でFLEXSCHE WebViewerへの出力方法を設定し、データ出力処理を行います。ここで作った設定ファイルをFLEXSCHE CarryOutサーバー上に配置すれば、FLEXSCHE CarryOutからもデータ出力することができます。
ガントチャート内に表示する文字列、色、チャート行の構成の仕方などは、FLEXSCHEですでに馴染みのある計算式を使って定義することができます。チャート行をツリー状に階層化することもできます。
FLEXSCHE WebViewerサーバー
出力したデータを一般的なウェブサーバー上に配置してFLEXSCHE WebViewerを閲覧することもできますが、イントラネット内でFLEXSCHE WebViewerサーバーを稼働させることで、データが更新されるたびにブラウザへプッシュ配信できます。
なおWebViewerサーバーの動作環境は、Node.jsが動作しさえすればOSの種類は問いません。
FLEXSCHE WebViewerのサンプル画面をご覧いただけます。ただしFLEXSCHE WebViewerサーバーは動作していないので、更新データのプッシュ配信機能はここではご確認いただけません。
チャートの操作方法については、チャート画面右上の?をクリックしてご覧ください。
対応ブラウザはGoogle Chrome / Mozilla FireFoxです。Microsoft Edgeには現時点では対応しておらず、作業接続線を描画できないなど、一部の機能をご利用いただけません。
ソフトウェア方式ライセンス
ハードウェアキー(ドングル)の無いライセンスを選べるようになり、
データセンターでのライセンス管理も容易になりました。
ハードウェアを使用しないソフトウェアによるライセンスシステム、"ソフトウェア方式ライセンス"をリリースしました。 USBライセンスキーを必要としないので、USBポートのないサーバーマシンや仮想マシン上にもライセンスをインストールすることができます。 またハードウェアキーと異なり紛失のリスクも激減します。
万一ライセンスをインストールしたマシンが故障して起動できなくなっても保守サービスの一環としてライセンスの再発行もできます。
FLEXSCHE Performance Tuning Service
保守契約ユーザー様向けの新たなサービスとして
FLEXSCHE Performance Tuning Service (FPTS)をスタートします。
FLEXSCHEは最短のスケジューリング処理時間を目指して極限まで高速化していますが、 それでも大規模なデータや複雑なスケジューリングロジックにおいては、かなりの処理時間を要してしまうことがあります。 それを少しでも短縮したいというご要望をお受けすることがあり、それに応えるための新サービスを開始しました。
本サービスでは、お客様ご自身にFLEXSCHEを操作してプロファイルを収集していただき、 それに基づいて弊社がお客様専用のFLEXSCHEインストーラを作成してご提供します。 お客様固有のデータにフォーカスしてFLEXSCHEの各プログラムモジュールを最適化するので、 そのデータとロジックおよび操作手順においてはFLEXSCHEの処理速度がさらに高速化することが期待できます。
モジュール提供の流れ
- プロファイル収集
運用してるデータまたはそれと同等のデータをご準備ください。 運用時と同様の操作を行っていただき、その処理の特性・傾向情報(プロファイル)を作ります。 プロファイル収集用モジュールが同梱されているので、予めそのモジュールに入れ替えておく必要があります。
※プロファイル収集モジュールは実行に時間がかかります。従来の10倍程度の所要時間を想定してください。
- プロファイル提出
収集されたプロファイル情報をフレクシェ社へお送りいただきます。
- 専用モジュールをビルド(フレクシェ社)
送付いただいたプロファイルをもとに弊社内でユーザー専用モジュールをビルドします。
※数日程度お時間をいただきます。
- 専用モジュールの取得
ビルド完了時にメールでお知らせいたします。高速化が施された専用モジュールをダウンロードすることができます。
- 専用モジュールの適用
ダウンロードした専用モジュールをFLEXSCHEの既存モジュールと入れ替えていただけば高速化適用完了です。
FPTSManagerとFPTSサーバー
上記の流れの手続きを行うためにはFPTSManagerというツールを使います。 プロファイル作成や提出、ビルドされた専用モジュールの取得、モジュールの入れ替えなどの操作はこのツールで行います。 また弊社側にはFPTSサーバーというwebサービスが稼働しており、FPTSManagerはこのサーバーとやり取りを行いますので、 基本的に弊社スタッフと直接ご連絡を取っていただく必要はありません。 エクスプローラなどを用いて直接ファイル操作をする必要もありません。
メンテナンスモードでは、取得済みの専用モジュールと、標準モジュールとの入れ替えとFLEXSCHEの起動が行えます。高速化適用前の状態に戻したり、FPTSによる高速化の効果を比較検証するのに便利です。 また古いファイルの削除等もここから行えます。
サービスに関するその他補足
- 対応するのは保守対象バージョンにおける最新リリースバージョン/リビジョンに限ります。
- どれくらい高速化が見込めるかは具体的な数値は確約できません。
- FLEXSCHEはすでに極限まで高速化されているので、データや処理内容によっては効果が出ない場合もあります。ときにはむしろ遅くなることもありえます。
- プロファイル情報はあくまでプログラムの動作傾向に関する集計情報です。プロジェクト固有の情報は含まれませんのでご安心ください。
- FLEXSCHE導入PCがオフライン環境下であっても、オンライン接続された別PCにFPTSManagerをコピーすることでFPTSサーバーとの手続き部分のみを実施することができます。
- 修正リリースに対しても高速化モジュールを希望される場合は、プロファイル収集から手続きをやり直していただく必要があります。
本サービスは保守契約のあるライセンスに対してのみ、ご提供します。
デバッグトレース機能
スケジューリング中の状態を観察するための仕組みです。
スケジューリングルールの構築を進めていくと、スケジューリング結果が想定と異なるときに、そうなった原因を突き止めることが困難な場合がしばしばあります。 そのような状況においては、スケジューリング処理の途中で一時停止(=ブレイク)して、状態をじっくりと観察したくなるでしょう。
バージョン19で実装された「デバッグトレース機能」でそれが実現します。
スケジューリング処理の様々な箇所に「ブレイクポイント」が設けられており、それを有効にするとブレイクすることができます。 ブレイクポイントに計算式として「ブレイク条件」を与えることによって、特定の条件を満たすときだけブレイクすることもできます。 ブレイク時の内部状態を表示するための計算式もブレイクポイントごとに自由に定義することができます。
デバッグパネル
デバッグトレース機能を集約した「デバッグパネル」が様々なトレース操作を提供します。スケジューリング処理を1ステップずつ実行する「ステップ実行」、次にブレイク条件を満たすまで進める「コンティニュー」、 最後に処理したオブジェクト(作業など)をガントチャート上などで示す「対象オブジェクト検索」などを駆使することで、問題の原因となる箇所を素早く見つけることができるでしょう。
ブレイク中は、画面の切り替え、スクロール、拡大縮小、ダイアログの操作など、一部のコマンド等を除いては通常通りに操作できます。 マウスによる作業の移動すらできてしまうので、ルールによっては想定外の不整合な状態にもなりうるというリスクを伴いますが、スケジューリングルールの構築作業においては、とても強力な助けとなるはずです。
なお、ユーザー区分を「上級ユーザー」以上に設定している場合のみ有効で、「一般ユーザー」としているときには本機能は無効です。
高DPI対応
高DPI環境でFLEXSCHEが快適に動作するようになりました。
ノートPCやタブレットPCで高解像度のディスプレイで画面サイズが大きくない環境(高DPI環境)でFLEXSCHEが快適に動作するようになりました。高DPI環境では通常システムの文字サイズを拡大して使用します。 アプリケーションはそれに対応する必要がありますが、単純にアプリケーションを拡大しただけでは、文字がにじんだりギザギザになったり、UIの描画も粗さが目立ちます。
今回の対応ではシステムの文字サイズに応じた文字サイズをFLEXSCHEでも使用し、必要に応じてUI部品も拡大することで、従来の拡大モードに比べて滑らかな見た目を実現しました。
EDIF
EDIFおよびEDIF構成ツールに関して以下のような機能強化を施しました。
班資源サブタスクのエクスポート
班資源へ割り付けられたタスクのその詳細であるサブタスク(班メンバーへの割当て状態)をエクスポートできるようになりました。 フィールド分離、テーブル分離に対応しており、テーブル分離の場合は計算式も利用できます。
キー付きカスタムセットのトランザクション対応
t_tran、t_last_updatedフィールドとの連携に対応しました。 通常のFLEXSCHEテーブルと同様のトランザクション処理が行えます。
実績・在庫操作・自由カレンダーの差分対応
レコードにコードを保持できるようになり、差分インポート/エクスポートが行えるようになりました。
ADO、ADO.NETのコマンドタイムアウトの指定
外部テーブル設定にあったCSVファイル用のタイムアウト欄をADO/ADO.NETでも有効にし、 DBコマンドに対するタイムアウト設定として機能するようにしました。
インポート時にSQLを直接指定
任意のSQLクエリを実行し、DBから取得するレコードを自由にフィルタできるようになりました。 大量データからその一部をFLEXSCHEへインポートする場合に、従来よりも劇的な高速化が期待できます。
Select * from tableA where xxx='val1'
といったSQL文を指定することで、FLEXSCHEがデータを受け取る前段階でフィルタリングすることができます。 従来はすべてのレコードをFLEXSCHE側で精査しながら取捨するためDBからの取得コストがかかっていました。 データが多ければ多いほど恩恵は大きくなります。
自由度が高く、updateやdelete文なども書けてしまうの注意が必要です。
エラー箇所を分かりやすく
エラーが含まれるテーブルマッピングには、そのタブにもエラーマークを表示するようにしました。 従来から保存時にエラーが含まれる場合は警告メッセージが表示されていましたが、 どのテーブルマッピングにエラーがあるのか、探すのが困難な場合がありました。 また、タブにエラーマークを表示するだけでなく、グループフィルタでも 「エラーが含まれているテーブルマッピング」で絞り込みできるようにしたので 修正作業の効率が大幅に上がります。
作業実績細分
作業実績に関する補足情報を指定するための作業実績細分テーブルを追加し、班資源メンバーのサブタスクに対する実績情報を指定できるようになりました。
たとえば大規模な組立作業等で、複数の作業者が自由なタイミングで交代しながら作業する場合に、各作業者が実績としてどの時間帯に担当したかを資源ガントチャート上に緻密に再現することができます。
※班資源メンバーに割付けを反映するには上級オプション「班資源」(2ユニット)が必要です。スケジューリング
[データ検証]違反レコードにマーク
データ検証メソッドで、検証項目毎に、違反したレコードにマーク(自由なフラグ)を設定できるようになりました。 たとえばFLEXSCHE Editorのフィルタ機能と連携させれば、異常の見つかったデータを簡単に抽出できます。
[補充生産]期間まとめ補充で上限指定&平準化
期間まとめ補充で、生成される補充オーダーのオーダー数量の上限を指定できるようになりました。 それを超える量が必要な場合は、複数の補充オーダーに分けて生成されます。 その際に生成される補充オーダーの数量を平準化することもできます。
[補充生産]補充まとめ期間を計算式で定義
補充まとめ期間の区切り日時を計算式で自由に定義できるようになりました。 たとえば「月毎に必要な量をまとめて補充する」という場合、月によってまとめ期間の長さが異なります。従来は、補充まとめ期間の長さは品目毎に固定値を設定するしかありませんでしたが、各月の最初の日付をリストで返す計算式を指定するだけで実現できるようになりました。
[モデリング]計算式によるセレクタ
選択される側のセレクタ条件を計算式で指定できるようになりました。たとえば、生産オーダーの納期に応じて生産工程を自動的に切り替えることも簡単に実現できます。
また、従来のオーダーセレクタとの一致判定においてAND条件とOR条件を混在させることはできませんでしたが、計算式により完全に自由に記述できるようになりました。
[モデリング]段取りだけの同時積み・並行条件に対応
段取りだけに関与するタスク資源でも同時積み条件や並行条件を遵守できるようになりました。 前段取り・製造・後段取りのそれぞれのパートで条件を遵守できる日時を無駄なく見つけるので、効率の良い計画を立案できます。
[作業場計画]マージン処理の精度向上
ワーク形状が凹多角形の場合にも、マージンを考慮した配置計画を正確に行えるよう改良しました。
ユーザーインターフェース
[時系列チャート]任意の時間帯による集計表示
在庫量チャート・負荷チャート・資源滞留チャート・品目滞留チャートやサインボード(計算式)で、1日の中での区切り時刻をチャート行毎に自由に指定して、それらの間で集計表示ができるようになりました。
[作業接続線]作業接続線の形状
資源ガントチャートやオーダーガントチャートでの作業間の接続線の形状として、直線だけでなく鉤型も選べるようになりました。
[差立てチャート]表示時間帯限定
差立てチャートで表示時間帯をチャート毎に限定できるようになりました。 たとえば交替勤務の場合に担当するシフトの時間帯だけを表示できます。
[差立てチャート]改ページは期間の区切りで
差立てチャートで期間毎に区切って表示している場合に、印刷時に期間の区切り以外ではなるべく改ページされないよう指定できるようになりました。
[差立てチャート]Excelで開く
差立てチャートの表示内容をそのままExcelで開くことができるようになりました。セルの背景色や文字色なども再現されます。
[ウィンドウ]ウィンドウのタブに色
各ウィンドウのタブに色を設定できるようになりました。 時系列チャートやFLEXSCHE Editorのテーブルなど、種類に応じた色が設定されます。さらにウィンドウ毎に個別の色を指定することもできます。 様々なウィンドウを開いている場合に一目で把握できます。
[スキルエディタ]スキル以外も編集可能に
スキルエディタで、資源のスキルだけでなく、資源・品目・オーダーの数値仕様も編集できるようになりました。 複数の数値仕様キーに対して大量に入力する必要がある場合に、表形式で簡単に入力できるので便利です。
計算式
リストやマップを書き換える関数
リストやマップの一部の値だけを書き換えるための関数を追加しました。
計算式の記述が簡潔になり、パフォーマンスが向上します。
従来、FLEXSCHEのリストやマップには不変性(immutability)と呼ばれる性質があり、一度作成した後で一部の値を書き換えることはできませんでした。 つまり、あるリスト(またはマップ)を変更するためには、元のコピーに対して操作をして、新しいリスト/マップを返す必要がありました。
例えば、{'a', 'b', 'c', 'd'}というリストがあり、変数$listが参照するとします。
このリストの末尾に'X'を追加するための計算式は、従来は以下のようになります。元のリストは不変であり、複製されたリストを操作したものが処理結果となります。
$list := $list.Append('X')
巨大なリスト/マップを扱う場合には、このような複製操作の処理コストは低くありません。 バージョン19では、以下のように書くことで元のリストを複製することなく直接書き換えることができるようになります。
$list.Append_('X')
末尾に値を追加する.Append_()に加えて、一部の値を書き換える.Set_()、要素を取り除く.Remove_()などが追加されています。
この仕組みは、保有変数などが参照するリストやマップへ累積的に要素を追加していく場合などのパフォーマンス向上に特に効果があります。
計算式の編集サポート機能
評価の対象を選んで計算式のテストをすることがプロジェクトパネルから簡単にできるようになりました。また、計算式内に記述したコメントを緑色で表示することで、式が従来よりも見やすくなりました。
保有変数の拡張
計算式が評価されるタイミングを制御してパフォーマンスを向上させるための仕組みとして保有変数がありますが、今回、新たにデータの保有変数が追加されました。 類似の機能としてプロジェクトのプロパティがありますが、それよりも扱えるデータの型が増えたり、変数として直接扱えたり、説明文を記述できるなど、様々な面で便利に使えます。
また保有変数の値をファイルに書き出すかどうかを指定できるようになりました。これによって、スケジューリング中などに一時的に利用するだけの大きなリストやマップをファイルへ読み書きすることによる無駄なオーバーヘッドを回避できます。
パラメタ
パラメタにカスタムデータセットのレコードを指定できるようになりました。 レコード型のパラメタを宣言して、Custom('dataset').From($rec) のように変換しなくても直接的にレコードを受け渡しできます。
外部プログラムとの連携
任意のプログラムを実行し、その標準出力を計算式に取り込むことのできる関数Project.ConsoleCommandを追加しました。 例えば、数値計算のライブラリが充実しているpython等の言語で何らかの計算をさせて、その結果をFLEXSCHEに反映させる という使い方が考えられます。
また、外部のプログラムに構造的な値を受け渡すときにJSON形式でデータを表現したいことがあります。 新しく追加された関数、<Map>.ToJSON / <List>.ToJSON / <Variant>.TOJSON / Variant.FromJSONにより、taktデータとJSONを相互変換できるようになりました。
これらを組み合わせると、例えば、外部のpytest.pyファイルに
import sys import json import numpy as np data = json.loads( sys.argv[1] ) angle = np.deg2rad( data["angle"] ) vec = np.array(data['value']) cos = np.cos( angle ) sin = np.sin( angle ) matrix = np.array([[cos,-sin],[sin,cos]]) vec = np.dot( matrix, vec.transpose() ) print( json.dumps( vec.tolist() ) )
と記述し、FLEXSCHE上で
$vec := {22, -4}, $data := { "value"=>$vec, "angle"=>45 }, $a1 := "pytest\pconv.py", $a2 := $data.ToJSON, $res := Project.ConsoleCommand( "python", { $a1, $a2 } ), Variant.FromJSON( $res )
を評価させることで、外部のプログラムにベクトル計算をさせて、その結果をFLEXSCHEに取り込むことができます。
様々な関数を追加
計算式で使える関数がさらに増えて、できることが一層広がりました。
その他
UI部品としてのデータキューブエディタ
表形式のUIを持つGUI組込み画面を簡単に開発できるようになりました。
セルの値を編集可能なデータキューブビューワーを作成できるようになりました。セルや行/列ごとの有効無効、背景色、編集の可否編集などをアドインから自由に制御できます。
例えば、横軸が日付、縦軸が品目の表を作成して、それぞれの日に対して品目の要求量を入力させるようなUIを作成できます。例えば、値が入力されたら、アドインからその日を納期としたオーダーを作成して作業を割り付けるような使い方ができるでしょう。
※FLEXSCHE Analyzerのライセンスが必要です
以下のアドインスクリプトは、完成品の要求量に応じて中間材料の生産量を人が調整するためのUIを実装したものです
// このFLEXSCHEアドインのコードは自由に改変してお使いいただくことができます // GUIアドインの登録 function SelfRegistration(addIns) { var addin; addin = addIns.AddScript("Setup", FLEXSCHE.AddInKeyHookAfterLoadingProject); addin = addIns.AddScript("Command", FLEXSCHE.AddInKeyEventFSEventFired); addin.LongValue = addIns.Application.Environment.RegisterProjectEvent( "gui.tree-node.command.invoked" ); // プロジェクト読み込み時のDataCubeView用データキューブを作る addin = addIns.AddScript( "ObtainDatacube", FLEXSCHE.AddInKeyEventFSEventFired ); addin.LongValue = addIns.Application.Environment.RegisterProjectEvent( "fse.datacube.obtain" ); // 全体を更新 addin = addIns.AddScript( "AfterLoadingData", FLEXSCHE.AddInKeyHookAfterLoadingData ); addin = addIns.AddScript("Rescheduled", FLEXSCHE.AddInKeyEventFSEventFired); addin.LongValue = FLEXSCHE.FSEventRescheduled; } // Editorアドインの登録 function SelfRegistrationForFSEditor(addIns) { var addin = addIns.AddScript( "ValueChanged", FSEditor.FSEAddInKeyEventOnBodyOnDataCubeEditor ); // セルが編集された => 当該中間品とその下位製品だけを更新 addin.SubKeyID = FSEditor.FSEAddInSubKeyEvent_Edited; } function _Setup(keyEntity) { // プロジェクトパネルの「カスタムビュー」ノード var project = keyEntity.ParamObject(FLEXSCHE.ParamIDProject); var str = "<?xml version='1.0'?>"; str += "<tree-node str='カスタムビュー' " str += "guid='{41D5879D-EF89-48C1-A681-39D57EEC5423}' type-name='top'>"; str += "<command str='追加' id='add-view' is-default='true' />"; str += "</tree-node>"; var cmd = new ActiveXObject("Microsoft.XMLDOM"); cmd.loadXML(str); project.BehaviorProperty( "#gui.project-panel.additional-tree.custom-dc" ) = cmd.documentElement; // 既存の子ノードを追加 var items = project.ObtainStoredData("jsobject.DCView.Items.1", []); for( var i = 0; i < items.length; i++ ) AddView(project, items[i].guid, false); return true; } // ツリー項目のコマンドを実行 function _Command(keyEntity) { var project = keyEntity.ParamObject(FLEXSCHE.ParamIDProject); var nodeType = keyEntity.ParamString(FLEXSCHE.ParamIDVariant1); var guid = keyEntity.ParamString(FLEXSCHE.ParamIDVariant2); var command = keyEntity.ParamString(FLEXSCHE.ParamIDVariant3); if( nodeType == "top" && command == "add-view" ) { // 追加 var sdlUtil = project.Application.CreateCOMInstance("SDLib.SDLUtility"); var newGuid = sdlUtil.GenerateGUID(true); AddView(project, newGuid, true); var items = project.ObtainStoredData("jsobject.DCView.Items.1", []); items.push({ guid: newGuid }); project.StoredData("jsobject.DCView.Items.1") = items; } if( nodeType == "view" && command == "show-view" ) { // 表示 var dc = GetDatacube(project, guid); var fseProject = GetFSEditorProject(project); if( fseProject != null ) { var dcEditor = fseProject.ViewDataCube(dc, true); } } } // プロジェクト読み込み時のDataCubeView用データキューブを作る function _ObtainDatacube(keyEntity) { var project = keyEntity.ParamObject(FLEXSCHE.ParamIDProject); var guid = keyEntity.ParamString(FLEXSCHE.ParamIDVariant2); var items = project.ObtainStoredData("jsobject.DCView.Items.1", []); for( var i = 0; i < items.length; i++ ) { if( items[i].guid != guid ) continue; var dc = GetDatacube(project, guid); if( dc == null ) break; keyEntity.Param(FLEXSCHE.ParamIDVariant3) = dc; return true; } return false; } function GetDatacube(project, guid) { var cDCs = project.CountOfStockedObjects("datacube"); for( var iDC = 0; iDC < cDCs; iDC++ ) { var dc = project.StockedObject("datacube", iDC); if( dc != null && dc.guid == guid ) return dc; // 既存のデータキューブを返す } var sdSpace = project.DataSpace; var dc = project.Application.CreateCOMInstance("SDLib.DataCube"); dc.GUID = guid; dc.Name = "PSI-01"; project.AddStockedObject("datacube", dc); // 日時ディメンジョン var dimTime = dc.AddDimension("Time", SDLib.DCVTypeTime, ""); dimTime.DisplayName = "日付"; var startDate = VBDateToJDate(project.HorizonStartDate); var endDate = VBDateToJDate(project.HorizonEndDate); var exp = sdSpace.CreateTypedExpression( SData.SDVTypeTime, SData.SDVTypeString ); exp.Parse(".ToString('%^m/%^d')"); for( var date = startDate; date <= endDate; date.setDate(date.getDate() + 1) ) { var _d = VBDateFromJDate(date); var elem = dimTime.PrimitiveLayer.AddElement(_d); elem.Description = exp.CalculateFor(_d); } // 品目ディメンジョン var dimItem = dc.AddDimension("Item", SDLib.DCVTypeCustomString, "Item"); dimItem.DisplayName = "品目"; var layerItemIntermediate = dimItem.AddLayer("intermediate"); dimItem.RootLayer.AddChild(layerItemIntermediate); layerItemIntermediate.AddChild(dimItem.PrimitiveLayer); var intItems = sdSpace.Calculate("Item.Records.Select([.IsIntermediate])"); var cIntItems = intItems.Count; for( var iIntItem = 0; iIntItem < cIntItems; iIntItem++ ) { var intItemRec = intItems.ItemRec(iIntItem); var layerElem = layerItemIntermediate.AddElement(intItemRec.Code); var prodItems = sdSpace.Calculate( "Item.Records.Select([.IsFinal and .Comment('mtrl')='" + intItemRec.Code + "'])" ); var cProdItems = prodItems.Count; for( var iProdItem = 0; iProdItem < cProdItems; iProdItem++ ) { var prodItemRec = prodItems.ItemRec(iProdItem); layerElem.AddChild(dimItem.PrimitiveLayer.AddElement(prodItemRec.Code)); } } // メジャーの定義 var mdefLimit = dc.AddMeasureElementDefinition(SDLib.DCMEDTypeNone, "Limit"); mdefLimit.ValueType = SDLib.DCVTypeDouble; mdefLimit.DisplayName = "要求上限"; var mdefRequired = dc.AddMeasureElementDefinition( SDLib.DCMEDTypeNone, "Required" ); mdefRequired.ValueType = SDLib.DCVTypeDouble; mdefRequired.DisplayName = "要求量"; var mdefProduce = dc.AddMeasureElementDefinition( SDLib.DCMEDTypeNone, "Produce" ); mdefProduce.ValueType = SDLib.DCVTypeDouble; mdefProduce.DisplayName = "生産量"; var mdefStock = dc.AddMeasureElementDefinition(SDLib.DCMEDTypeNone, "Stock"); mdefStock.ValueType = SDLib.DCVTypeDouble; mdefStock.DisplayName = "残量"; var mdefVirtual = dc.AddMeasureElementDefinition( SDLib.DCMEDTypeNone, "Virtual" ); mdefVirtual.ValueType = SDLib.DCVTypeDouble; mdefVirtual.DisplayName = "仮想生産量"; dc.FinishToSetup(); // 表示・読み取り専用の制御 var iStock = dc.MeasureElementIndexByName("Stock"); var iProduce = dc.MeasureElementIndexByName("Produce"); var iLimit = dc.MeasureElementIndexByName("Limit"); var iVirtual = dc.MeasureElementIndexByName("Virtual"); layerItemIntermediate.MeasureElementHidden(iLimit) = true; dimItem.PrimitiveLayer.MeasureElementHidden(iStock) = true; layerItemIntermediate.MeasureElementHidden(iVirtual) = true; dimItem.PrimitiveLayer.MeasureElementHidden(iVirtual) = true; dc.ReadOnly = false; layerItemIntermediate.MeasureElementReadOnly(iStock) = true; layerItemIntermediate.MeasureElementReadOnly(iProduce) = true; dimItem.PrimitiveLayer.MeasureElementReadOnly(iLimit) = true; dimItem.PrimitiveLayer.MeasureElementReadOnly(iProduce) = true; // 過去期間を読み取り専用に var roDays = sdSpace.Calculate( "TimeList.MakeInRange(project.HorizonStart,Project.SchedulingStartTime)" ); var cROs = roDays.Count; for( var iRO = 0; iRO < cROs; iRO++ ) dimTime.PrimitiveLayer.ElementByValue( roDays.Value(iRO) ) .MeasureElementReadOnly(-1) = true; UpdateValues(dc, project); return dc; } // ビューのツリー項目を追加する function AddView(project, guid, diff) { var topElem = project.BehaviorProperty( "#gui.project-panel.additional-tree.custom-dc" ); var doc = topElem.ownerDocument; if (diff) topElem.setAttribute("sync", "true"); // <tree-node str='PSI-01' guid='4E16AE33E53440069A71A5083275BA95' // type-name='view' icon='datacube' /> var viewElem = doc.createElement("tree-node"); topElem.appendChild(viewElem); viewElem.setAttribute("str", "PSI001"); viewElem.setAttribute("guid", guid); viewElem.setAttribute("type-name", "view"); viewElem.setAttribute("icon", "datacube"); // <command str='表示' id='show-view' is-default='true' /'> var commandElem = doc.createElement("command"); viewElem.appendChild(commandElem); commandElem.setAttribute("str", "表示"); commandElem.setAttribute("id", "show-view"); commandElem.setAttribute("is-default", "true"); project.BehaviorProperty( "#gui.project-panel.additional-tree.custom-dc" ) = topElem; } function GetFSEditorProject(project) { var fse = project.Environment.AddInManager.InterfaceByProgID( "FSEditor.Entries" ); if( fse == null ) return null; var fseEnv = fse.Accessor.Environment; if( fseEnv == null ) return null; return fseEnv.Project; } /***************************************************************************************/ // セルの値が変わった function _ValueChanged(keyEntity) { var editor = keyEntity.ParamObject(FSEditor.FSEParamIDDataCubeEditor); var row = keyEntity.ParamLong(FSEditor.FSEParamIDRow); var col = keyEntity.ParamLong(FSEditor.FSEParamIDCol); var locator = keyEntity.ParamObject(FSEditor.FSEParamIDDataCubeLocator); var meIndex = keyEntity.ParamLong(FSEditor.FSEParamIDLong); var measure = locator.FindMeasure(); if (measure == null) return false; var util = Script.CreateCOMInstance("SDLib.SDLUtility"); var project = editor.Project.FSProject; var sdSpace = project.DataSpace; var dc = locator.DataCube; var iTime = dc.DimensionByName("Time").IndexInDataCube; var iItem = dc.DimensionByName("Item").IndexInDataCube; var time = locator.DimensionLayerElement(iTime).Value; var itemRec = sdSpace.ItemSet.ItemRecByCode( locator.DimensionLayerElement(iItem).Value ); if (!itemRec.IsBound) return false; var meDef = dc.MeasureElementDefinition(meIndex); var qty = measure.Value(meIndex); if( qty == undefined || !util.IsDoubleNormal(qty) ) { qty = 0.0; measure.Value(meIndex) = 0.0; // 値が削除されたら強制的に0に } // 該当するオーダーを追加・更新・削除 var code = itemRec.Code + "-" + util.FormatTime(time, "%y%m%d"); var orderRec = sdSpace.OrderSet.OrderRecByCode(code); if( orderRec.IsBound ) orderRec.Qty = qty; else if( qty > 0.0 ) { orderRec = sdSpace.OrderSet.OrderRecByCode(code); if (!orderRec.IsBound) { orderRec = sdSpace.OrderSet.CreateOrderRec(SData.SDOTypeProduction, code); orderRec.ItemRec = itemRec; orderRec.AssignmentMethod = SData.SDTDirectionBackward; orderRec.Color = itemRec.Color; orderRec.LatestEndTime = time; } orderRec.Qty = qty; } // 関連する値を変える var code = itemRec.Comment("mtrl"); if( code != "" ) itemRec = sdSpace.ItemSet.ItemRecByCode(code); UpdateValuesForIntermediateItem(dc, project, itemRec); // 不要になったオーダーを削除 if( orderRec.IsBound && orderRec.Qty == 0.0 ) orderRec.Delete(); editor.Project.FireEvent(FSEditor.FSEEventOrderRecsAreUpdated); } // リスケジューリングされた function _Update(keyEntity) { var project = keyEntity.ParamObject(FLEXSCHE.ParamIDProject); var views = project.views; var c = views.CountOfViews; for( var i = 0; i < c; i++ ) { var view = views.View(i); if( view.ViewType != FSVTypeCtrlView ) continue; if( view.SpecificationKey != "FSEditor.DataCubeEditor" ) continue; var dc = view.Control.DataCube; UpdateValues(dc, project); } } /***************************************************************************************/ // 与えられたデータキューブのすべての値を更新する function UpdateValues(dc, project) { var sdSpace = project.DataSpace; if( dc == null ) return; // クリア dc.ClearMeasures(); // 要求量 UpdateValuesForRequired(dc, project); // 生産量 UpdateValuesForProduction(dc, project, null); // 生産量調整 - 要求量調整後にスケジューリングしていない分の仮反映 UpdateValuesForProductionAdjust(dc, project, null); // 中間品の残量と製品の要求上限 var dimItem = dc.DimensionByName("Item"); var layerItemIntermediate = dimItem.LayerByName("intermediate"); var cIntermediateElems = layerItemIntermediate.CountOfElements; for( var iIntermediateElem = 0; iIntermediateElem < cIntermediateElems; iIntermediateElem++ ) { var elem = layerItemIntermediate.ElementByIndex(iIntermediateElem); var intermediateItemRec = sdSpace.ItemSet.ItemRecByCode(elem.Value); if (!intermediateItemRec.IsBound) continue; UpdateValuesForStock(dc, project, elem, intermediateItemRec); } } // 部分更新 - 与えられた中間品とその関連製品の値を更新する // 効率化のため別に実装した function UpdateValuesForIntermediateItem(dc, project, intermediateItemRec) { var sdSpace = project.DataSpace; var cItems = sdSpace.ItemSet.CountOfRecords; for( var iItem = 0; iItem < cItems; iItem++ ) { var itemRec = sdSpace.ItemSet.ItemRec(iItem); if( !itemRec.IsBound || itemRec.IsGroup) continue; if( (itemRec.Role & SData.SDIRoleIntermediate) == 0 && (itemRec.Role & SData.SDIRoleFinal) == 0 ) continue; if( itemRec.RecordKey != intermediateItemRec.RecordKey && itemRec.Comment("mtrl") != intermediateItemRec.Code ) continue; // 生産量 UpdateValuesForProduction(dc, project, itemRec); // 生産量調整 - 要求量調整後にスケジューリングしていない分の仮反映 UpdateValuesForProductionAdjust(dc, project, itemRec); } // 中間品の残量と製品の要求上限 var dimItem = dc.DimensionByName("Item"); var layerItemIntermediate = dimItem.LayerByName("intermediate"); var elem = layerItemIntermediate.ElementByValue(intermediateItemRec.Code); UpdateValuesForStock(dc, project, elem, intermediateItemRec); } // 要求量 - すべてのオーダーの数量を拾い集める function UpdateValuesForRequired(dc, project) { var iRequired = dc.MeasureElementIndexByName("Required"); var expFloor = project.DataSpace.CreateTypedExpression( SData.SDVTypeTime, SData.SDVTypeTime ); expFloor.Parse(".Floor"); var layerTime = dc.DimensionByName("Time").PrimitiveLayer; var layerIntermediate = dc .DimensionByName("Item") .LayerByName("intermediate"); var layerProduct = dc.DimensionByName("Item").PrimitiveLayer; var loc = dc.CreateLocator(); var orderSet = project.DataSpace.OrderSet; var cOrders = orderSet.CountOfRecords; for( var iOrder = 0; iOrder < cOrders; iOrder++ ) { var orderRec = orderSet.OrderRec(iOrder); var itemRec = orderRec.ItemRec; var qty = orderRec.Qty; var time = expFloor.CalculateFor(orderRec.LatestEndTime); loc.SetDimensionLayerElement(layerTime.ElementByValue(time)); var elem = itemRec.Role & SData.SDIRoleIntermediate ? layerIntermediate.ElementByValue(itemRec.Code) : layerProduct.ElementByValue(itemRec.Code); loc.SetDimensionLayerElement(elem); loc.ObtainMeasure().Value(iRequired) = qty; } } // 生産量 - 品目を特定しない場合は全品目 function UpdateValuesForProduction(dc, project, targetItemRec) { var sdSpace = project.DataSpace; var itemSet = sdSpace.ItemSet; var cItems = itemSet.CountOfRecords; var layerTime = dc.DimensionByName("Time").PrimitiveLayer; var layerIntermediate = dc.DimensionByName("Item").LayerByName("intermediate"); var layerProduct = dc.DimensionByName("Item").PrimitiveLayer; var iProduce = dc.MeasureElementIndexByName("Produce"); var loc = dc.CreateLocator(); var exp = project.DataSpace.CreateTypedExpression( SData.SDVTypeItemRec, SData.SDVTypeVariantList ); exp.Parse( "TimeList.MakeInRange(Project.HorizonStart,Project.HorizonEnd)" + ".Convert([$$_object.InventorySubtotalQty(InventoryPCType" +".Produce,$_object,$_object+#P1D#,false)])" ); for( var iItem = 0; iItem < cItems; iItem++ ) { var itemRec = targetItemRec == null ? itemSet.ItemRec(iItem) : targetItemRec; if( itemRec.IsGroup ) continue; var layer = itemRec.Role & SData.SDIRoleIntermediate ? layerIntermediate : layerProduct; var itemElem = layer.ElementByValue(itemRec.Code); if( itemElem == null ) continue; loc.SetDimensionLayerElement(itemElem); var qtyList = exp.CalculateFor(itemRec); // データキューブに格納 var c = qtyList.Count; for( var i = 0; i < c; i++ ) { loc.SetDimensionLayerElement(layerTime.ElementByIndex(i)); loc.ObtainMeasure().Value(iProduce) = qtyList.Value(i); } if( targetItemRec != null ) break; } } // 生産量調整 - 要求量調整後にスケジューリングしていない分の仮反映 function UpdateValuesForProductionAdjust(dc, project, targetItemRec) { var sdSpace = project.DataSpace; var orderSet = sdSpace.OrderSet; var cOrders = orderSet.CountOfRecords; var expQty = sdSpace.CreateTypedExpression( SData.SDVTypeOrderRec, SData.SDVTypeDouble ); expQty.Parse( ".Qty" + " - ( ( .Operation.DoesExist and .Operation.IsAssigned ) ? .Operation.LinkQty : 0.0 )" ); var expIndex = sdSpace.CreateTypedExpression( SData.SDVTypeOrderRec, SData.SDVTypeLong ); expIndex.Parse("( .LatestEndTime - Project.HorizonStart ).Days"); var loc = dc.CreateLocator(); var layerTime = dc.DimensionByName("Time").PrimitiveLayer; var layerIntermediate = dc.DimensionByName("Item").LayerByName("intermediate"); var layerProduct = dc.DimensionByName("Item").PrimitiveLayer; var iProduce = dc.MeasureElementIndexByName("Produce"); var iVirtual = dc.MeasureElementIndexByName("Virtual"); // 品目が指定されている場合は仮想生産量をクリアする if( targetItemRec != null ) { var layer = targetItemRec.Role & SData.SDIRoleIntermediate ? layerIntermediate : layerProduct; loc.SetDimensionLayerElement(layer.ElementByValue(targetItemRec.Code)); loc.SetMeasureValues(iVirtual, 0.0); // まとめてクリア } // オーダーをスキャン for (var iOrder = 0; iOrder < cOrders; iOrder++) { var orderRec = orderSet.OrderRec(iOrder); if( !orderRec.IsBound ) continue; var itemRec = orderRec.ItemRec; if( targetItemRec != null && itemRec.RecordKey != targetItemRec.RecordKey ) continue; var qty = expQty.CalculateFor(orderRec); if( qty == 0.0 ) continue; var index = expIndex.CalculateFor(orderRec); if( index < 0 ) continue; var layer = itemRec.Role & SData.SDIRoleIntermediate ? layerIntermediate : layerProduct; var itemElem = layer.ElementByValue(itemRec.Code); if( itemElem == null ) continue; loc.SetDimensionLayerElement(itemElem); loc.SetDimensionLayerElement(layerTime.ElementByIndex(index)); loc.ObtainMeasure().Value(iVirtual) = qty; var v = loc.ObtainMeasure().Value(iProduce); loc.ObtainMeasure().Value(iProduce) = v == undefined ? qty : qty + v; } } // 中間品の残量と製品の要求上限 function UpdateValuesForStock( dc, project, intermediateElem, intermediateItemRec ) { var sdSpace = project.DataSpace; var sdlUtil = project.Application.CreateCOMInstance("SDLib.SDLUtility"); var exp = sdSpace.CreateTypedExpression( SData.SDVTypeItemRec, SData.SDVTypeVariantList ); exp.Parse( "TimeList.MakeInRange(Project.HorizonStart,Project.HorizonEnd)" + ".Convert([$$_object.InventoryQty($_object,false)])" ); var qtyList = exp.CalculateFor(intermediateItemRec); var layerTime = dc.DimensionByName("Time").PrimitiveLayer; var cTimes = layerTime.CountOfElements; var loc = dc.CreateLocator(); var iStock = dc.MeasureElementIndexByName("Stock"); var iVirtual = dc.MeasureElementIndexByName("Virtual"); var iLimit = dc.MeasureElementIndexByName("Limit"); var iRequired = dc.MeasureElementIndexByName("Required"); // 子製品リスト var children = []; var cChildren = intermediateElem.CountOfChildren; for( var iChild = 0; iChild < cChildren; iChild++ ) { var child = intermediateElem.Child(iChild); var itemRec = sdSpace.ItemSet.ItemRecByCode(child.Value); var ratio = itemRec.SpecCollection.NumSpec("ratio"); ratio = sdlUtil.IsDoubleNormal(ratio) ? ratio : 1.0; children.push({ child: child, ratio: ratio }); } // 中間品の残量 var virtualQty = 0.0; loc.SetDimensionLayerElement(intermediateElem); var locChild = dc.CreateLocator(); for( var iTime = 0; iTime < cTimes; iTime++ ) { var timeElem = layerTime.ElementByIndex(iTime); loc.SetDimensionLayerElement(timeElem); locChild.SetDimensionLayerElement(timeElem); virtualQty += loc.ObtainMeasure().Value(iVirtual); var cChildren = children.length; for (var iChild = 0; iChild < cChildren; iChild++) { var v = children[iChild]; locChild.SetDimensionLayerElement(v.child); virtualQty -= locChild.ObtainMeasure().Value(iVirtual) * v.ratio; } loc.ObtainMeasure().Value(iStock) = qtyList.Value(iTime) + virtualQty; } // 製品の要求上限 var cChildren = children.length; for( var iChild = 0; iChild < cChildren; iChild++ ) { var v = children[iChild]; locChild.SetDimensionLayerElement(v.child); var futureMin = 9999999.9; for( var iTime = cTimes - 1; iTime >= 0; iTime-- ) { var timeElem = layerTime.ElementByIndex(iTime); loc.SetDimensionLayerElement(timeElem); locChild.SetDimensionLayerElement(timeElem); var qty = loc.MeasureValue(iStock); if( futureMin > qty ) futureMin = qty; var req = locChild.MeasureValue(iRequired); var q = Math.floor(futureMin / v.ratio) + req; locChild.ObtainMeasure().Value(iLimit) = q < 0.0 ? 0.0 : q; } } } /***************************************************************************************/ // generated by WSCGEN function Setup(keyEntity) { ret = _Setup(keyEntity); CollectGarbage(); return ret; } function Command(keyEntity) { ret = _Command(keyEntity); CollectGarbage(); return ret; } function ObtainDatacube(keyEntity) { ret = _ObtainDatacube(keyEntity); CollectGarbage(); return ret; } function ValueChanged(keyEntity) { ret = _ValueChanged(keyEntity); CollectGarbage(); return ret; } function Rescheduled(keyEntity) { ret = _Update(keyEntity); CollectGarbage(); return ret; } function AfterLoadingData(keyEntity) { ret = _Update(keyEntity); CollectGarbage(); return ret; } function VBDateFromJDate(d) { return Script.VBDateFromSeconds( d.getTime() / 1000 - d.getTimezoneOffset() * 60 ); } function VBDateFromLiteral(str) { var d = new Date(str); return Script.VBDateFromSeconds( d.getTime() / 1000 - d.getTimezoneOffset() * 60 ); } function VBDateToJDate(t) { d = new Date(); d.setTime(Script.VBDateToSeconds(t) * 1000 + d.getTimezoneOffset() * 60000); return d; } function alert(msg) { Script.ShowMessageBox(msg, "FLEXSCHE", AIM.MBTOk); }
Communicator上のd-MPSで品目単位でチェックアウト
CommunicatorのプロジェクトでFLEXSCHE d-MPSを使用している場合に、品目のレコード単位のチェックアウトでMPSデータを編集できるようになりました。 従来はデータ全体のチェックアウトが必要だったため、一度に1人の担当者が編集することしかできませんでしたが、品目毎に担当者が違うような場合でもスムーズに編集作業が行えます。 でしょう。