296 lines
13 KiB
Vue
296 lines
13 KiB
Vue
|
<template>
|
||
|
<div v-if="options_med && inputs" class="row">
|
||
|
<div class="col-12">
|
||
|
<div class="mb-3">
|
||
|
<ViewOrderableRawLookup :client="client" label="Orderable" dgnm="O RX" :query="options_med ? options_med.Medication.default.text : null" v-model="common.ORDERABLE" />
|
||
|
</div>
|
||
|
</div>
|
||
|
<template v-for="(instance, idx) in inputs" :key="instance">
|
||
|
<ViewOrderDialogMedOutptInstance :multi="inputs.length > 1" :more="idx + 1 < inputs.length" :dlgdef="dlgdef" :options_med="options_med" :options_schedule="options_schedule" :loadrsp_group="loadrsp_groups[idx]" v-model="inputs[idx]" @add="e => inputs.splice(idx + 1, 0, Object.assign({}, instance))" @remove="e => inputs.splice(idx, 1)" />
|
||
|
</template>
|
||
|
<div class="col-4">
|
||
|
<div class="mb-3">
|
||
|
<Autocomplete label="Indication" :items="options_med['Indication'].items" v-model="common.INDICATION" />
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-8">
|
||
|
<div class="form-floating mb-3">
|
||
|
<input type="text" class="form-control" placeholder=" " v-model="common.COMMENT" />
|
||
|
<label>Comment</label>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-2">
|
||
|
<div class="form-floating mb-3">
|
||
|
<input type="number" class="form-control" placeholder=" " v-model="common.SUPPLY" />
|
||
|
<label>Days Supply</label>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-2">
|
||
|
<div class="form-floating mb-3">
|
||
|
<input type="number" class="form-control" placeholder=" " v-model="common.QTY" />
|
||
|
<label>{{base_shortform ? 'Qty (in ' + base_shortform + ')' : 'Quantity'}}</label>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-2">
|
||
|
<div class="form-floating mb-3">
|
||
|
<input type="number" class="form-control" placeholder=" " v-model="common.REFILLS" />
|
||
|
<label>Refills</label>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-3">
|
||
|
<div class="form-floating mb-3">
|
||
|
<select class="form-select" v-model="common.PICKUP">
|
||
|
<option value="M">Mail</option>
|
||
|
<option value="W">Window</option>
|
||
|
</select>
|
||
|
<label>Pickup</label>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-3">
|
||
|
<div v-if="options_fill && options_fill.Priority && options_fill.Priority.items" class="form-floating mb-3">
|
||
|
<select class="form-select" v-model="common.URGENCY"><option v-for="item in options_fill.Priority.items" :value="item.value">{{item.text}}</option></select>
|
||
|
<label>Priority</label>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-10">
|
||
|
<div class="card mb-3" style="background-color: #fdfbd7;"><div class="card-body" style="font-family: monospace; white-space: pre-wrap;">{{preview}}</div></div>
|
||
|
</div>
|
||
|
<div class="col-2">
|
||
|
<div class="btn-group-vertical mb-3" style="width: 100%;">
|
||
|
<button type="button" class="btn btn-primary" @click="e => $emit('submit', output)">Submit</button>
|
||
|
<button type="button" class="btn btn-danger" @click="e => $emit('cancel')">Cancel</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<script>
|
||
|
import { debounce, groupByArray } from './util.mjs';
|
||
|
import { translate } from './numberwords.mjs';
|
||
|
|
||
|
import ViewOrderableRawLookup from './ViewOrderableRawLookup.vue';
|
||
|
import Autocomplete from './Autocomplete.vue';
|
||
|
import DateTimePicker from './DateTimePicker.vue';
|
||
|
import ViewOrderDialogMedOutptInstance from './ViewOrderDialogMedOutptInstance.vue';
|
||
|
|
||
|
const C_COMMON = ['ORDERABLE', 'INDICATION', 'COMMENT', 'SUPPLY', 'QTY', 'REFILLS', 'PICKUP', 'URGENCY'];
|
||
|
|
||
|
const C_DAYS = {
|
||
|
'': 1,
|
||
|
'MONTH': 30,
|
||
|
'WEEK': 7,
|
||
|
'DAY': 1,
|
||
|
'HOUR': 1/24,
|
||
|
'MINUTE': 1/24/60
|
||
|
};
|
||
|
|
||
|
const C_WP = { '15': true, '758': true, '763': true };
|
||
|
|
||
|
function input_dose_parse(value) {
|
||
|
value = (value) && (value.indexOf('&') >= 0) ? value.split('&') : [parseFloat(value) || null, value ? value.replace(/^\s*([+-]?(?:\d+|\d+\.\d+))\s*|\s+$/g, '').toUpperCase() || null : null, null, null, value, null, null, null];
|
||
|
value.dose = value[0];
|
||
|
value.unit = value[1];
|
||
|
value.count = value[2];
|
||
|
value.form = value[3];
|
||
|
value.text = value[4];
|
||
|
value.id = value[5];
|
||
|
value.base_dose = value[6];
|
||
|
value.base_unit = value[7];
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
function greatest_common_base(values, products) {
|
||
|
for(var i = 0; i < values.length; ++i) {
|
||
|
var value = values[i];
|
||
|
if((isNaN(value.dose)) || (isNaN(parseFloat(value.dose)))) return;
|
||
|
products = products.filter(x => (x.dose) && (x.unit == value.unit) && ((Math.abs((value.dose/x.dose)%1) < 0.001) || ((x.split == '1') && (Math.abs((value.dose/x.dose)%1 - 0.5) < 0.001))));
|
||
|
if(products.length < 1) return;
|
||
|
}
|
||
|
return products.sort((a, b) => b.dose - a.dose)[0];
|
||
|
}
|
||
|
|
||
|
export default {
|
||
|
components: {
|
||
|
ViewOrderableRawLookup, Autocomplete, DateTimePicker, ViewOrderDialogMedOutptInstance
|
||
|
},
|
||
|
props: {
|
||
|
client: Object,
|
||
|
ien: String,
|
||
|
dlgdef: Object,
|
||
|
bldqrsp: Object,
|
||
|
modelValue: Object
|
||
|
},
|
||
|
emits: [
|
||
|
'submit',
|
||
|
'cancel',
|
||
|
'update:modelValue'
|
||
|
],
|
||
|
data() {
|
||
|
return {
|
||
|
loadrsp: null,
|
||
|
loadrsp_groups: [],
|
||
|
inputs: null,
|
||
|
options_schedule: null,
|
||
|
options_med: null,
|
||
|
lastorderable: null,
|
||
|
common: {}
|
||
|
};
|
||
|
},
|
||
|
computed: {
|
||
|
options_med_route() {
|
||
|
if((this.options_med) && (this.options_med['Route'])) {
|
||
|
var opt, rsp, res = this.options_med['Route'].items.slice();
|
||
|
res.mapping = res.reduce((acc, val) => (acc[val.value] = val, acc), {});
|
||
|
if((this.loadrsp) && (rsp = this.loadrsp['ROUTE']) && (!res.find(x => x.value == rsp.iValue))) res.push({ value: rsp.iValue, text: rsp.eValue, abbr: rsp.eValue, sig: rsp.eValue });
|
||
|
return res;
|
||
|
} else return [];
|
||
|
},
|
||
|
input_doses() {
|
||
|
return this.inputs ? this.inputs.map(x => input_dose_parse(x.DOSE)) : [];
|
||
|
},
|
||
|
base_product() {
|
||
|
return this.options_med ? greatest_common_base(this.input_doses, this.options_med['Dispense'].items) : undefined;
|
||
|
},
|
||
|
base_form() {
|
||
|
try {
|
||
|
return this.options_med['AllDoses'].items.find(x => x.id == this.base_product.id).dosefields.split('&')[3];
|
||
|
} catch(ex) {}
|
||
|
},
|
||
|
base_shortform() {
|
||
|
try {
|
||
|
var dose_unit = this.base_product.dose + this.base_product.unit;
|
||
|
return this.options_med['Dosage'].items.find(x => x.text == dose_unit).form;
|
||
|
} catch(ex) {
|
||
|
return this.base_form;
|
||
|
}
|
||
|
},
|
||
|
sig() {
|
||
|
if((this.options_med) && (this.inputs)) {
|
||
|
var res = [];
|
||
|
var verb = this.options_med['Verb'].default;
|
||
|
var input_doses = this.input_doses;
|
||
|
var base_product = this.base_product;
|
||
|
var base_form = this.base_form;
|
||
|
for(var i = 0; i < this.inputs.length; ++i) {
|
||
|
var input = this.inputs[i], input_route = input.ROUTE, input_dose = input_doses[i];
|
||
|
var route = (this.options_med_route.find(x => x.value == input_route) || { sig: input_route }).sig;
|
||
|
var prep = this.options_med_route.mapping[input_route] ? this.options_med['Preposition'].default : null;
|
||
|
var schedule = (this.options_schedule) && (this.options_schedule[input.SCHEDULE]) ? this.options_schedule[input.SCHEDULE].text : input.SCHEDULE;
|
||
|
res.push((input_dose ? verb + ' ' + (base_product ? translate(input_dose.dose/base_product.dose).toUpperCase() + ' ' + (base_form || 'UNIT') + (input_dose.dose/base_product.dose >= 2 ? 'S' : '') : input_dose.text) : '') + (route ? ' ' + (prep ? prep + ' ' : '') + route : '') + (schedule ? ' ' + schedule : '') + (input.SCHTYPE == 'P' ? ' AS NEEDED' : '') + (input.DAYS ? ' FOR ' + input.DAYS : '') + (i + 1 >= this.inputs.length ? '' : (' ' + (input.CONJ == 'T' ? 'THEN' : input.CONJ == 'A' ? 'AND' : 'OR'))));
|
||
|
}
|
||
|
return res.join(' ');
|
||
|
}
|
||
|
},
|
||
|
preview() {
|
||
|
var base_product = this.base_product;
|
||
|
var product = base_product ? base_product.text + '\n' : this.options_med ? this.options_med['Medication'].default.text + '\n' : '';
|
||
|
return product + (this.sig ? this.sig + (this.common.COMMENT ? ' ' + this.common.COMMENT : '') + '\n' : '') + 'Quantity: ' + (+this.common.QTY) + ' Refills: ' + (+this.common.REFILLS) + (this.common.INDICATION ? '\nIndication: ' + this.common.INDICATION : '');
|
||
|
},
|
||
|
output() {
|
||
|
if((this.dlgdef) && (this.inputs)) {
|
||
|
var res = {};
|
||
|
var inputs = this.inputs.length > 0 ? this.inputs.slice() : [{}];
|
||
|
inputs[0] = this.common ? Object.assign({}, inputs[0], this.common) : Object.assign({}, inputs[0]);
|
||
|
inputs[0].SIG = this.sig;
|
||
|
if(this.base_product) {
|
||
|
inputs[0].STRENGTH = this.base_product.dose + this.base_product.unit;
|
||
|
inputs[0].DRUG = this.base_product.id;
|
||
|
}
|
||
|
for(var i = 0; i < inputs.length; ++i) {
|
||
|
var seq = i + 1, instance = inputs[i];
|
||
|
for(var j = 0, desc, val; j < this.dlgdef.length; ++j) {
|
||
|
desc = this.dlgdef[j];
|
||
|
if(val = instance[desc.promptID]) {
|
||
|
if(C_WP[desc.promptIEN]) {
|
||
|
res['"' + desc.promptIEN + '","' + seq + '"'] = 'ORDIALOG("WP",' + desc.promptIEN + ',' + seq + ')';
|
||
|
res['"WP","' + desc.promptIEN + '","' + seq + '","1","0"'] = instance[desc.promptID];
|
||
|
} else res['"' + desc.promptIEN + '","' + seq + '"'] = instance[desc.promptID];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
created() {
|
||
|
this.$watch(
|
||
|
() => this.client,
|
||
|
async () => {
|
||
|
if(this.client) {
|
||
|
this.options_schedule = await this.client.ORWDPS1_SCHALL('', 0);
|
||
|
this.options_fill = await this.client.ORWDPS1_ODSLCT('O', '', '');
|
||
|
} else this.options_schedule = this.options_fill = null;
|
||
|
},
|
||
|
{ immediate: true }
|
||
|
);
|
||
|
this.$watch(
|
||
|
() => (this.client, this.dlgdef, this.bldqrsp, this.options_fill, {}),
|
||
|
async () => {
|
||
|
if((this.client) && (this.dlgdef) && (this.bldqrsp)) {
|
||
|
this.inputs = [this.dlgdef.reduce((acc, val) => (acc[val.promptID] = acc[val.promptID], acc), { ORDERABLE: this.common ? this.common.ORDERABLE : null })];
|
||
|
if(this.options_fill) {
|
||
|
if((this.options_fill.Pickup) && (this.options_fill.Pickup.default)) this.inputs.PICKUP = this.options_fill.Pickup.default.value;
|
||
|
if((this.options_fill.Refills) && (this.options_fill.Refills.default)) this.inputs.REFILLS = this.options_fill.Refills.default.value;
|
||
|
if((this.options_fill.Priority) && (this.options_fill.Priority.default)) this.inputs.URGENCY = this.options_fill.Priority.default.value;
|
||
|
}
|
||
|
this.loadrsp_groups = [];
|
||
|
if((this.bldqrsp.ResponseID) && (this.bldqrsp.ResponseID != '0')) {
|
||
|
this.loadrsp = await this.client.ORWDX_LOADRSP(this.bldqrsp.ResponseID, 0, 0);
|
||
|
if((this.loadrsp) && (this.loadrsp.length > 0)) {
|
||
|
this.loadrsp_groups = groupByArray(this.loadrsp, 'instance').sort((a, b) => a.key - b.key).map(x => x.values.reduce((acc, val) => (acc[val.promptID] = val, acc), x.values));
|
||
|
Object.assign(this.inputs[0], this.loadrsp_groups[0].reduce((acc, val) => (acc[val.promptID] = val.iValue != '^WP^' ? val.iValue : val.eValue, acc), {}));
|
||
|
for(var i = 1; i < this.loadrsp_groups.length; ++i) this.inputs.push(this.loadrsp_groups[i].reduce((acc, val) => (acc[val.promptID] = val.iValue != '^WP^' ? val.iValue : val.eValue, acc), {}));
|
||
|
}
|
||
|
}
|
||
|
this.common = C_COMMON.reduce((acc, val) => (acc[val] = this.inputs[0][val], acc), {});
|
||
|
} else {
|
||
|
this.inputs = this.loadrsp = null;
|
||
|
this.loadrsp_groups = [];
|
||
|
}
|
||
|
},
|
||
|
{ immediate: true }
|
||
|
);
|
||
|
this.$watch(
|
||
|
() => (this.client, (this.common) && (this.common.ORDERABLE), {}),
|
||
|
async () => {
|
||
|
this.options_med = (this.client) && (this.common) && (this.common.ORDERABLE) ? await this.client.ORWDPS2_OISLCT(this.common.ORDERABLE, '', '', 'Y', '') : null;
|
||
|
}
|
||
|
);
|
||
|
this.$watch(
|
||
|
() => ((this.common) && (this.common.ORDERABLE), {}),
|
||
|
() => {
|
||
|
if((this.common) && (this.common.ORDERABLE)) {
|
||
|
if((this.lastorderable) && (this.lastorderable != this.common.ORDERABLE)) {
|
||
|
for(var k in this.common) if((k != 'ORDERABLE') && (this.common.hasOwnProperty(k))) this.common[k] = null;
|
||
|
this.inputs = [{}];
|
||
|
this.loadrsp_groups = [];
|
||
|
}
|
||
|
this.lastorderable = this.common.ORDERABLE;
|
||
|
}
|
||
|
},
|
||
|
{ immediate: true }
|
||
|
);
|
||
|
this.$watch(
|
||
|
() => this.inputs ? this.inputs.map(x => x.DAYS ? parseFloat(x.DAYS)*C_DAYS[x.DAYS.replace(/^\s*([+-]?(?:\d+|\d+\.\d+))\s*|S\b|\s+$/g, '')] : 0).reduce((acc, val) => acc + val, 0) : 0,
|
||
|
(value) => { this.common.SUPPLY = value; },
|
||
|
{ immediate: true }
|
||
|
);
|
||
|
this.$watch(
|
||
|
() => (this.inputs) && (this.inputs.length > 0) && (this.base_product) && (this.common.SUPPLY > 0) ? this.inputs.map(x => [(x.DOSE) && (x.DOSE).indexOf('&') >= 0 ? x.DOSE.split('&')[2] : '~', x.SCHEDULE || '~', x.DAYS || '~']).reduce((acc, val) => (acc[0] += val[0] + '^', acc[1] += val[1] + '^', acc[2] += val[2] + '^', acc), ['', '', '']) : null,
|
||
|
debounce(async (value) => {
|
||
|
if(value) {
|
||
|
var res = await this.client.ORWDPS2_DAY2QTY(this.common.SUPPLY, value[0], value[1], value[2], '', '');
|
||
|
if(!isNaN(parseFloat(res))) this.common.QTY = res;
|
||
|
};
|
||
|
}, 500),
|
||
|
{ immediate: true }
|
||
|
);
|
||
|
this.$watch(
|
||
|
() => { return { dlgdef: this.dlgdef, inputs: this.inputs, common: this.common, sig: this.sig }; },
|
||
|
debounce(() => this.$emit('update:modelValue', this.output), 100),
|
||
|
{ deep: true, immediate: true }
|
||
|
);
|
||
|
}
|
||
|
};
|
||
|
</script>
|