<field name="id" reporter:datatype="id" />
<field name="org_unit" reporter:datatype="org_unit"/>
<field name="reason" reporter:datatype="text"/>
+ <field name="full_day" reporter:datatype="bool"/>
+ <field name="multi_day" reporter:datatype="bool"/>
</fields>
<links>
<link field="org_unit" reltype="has_a" key="id" map="" class="aou"/>
{column => 'id', alias => 'call_number'},
{column => 'owning_lib', alias => 'call_number_owning_lib'}
],
- circ => ['due_date'],
+ circ => ['due_date',{column => 'circ_lib', alias => 'circ_circ_lib'}],
acnp => [
{column => 'label', alias => 'call_number_prefix_label'},
{column => 'id', alias => 'call_number_prefix'}
$c->$circ_lib_method, 'circ.fines.truncate_to_max_fine');
$truncate_to_max_fine = $U->is_true($truncate_to_max_fine);
+ my $tz = $U->ou_ancestor_setting_value(
+ $c->$circ_lib_method, 'lib.timezone') || 'local';
+
my ($latest_billing_ts, $latest_amount) = ('',0);
for (my $bill = 1; $bill <= $pending_fine_count; $bill++) {
last;
}
- # XXX Use org time zone (or default to 'local') once we have the ou setting built for that
- my $billing_ts = DateTime->from_epoch( epoch => $last_fine, time_zone => 'local' );
+ # Use org time zone (or default to 'local')
+ my $billing_ts = DateTime->from_epoch( epoch => $last_fine, time_zone => $tz );
my $current_bill_count = $bill;
while ( $current_bill_count ) {
$billing_ts->add( seconds_to_interval_hash( $fine_interval ) );
__PACKAGE__->table( 'actor_org_unit_closed' );
__PACKAGE__->columns( Primary => qw/id/);
-__PACKAGE__->columns( Essential => qw/org_unit close_start close_end reason/);
+__PACKAGE__->columns( Essential => qw/org_unit close_start close_end reason full_day multi_day/);
#-------------------------------------------------------------------------------
# turns an ISO date into something TT can understand
$locale_subs->{parse_datetime} = sub {
my $date = shift;
+ my $context_org = shift; # optional, for setting timezone via YAOUS
# Calling parse_datetime() with empty $date will lead to Internal Server Error
return '' if (!defined($date) or $date eq '');
my $cleansed_date = cleanse_ISO8601($date);
$date = DateTime::Format::ISO8601->new->parse_datetime($cleansed_date);
+ if ($context_org) {
+ $context_org = $context_org->id if ref($context_org);
+ my $tz = $locale_subs->{get_org_setting}->($context_org,'lib.timezone');
+ $date->set_time_zone($tz) if ($tz);
+ }
return sprintf(
"%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
$date->hour,
org_unit INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
close_start TIMESTAMP WITH TIME ZONE NOT NULL,
close_end TIMESTAMP WITH TIME ZONE NOT NULL,
+ full_day BOOLEAN NOT NULL DEFAULT FALSE,
+ multi_day BOOLEAN NOT NULL DEFAULT FALSE,
reason TEXT
);
);
INSERT INTO config.copy_tag_type (code, label, owner) VALUES ('bookplate', 'Digital Bookplate', 1);
+
+INSERT into config.org_unit_setting_type
+( name, grp, label, description, datatype ) VALUES
+
+( 'lib.timezone', 'lib',
+ oils_i18n_gettext('lib.timezone',
+ 'Library time zone',
+ 'coust', 'label'),
+ oils_i18n_gettext('lib.timezone',
+ 'Define the time zone in which a library physically resides',
+ 'coust', 'description'),
+ 'string');
--- /dev/null
+BEGIN;
+
+INSERT into config.org_unit_setting_type
+( name, grp, label, description, datatype ) VALUES
+
+( 'lib.timezone', 'lib',
+ oils_i18n_gettext('lib.timezone',
+ 'Library time zone',
+ 'coust', 'label'),
+ oils_i18n_gettext('lib.timezone',
+ 'Define the time zone in which a library physically resides',
+ 'coust', 'description'),
+ 'string');
+
+ALTER TABLE actor.org_unit_closed ADD COLUMN full_day BOOLEAN DEFAULT FALSE;
+ALTER TABLE actor.org_unit_closed ADD COLUMN multi_day BOOLEAN DEFAULT FALSE;
+
+UPDATE actor.org_unit_closed SET multi_day = TRUE
+ WHERE close_start::DATE <> close_end::DATE;
+
+UPDATE actor.org_unit_closed SET full_day = TRUE
+ WHERE close_start::DATE = close_end::DATE
+ AND SUBSTRING(close_start::time::text FROM 1 FOR 8) = '00:00:00'
+ AND SUBSTRING(close_end::time::text FROM 1 FOR 8) = '23:59:59';
+
+COMMIT;
+
[% date.format(ctx.parse_datetime(circ.circ.xact_start),DATE_FORMAT); %]
</td>
<td>
- [% date.format(ctx.parse_datetime(circ.circ.due_date),DATE_FORMAT); %]
+ [% date.format(ctx.parse_datetime(circ.circ.due_date, circ.circ.circ_lib),DATE_FORMAT); %]
</td>
<td>
[% IF circ.circ.checkin_time;
[% circ.circ.renewal_remaining %]
</td>
[%
- due_date = ctx.parse_datetime(circ.circ.due_date);
+ due_date = ctx.parse_datetime(circ.circ.due_date, circ.circ.circ_lib);
due_class = (date.now > date.format(due_date, '%s')) ? 'error' : '';
%]
<td name="due_date" class='[% due_class %]'>
</td>
<td name='myopac_circ_trans_due'>
[% ts = f.xact.circulation.due_date || f.xact.reservation.end_time || 0;
+ due_org = f.xact.circulation.circ_lib || f.xact.reservation.pickup_lib;
IF ts;
- date.format(ctx.parse_datetime(ts), DATE_FORMAT);
+ date.format(ctx.parse_datetime(ts, due_org), DATE_FORMAT);
END %]
</td>
<td name='myopac_circ_trans_finished'>
<td>[% bib.target_copy.barcode | html %]</td>
<td>[% copy_info.copy_location | html %]</td>
<td>[% copy_info.copy_status | html %]</td>
- <td>[% copy_info.due_date | html %]</td>
+ <td>[% date.format(ctx.parse_datetime(copy_info.due_date, copy_info.circ_circ_lib),DATE_FORMAT) %]</td>
</tr>
[%- END; # FOREACH peer
END; # FOREACH bib
<td>[%
IF copy_info.due_date;
date.format(
- ctx.parse_datetime(copy_info.due_date),
+ ctx.parse_datetime(copy_info.due_date, copy_info.circ_circ_lib),
DATE_FORMAT
);
ELSE;
<script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/iframeResizer.min.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/ng-order-object-by.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/lovefield.min.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/moment-with-locales.min.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/moment-timezone-with-data.min.js"></script>
<!-- IDL / opensrf (network) -->
<script src="[% ctx.media_prefix %]/js/dojo/opensrf/JSON_v1.js"></script>
<div class="flex-cell">[% l('Check Out Date') %]</div>
<div class="flex-cell well">{{circ.xact_start() | date:egDateAndTimeFormat}}</div>
<div class="flex-cell">[% l('Due Date') %]</div>
- <div class="flex-cell well">{{circ.due_date() | date:egDateAndTimeFormat}}</div>
+ <div class="flex-cell well">{{circ.due_date() | egDueDate:egDateAndTimeFormat:circ.circ_lib():circ.duration()}}</div>
<div class="flex-cell">[% l('Stop Fines Time') %]</div>
<div class="flex-cell well">{{circ.stop_fines_time() | date:egDateAndTimeFormat}}</div>
<div class="flex-cell">[% l('Checkin Time') %]</div>
<eg-grid-field label="[% l('Alert Message') %]" path='alert_message' visible></eg-grid-field>
<eg-grid-field label="[% l('Barcode') %]" path='barcode' visible></eg-grid-field>
<eg-grid-field label="[% l('Call Number') %]" path="call_number.label" visible></eg-grid-field>
- <eg-grid-field label="[% l('Due Date') %]" path="_circ.due_date" datatype="timestamp" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Due Date') %]" path="_circ.due_date" datecontext="_circ_lib" dateonlyinterval="_duration" datatype="timestamp" visible></eg-grid-field>
<eg-grid-field label="[% l('Location') %]" path="location.name" visible></eg-grid-field>
<eg-grid-field label="[% l('Copy Status') %]" path="status.name" visible></eg-grid-field>
<div class="flex-cell well">{{copy.call_number().label()}}</div>
<div class="flex-cell">[% l('Due Date') %]</div>
- <div class="flex-cell well">{{circ.due_date() | date:egDateAndTimeFormat}}</div>
+ <div class="flex-cell well">{{circ.due_date() | egDueDate:egDateAndTimeFormat:circ.circ_lib():circ.duration()}}</div>
</div>
<div class="flex-row">
</eg-grid-field>
<eg-grid-field label="[% l('Due Date') %]"
- path='circ.due_date' datatype="timestamp" hidden></eg-grid-field>
+ path='circ.due_date' dateonlyinterval="duration" datecontext="circ_lib" datatype="timestamp" hidden></eg-grid-field>
<eg-grid-field label="[% l('Author') %]"
path="author" hidden></eg-grid-field>
<eg-grid-field path='grocery.*' hidden> </eg-grid-field>
<eg-grid-field label="[% l('Billing Location') %]"
path='grocery.billing_location.shortname' required hidden> </eg-grid-field>
+ <eg-grid-field path='circulation.circ_lib' required hidden></eg-grid-field>
+ <eg-grid-field path='circulation.duration' required hidden></eg-grid-field>
+ <eg-grid-field path='circulation.due_date' required hidden>
+ {{ circulation.due_date | egDueDate:$root.egDateAndTimeFormat:circulation.circ_lib:circulation.duration}}
+ </eg-grid-field>
<eg-grid-field path='circulation.*' hidden> </eg-grid-field>
<eg-grid-field label="[% l('Checkout / Renewal Library') %]"
path='circulation.circ_lib.shortname' required hidden> </eg-grid-field>
path="acn.label"></eg-grid-field>
<eg-grid-field label="[% l('Due Date') %]"
- path='circ.due_date' datatype="timestamp"></eg-grid-field>
+ path='circ.due_date' datecontext="circ_lib" dateonlyinterval="duration" datatype="timestamp"></eg-grid-field>
<eg-grid-field label="[% l('Family Name') %]"
path='au.family_name'></eg-grid-field>
<eg-grid-field label="[% l('Item Type') %]" path='item_type.name'></eg-grid-field>
<eg-grid-field label="[% l('Checkout Library') %]" path='circ_lib.shortname'></eg-grid-field>
<eg-grid-field label="[% l('Checkout Date') %]" path='circ_time' datatype="timestamp"></eg-grid-field>
- <eg-grid-field label="[% l('Due Date') %]" path='duedate' datatype="timestamp"></eg-grid-field>
+ <eg-grid-field label="[% l('Due Date') %]" path='duedate' dateformat="shortDate" datatype="timestamp"></eg-grid-field>
<eg-grid-field label="[% l('Checkout Staff') %]" path='staff.usrname'></eg-grid-field>
</eg-grid>
{{item.target_copy().barcode()}}
</a>
</eg-grid-field>
- <eg-grid-field label="[% l('Due Date') %]" path='due_date' datatype="timestamp"></eg-grid-field>
+ <eg-grid-field label="[% l('Due Date') %]" path='due_date' datefilter="egDueDate" dateonlyinterval="duration" datecontext="circ_lib" datatype="timestamp"></eg-grid-field>
<eg-grid-field label="[% l('Workstation') %]" path='workstation.name'></eg-grid-field>
<eg-grid-field label="[% l('Checkin Workstation') %]" path='checkin_workstation.name'></eg-grid-field>
<eg-grid-field label="[% l('Checkout / Renewal Library') %]" path='circ_lib.shortname'></eg-grid-field>
<div class="col-md-2 strong-text">[% l('Total Billed') %]</div>
<div class="col-md-2">{{xact.summary().balance_owed() | currency}}</div>
<div class="col-md-2 strong-text">[% l('Due Date') %]</div>
- <div class="col-md-2">{{xact.circulation().due_date() | date:$root.egDateAndTimeFormat}}</div>
+ <div class="col-md-2">{{xact.circulation().due_date() | egDueDate:$root.egDateAndTimeFormat:xact.circulation().circ_lib():xact.circulation().duration()}}</div>
</div>
<div class="row">
<div class="col-md-2 strong-text">[% l('Finish') %]</div>
path="acn.label"></eg-grid-field>
<eg-grid-field label="[% l('Due Date') %]"
- path='circ.due_date' datatype="timestamp"></eg-grid-field>
+ path='circ.due_date' datecontext="circ_lib" dateonlyinterval="duration" datatype="timestamp"></eg-grid-field>
<eg-grid-field label="[% l('Family Name') %]"
path='au.family_name'></eg-grid-field>
<div>{{checkout.title}}</div>
<div>[% l('Barcode: [_1] Due: [_2]',
'{{checkout.copy.barcode}}',
- '{{checkout.circ.due_date | date:$root.egDateAndTimeFormat}}') %]</div>
+ '{{checkout.circ.due_date | egDueDate:$root.egDateAndTimeFormat:checkout.circ.circ_lib:checkout.circ.duration}}') %]</div>
</li>
</ol>
<hr/>
<div>{{checkout.title}}</div>
<div>[% l('Barcode: [_1] Due: [_2]',
'{{checkout.copy.barcode}}',
- '{{checkout.circ.due_date | date:$root.egDateAndTimeFormat}}') %]</div>
+ '{{checkout.circ.due_date | egDueDate:$root.egDateAndTimeFormat:checkout.circ.circ_lib:checkout.circ.duration}}') %]</div>
</li>
</ol>
<hr/>
<div>{{renewal.title}}</div>
<div>[% l('Barcode: [_1] Due: [_2]',
'{{renewal.copy.barcode}}',
- '{{renewal.circ.due_date | date:$root.egDateAndTimeFormat}}') %]</div>
+ '{{renewal.circ.due_date | egDueDate:$root.egDateAndTimeFormat:renewal.circ.circ_lib:renewal.circ.duration}}') %]</div>
</li>
</ol>
<hr/>
<!-- otherwise, simply display the item value, which may
pass through datatype-specific filtering. -->
<span ng-if="!col.template" style="padding-left:5px; padding-right:10px;">
- {{itemFieldValue(item, col) | egGridValueFilter:col}}
+ {{itemFieldValue(item, col) | egGridValueFilter:col:item}}
</span>
</div>
</div>
'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js',
'node_modules/angular-order-object-by/src/ng-order-object-by.js',
'node_modules/lovefield/dist/lovefield.min.js',
- 'node_modules/lovefield/dist/lovefield.min.js.map'
+ 'node_modules/lovefield/dist/lovefield.min.js.map',
+ 'node_modules/moment/min/moment-with-locales.min.js',
+ 'node_modules/moment-timezone/builds/moment-timezone-with-data.min.js'
]
}]
},
'build/js/angular-tree-control.js',
'build/js/ngToast.min.js',
'build/js/lovefield.min.js',
+ 'bulid/js/moment-with-locales.min.js',
+ 'build/js/moment-timezone-with-data.min.js',
// NOTE: OpenSRF must be installed
// XXX: Should not be hard-coded
'/openils/lib/javascript/JSON_v1.js',
checkins : [
{
due_date : new Date().toISOString(),
+ circ_lib : 1,
+ duration : '7 days',
target_copy : seed_copy,
copy_barcode : seed_copy.barcode,
call_number : seed_copy.call_number,
{
circ : {
due_date : new Date().toISOString(),
+ circ_lib : 1,
+ duration : '7 days'
},
copy : seed_copy,
title : seed_record.title,
if (copyData.circ) {
flatCopy._circ = egCore.idl.toHash(copyData.circ, true);
flatCopy._circ_summary = egCore.idl.toHash(copyData.circ_summary, true);
+ flatCopy._circ_lib = copyData.circ.circ_lib();
+ flatCopy._duration = copyData.circ.duration();
}
flatCopy.index = service.index++;
service.copies.unshift(flatCopy);
data.isbn = final_resp.evt[0].isbn;
data.route_to = final_resp.evt[0].route_to;
+ if (payload.circ) data.duration = payload.circ.duration();
+ if (payload.circ) data.circ_lib = payload.circ.circ_lib();
+
// for checkin, the mbts lives on the main circ
if (payload.circ && payload.circ.billable_transaction())
data.mbts = payload.circ.billable_transaction().summary();
"angular-tree-control": "~0.2.28",
"angular-order-object-by": "rxfork/ngOrderObjectBy#npm",
"lovefield": "*",
+ "moment": "*",
+ "moment-timezone": "*",
"bootstrap": "~3.3.6",
"grunt": "~0.4.4",
"grunt-cli": "^0.1.13",
menuLabel : '@',
dateformat : '@', // optional: passed down to egGridValueFilter
+ datecontext: '@', // optional: passed down to egGridValueFilter to choose TZ
+ datefilter: '@', // optional: passed down to egGridValueFilter to choose specialized date filters
+ dateonlyinterval: '@', // optional: passed down to egGridValueFilter to choose a "better" format
// Hash of control functions.
//
defaultToHidden : (features.indexOf('-display') > -1),
defaultToNoSort : (features.indexOf('-sort') > -1),
defaultToNoMultiSort : (features.indexOf('-multisort') > -1),
- defaultDateFormat : $scope.dateformat
+ defaultDateFormat : $scope.dateformat,
+ defaultDateContext : $scope.datecontext,
+ defaultDateFilter : $scope.datefilter,
+ defaultDateOnlyInterval : $scope.dateonlyinterval
});
$scope.canMultiSelect = (features.indexOf('-multiselect') == -1);
// bare value
var val = grid.dataProvider.itemFieldValue(item, col);
// filtered value (dates, etc.)
- val = $filter('egGridValueFilter')(val, col);
+ val = $filter('egGridValueFilter')(val, col, item);
csvStr += grid.csvDatum(val);
csvStr += ',';
}
flex : '@', // optional; default flex width
align : '@', // optional; default alignment, left/center/right
dateformat : '@', // optional: passed down to egGridValueFilter
+ datecontext: '@', // optional: passed down to egGridValueFilter to choose TZ
+ datefilter: '@', // optional: passed down to egGridValueFilter to choose specialized date filters
+ dateonlyinterval: '@', // optional: passed down to egGridValueFilter to choose a "better" format
// if a field is part of an IDL object, but we are unable to
// determine the class, because it's nested within a hash
cols.defaultToNoSort = args.defaultToNoSort;
cols.defaultToNoMultiSort = args.defaultToNoMultiSort;
cols.defaultDateFormat = args.defaultDateFormat;
+ cols.defaultDateContext = args.defaultDateContext;
// resets column width, visibility, and sort behavior
// Visibility resets to the visibility settings defined in the
multisortable : colSpec.multisortable,
nonmultisortable : colSpec.nonmultisortable,
dateformat : colSpec.dateformat,
+ datecontext : colSpec.datecontext,
+ datefilter : colSpec.datefilter,
+ dateonlyinterval : colSpec.dateonlyinterval,
parentIdlClass : colSpec.parentIdlClass
};
}
column.dateformat = cols.defaultDateFormat;
}
+ if (cols.defaultDateOnlyInterval && ! column.dateonlyinterval) {
+ column.dateonlyinterval = cols.defaultDateOnlyInterval;
+ }
+
+ if (cols.defaultDateContext && ! column.datecontext) {
+ column.datecontext = cols.defaultDateContext;
+ }
+
+ if (cols.defaultDateFilter && ! column.datefilter) {
+ column.datefilter = cols.defaultDateFilter;
+ }
+
cols.columns.push(column);
// Track which columns are visible by default in case we
* value. (Though we could manually translate instead..)
* Others likely to follow...
*/
-.filter('egGridValueFilter', ['$filter', function($filter) {
- return function(value, column) {
- switch(column.datatype) {
- case 'bool':
+.filter('egGridValueFilter', ['$filter','egCore', function($filter,egCore) {
+ var GVF = function(value, column, item) {
+ switch(column.datatype) {
+ case 'bool':
switch(value) {
- // Browser will translate true/false for us
+ // Browser will translate true/false for us
case 't' :
case '1' : // legacy
case true:
// value may be null, '', etc.
default : return '';
}
- case 'timestamp':
- // canned angular date filter FTW
- if (!column.dateformat)
- column.dateformat = 'shortDate';
- return $filter('date')(value, column.dateformat);
- case 'money':
+ case 'timestamp':
+ var interval = angular.isFunction(item[column.dateonlyinterval])
+ ? item[column.dateonlyinterval]()
+ : item[column.dateonlyinterval];
+
+ var context = angular.isFunction(item[column.datecontext])
+ ? item[column.datecontext]()
+ : item[column.datecontext];
+
+ var date_filter = column.datefilter || 'egOrgDateInContext';
+
+ return $filter(date_filter)(value, column.dateformat, context, interval);
+ case 'money':
return $filter('currency')(value);
- default:
- return value;
- }
- }
+ default:
+ return value;
+ }
+ };
+
+ GVF.$stateful = true;
+ return GVF;
}]);
};
})
+// 'egOrgDate' filter
+// Uses moment.js and moment-timezone.js to put dates into the most appropriate
+// timezone for a given (optional) org unit based on its lib.timezone setting
+.filter('egOrgDate',['egCore',
+ function(egCore) {
+
+ var formatMap = {
+ short : 'l LT',
+ medium : 'lll',
+ long : 'LLL',
+ full : 'LLLL',
+
+ shortDate : 'l',
+ mediumDate : 'll',
+ longDate : 'LL',
+ fullDate : 'LL',
+
+ shortTime : 'LT',
+ mediumTime : 'LTS'
+ };
+
+ var formatReplace = [
+ [ /yyyy/g, 'YYYY' ],
+ [ /yy/g, 'YY' ],
+ [ /y/g, 'Y' ],
+ [ /ww/g, 'WW' ],
+ [ /w/g, 'W' ],
+ [ /dd/g, 'DD' ],
+ [ /d/g, 'D' ],
+ [ /sss/g, 'SSS' ],
+ [ /EEEE/g, 'dddd' ],
+ [ /EEE/g, 'ddd' ],
+ [ /Z/g, 'ZZ' ]
+ ];
+
+ var tzcache = {'*':null};
+
+ function eg_date_filter (date, format, ouID) {
+ var fmt = formatMap[format] || format;
+ angular.forEach(formatReplace, function (r) {
+ fmt = fmt.replace(r[0],r[1]);
+ });
+
+ var d;
+ if (ouID) {
+ if (angular.isObject(ouID)) {
+ if (angular.isFunction(ouID.id)) {
+ ouID = ouID.id();
+ } else {
+ ouID = ouID.id;
+ }
+ }
+
+ if (tzcache[ouID] && tzcache[ouID] !== '-') {
+ d = moment(date).tz(tzcache[ouID]);
+ } else {
+
+ if (!tzcache[ouID]) {
+ tzcache[ouID] = '-';
+
+ egCore.org.settings('lib.timezone', ouID)
+ .then(function(s) {
+ tzcache[ouID] = s['lib.timezone'] || OpenSRF.tz;
+ });
+ }
+
+ d = moment(date);
+ }
+ } else {
+ d = moment(date);
+ }
+
+ return d.isValid() ? d.format(fmt) : '';
+ }
+
+ eg_date_filter.$stateful = true;
+
+ return eg_date_filter;
+}])
+
+// 'egOrgDateInContext' filter
+// Uses the egOrgDate filter to make time and date location aware, and further
+// modifies the format if one of [short, medium, long, full] to show only the
+// date if the optional interval parameter is day-granular. This is
+// particularly useful for due dates on circulations.
+.filter('egOrgDateInContext',['$filter','egCore',
+ function($filter , egCore) {
+
+ function eg_context_date_filter (date, format, orgID, interval) {
+ var fmt = format;
+ if (!fmt) fmt = 'shortDate';
+
+ // if this is a simple, one-word format, and it doesn't say "Date" in it...
+ if (['short','medium','long','full'].filter(function(x){return fmt == x}).length > 0 && interval) {
+ var secs = egCore.date.intervalToSeconds(interval);
+ if (secs !== null && secs % 86400 == 0) fmt += 'Date';
+ }
+
+ return $filter('egOrgDate')(date, fmt, orgID);
+ }
+
+ eg_context_date_filter.$stateful = true;
+
+ return eg_context_date_filter;
+}])
+
+// 'egDueDate' filter
+// Uses the egOrgDateInContext filter to make time and date location aware, but
+// only if the supplied interval is day-granular. This is as wrapper for
+// egOrgDateInContext to be used for circulation due date /only/.
+.filter('egDueDate',['$filter','egCore',
+ function($filter , egCore) {
+
+ function eg_context_due_date_filter (date, format, orgID, interval) {
+ if (interval) {
+ var secs = egCore.date.intervalToSeconds(interval);
+ if (secs === null || secs % 86400 != 0) {
+ orgID = null;
+ interval = null;
+ }
+ }
+ return $filter('egOrgDateInContext')(date, format, orgID, interval);
+ }
+
+ eg_context_due_date_filter.$stateful = true;
+
+ return eg_context_due_date_filter;
+}])
+
/**
* Progress Dialog.
*
+dojo.require('fieldmapper.AutoIDL');
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('fieldmapper.OrgUtils');
+dojo.require('openils.Event');
+
var myPackageDir = 'open_ils_staff_client'; var IAMXUL = true; var g = {};
var FETCH_CLOSED_DATES = 'open-ils.actor:open-ils.actor.org_unit.closed.retrieve.all';
var FETCH_CLOSED_DATE = 'open-ils.actor:open-ils.actor.org_unit.closed.retrieve';
var cdTbody;
var cdDateCache = {};
+var orgTZ = {};
var selectedStart;
var selectedEnd;
removeChildren(cdTbody);
for( var d = 0; d < dates.length; d++ ) {
var date = dates[d];
- var row = cdBuildRow( date );
- cdTbody.appendChild(row);
+ // super-closure!
+ (function (date) {
+ cdGetTZ(date.org_unit(), function () {
+ var row = cdBuildRow( date );
+ cdTbody.appendChild(row);
+ })
+ })(date);
}
}
-function cdDateToHours(date) {
- var date_obj = new Date(Date.parse(date));
- var hrs = date_obj.getHours();
- var mins = date_obj.getMinutes();
+function cdDateToHours(date, org) {
+ var date_obj = moment(date).tz(orgTZ[org]);
+ var hrs = date_obj.hours();
+ var mins = date_obj.minutes();
// wee, strftime
if (hrs < 10) hrs = '0' + hrs;
if (mins < 10) mins = '0' + mins;
return hrs + ':' + mins;
}
-function cdDateToDate(date) {
- var date_obj = new Date(Date.parse(date));
- return date_obj.toLocaleDateString();
+function cdDateToDate(date, org) {
+ var date_obj = moment(date).tz(orgTZ[org]);
+ return date_obj.format('YYYY-MM-DD');
+}
+
+function cdGetTZ(org, callback) {
+ if (orgTZ[org]) {
+ if (callback) callback();
+ return;
+ }
+
+ fieldmapper.standardRequest(
+ [ 'open-ils.actor',
+ 'open-ils.actor.ou_setting.ancestor_default.batch'],
+ { async: true,
+ params: [org, ['lib.timezone'], SESSION],
+ oncomplete: function(r) {
+ var data = r.recv().content();
+ if(e = openils.Event.parse(data))
+ return alert(e);
+ orgTZ[org] = data['lib.timezone'].value || OpenSRF.tz;
+ if (callback) callback();
+ }
+ }
+ );
}
cdDateCache[date.id()] = date;
- var sh = cdDateToHours(date.close_start());
- var sd = cdDateToDate(date.close_start());
- var eh = cdDateToHours(date.close_end());
- var ed = cdDateToDate(date.close_end());
+ var sh = cdDateToHours(date.close_start(), date.org_unit());
+ var sd = cdDateToDate(date.close_start(), date.org_unit());
+ var eh = cdDateToHours(date.close_end(), date.org_unit());
+ var ed = cdDateToDate(date.close_end(), date.org_unit());
var row;
var flesh = false;
- if( sh == '00:00' && eh == '23:59' ) {
+ if( isTrue(date.full_day()) ) {
- if( sd == ed ) {
+ if( !isTrue(date.multi_day()) ) {
row = cdAllDayTemplate.cloneNode(true);
$n(row, 'start_date').appendChild(text(sd));
}
function cdEditFleshRow(row, date) {
- $n(row, 'start_time').appendChild(text(cdDateToHours(date.close_start())));
- $n(row, 'start_date').appendChild(text(cdDateToDate(date.close_start())));
- $n(row, 'end_time').appendChild(text(cdDateToHours(date.close_end())));
- $n(row, 'end_date').appendChild(text(cdDateToDate(date.close_end())));
+ $n(row, 'start_time').appendChild(text(cdDateToHours(date.close_start(), date.org_unit())));
+ $n(row, 'start_date').appendChild(text(cdDateToDate(date.close_start(), date.org_unit())));
+ $n(row, 'end_time').appendChild(text(cdDateToHours(date.close_end(), date.org_unit())));
+ $n(row, 'end_date').appendChild(text(cdDateToDate(date.close_end(), date.org_unit())));
}
return t && t.match(/\d{2}:\d{2}:\d{2}/);
}
-function cdDateStrToDate( str ) {
+function cdDateStrToDate( str, org, callback ) {
+ if (!org) org = cdCurrentOrg();
- var date = new Date();
- var data = str.split(/ /);
+ if (callback) { // async mode
+ if (!orgTZ[org]) { // fetch then call again
+ return cdGetTZ(org, function () {
+ cdDateStrToDate( str, org, callback );
+ });
+ } else {
+ var d = cdDateStrToDate( str, org );
+ return callback(d);
+ }
+ }
+
+ var date;
+ if (orgTZ[org]) {
+ date = moment(new Date()).tz(orgTZ[org]);
+ } else {
+ date = moment(new Date());
+ }
+
+ var data = str.replace(/\s+/, 'T').split(/T/);
var year = data[0];
var time = data[1];
/* seed the date with day = 1, which is a valid day for any month.
this prevents automatic date correction by the date code for days that
fall outside of the current or target month */
- date.setDate(1);
+ date.date(1);
- date.setFullYear(new Number(yeardata[0]));
- date.setMonth(new Number(yeardata[1]) - 1);
- date.setDate(new Number(yeardata[2]));
+ date.year(new Number(yeardata[0]));
+ date.month(new Number(yeardata[1]) - 1);
+ date.date(new Number(yeardata[2]));
- date.setHours(new Number(timedata[0]));
- date.setMinutes(new Number(timedata[1]));
- date.setSeconds(new Number(timedata[2]));
+ date.hour(new Number(timedata[0]));
+ date.minute(new Number(timedata[1]));
+ date.second(new Number(timedata[2]));
return date;
}
var start;
var end;
+ var full_day = 0;
+ var multi_day = 0;
if( ! $('cd_edit_allday_row').className.match(/hide_me/) ) {
var date = $('cd_edit_allday_start_date').value;
- start = cdDateStrToDate(date + ' 00:00:00');
- end = cdDateStrToDate(date + ' 23:59:59');
+ start = cdDateStrToDate(date + 'T00:00:00');
+ end = cdDateStrToDate(date + 'T23:59:59');
+ full_day = 1;
} else if( ! $('cd_edit_allmultiday_row').className.match(/hide_me/) ) {
var sdate = $('cd_edit_allmultiday_start_date').value;
var edate = $('cd_edit_allmultiday_end_date').value;
- start = cdDateStrToDate(sdate + ' 00:00:00');
- end = cdDateStrToDate(edate + ' 23:59:59');
+ start = cdDateStrToDate(sdate + 'T00:00:00');
+ end = cdDateStrToDate(edate + 'T23:59:59');
+ full_day = 1;
+ multi_day = 1;
} else {
etime += ':00';
}
- start = cdDateStrToDate(sdate + ' ' + stime);
- end = cdDateStrToDate(edate + ' ' + etime);
+ start = cdDateStrToDate(sdate + 'T' + stime);
+ end = cdDateStrToDate(edate + 'T' + etime);
}
- if (end.getTime() < start.getTime()) {
+ if (end.unix() < start.unix()) {
alertId('cd_invalid_date_span');
return;
}
- cdCreate(start, end, $('cd_edit_note').value);
+ cdCreate(start, end, $('cd_edit_note').value, full_day, multi_day);
}
-function cdCreate(start, end, note) {
+function cdCreate(start, end, note, full_day, multi_day) {
if( $('cd_apply_all').checked ) {
var list = cdGetOrgList();
for( var o = 0; o < list.length; o++ ) {
var id = list[o].id();
- cdCreateOne( id, start, end, note, (id == cdCurrentOrg()) );
+ cdCreateOne( id, start, end, note, full_day, multi_day, (id == cdCurrentOrg()) );
}
} else {
- cdCreateOne( cdCurrentOrg(), start, end, note, true );
+ cdCreateOne( cdCurrentOrg(), start, end, note, full_day, multi_day, true );
}
}
return list;
}
-
-function cdCreateOne( org, start, end, note, refresh ) {
+function cdCreateOne( org, start, end, note, full_day, multi_day, refresh ) {
var date = new aoucd();
- date.close_start(start.toISOString());
- date.close_end(end.toISOString());
- date.org_unit(org);
- date.reason(note);
+ // force TZ normalization
+ cdDateStrToDate(start.format('YYYY-MM-DD HH:mm:ss'), org, function (s) {
+ start = s;
+ cdDateStrToDate(end.format('YYYY-MM-DD HH:mm:ss'), org, function (e) {
+ end = e;
+
+ date.close_start(start.toISOString());
+ date.close_end(end.toISOString());
+ date.org_unit(org);
+ date.reason(note);
+ date.full_day(full_day);
+ date.multi_day(multi_day);
+
+ var req = new Request(CREATE_CLOSED_DATE, SESSION, date);
+ req.callback(
+ function(r) {
+ var res = r.getResultObject();
+ if( checkILSEvent(res) ) alertILSEvent(res);
+ if(refresh) cdDrawRange(selectedStart, selectedEnd, true);
+ }
+ );
+ req.send();
+ });
+ });
- var req = new Request(CREATE_CLOSED_DATE, SESSION, date);
- req.callback(
- function(r) {
- var res = r.getResultObject();
- if( checkILSEvent(res) ) alertILSEvent(res);
- if(refresh) cdDrawRange(selectedStart, selectedEnd, true);
- }
- );
- req.send();
}
-
-
<head>
<title>&staff.server.admin.closed_dates.title;</title>
+
+ <!-- welp, hope nobody uses media_prefix... -->
+ <script src="/js/ui/default/staff/build/js/moment-with-locales.min.js"></script>
+ <script src="/js/ui/default/staff/build/js/moment-timezone-with-data.min.js"></script>
+
<script type="text/javascript" djConfig="parseOnLoad: true,isDebug:false" src="/js/dojo/dojo/dojo.js"></script>
<script type="text/javascript" djConfig="parseOnLoad: true,isDebug:false" src="/js/dojo/dojo/openils_dojo.js"></script>
<script type='text/javascript' src='/opac/common/js/utils.js'> </script>